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. |
CAD on a still-running SX126x duty-cycle sequencer can lose CAD-done IRQ when the RTC knocks the chip back to standby, leaving scanChannel() waiting forever.
|
Yes, thanks for the new fix. |

Description
This PR adds configurable RX duty-cycle power saving for SX1262- and
LR1110-based RadioLib radios. It includes CLI configuration, persisted
settings for CLI-managed node roles, fixed compile-time profiles for selected
companion builds, automatic timing calculation, recovery mechanisms, and
updated CLI documentation.
What it does
Adds RX power-saving configuration:
set radio.rxps offset radio.rxps onset radio.rxps conservativeset radio.rxps balancedset radio.rxps <1-10>set radio.rxps level <1-10>set radio.rxps level <1-10> preamble <16|32>set radio.rxps <rx_us> <sleep_us>Adds status and diagnostic commands:
get radio.rxpsget rxps.wdPersists the enabled state, calculated timings, selected level, and optional
preamble override in
NodePrefs.Applies changes immediately and restores persisted settings after reboot.
Automatically recalculates level-based timings after SF/BW changes. Manual
timings remain fixed.
Supports manual RX and sleep periods from 1,000 to 30,000,000 microseconds.
Adds RX duty-cycle support for:
tested less extensively than SX1262.
Falls back to continuous RX if starting duty-cycle RX fails.
Uses continuous-RX calibration windows to update the adaptive noise floor
without sampling RSSI while the frontend is sleeping or still settling.
Adds a staged RXPS watchdog:
get rxps.wd.Correctly handles timeout/header-error IRQs generated by false preamble
detections, avoiding stale-buffer/ghost packet delivery.
Preserves the RSSI and SNR metadata of the last successfully received packet.
Integrates RXPS into repeater, room-server, sensor, and companion builds.
Selected companion targets use a fixed compile-time level 5 / preamble 16
profile and recalculate its timings after radio parameter changes.
Compatibility
For CLI-managed repeater, room-server, and sensor roles, RX power saving is
disabled by default. Existing configurations therefore continue to use
continuous RX unless RXPS is explicitly enabled.
Selected companion builds enable a fixed compile-time RXPS profile using
level 5 and a 16-symbol preamble.
Unsupported radio types return an error when RXPS is enabled.
Preamble compatibility warning
The receiver must wake early enough to detect the transmitter's preamble.
A profile calculated for a 32-symbol preamble may miss packets transmitted
with a 16-symbol preamble.
MeshCore currently uses 32 symbols for SF <= 8 and 16 symbols for SF > 8,
while older firmware may always use 16 symbols. Use
preamble 16whencompatibility with 16-symbol senders is required.
More info
Testing is described here: https://discord.com/channels/1472633259799220224/1472634841077121144/1524118609134096405 (IoTThinks discord)
Does it save power?
Hell yes!
Tests below are done using PPK2 set to 4V output mode connected to device battery connector.
Radio settings used: SF8 BW62.5kHz (Europe preset).
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.