feat(power): CAD-windowed receive (battery saver) — phase 1

Add an optional low-power RX mode: instead of listening continuously, the
radio periodically runs a Channel Activity Detection scan and sits in standby
between scans; on detected activity it drops into a full receive to catch the
packet. Cuts average RX current several-fold (the dominant draw on this node).

- RadioLibWrapper: CAD state machine (scan → sleep window → rx) driven from
  loop(), which runs before recvRaw each cycle. `state` stays STATE_RX through
  all phases so the dispatcher's not-in-RX watchdog never trips. Re-arm routes
  through armRecv() in both startRecv() and recvRaw(); toggling on/off switches
  mode safely at the next idle. Falls back to continuous RX if CAD is
  unsupported. setPowerSaving()/getPowerSaving() control it.
- Software CSMA is already disabled on this firmware (interference threshold 0),
  so duty-cycling doesn't affect listen-before-talk; airtime budget still gates
  TX, and isReceivingPacket() still defers TX while a packet is mid-air.
- Companion: new rx_powersave pref (schema 0xC0DE0008), Settings › Radio ›
  "Pwr save" toggle, applied at boot (MyMesh) and on change (UITask).

CAD window = 45 ms (< the 16-symbol preamble at SF8/BW62.5) and CAD params use
RadioLib defaults — both will want on-hardware tuning + a before/after current
measurement to confirm reception is intact.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-06-10 17:21:24 +02:00
co-authored by Claude Opus 4.8
parent baaac3179c
commit 90ca21432c
8 changed files with 141 additions and 9 deletions
+5
View File
@@ -120,6 +120,11 @@ struct NodePrefs { // persisted to file
// Trail Summary readout: 0=speed (km/h or mph), 1=pace (min/km or min/mi).
// The km-vs-mi choice now comes from units_imperial, so this is just the mode.
uint8_t trail_show_pace;
// CAD-windowed receive (battery saver): 0=continuous RX (default), 1=on.
// Periodically scans for activity and idles between scans instead of
// listening continuously — cuts average RX current at the cost of a little
// receive latency. See RadioLibWrapper power-save.
uint8_t rx_powersave;
// Tail sentinel written at the end of /new_prefs. Bump the low byte when
// adding/removing/reordering fields in DataStore::savePrefs/loadPrefsInt so