Added RX duty-cycle power saving support#11
Conversation
|
Problem After extended operation with radio.rxps enabled, an SX1262-based repeater (Heltec T096, nRF52) stopped responding on the radio while the MCU kept running. The radio wrapper in my opinion was stuck in STATE_RX with the chip silently sitting in STDBY — no DIO1 IRQ ever arrives in that state, so the firmware never re-arms RX. Possible root cause: SX126x RTC not stopped after duty-cycle reception In SetRxDutyCycle mode the RX/SLEEP windows are driven by the chip's internal RTC. Exiting the duty cycle (RxDone, or standby before reconfig/TX) does not reliably stop that RTC; its pending timeout event can later terminate a subsequently started RX, dropping the chip to STDBY without raising any IRQ. Semtech treats stopping the RTC as mandatory, not as an implicit-header-only fix:
RadioLib only implements this workaround in fixImplicitTimeout(), which is gated to implicit LoRa header mode and never reached from MeshCore's explicit-header path. Fix: CustomSX1262::stopRTC() (port of sx126x_stop_rtc()), called via stopReceiveDutyCycle() (standby + RTC stop) whenever a duty-cycle receive is torn down — before every RX re-arm and before TX. Guarded by a new _rx_ps_armed flag, so the stock continuous-RX path (RX_TIMEOUT_INF, RTC never runs) is untouched. The writes themselves are idempotent (LoRaMac-node issues them after every packet). Related fixes
PS. Sorry for em-dashes 🤣 I have used AI to translate my notes from Polish to English. |
|
Let me test with Heltec T114. |
In my opinion - yes, because RadioLib handles this only for implicit header (like described in the semtech datasheet), but semtech drivers and LoraMac firmware is handling this like I am here. |
Dom4n
left a comment
There was a problem hiding this comment.
Noise floor reported 0 with rxps enabled
The initial rxps commit disabled adaptive noise-floor sampling entirely in duty-cycle mode (an RSSI read during the sleep window reads a powered-down frontend and would corrupt the average and the SPI access stalls until the next listen window, since RadioLib waits on BUSY before every transfer). As a result _noise_floor never left its initial 0, and RSSI-based interference detection (int.thresh) was dead in rxps mode.
Fixed by gating per-sample instead of disabling: a new isChipBusy() helper (BUSY pin high = chip in the duty-cycle sleep window or processing a command) lets loop() sample RSSI only during listen windows, exactly when the receiver is actually listening, which is the RSSI that matters in this mode. The same gate re-enables the int.thresh interference check in isChannelActive(). Listen windows are long relative to the main loop (e.g. ~49 ms RX / ~8 ms sleep for conservative at SF8/BW62.5), so the 64-sample average converges as usual. Continuous-RX behavior is unchanged (isChipBusy() defaults to false; gate only active while duty-cycle is armed).
Dom4n
left a comment
There was a problem hiding this comment.
This commit adds watchdog-based recovery for the RX power-saving duty cycle.
The issue observed in long-running tests was that the radio could become permanently deaf after a few hours of operation. Once this happened, the node stopped receiving packets and did not recover through normal receive rearming. The only reliable recovery was a full hard power reset of the device.
I investigated whether this was caused by CAD behavior or AGC state, including AGC reset attempts, but those paths did not restore receive operation. The failure mode appears to be the radio falling out of the expected RX duty-cycle behavior without generating an interrupt or otherwise giving the existing state machine a chance to recover.
This change adds an RX power-saving watchdog that monitors whether the radio still shows the expected duty-cycle activity. If the watchdog detects that RX duty cycling is stuck, it first performs a soft rearm by forcing the wrapper back to idle so receive mode can be started again. If the radio remains stuck, it escalates to a hard radio reinitialization through the SX1262 reset/init path, then reapplies cached radio parameters and TX power.
The commit also exposes watchdog recovery counters through get rxps.wd, reporting soft and hard recovery counts. This should make field verification easier and help confirm whether devices are recovering from the deaf-radio condition without requiring a full device power cycle.
|
It may relate to the wrong noisefloor just now. |
|
The noise floor calculation is working again, and was working when I tested it through the night today. With working noise floor calculation it still hanged my radio module (one of three, maybe my module has a flaw). |
There was a problem hiding this comment.
Yeah, this time I have nailed it. Noise floor calculation is working as it should or even better on esp32 variant than it was on original powersaving branch with no noticeable addition to the current needed to do it.
I will test for radio hangs in coming days, but it looks like the culprit has been found and fixed.
|
As this gets figured out, tested and made to work well - It will be amazing. We have lots of summer to test this and make perfect,so, when cloudy months hit October/early November. we are ready. Looks like at the highest savings 52% will be huge when we have no sun for more then 2 weeks. happens all the time and longer in the PNW. |
|
Wow, great. As there is rssi check before sending, TX may be paused if RSSI from noisefloor is bad. |
|
@towerviewcams 5w panels were fine last winter in freezing temperature. With this PR, 2w panels are likely sufficient too. |
Most people in PNW have 2 watt panels. We also have more then 400% additional activity since then. I know what happen with just 4.5 days of no sun just over a week ago. This winter will be MUCH different |
|
Testing max RXPS now. Level 10 and preamble 32. Working so well in very noisy location in very busy PNW cascadia mesh system. very responsive |
e89b869 to
16fbf33
Compare
|
rebased to current |
|
Let me test and merge this weekends. Then we start adjusting minor stuffs from there. Things work beautifully. |
|
After exactly 1 minute, the noise floor will be from 0 to normal -115. |
…tion is now performed right after radio module start.
…si now should be good)
|
|
it works great on my T096 immediately after reboot. I will test L1110 on my T1000e. Should be ok. |
|
Now it is easy to set fixed rxps for companions via build flags in platformio.ini files. |
|
Level 5 preample 16 should be fine and safe. |

