Power & sleep
Goal: a solar/battery client node spends most of its life with the CPU
halted, waking only for its scheduled lane windows and for radio interrupts.
The design borrows Meshtastic’s proven mechanics (their PowerFSM.cpp +
sleep.cpp) and simplifies the state space because chIRpChat’s lane schedule
gives us something Meshtastic doesn’t have: the node knows in advance when
traffic can possibly arrive.
States
Section titled “States”ACTIVE ──idle 15 s──► DOZE ──idle 5 min──► NAP ◄──► (scheduled wakes) ▲ ◄──any event── ▲ ◄──IRC/radio── │ └── USB/WiFi session active pins ACTIVE └─ battery < cutoff ──► HIBERNATE- ACTIVE — USB host attached / WiFi session open / TUI in use / traffic in flight. Full clocks. (USB-ECM means a laptop session pins ACTIVE; USB supplies power then anyway.)
- DOZE — radio in RX on the current lane, CPU in light sleep between
events. ESP32:
esp_light_sleep_start()with GPIO wake onDIO1(RX-done) and timer wake for the next lane-window edge — identical wiring to Meshtastic’senableLoraInterrupt()(DIO1 high-level wake, GPIO 39 on the XIAO Wio). Wake latency ~1 ms; the SX1262 receives autonomously while the CPU sleeps, so nothing is missed. - NAP — between scheduled windows the radio itself sleeps too
(SX126x sleep, ~1.6 µA, warm start). The node wakes by timer for: its
router’s lane windows (uplink/downlink), beacon slots, and a slow
housekeeping tick. Clock drift is re-disciplined by each heard beacon
(
BEACON.time); the wake margin widens automatically with measured drift (±(drift_ppm × seconds_asleep)guard band). - HIBERNATE — deep sleep, radio off, RTC timer + button wake only. Voltage thresholds and hysteresis copied from Meshtastic field practice.
Routers ship with sleep disabled (they are the schedule); solar routers may enable NAP between their own advertised windows — their published lane plan already tells every client when the router is deaf, so sleeping is free of protocol consequences. That’s the design’s quiet win: the lane plan is simultaneously the radio schedule and the power schedule.
No light-sleep equivalent needed: the nRF52’s idle current with SoftDevice
and sd_app_evt_wait() is already ~µA-class between events; DIO1 is a
GPIOTE wake. NAP/HIBERNATE map to System ON idle / System OFF. The core’s
lrc::Clock/lrc::PowerGov interfaces hide the difference; the firmware
HALs implement them per-platform (see firmware/).
Rules the code must keep
Section titled “Rules the code must keep”- No busy-waiting anywhere — every wait is an event or a timer.
- The radio is never left in TX-capable limbo: RF switch state is re-asserted on every wake (HARDWARE.md §RF switch safety).
- Flash flushes (STORAGE.md) run only in ACTIVE/DOZE on the idle hook.
- Every state transition increments a telemetry counter; unexplained
wake storms must be visible in
/msg *lrc stats power(field debugging beats lab guessing).