diff --git a/FEATURES.md b/FEATURES.md index e9a8bfe8..0e90e281 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -339,7 +339,61 @@ A fuller position-centred navigator (you fixed at screen centre, fixed/preset zoom, pan) is a larger separate feature; start with the rotate-the-fit version if pursued. -### πŸ“‹ Battery / power optimization (CAD RX windowing + APC) β€” after trail polish +### 🚧 Battery / power optimization (CAD RX windowing + APC) + +**Phase 1 shipped on branch `feat/power-saving`** (CAD-windowed receive). Not yet +merged β€” under field testing. Where things stand and what to return to: + +**βœ… Done (phase 1) β€” CAD-windowed RX, behind a setting** +- `RadioLibWrapper` gained a CAD state machine (scan β†’ standby window β†’ on + detect, full RX), driven from `loop()` (runs before `recvRaw`). `state` stays + `STATE_RX` through all phases so the dispatcher's not-in-RX watchdog never + trips; re-arm routes through `armRecv()`; falls back to continuous RX if CAD + is unsupported. `setPowerSaving()`/`getPowerSaving()`. +- Companion: `rx_powersave` pref (schema `0xC0DE0008`), **Settings β€Ί Radio β€Ί + "Pwr save"** toggle, applied at boot (MyMesh) and on change (UITask). +- CSMA unaffected: this firmware's interference threshold is already 0, so + listen-before-talk was a no-op; airtime budget + `isReceivingPacket()` still + gate TX. CAD window = 45 ms (< the ~66 ms 16-symbol preamble at SF8/BW62.5), + RadioLib default CAD params. +- **Field test:** the **base (Pwr save OFF) is healthy** β€” no regression vs + stock; an earlier "drops vs stock" scare turned out to be weak repeater + coverage at the test site (direct device-to-device was fine). With **Pwr save + ON, occasional message drops** were observed (CAD misses some), so it stays + **default OFF** and is experimental until detection is made reliable. + +**⏸ To return to (not done)** +- **WAITING ON: upstream preamble 16 β†’ 32.** Upstream `dev` raised the LoRa + preamble from 16 to 32 symbols; we'll pick it up via the next upstream + release/merge (our radio init still hardcodes 16 in `CustomSX1262.h` + `begin(..., 16, tcxo)`). This is the natural fix for the CAD drops: at + SF8/BW62.5 the preamble goes ~66 ms β†’ ~131 ms, so the conservative 45 ms scan + window gains a huge margin (~21 ms β†’ ~86 ms) and the occasional misses should + disappear β€” **revisit power-save right after that lands.** It also un-no-ops + `startReceiveDutyCycleAuto(32)` (32 β‰₯ 2Β·minSymbols+1), enabling the SX126x + hardware RX duty cycle as an alternative to the software CAD loop. Caveat: the + scan window must be sized for the *shortest* preamble on the mesh, so a longer + window / `startReceiveDutyCycleAuto(32)` only pays off once senders are + broadly on 32 β€” keep 45 ms while the mesh is mixed. +- **CAD detection reliability** β€” the open blocker (see above; the preamble + bump is expected to resolve most of it). If misses persist after preamble 32, + tune the CAD parameters (`setCad` symbolNum / detPeak / detMin via a + `ChannelScanConfig_t`). Needs the current measurement below to judge the + reliability-vs-savings trade. +- **Current measurement** β€” never taken (no PPK2/meter to hand). The actual mA + win is unquantified. Do this alongside the detection-reliability tuning. +- **Warm sleep between scans** β€” tried (`cadSleep()`/`cadWake()` hooks exist) but + **reverted**: warm sleep dropped longer packets because the **TCXO** hadn't + re-stabilised before the post-CAD receive β†’ frequency drift β†’ symbol errors + over a long payload. To revisit: configure a proper SX126x TCXO startup delay + and ensure RX waits for "TCXO ready", then re-test long-packet reception. Worth + ~another 2Γ— on idle-window current (standby ~mA β†’ sleep ~Β΅A). +- **CAD window tuning** β€” 45 ms is conservative; could push toward the preamble + limit (~55 ms) for fewer scans / lower average current, once measured. +- **Adaptive Power Control (APC)** β€” drop `tx_power_dbm` dynamically on good + links. Small win for a mostly-listening node; lower priority. + +**Original analysis (kept for context):** Goal: multiply battery life **without losing functionality**. The dominant draw on this node is the radio in **continuous RX** (always-on `startReceive()` @@ -379,12 +433,9 @@ at preamble 16). Prefer adopting/extending upstream work over a parallel implementation. Note ZephCore's licence before copying any code verbatim (architecture inspiration is fine). -Sequencing: **deferred until the trail/waypoints/nav polishing is finished.** -The radio-side CAD windowing lives on the `feat/power-saving` branch and is now -**unblocked** by the preamble fix. First step when picked up: rebase it onto the -v1.16 merge (it will hit the removed `radio_set_tx_power()`/`radio_set_params()` -globals β€” replace with `radio_driver.*` like the merge did), then re-test whether -long messages still drop at preamble 32, ideally profiling with a PPK2/meter. +Sequencing: phase 1 (CAD windowing) is done and in field test on +`feat/power-saving`; see the status block at the top of this entry for what's +left (measurement, warm sleep, window tuning, APC). ### SOS broadcast diff --git a/src/helpers/radiolib/CustomSX1262Wrapper.h b/src/helpers/radiolib/CustomSX1262Wrapper.h index cc7bb223..1889a866 100644 --- a/src/helpers/radiolib/CustomSX1262Wrapper.h +++ b/src/helpers/radiolib/CustomSX1262Wrapper.h @@ -40,6 +40,17 @@ public: void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } + // Warm sleep between CAD scans: ~1.5 Β΅A vs ~600 Β΅A in standby-RC. + // cadWake() delays 5 ms so the TCXO fully re-stabilises before the next scan + // (previous attempt without the delay corrupted long packets via freq drift). + void cadSleep() override { + ((CustomSX1262 *)_radio)->sleep(true); // warm sleep β€” config retained + } + void cadWake() override { + _radio->standby(); + delayMicroseconds(5000); // TCXO stabilisation after warm sleep + } + void setRxBoostedGainMode(bool en) override { ((CustomSX1262 *)_radio)->setRxBoostedGainMode(en); } diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index 45b266ef..57e27f63 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -19,10 +19,10 @@ #define PS_SLEEPING 2 // standby between scans, waiting for _ps_timer #define PS_RXING 3 // CAD hit β†’ full receive armed, waiting for the packet -// Time between CAD scans. Must be shorter than the on-air preamble so a scan -// always lands inside it (16-symbol preamble β‰ˆ 66 ms at SF8/BW62.5). Tuned for -// this firmware's fixed LoRa params β€” revisit if SF/BW change. -#define PS_CAD_INTERVAL_MS 45 +// Time between CAD scans. Must be shorter than half the on-air preamble so at +// least two scans land inside it. 32-symbol preamble β‰ˆ 131 ms at SF8/BW62.5 +// β†’ 65 ms gives 2 guaranteed windows with margin. Revisit if SF/BW change. +#define PS_CAD_INTERVAL_MS 65 // If CAD detects activity but no packet actually lands, fall back to scanning // instead of sitting in continuous RX forever. #define PS_RX_TIMEOUT_MS 1500 @@ -144,6 +144,7 @@ void RadioLibWrapper::startRecv() { // If CAD isn't supported by the module, permanently fall back to continuous RX. void RadioLibWrapper::armRecv() { if (_power_save) { + cadWake(); // bring the radio out of warm sleep first int16_t e = _radio->startChannelScan(); if (e == RADIOLIB_ERR_NONE) { _ps_phase = PS_SCANNING; state = STATE_RX; return; } MESH_DEBUG_PRINTLN("RadioLibWrapper: CAD unsupported (%d) β€” power-save off", (int)e); @@ -159,7 +160,7 @@ void RadioLibWrapper::armRecv() { } void RadioLibWrapper::enterCadSleep() { - _radio->standby(); // low-power between scans (TODO: warm sleep for more savings) + cadSleep(); // warm sleep (SX126x) or standby fallback between scans _ps_phase = PS_SLEEPING; _ps_timer = millis() + PS_CAD_INTERVAL_MS; state = STATE_RX; // keep isInRecvMode() true so the dispatcher doesn't flag a stuck radio diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index ea98f627..d39e3ebf 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -30,6 +30,11 @@ protected: void armRecv(); // arm RX honouring power-save (CAD) or continuous void powerSaveLoop(); void enterCadSleep(); + // Low-power state between CAD scans. Default = standby; SX126x overrides with + // a warm (config-retained) sleep for ~Β΅A idle. cadWake() brings it back to a + // responsive standby before the next scan. + virtual void cadSleep() { _radio->standby(); } + virtual void cadWake() { _radio->standby(); } public: RadioLibWrapper(PhysicalLayer& radio, mesh::MainBoard& board) : _radio(&radio), _board(&board), _preamble_sf(0) { n_recv = n_sent = 0; }