Description
This PR adds RX duty-cycle / power saving support for RadioLib-based radios, with CLI configuration and persisted node preferences.
What it does
Adds a new CLI command for RX power saving:
set radio.rxps ...Supported modes:
offon/conservativebalancedlevel <1-10>level <1-10> preamble <16|32><rx_us> <sleep_us>Adds a read command:
get radio.rxpsPersists RX power saving settings in
NodePrefs:Automatically recalculates RX power saving timings after radio
SF/BWchanges when level-based mode is used.Adds RX duty-cycle support to RadioLib wrappers for:
Falls back to normal continuous RX if duty-cycle RX cannot be started.
Skips adaptive noise floor sampling while RX power saving is enabled, because the radio sleeps between receive windows and RSSI reads from a sleeping frontend can corrupt the noise floor estimate.
Integrates the feature into the example apps:
Behavior
When
radio.rxpsis enabled, the radio no longer stays in continuous receive mode. Instead, it alternates between a short RX window and a sleep period. This can reduce power consumption, with the tradeoff that timings must match the LoRa parameters and preamble length closely enough to catch incoming packets.The
levelmode calculatesrx_usandsleep_usfrom the currentSF,BW, and preamble length, so users can select a simple power saving level instead of manually calculating timings.Compatibility
RX power saving is disabled by default, so existing configurations should continue to behave as before. Radios that do not support this feature report an error when enabling it is requested.
More info
Testing is described here: https://discord.com/channels/1472633259799220224/1472634841077121144/1524118609134096405 (IoTThinks discord)
Does it save power?
Hell yes!
Xiao NRF52 + Wio SX1262
4V to battery connector (powersaving on), current average measured by PPK2:
set radio.rxps off- 6.92mAset radio.rxps conservative- 6.2mA (in reality this islevel 1 preamble 16)set radio.rxps balanced- 4.66mA (in reality this islevel 5 preamble 16)set radio.rxps level 1 preamble 16- 6.2mAset radio.rxps level 2 preamble 16- 5.76mAset radio.rxps level 3 preamble 16- 5.37mAset radio.rxps level 4 preamble 16- 5mAset radio.rxps level 5 preamble 16- 4.66mAset radio.rxps level 6 preamble 16- 4.35mAset radio.rxps level 7 preamble 16- 4.05mAset radio.rxps level 8 preamble 16- 3.79mAset radio.rxps level 9 preamble 16- 3.52mAset radio.rxps level 10 preamble 16- 3.3mAset radio.rxps level 1 preamble 32- 4mAset radio.rxps level 2 preamble 32- 3.77mAset radio.rxps level 3 preamble 32- 3.55mAset radio.rxps level 4 preamble 32- 3.33mAset radio.rxps level 5 preamble 32- 3.12mAset radio.rxps level 6 preamble 32- 2.92mAset radio.rxps level 7 preamble 32- 2.72mAset radio.rxps level 8 preamble 32- 2.54mAset radio.rxps level 9 preamble 32- 2.36mAset radio.rxps level 10 preamble 32- 2.19mA... and with RX gain off we have 2.1mA
Heltec T096
4V to battery connector (powersaving on), current average measured by PPK2:
set radio.rxps off- 8.6mAset radio.rxps conservative- 8.07mA (in reality this islevel 1 preamble 16)set radio.rxps balanced- 6.55mA (in reality this islevel 5 preamble 16)set radio.rxps level 1 preamble 16- 8.07mA (6% lower power consumption)set radio.rxps level 2 preamble 16- 7.6mAset radio.rxps level 3 preamble 16- 7.2mAset radio.rxps level 4 preamble 16- 6.85mAset radio.rxps level 5 preamble 16- 6.56mA (24%)set radio.rxps level 6 preamble 16- 6.2mAset radio.rxps level 7 preamble 16- 5.92mAset radio.rxps level 8 preamble 16- 5.66mAset radio.rxps level 9 preamble 16- 5.42mA (32%)set radio.rxps level 10 preamble 16- 5.2mA (39.5% lower power consumption)set radio.rxps level 1 preamble 32- 5.85mA (31%)set radio.rxps level 2 preamble 32- 5.62mAset radio.rxps level 3 preamble 32- 5.4mAset radio.rxps level 4 preamble 32- 5.2mAset radio.rxps level 5 preamble 32- 5mA (42%)set radio.rxps level 6 preamble 32- 4.8mAset radio.rxps level 7 preamble 32- 4.6mAset radio.rxps level 8 preamble 32- 4.43mAset radio.rxps level 9 preamble 32- 4.25mA (50%)set radio.rxps level 10 preamble 32- 4.1mA (52%)The uglies
All clients with firmware older than 1.16.0 (where preamble is set to 16) will not be heard by your repeater if you set rxps preamble to 32 !!!
In that case you need to set rx powersaving with
preamble 16, this does not save as much power, but the repeater will be able to detect messages with old short preamble.