diff --git a/docs/cli_commands.md b/docs/cli_commands.md index fb698228..c17910a5 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -277,6 +277,20 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- +#### View or change the LoRa FEM receive-path gain state on supported boards +**Usage:** +- `get radio.fem.rxgain` +- `set radio.fem.rxgain ` + +**Parameters:** +- `state`: `on`|`off` + +**Notes:** +- This controls the external LoRa FEM receive-path LNA where the board supports it. +- This is separate from `radio.rxgain`, which controls the radio chip receive gain mode. + +--- + ### System #### View or change this node's name diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index c7988bb3..362ba68a 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -233,6 +233,7 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no file.read((uint8_t *)&_prefs.rx_boosted_gain, sizeof(_prefs.rx_boosted_gain)); // 89 file.read((uint8_t *)_prefs.default_scope_name, sizeof(_prefs.default_scope_name)); // 90 file.read((uint8_t *)_prefs.default_scope_key, sizeof(_prefs.default_scope_key)); // 121 + file.read((uint8_t *)&_prefs.radio_fem_rxgain, sizeof(_prefs.radio_fem_rxgain)); // 122 file.close(); } @@ -273,6 +274,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_ file.write((uint8_t *)&_prefs.rx_boosted_gain, sizeof(_prefs.rx_boosted_gain)); // 89 file.write((uint8_t *)_prefs.default_scope_name, sizeof(_prefs.default_scope_name)); // 90 file.write((uint8_t *)_prefs.default_scope_key, sizeof(_prefs.default_scope_key)); // 121 + file.write((uint8_t *)&_prefs.radio_fem_rxgain, sizeof(_prefs.radio_fem_rxgain)); // 122 file.close(); } diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 67ff0a81..8a6f63c5 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -46,9 +46,7 @@ #define CMD_SET_CUSTOM_VAR 41 #define CMD_GET_ADVERT_PATH 42 #define CMD_GET_TUNING_PARAMS 43 -#define CMD_GET_RADIO_FEM_RXGAIN 44 -#define CMD_SET_RADIO_FEM_RXGAIN 45 -// NOTE: CMD range 46..49 parked, potentially for WiFi operations +// NOTE: CMD range 44..49 parked, potentially for WiFi operations #define CMD_SEND_BINARY_REQ 50 #define CMD_FACTORY_RESET 51 #define CMD_SEND_PATH_DISCOVERY_REQ 52 @@ -63,6 +61,8 @@ #define CMD_SEND_CHANNEL_DATA 62 #define CMD_SET_DEFAULT_FLOOD_SCOPE 63 #define CMD_GET_DEFAULT_FLOOD_SCOPE 64 +#define CMD_GET_RADIO_FEM_RXGAIN 65 +#define CMD_SET_RADIO_FEM_RXGAIN 66 // Stats sub-types for CMD_GET_STATS #define STATS_TYPE_CORE 0 @@ -1808,9 +1808,9 @@ void MyMesh::handleCmdFrame(size_t len) { writeErrFrame(ERR_CODE_UNSUPPORTED_CMD); } else { out_frame[0] = RESP_CODE_OK; - uint32_t value = board.isLoRaFemLnaEnabled() ? 1 : 0; - memcpy(&out_frame[1], &value, 4); - _serial->writeFrame(out_frame, 5); + uint8_t value = board.isLoRaFemLnaEnabled() ? 1 : 0; + memcpy(&out_frame[1], &value, 1); + _serial->writeFrame(out_frame, 2); } } else if (cmd_frame[0] == CMD_SET_RADIO_FEM_RXGAIN && len >= 2) { uint8_t value = cmd_frame[1]; diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 6598a69c..ecb117bd 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -35,4 +35,4 @@ struct NodePrefs { // persisted to file uint8_t autoadd_max_hops; // 0 = no limit, 1 = direct (0 hops), N = up to N-1 hops (max 64) char default_scope_name[31]; uint8_t default_scope_key[16]; -}; \ No newline at end of file +}; diff --git a/examples/simple_repeater/UITask.cpp b/examples/simple_repeater/UITask.cpp index acb46325..ac6958ba 100644 --- a/examples/simple_repeater/UITask.cpp +++ b/examples/simple_repeater/UITask.cpp @@ -41,7 +41,8 @@ void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* fi } // v1.2.3 (1 Jan 2025) - sprintf(_version_info, "%s (%s)", version, build_date); + snprintf(_version_info, sizeof(_version_info), "%s (%s)", version, build_date); + free(version); } void UITask::renderCurrScreen() { diff --git a/examples/simple_room_server/UITask.cpp b/examples/simple_room_server/UITask.cpp index 42bc14d4..6445baf6 100644 --- a/examples/simple_room_server/UITask.cpp +++ b/examples/simple_room_server/UITask.cpp @@ -41,7 +41,8 @@ void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* fi } // v1.2.3 (1 Jan 2025) - sprintf(_version_info, "%s (%s)", version, build_date); + snprintf(_version_info, sizeof(_version_info), "%s (%s)", version, build_date); + free(version); } void UITask::renderCurrScreen() { diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index b8fe1e57..05b4a9dd 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -731,6 +731,7 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise _prefs.gps_enabled = 0; _prefs.gps_interval = 0; _prefs.advert_loc_policy = ADVERT_LOC_PREFS; + _prefs.radio_fem_rxgain = 1; memset(default_scope.key, 0, sizeof(default_scope.key)); } @@ -766,6 +767,7 @@ void SensorMesh::begin(FILESYSTEM* fs) { radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); radio_set_tx_power(_prefs.tx_power_dbm); + board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain); updateAdvertTimer(); updateFloodAdvertTimer(); diff --git a/examples/simple_sensor/UITask.cpp b/examples/simple_sensor/UITask.cpp index 0e78fee0..698b9fd2 100644 --- a/examples/simple_sensor/UITask.cpp +++ b/examples/simple_sensor/UITask.cpp @@ -41,7 +41,8 @@ void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* fi } // v1.2.3 (1 Jan 2025) - sprintf(_version_info, "%s (%s)", version, build_date); + snprintf(_version_info, sizeof(_version_info), "%s (%s)", version, build_date); + free(version); } void UITask::renderCurrScreen() { diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 1aa4265c..1322a36c 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -90,7 +90,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166 file.read((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170 file.read((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); // 290 - // next: 291 + file.read((uint8_t *)&_prefs->radio_fem_rxgain, sizeof(_prefs->radio_fem_rxgain)); // 291 + // next: 292 // sanitise bad pref values _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f); @@ -182,7 +183,6 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166 file.write((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170 file.write((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); // 290 - // next: 291 file.write((uint8_t *)&_prefs->radio_fem_rxgain, sizeof(_prefs->radio_fem_rxgain)); // 291 // next: 292 @@ -522,279 +522,279 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep sprintf(reply, "OK - %d.%d%%", a_int, a_frac); } } else if (memcmp(config, "af ", 3) == 0) { - _prefs->airtime_factor = atof(&config[3]); - savePrefs(); - strcpy(reply, "OK"); - } else if (memcmp(config, "int.thresh ", 11) == 0) { - _prefs->interference_threshold = atoi(&config[11]); - savePrefs(); - strcpy(reply, "OK"); - } else if (memcmp(config, "agc.reset.interval ", 19) == 0) { - _prefs->agc_reset_interval = atoi(&config[19]) / 4; - savePrefs(); - sprintf(reply, "OK - interval rounded to %d", ((uint32_t) _prefs->agc_reset_interval) * 4); - } else if (memcmp(config, "multi.acks ", 11) == 0) { - _prefs->multi_acks = atoi(&config[11]); - savePrefs(); - strcpy(reply, "OK"); - } else if (memcmp(config, "allow.read.only ", 16) == 0) { - _prefs->allow_read_only = memcmp(&config[16], "on", 2) == 0; - savePrefs(); - strcpy(reply, "OK"); - } else if (memcmp(config, "flood.advert.interval ", 22) == 0) { - int hours = _atoi(&config[22]); - if ((hours > 0 && hours < 3) || (hours > 168)) { - strcpy(reply, "Error: interval range is 3-168 hours"); - } else { - _prefs->flood_advert_interval = (uint8_t)(hours); - _callbacks->updateFloodAdvertTimer(); - savePrefs(); - strcpy(reply, "OK"); - } - } else if (memcmp(config, "advert.interval ", 16) == 0) { - int mins = _atoi(&config[16]); - if ((mins > 0 && mins < MIN_LOCAL_ADVERT_INTERVAL) || (mins > 240)) { - sprintf(reply, "Error: interval range is %d-240 minutes", MIN_LOCAL_ADVERT_INTERVAL); - } else { - _prefs->advert_interval = (uint8_t)(mins / 2); - _callbacks->updateAdvertTimer(); - savePrefs(); - strcpy(reply, "OK"); - } - } else if (memcmp(config, "guest.password ", 15) == 0) { - StrHelper::strncpy(_prefs->guest_password, &config[15], sizeof(_prefs->guest_password)); - savePrefs(); - strcpy(reply, "OK"); - } else if (memcmp(config, "prv.key ", 8) == 0) { - uint8_t prv_key[PRV_KEY_SIZE]; - bool success = mesh::Utils::fromHex(prv_key, PRV_KEY_SIZE, &config[8]); - // only allow rekey if key is valid - if (success && mesh::LocalIdentity::validatePrivateKey(prv_key)) { - mesh::LocalIdentity new_id; - new_id.readFrom(prv_key, PRV_KEY_SIZE); - _callbacks->saveIdentity(new_id); - strcpy(reply, "OK, reboot to apply! New pubkey: "); - mesh::Utils::toHex(&reply[33], new_id.pub_key, PUB_KEY_SIZE); - } else { - strcpy(reply, "Error, bad key"); - } - } else if (memcmp(config, "name ", 5) == 0) { - if (isValidName(&config[5])) { - StrHelper::strncpy(_prefs->node_name, &config[5], sizeof(_prefs->node_name)); - savePrefs(); - strcpy(reply, "OK"); - } else { - strcpy(reply, "Error, bad chars"); - } - } else if (memcmp(config, "repeat ", 7) == 0) { - _prefs->disable_fwd = memcmp(&config[7], "off", 3) == 0; - savePrefs(); - strcpy(reply, _prefs->disable_fwd ? "OK - repeat is now OFF" : "OK - repeat is now ON"); + _prefs->airtime_factor = atof(&config[3]); + savePrefs(); + strcpy(reply, "OK"); + } else if (memcmp(config, "int.thresh ", 11) == 0) { + _prefs->interference_threshold = atoi(&config[11]); + savePrefs(); + strcpy(reply, "OK"); + } else if (memcmp(config, "agc.reset.interval ", 19) == 0) { + _prefs->agc_reset_interval = atoi(&config[19]) / 4; + savePrefs(); + sprintf(reply, "OK - interval rounded to %d", ((uint32_t) _prefs->agc_reset_interval) * 4); + } else if (memcmp(config, "multi.acks ", 11) == 0) { + _prefs->multi_acks = atoi(&config[11]); + savePrefs(); + strcpy(reply, "OK"); + } else if (memcmp(config, "allow.read.only ", 16) == 0) { + _prefs->allow_read_only = memcmp(&config[16], "on", 2) == 0; + savePrefs(); + strcpy(reply, "OK"); + } else if (memcmp(config, "flood.advert.interval ", 22) == 0) { + int hours = _atoi(&config[22]); + if ((hours > 0 && hours < 3) || (hours > 168)) { + strcpy(reply, "Error: interval range is 3-168 hours"); + } else { + _prefs->flood_advert_interval = (uint8_t)(hours); + _callbacks->updateFloodAdvertTimer(); + savePrefs(); + strcpy(reply, "OK"); + } + } else if (memcmp(config, "advert.interval ", 16) == 0) { + int mins = _atoi(&config[16]); + if ((mins > 0 && mins < MIN_LOCAL_ADVERT_INTERVAL) || (mins > 240)) { + sprintf(reply, "Error: interval range is %d-240 minutes", MIN_LOCAL_ADVERT_INTERVAL); + } else { + _prefs->advert_interval = (uint8_t)(mins / 2); + _callbacks->updateAdvertTimer(); + savePrefs(); + strcpy(reply, "OK"); + } + } else if (memcmp(config, "guest.password ", 15) == 0) { + StrHelper::strncpy(_prefs->guest_password, &config[15], sizeof(_prefs->guest_password)); + savePrefs(); + strcpy(reply, "OK"); + } else if (memcmp(config, "prv.key ", 8) == 0) { + uint8_t prv_key[PRV_KEY_SIZE]; + bool success = mesh::Utils::fromHex(prv_key, PRV_KEY_SIZE, &config[8]); + // only allow rekey if key is valid + if (success && mesh::LocalIdentity::validatePrivateKey(prv_key)) { + mesh::LocalIdentity new_id; + new_id.readFrom(prv_key, PRV_KEY_SIZE); + _callbacks->saveIdentity(new_id); + strcpy(reply, "OK, reboot to apply! New pubkey: "); + mesh::Utils::toHex(&reply[33], new_id.pub_key, PUB_KEY_SIZE); + } else { + strcpy(reply, "Error, bad key"); + } + } else if (memcmp(config, "name ", 5) == 0) { + if (isValidName(&config[5])) { + StrHelper::strncpy(_prefs->node_name, &config[5], sizeof(_prefs->node_name)); + savePrefs(); + strcpy(reply, "OK"); + } else { + strcpy(reply, "Error, bad chars"); + } + } else if (memcmp(config, "repeat ", 7) == 0) { + _prefs->disable_fwd = memcmp(&config[7], "off", 3) == 0; + savePrefs(); + strcpy(reply, _prefs->disable_fwd ? "OK - repeat is now OFF" : "OK - repeat is now ON"); #if defined(USE_SX1262) || defined(USE_SX1268) } else if (memcmp(config, "radio.rxgain ", 13) == 0) { - _prefs->rx_boosted_gain = memcmp(&config[13], "on", 2) == 0; - strcpy(reply, "OK"); - savePrefs(); - _callbacks->setRxBoostedGain(_prefs->rx_boosted_gain); + _prefs->rx_boosted_gain = memcmp(&config[13], "on", 2) == 0; + strcpy(reply, "OK"); + savePrefs(); + _callbacks->setRxBoostedGain(_prefs->rx_boosted_gain); #endif - } else if (memcmp(config, "radio.fem.rxgain ", 17) == 0) { - if (!_board->canControlLoRaFemLna()) { - strcpy(reply, "Error: unsupported by this board"); - } else if (memcmp(&config[17], "on", 2) == 0) { - if (_board->setLoRaFemLnaEnabled(true)) { - _prefs->radio_fem_rxgain = 1; - savePrefs(); - strcpy(reply, "OK - LoRa FEM RX gain on"); - } else { - strcpy(reply, "Error: failed to apply LoRa FEM RX gain"); - } - } else if (memcmp(&config[17], "off", 3) == 0) { - if (_board->setLoRaFemLnaEnabled(false)) { - _prefs->radio_fem_rxgain = 0; - savePrefs(); - strcpy(reply, "OK - LoRa FEM RX gain off"); - } else { - strcpy(reply, "Error: failed to apply LoRa FEM RX gain"); - } - } else { - strcpy(reply, "Error: state must be on or off"); - } - } else if (memcmp(config, "radio ", 6) == 0) { - strcpy(tmp, &config[6]); - const char *parts[4]; - int num = mesh::Utils::parseTextParts(tmp, parts, 4); - float freq = num > 0 ? strtof(parts[0], nullptr) : 0.0f; - float bw = num > 1 ? strtof(parts[1], nullptr) : 0.0f; - uint8_t sf = num > 2 ? atoi(parts[2]) : 0; - uint8_t cr = num > 3 ? atoi(parts[3]) : 0; + } else if (memcmp(config, "radio.fem.rxgain ", 17) == 0) { + if (!_board->canControlLoRaFemLna()) { + strcpy(reply, "Error: unsupported"); + } else if (memcmp(&config[17], "on", 2) == 0) { + if (_board->setLoRaFemLnaEnabled(true)) { + _prefs->radio_fem_rxgain = 1; + savePrefs(); + strcpy(reply, "OK - LoRa FEM RX gain on"); + } else { + strcpy(reply, "Error: failed to apply LoRa FEM RX gain"); + } + } else if (memcmp(&config[17], "off", 3) == 0) { + if (_board->setLoRaFemLnaEnabled(false)) { + _prefs->radio_fem_rxgain = 0; + savePrefs(); + strcpy(reply, "OK - LoRa FEM RX gain off"); + } else { + strcpy(reply, "Error: failed to apply LoRa FEM RX gain"); + } + } else { + strcpy(reply, "Error: state must be on or off"); + } + } else if (memcmp(config, "radio ", 6) == 0) { + strcpy(tmp, &config[6]); + const char *parts[4]; + int num = mesh::Utils::parseTextParts(tmp, parts, 4); + float freq = num > 0 ? strtof(parts[0], nullptr) : 0.0f; + float bw = num > 1 ? strtof(parts[1], nullptr) : 0.0f; + uint8_t sf = num > 2 ? atoi(parts[2]) : 0; + uint8_t cr = num > 3 ? atoi(parts[3]) : 0; if (freq >= 150.0f && freq <= 2500.0f && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f) { - _prefs->sf = sf; - _prefs->cr = cr; - _prefs->freq = freq; - _prefs->bw = bw; - _callbacks->savePrefs(); - strcpy(reply, "OK - reboot to apply"); - } else { - strcpy(reply, "Error, invalid radio params"); - } - } else if (memcmp(config, "lat ", 4) == 0) { - _prefs->node_lat = atof(&config[4]); - savePrefs(); - strcpy(reply, "OK"); - } else if (memcmp(config, "lon ", 4) == 0) { - _prefs->node_lon = atof(&config[4]); - savePrefs(); - strcpy(reply, "OK"); - } else if (memcmp(config, "rxdelay ", 8) == 0) { - float db = atof(&config[8]); - if (db >= 0) { - _prefs->rx_delay_base = db; - savePrefs(); - strcpy(reply, "OK"); - } else { - strcpy(reply, "Error, cannot be negative"); - } - } else if (memcmp(config, "txdelay ", 8) == 0) { - float f = atof(&config[8]); - if (f >= 0) { - _prefs->tx_delay_factor = f; - savePrefs(); - strcpy(reply, "OK"); - } else { - strcpy(reply, "Error, cannot be negative"); - } - } else if (memcmp(config, "flood.max ", 10) == 0) { - uint8_t m = atoi(&config[10]); - if (m <= 64) { - _prefs->flood_max = m; - savePrefs(); - strcpy(reply, "OK"); - } else { - strcpy(reply, "Error, max 64"); - } - } else if (memcmp(config, "direct.txdelay ", 15) == 0) { - float f = atof(&config[15]); - if (f >= 0) { - _prefs->direct_tx_delay_factor = f; - savePrefs(); - strcpy(reply, "OK"); - } else { - strcpy(reply, "Error, cannot be negative"); - } - } else if (memcmp(config, "owner.info ", 11) == 0) { - config += 11; - char *dp = _prefs->owner_info; - while (*config && dp - _prefs->owner_info < sizeof(_prefs->owner_info)-1) { - *dp++ = (*config == '|') ? '\n' : *config; // translate '|' to newline chars - config++; - } - *dp = 0; - savePrefs(); - strcpy(reply, "OK"); - } else if (memcmp(config, "path.hash.mode ", 15) == 0) { - config += 15; - uint8_t mode = atoi(config); - if (mode < 3) { - _prefs->path_hash_mode = mode; - savePrefs(); - strcpy(reply, "OK"); - } else { - strcpy(reply, "Error, must be 0,1, or 2"); - } - } else if (memcmp(config, "loop.detect ", 12) == 0) { - config += 12; - uint8_t mode; - if (memcmp(config, "off", 3) == 0) { - mode = LOOP_DETECT_OFF; - } else if (memcmp(config, "minimal", 7) == 0) { - mode = LOOP_DETECT_MINIMAL; - } else if (memcmp(config, "moderate", 8) == 0) { - mode = LOOP_DETECT_MODERATE; - } else if (memcmp(config, "strict", 6) == 0) { - mode = LOOP_DETECT_STRICT; - } else { - mode = 0xFF; - strcpy(reply, "Error, must be: off, minimal, moderate, or strict"); - } - if (mode != 0xFF) { - _prefs->loop_detect = mode; - savePrefs(); - strcpy(reply, "OK"); - } - } else if (memcmp(config, "tx ", 3) == 0) { - _prefs->tx_power_dbm = atoi(&config[3]); - savePrefs(); - _callbacks->setTxPower(_prefs->tx_power_dbm); - strcpy(reply, "OK"); - } else if (sender_timestamp == 0 && memcmp(config, "freq ", 5) == 0) { - _prefs->freq = atof(&config[5]); - savePrefs(); - strcpy(reply, "OK - reboot to apply"); + _prefs->sf = sf; + _prefs->cr = cr; + _prefs->freq = freq; + _prefs->bw = bw; + _callbacks->savePrefs(); + strcpy(reply, "OK - reboot to apply"); + } else { + strcpy(reply, "Error, invalid radio params"); + } + } else if (memcmp(config, "lat ", 4) == 0) { + _prefs->node_lat = atof(&config[4]); + savePrefs(); + strcpy(reply, "OK"); + } else if (memcmp(config, "lon ", 4) == 0) { + _prefs->node_lon = atof(&config[4]); + savePrefs(); + strcpy(reply, "OK"); + } else if (memcmp(config, "rxdelay ", 8) == 0) { + float db = atof(&config[8]); + if (db >= 0) { + _prefs->rx_delay_base = db; + savePrefs(); + strcpy(reply, "OK"); + } else { + strcpy(reply, "Error, cannot be negative"); + } + } else if (memcmp(config, "txdelay ", 8) == 0) { + float f = atof(&config[8]); + if (f >= 0) { + _prefs->tx_delay_factor = f; + savePrefs(); + strcpy(reply, "OK"); + } else { + strcpy(reply, "Error, cannot be negative"); + } + } else if (memcmp(config, "flood.max ", 10) == 0) { + uint8_t m = atoi(&config[10]); + if (m <= 64) { + _prefs->flood_max = m; + savePrefs(); + strcpy(reply, "OK"); + } else { + strcpy(reply, "Error, max 64"); + } + } else if (memcmp(config, "direct.txdelay ", 15) == 0) { + float f = atof(&config[15]); + if (f >= 0) { + _prefs->direct_tx_delay_factor = f; + savePrefs(); + strcpy(reply, "OK"); + } else { + strcpy(reply, "Error, cannot be negative"); + } + } else if (memcmp(config, "owner.info ", 11) == 0) { + config += 11; + char *dp = _prefs->owner_info; + while (*config && dp - _prefs->owner_info < sizeof(_prefs->owner_info)-1) { + *dp++ = (*config == '|') ? '\n' : *config; // translate '|' to newline chars + config++; + } + *dp = 0; + savePrefs(); + strcpy(reply, "OK"); + } else if (memcmp(config, "path.hash.mode ", 15) == 0) { + config += 15; + uint8_t mode = atoi(config); + if (mode < 3) { + _prefs->path_hash_mode = mode; + savePrefs(); + strcpy(reply, "OK"); + } else { + strcpy(reply, "Error, must be 0,1, or 2"); + } + } else if (memcmp(config, "loop.detect ", 12) == 0) { + config += 12; + uint8_t mode; + if (memcmp(config, "off", 3) == 0) { + mode = LOOP_DETECT_OFF; + } else if (memcmp(config, "minimal", 7) == 0) { + mode = LOOP_DETECT_MINIMAL; + } else if (memcmp(config, "moderate", 8) == 0) { + mode = LOOP_DETECT_MODERATE; + } else if (memcmp(config, "strict", 6) == 0) { + mode = LOOP_DETECT_STRICT; + } else { + mode = 0xFF; + strcpy(reply, "Error, must be: off, minimal, moderate, or strict"); + } + if (mode != 0xFF) { + _prefs->loop_detect = mode; + savePrefs(); + strcpy(reply, "OK"); + } + } else if (memcmp(config, "tx ", 3) == 0) { + _prefs->tx_power_dbm = atoi(&config[3]); + savePrefs(); + _callbacks->setTxPower(_prefs->tx_power_dbm); + strcpy(reply, "OK"); + } else if (sender_timestamp == 0 && memcmp(config, "freq ", 5) == 0) { + _prefs->freq = atof(&config[5]); + savePrefs(); + strcpy(reply, "OK - reboot to apply"); #ifdef WITH_BRIDGE - } else if (memcmp(config, "bridge.enabled ", 15) == 0) { - _prefs->bridge_enabled = memcmp(&config[15], "on", 2) == 0; - _callbacks->setBridgeState(_prefs->bridge_enabled); - savePrefs(); - strcpy(reply, "OK"); - } else if (memcmp(config, "bridge.delay ", 13) == 0) { - int delay = _atoi(&config[13]); - if (delay >= 0 && delay <= 10000) { - _prefs->bridge_delay = (uint16_t)delay; - savePrefs(); - strcpy(reply, "OK"); - } else { - strcpy(reply, "Error: delay must be between 0-10000 ms"); - } - } else if (memcmp(config, "bridge.source ", 14) == 0) { - _prefs->bridge_pkt_src = memcmp(&config[14], "rx", 2) == 0; - savePrefs(); - strcpy(reply, "OK"); + } else if (memcmp(config, "bridge.enabled ", 15) == 0) { + _prefs->bridge_enabled = memcmp(&config[15], "on", 2) == 0; + _callbacks->setBridgeState(_prefs->bridge_enabled); + savePrefs(); + strcpy(reply, "OK"); + } else if (memcmp(config, "bridge.delay ", 13) == 0) { + int delay = _atoi(&config[13]); + if (delay >= 0 && delay <= 10000) { + _prefs->bridge_delay = (uint16_t)delay; + savePrefs(); + strcpy(reply, "OK"); + } else { + strcpy(reply, "Error: delay must be between 0-10000 ms"); + } + } else if (memcmp(config, "bridge.source ", 14) == 0) { + _prefs->bridge_pkt_src = memcmp(&config[14], "rx", 2) == 0; + savePrefs(); + strcpy(reply, "OK"); #endif #ifdef WITH_RS232_BRIDGE - } else if (memcmp(config, "bridge.baud ", 12) == 0) { - uint32_t baud = atoi(&config[12]); - if (baud >= 9600 && baud <= BRIDGE_MAX_BAUD) { - _prefs->bridge_baud = (uint32_t)baud; - _callbacks->restartBridge(); - savePrefs(); - strcpy(reply, "OK"); - } else { - sprintf(reply, "Error: baud rate must be between 9600-%d",BRIDGE_MAX_BAUD); - } + } else if (memcmp(config, "bridge.baud ", 12) == 0) { + uint32_t baud = atoi(&config[12]); + if (baud >= 9600 && baud <= BRIDGE_MAX_BAUD) { + _prefs->bridge_baud = (uint32_t)baud; + _callbacks->restartBridge(); + savePrefs(); + strcpy(reply, "OK"); + } else { + sprintf(reply, "Error: baud rate must be between 9600-%d",BRIDGE_MAX_BAUD); + } #endif #ifdef WITH_ESPNOW_BRIDGE - } else if (memcmp(config, "bridge.channel ", 15) == 0) { - int ch = atoi(&config[15]); - if (ch > 0 && ch < 15) { - _prefs->bridge_channel = (uint8_t)ch; - _callbacks->restartBridge(); - savePrefs(); - strcpy(reply, "OK"); - } else { - strcpy(reply, "Error: channel must be between 1-14"); - } - } else if (memcmp(config, "bridge.secret ", 14) == 0) { - StrHelper::strncpy(_prefs->bridge_secret, &config[14], sizeof(_prefs->bridge_secret)); - _callbacks->restartBridge(); - savePrefs(); - strcpy(reply, "OK"); + } else if (memcmp(config, "bridge.channel ", 15) == 0) { + int ch = atoi(&config[15]); + if (ch > 0 && ch < 15) { + _prefs->bridge_channel = (uint8_t)ch; + _callbacks->restartBridge(); + savePrefs(); + strcpy(reply, "OK"); + } else { + strcpy(reply, "Error: channel must be between 1-14"); + } + } else if (memcmp(config, "bridge.secret ", 14) == 0) { + StrHelper::strncpy(_prefs->bridge_secret, &config[14], sizeof(_prefs->bridge_secret)); + _callbacks->restartBridge(); + savePrefs(); + strcpy(reply, "OK"); #endif - } else if (memcmp(config, "adc.multiplier ", 15) == 0) { - _prefs->adc_multiplier = atof(&config[15]); - if (_board->setAdcMultiplier(_prefs->adc_multiplier)) { - savePrefs(); - if (_prefs->adc_multiplier == 0.0f) { - strcpy(reply, "OK - using default board multiplier"); - } else { - sprintf(reply, "OK - multiplier set to %.3f", _prefs->adc_multiplier); - } - } else { - _prefs->adc_multiplier = 0.0f; - strcpy(reply, "Error: unsupported by this board"); - }; + } else if (memcmp(config, "adc.multiplier ", 15) == 0) { + _prefs->adc_multiplier = atof(&config[15]); + if (_board->setAdcMultiplier(_prefs->adc_multiplier)) { + savePrefs(); + if (_prefs->adc_multiplier == 0.0f) { + strcpy(reply, "OK - using default board multiplier"); } else { - sprintf(reply, "unknown config: %s", config); + sprintf(reply, "OK - multiplier set to %.3f", _prefs->adc_multiplier); } + } else { + _prefs->adc_multiplier = 0.0f; + strcpy(reply, "Error: unsupported"); + }; + } else { + strcpy(reply, "unknown config: "); + } } void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* reply) { @@ -837,6 +837,12 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep } else if (memcmp(config, "radio.rxgain", 12) == 0) { sprintf(reply, "> %s", _prefs->rx_boosted_gain ? "on" : "off"); #endif + } else if (memcmp(config, "radio.fem.rxgain", 16) == 0) { + if (!_board->canControlLoRaFemLna()) { + strcpy(reply, "Error: unsupported"); + } else { + sprintf(reply, "> %s", _board->isLoRaFemLnaEnabled() ? "on" : "off"); + } } else if (memcmp(config, "radio", 5) == 0) { char freq[16], bw[16]; strcpy(freq, StrHelper::ftoa(_prefs->freq)); @@ -868,9 +874,9 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep strcpy(reply, "> minimal"); } else if (_prefs->loop_detect == LOOP_DETECT_MODERATE) { strcpy(reply, "> moderate"); - } else { + } else { strcpy(reply, "> strict"); - } + } } else if (memcmp(config, "tx", 2) == 0 && (config[2] == 0 || config[2] == ' ')) { sprintf(reply, "> %d", (int32_t) _prefs->tx_power_dbm); } else if (memcmp(config, "freq", 4) == 0) { @@ -917,12 +923,12 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep strcpy(reply, "> unknown"); } #else - strcpy(reply, "ERROR: unsupported"); + strcpy(reply, "Error: unsupported"); #endif } else if (memcmp(config, "adc.multiplier", 14) == 0) { float adc_mult = _board->getAdcMultiplier(); if (adc_mult == 0.0f) { - strcpy(reply, "Error: unsupported by this board"); + strcpy(reply, "Error: unsupported"); } else { sprintf(reply, "> %.3f", adc_mult); } diff --git a/variants/heltec_t096/LoRaFEMControl.h b/variants/heltec_t096/LoRaFEMControl.h index 2c50b742..a3b5c4ed 100644 --- a/variants/heltec_t096/LoRaFEMControl.h +++ b/variants/heltec_t096/LoRaFEMControl.h @@ -12,10 +12,11 @@ class LoRaFEMControl void setRxModeEnable(void); void setRxModeEnableWhenMCUSleep(void); void setLNAEnable(bool enabled); - bool isLnaCanControl(void) { return lna_can_control; } + bool isLnaCanControl(void) const { return lna_can_control; } void setLnaCanControl(bool can_control) { lna_can_control = can_control; } + bool isLNAEnabled(void) const { return lna_enabled; } private: - bool lna_enabled = false; + bool lna_enabled = true; bool lna_can_control = false; }; diff --git a/variants/heltec_t096/T096Board.cpp b/variants/heltec_t096/T096Board.cpp index 55013157..54425145 100644 --- a/variants/heltec_t096/T096Board.cpp +++ b/variants/heltec_t096/T096Board.cpp @@ -123,4 +123,22 @@ void T096Board::powerOff() { const char* T096Board::getManufacturerName() const { return "Heltec T096"; -} \ No newline at end of file +} + +bool T096Board::setLoRaFemLnaEnabled(bool enable) { + if (!loRaFEMControl.isLnaCanControl()) { + return false; + } + + loRaFEMControl.setLNAEnable(enable); + loRaFEMControl.setRxModeEnable(); + return true; +} + +bool T096Board::canControlLoRaFemLna() const { + return loRaFEMControl.isLnaCanControl(); +} + +bool T096Board::isLoRaFemLnaEnabled() const { + return loRaFEMControl.isLNAEnabled(); +} diff --git a/variants/heltec_t096/T096Board.h b/variants/heltec_t096/T096Board.h index d1e3bdfd..15c7e68b 100644 --- a/variants/heltec_t096/T096Board.h +++ b/variants/heltec_t096/T096Board.h @@ -25,4 +25,7 @@ public: uint16_t getBattMilliVolts() override; const char* getManufacturerName() const override ; void powerOff() override; + bool setLoRaFemLnaEnabled(bool enable) override; + bool canControlLoRaFemLna() const override; + bool isLoRaFemLnaEnabled() const override; }; diff --git a/variants/heltec_tracker_v2/LoRaFEMControl.h b/variants/heltec_tracker_v2/LoRaFEMControl.h index 0ce60fff..a3b5c4ed 100644 --- a/variants/heltec_tracker_v2/LoRaFEMControl.h +++ b/variants/heltec_tracker_v2/LoRaFEMControl.h @@ -17,6 +17,6 @@ class LoRaFEMControl bool isLNAEnabled(void) const { return lna_enabled; } private: - bool lna_enabled = false; + bool lna_enabled = true; bool lna_can_control = false; }; diff --git a/variants/heltec_v4/HeltecV4Board.cpp b/variants/heltec_v4/HeltecV4Board.cpp index f9dafb3b..87791e6b 100644 --- a/variants/heltec_v4/HeltecV4Board.cpp +++ b/variants/heltec_v4/HeltecV4Board.cpp @@ -20,7 +20,7 @@ void HeltecV4Board::begin() { rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS); rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1); } -} + } void HeltecV4Board::onBeforeTransmit(void) { digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on diff --git a/variants/heltec_v4/HeltecV4Board.h b/variants/heltec_v4/HeltecV4Board.h index fe77caed..f96e161c 100644 --- a/variants/heltec_v4/HeltecV4Board.h +++ b/variants/heltec_v4/HeltecV4Board.h @@ -17,10 +17,10 @@ public: void onAfterTransmit(void) override; void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1); void powerOff() override; - uint16_t getBattMilliVolts() override; - const char* getManufacturerName() const override ; bool setLoRaFemLnaEnabled(bool enable) override; bool canControlLoRaFemLna() const override; bool isLoRaFemLnaEnabled() const override; + uint16_t getBattMilliVolts() override; + const char* getManufacturerName() const override ; };