From fc660cc0616961713fd1bb830b44737e5af13d57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaros=C5=82aw=20Doma=C5=84ski?= Date: Mon, 10 Aug 2026 14:50:34 +0200 Subject: [PATCH] Fix RX_EN handling during RX power saving --- docs/cli_commands.md | 6 ++++++ examples/simple_repeater/MyMesh.cpp | 17 +++++++++++++++ examples/simple_repeater/MyMesh.h | 3 +++ src/helpers/CommonCLI.cpp | 23 ++++++++++++++++++++ src/helpers/CommonCLI.h | 7 ++++++ src/helpers/radiolib/CustomSX1262.h | 25 +++++++++++++++++++++- src/helpers/radiolib/CustomSX1262Wrapper.h | 23 ++++++++++++++++++++ src/helpers/radiolib/RadioLibWrappers.h | 3 +++ 8 files changed, 106 insertions(+), 1 deletion(-) diff --git a/docs/cli_commands.md b/docs/cli_commands.md index 9a19dcc27e..0cbd518f8e 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -280,7 +280,9 @@ This document provides an overview of CLI commands that can be sent to MeshCore #### View or change RX duty-cycle power saving **Usage:** - `get radio.rxps` +- `get radio.rxps.rfrx_disabled` - `get rxps.wd` +- `set radio.rxps.rfrx_disabled ` - `set radio.rxps off` - `set radio.rxps on` - `set radio.rxps conservative` @@ -294,9 +296,13 @@ This document provides an overview of CLI commands that can be sent to MeshCore - `rx_us`, `sleep_us`: Receive and sleep durations in microseconds (`1000`-`30000000`). - `level`: A power-saving level from `1` (most conservative) to `10` (least power saving). - `preamble`: LoRa preamble length in symbols; `16` or `32`. +- `state`: `on` or `off`. **Notes:** - `get rxps.wd` reports the RXPS watchdog's soft and hard recovery counts. +- `radio.rxps.rfrx_disabled` is a runtime-only diagnostic setting and resets to `off` after reboot. +- Its default `off` state keeps the host-controlled SX1262 receive path enabled during RX duty-cycle mode. Setting it to `on` reproduces the old missing-RF_RX behavior and can significantly reduce receive sensitivity, making remote commands harder to receive. +- `radio.rxps.rfrx_disabled` is supported only on SX1262 targets with a host-controlled RX enable pin. - `on` and `conservative` select level `1` with a 16-symbol preamble; `balanced` selects level `5` with a 16-symbol preamble. - Level-based settings automatically recalculate their timings when the spreading factor or bandwidth changes. Custom ` ` timings remain fixed. - The selected mode is applied immediately, persisted, and restored after reboot. diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 8502834c70..7971d66dc2 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -1076,6 +1076,23 @@ bool MyMesh::setRxPowerSaving(bool enable, uint32_t rx_us, uint32_t sleep_us) { ok ? "" : " unsupported"); return ok; } + +bool MyMesh::supportsRxPowerSavingRfRxDisable() const { + return radio_driver.supportsRxPowerSavingRfRxDisable(); +} + +bool MyMesh::setRxPowerSavingRfRxDisabled(bool disabled) { + bool ok = radio_driver.setRxPowerSavingRfRxDisabled(disabled); + MESH_DEBUG_PRINTLN("RX Power Saving RF_RX control: %s, %s", + disabled ? "disabled" : "enabled", + ok ? "accepted" : "unsupported"); + return ok; +} + +bool MyMesh::isRxPowerSavingRfRxDisabled() const { + return radio_driver.isRxPowerSavingRfRxDisabled(); +} + void MyMesh::getRxPsWatchdogCounts(uint32_t* soft, uint32_t* hard) { *soft = radio_driver.getRxPsWatchdogSoftCount(); *hard = radio_driver.getRxPsWatchdogHardCount(); diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 25e53795b9..e584435295 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -220,6 +220,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { void dumpLogFile() override; void setTxPower(int8_t power_dbm) override; bool setRxPowerSaving(bool enable, uint32_t rx_us, uint32_t sleep_us) override; + bool supportsRxPowerSavingRfRxDisable() const override; + bool setRxPowerSavingRfRxDisabled(bool disabled) override; + bool isRxPowerSavingRfRxDisabled() const override; void getRxPsWatchdogCounts(uint32_t* soft, uint32_t* hard) override; void formatNeighborsReply(char *reply) override; void removeNeighbor(const uint8_t* pubkey, int key_len) override; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index a32051e380..577806d754 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -761,6 +761,23 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep } else { strcpy(reply, "Error: state must be on or off"); } + } else if (strncmp(config, "radio.rxps.rfrx_disabled ", 25) == 0) { + const char* value = &config[25]; + bool disabled; + if (strcmp(value, "on") == 0) { + disabled = true; + } else if (strcmp(value, "off") == 0) { + disabled = false; + } else { + strcpy(reply, "Error: state must be on or off"); + return; + } + + if (!_callbacks->setRxPowerSavingRfRxDisabled(disabled)) { + strcpy(reply, "Error: unsupported"); + } else { + sprintf(reply, "OK - radio.rxps.rfrx_disabled %s", disabled ? "on" : "off"); + } } else if (memcmp(config, "radio.rxps ", 11) == 0) { const char* value = &config[11]; uint8_t enable = _prefs->rx_powersaving_enabled; @@ -1121,6 +1138,12 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep } else { sprintf(reply, "> %s", _board->isLoRaFemLnaEnabled() ? "on" : "off"); } + } else if (strcmp(config, "radio.rxps.rfrx_disabled") == 0) { + if (!_callbacks->supportsRxPowerSavingRfRxDisable()) { + strcpy(reply, "Error: unsupported"); + } else { + sprintf(reply, "> %s", _callbacks->isRxPowerSavingRfRxDisabled() ? "on" : "off"); + } } else if (memcmp(config, "radio.rxps", 10) == 0) { ensureRxPowerSavingDefaults(_prefs); sprintf(reply, "> %s,%lu,%lu", _prefs->rx_powersaving_enabled ? "on" : "off", diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 6ec5aa0133..7f60903dfc 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -137,6 +137,13 @@ class CommonCLICallbacks { return !enable; }; + virtual bool supportsRxPowerSavingRfRxDisable() const { return false; } + virtual bool setRxPowerSavingRfRxDisabled(bool disabled) { + (void)disabled; + return false; + } + virtual bool isRxPowerSavingRfRxDisabled() const { return false; } + virtual void getRxPsWatchdogCounts(uint32_t* soft, uint32_t* hard) { *soft = 0; *hard = 0; }; diff --git a/src/helpers/radiolib/CustomSX1262.h b/src/helpers/radiolib/CustomSX1262.h index f24604e858..7bf4902fb5 100644 --- a/src/helpers/radiolib/CustomSX1262.h +++ b/src/helpers/radiolib/CustomSX1262.h @@ -6,6 +6,8 @@ #define SX126X_IRQ_PREAMBLE_DETECTED 0x04 class CustomSX1262 : public SX1262 { + bool _rx_ps_rf_rx_disabled = false; + public: CustomSX1262(Module *mod) : SX1262(mod) { } @@ -86,6 +88,27 @@ class CustomSX1262 : public SX1262 { return true; // success } + int16_t startReceiveDutyCycle(uint32_t rxPeriod, uint32_t sleepPeriod, + RadioLibIrqFlags_t irqFlags = RADIOLIB_IRQ_RX_DEFAULT_FLAGS, + RadioLibIrqFlags_t irqMask = RADIOLIB_IRQ_RX_DEFAULT_MASK) { + int16_t state = SX1262::startReceiveDutyCycle(rxPeriod, sleepPeriod, irqFlags, irqMask); + if (state == RADIOLIB_ERR_NONE && !_rx_ps_rf_rx_disabled) { + // RadioLib stages RX duty-cycle through standby, which leaves a + // host-controlled RXEN switch in IDLE. Keep the receive path enabled + // while the SX1262 alternates between its RX and sleep windows. + this->mod->setRfSwitchState(Module::MODE_RX); + } + return state; + } + + void setRxPowerSavingRfRxDisabled(bool disabled) { + _rx_ps_rf_rx_disabled = disabled; + } + + bool isRxPowerSavingRfRxDisabled() const { + return _rx_ps_rf_rx_disabled; + } + // BUSY high means the chip is asleep (RX duty-cycle sleep window) or mid // command; any SPI access would stall until the chip's next listen window. bool isChipBusy() { @@ -124,4 +147,4 @@ class CustomSX1262 : public SX1262 { readRegister(RADIOLIB_SX126X_REG_RX_GAIN, &rxGain, 1); return (rxGain == RADIOLIB_SX126X_RX_GAIN_BOOSTED); } -}; \ No newline at end of file +}; diff --git a/src/helpers/radiolib/CustomSX1262Wrapper.h b/src/helpers/radiolib/CustomSX1262Wrapper.h index 1c9c0ab683..bf3e509504 100644 --- a/src/helpers/radiolib/CustomSX1262Wrapper.h +++ b/src/helpers/radiolib/CustomSX1262Wrapper.h @@ -41,6 +41,29 @@ class CustomSX1262Wrapper : public RadioLibWrapper { bool supportsRxPowerSaving() const override { return true; } + bool supportsRxPowerSavingRfRxDisable() const override { + #if defined(SX126X_RXEN) + return SX126X_RXEN != RADIOLIB_NC; + #else + return false; + #endif + } + + bool setRxPowerSavingRfRxDisabled(bool disabled) override { + if (!supportsRxPowerSavingRfRxDisable()) return false; + + if (_rx_ps_armed) stopReceiveDutyCycle(); + ((CustomSX1262 *)_radio)->setRxPowerSavingRfRxDisabled(disabled); + + // Re-arm the configured receive mode immediately without disturbing an + // unread RX interrupt or an in-flight transmission. + return setRxPowerSaving(_rx_ps_enabled, _rx_ps_rx_us, _rx_ps_sleep_us); + } + + bool isRxPowerSavingRfRxDisabled() const override { + return ((CustomSX1262 *)_radio)->isRxPowerSavingRfRxDisabled(); + } + protected: int startReceiveMode() override { if (_rx_ps_armed) { diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index 24f6da711e..64f3fc7ef2 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -101,6 +101,9 @@ class RadioLibWrapper : public mesh::Radio { void onSendFinished() override; bool isInRecvMode() const override; bool setRxPowerSaving(bool enabled, uint32_t rx_us, uint32_t sleep_us) override; + virtual bool supportsRxPowerSavingRfRxDisable() const { return false; } + virtual bool setRxPowerSavingRfRxDisabled(bool) { return false; } + virtual bool isRxPowerSavingRfRxDisabled() const { return false; } bool isChannelActive(); bool isReceiving() override {