From e905451dce01d7819907a437b4d11094c192a2d9 Mon Sep 17 00:00:00 2001 From: agessaman Date: Thu, 9 Jul 2026 11:20:07 -0700 Subject: [PATCH] feat(radio): drive LoRa FEM RX gain from radio_fem_rxgain (heltec_v4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completes the FEM RX-gain restoration begun in the CAD/prefs change, which persisted radio_fem_rxgain but didn't yet drive the hardware. Also dropped by the 22eb9b87 revert; restored to match upstream. - MainBoard: setLoRaFemLnaEnabled()/canControlLoRaFemLna()/isLoRaFemLnaEnabled() virtuals (default: can't control — non-FEM boards report unsupported) - heltec_v4: board overrides driving loRaFEMControl; LoRaFEMControl gains the isLNAEnabled() getter (it already tracked lna_enabled and drove the FEM) - CLI: `set radio.fem.rxgain on/off` / `get radio.fem.rxgain` (guarded by canControlLoRaFemLna, so it reports "unsupported" on non-FEM boards) - app startup applies the persisted pref: board.setLoRaFemLnaEnabled( _prefs.radio_fem_rxgain), beside setRxBoostedGainMode Default is ON (upstream), so on FEM boards the LNA is enabled after upgrade — a real reception behavior change to confirm on hardware. The other FEM variants (heltec_t096/tower_v2/tracker_v2) need the same small board-override addition; until then `radio.fem.rxgain` reports unsupported there (no regression — status quo). Builds: heltec_v4 repeater-observer + room-observer (FEM board), Heltec_v3 repeater (non-FEM, base virtuals no-op). NEEDS on-device validation on a Heltec V4. --- examples/companion_radio/MyMesh.cpp | 1 + examples/simple_repeater/MyMesh.cpp | 1 + examples/simple_room_server/MyMesh.cpp | 1 + examples/simple_sensor/SensorMesh.cpp | 1 + src/MeshCore.h | 6 ++++++ src/helpers/CommonCLI.cpp | 28 ++++++++++++++++++++++++++ variants/heltec_v4/HeltecV4Board.cpp | 17 ++++++++++++++++ variants/heltec_v4/HeltecV4Board.h | 4 ++++ variants/heltec_v4/LoRaFEMControl.h | 3 ++- 9 files changed, 61 insertions(+), 1 deletion(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index b95f3a47..6bbc563e 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -970,6 +970,7 @@ void MyMesh::begin(bool has_display) { radio_driver.setRxBoostedGainMode(_prefs.rx_boosted_gain); MESH_DEBUG_PRINTLN("RX Boosted Gain Mode: %s", radio_driver.getRxBoostedGainMode() ? "Enabled" : "Disabled"); + board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain); // LoRa FEM LNA (FEM boards only) } const char *MyMesh::getNodeName() { diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 672e8ea8..03a2e810 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -1073,6 +1073,7 @@ void MyMesh::begin(FILESYSTEM *fs) { radio_driver.setRxBoostedGainMode(_prefs.rx_boosted_gain); MESH_DEBUG_PRINTLN("RX Boosted Gain Mode: %s", radio_driver.getRxBoostedGainMode() ? "Enabled" : "Disabled"); + board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain); // LoRa FEM LNA (FEM boards only) updateAdvertTimer(); updateFloodAdvertTimer(); diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 69fd99fb..d9b93a69 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -735,6 +735,7 @@ void MyMesh::begin(FILESYSTEM *fs) { radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); radio_driver.setTxPower(_prefs.tx_power_dbm); + board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain); // LoRa FEM LNA (FEM boards only) updateAdvertTimer(); updateFloodAdvertTimer(); diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index bdd35fc0..475a38ff 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -772,6 +772,7 @@ void SensorMesh::begin(FILESYSTEM* fs) { radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); radio_driver.setTxPower(_prefs.tx_power_dbm); + board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain); // LoRa FEM LNA (FEM boards only) updateAdvertTimer(); updateFloodAdvertTimer(); diff --git a/src/MeshCore.h b/src/MeshCore.h index 6a17a4b6..f0c8f387 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -69,6 +69,12 @@ public: // dry_run is true the build is only reported, not flashed. Observer (ESP32+WiFi) builds only. virtual bool otaFromManifest(const char* current_ver, bool dry_run, char reply[]) { return false; } + // LoRa front-end-module LNA (RX gain) control. Only FEM-equipped boards override + // these; others report they can't control it. Driven by NodePrefs.radio_fem_rxgain. + virtual bool setLoRaFemLnaEnabled(bool enable) { return false; } + virtual bool canControlLoRaFemLna() const { return false; } + virtual bool isLoRaFemLnaEnabled() const { return false; } + // Power management interface (boards with power management override these) virtual bool isExternalPowered() { return false; } virtual uint16_t getBootVoltage() { return 0; } diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 9d832f93..8b9ef130 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -984,6 +984,28 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep _prefs->cad_enabled = memcmp(&config[4], "on", 2) == 0; savePrefs(); strcpy(reply, "OK"); + } 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, "agc.reset.interval ", 19) == 0) { _prefs->agc_reset_interval = atoi(&config[19]) / 4; savePrefs(); @@ -1272,6 +1294,12 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep sprintf(reply, "> %d", (uint32_t) _prefs->interference_threshold); } else if (memcmp(config, "cad", 3) == 0) { sprintf(reply, "> %s", _prefs->cad_enabled ? "on" : "off"); + } 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, "agc.reset.interval", 18) == 0) { sprintf(reply, "> %d", ((uint32_t) _prefs->agc_reset_interval) * 4); } else if (memcmp(config, "multi.acks", 10) == 0) { diff --git a/variants/heltec_v4/HeltecV4Board.cpp b/variants/heltec_v4/HeltecV4Board.cpp index 8f013797..ac804cc2 100644 --- a/variants/heltec_v4/HeltecV4Board.cpp +++ b/variants/heltec_v4/HeltecV4Board.cpp @@ -83,3 +83,20 @@ void HeltecV4Board::begin() { return loRaFEMControl.getFEMType() == KCT8103L_PA ? "Heltec V4.3 OLED" : "Heltec V4 OLED"; #endif } + + bool HeltecV4Board::setLoRaFemLnaEnabled(bool enable) { + if (!loRaFEMControl.isLnaCanControl()) { + return false; + } + loRaFEMControl.setLNAEnable(enable); + loRaFEMControl.setRxModeEnable(); + return true; + } + + bool HeltecV4Board::canControlLoRaFemLna() const { + return loRaFEMControl.isLnaCanControl(); + } + + bool HeltecV4Board::isLoRaFemLnaEnabled() const { + return loRaFEMControl.isLNAEnabled(); + } diff --git a/variants/heltec_v4/HeltecV4Board.h b/variants/heltec_v4/HeltecV4Board.h index 95def06c..d59f730d 100644 --- a/variants/heltec_v4/HeltecV4Board.h +++ b/variants/heltec_v4/HeltecV4Board.h @@ -36,4 +36,8 @@ public: } float getAdcMultiplier() const override { return adc_mult; } const char* getManufacturerName() const override; + + bool setLoRaFemLnaEnabled(bool enable) override; + bool canControlLoRaFemLna() const override; + bool isLoRaFemLnaEnabled() const override; }; diff --git a/variants/heltec_v4/LoRaFEMControl.h b/variants/heltec_v4/LoRaFEMControl.h index 13225bd5..4ab9b9c9 100644 --- a/variants/heltec_v4/LoRaFEMControl.h +++ b/variants/heltec_v4/LoRaFEMControl.h @@ -18,7 +18,8 @@ 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; } + bool isLNAEnabled(void) const { return lna_enabled; } void setLnaCanControl(bool can_control) { lna_can_control = can_control; } LoRaFEMType getFEMType(void) const { return fem_type; } private: