LYNXcat Power Management
Progressive power state system for battery-powered LYNXcat devices. Reduces consumption during idle periods through coordinated changes to scan rate, LED brightness, and radio state.
Overview
LYNXcat implements a progressive power management system with three active states:
- ACTIVE — Full performance during typing/input
- IDLE — Reduced scan rate and LED dimming after short idle
- DROWSY — Radio shutdown and minimal LEDs after extended idle
Power management is decoupled from hardware. The power_task state machine writes to a global atomic POWER_STATE. All other tasks read this state to adapt their behavior. No shared mutable state, no locks.
Implemented States
| State | Scan Rate | LED0 | LED1 | Radio | Current | Timeout |
|---|---|---|---|---|---|---|
| ACTIVE | 1kHz (1ms) | 10% | 10% | ON | ~50mA | — |
| IDLE | 100Hz (10ms) | 5% | 5% | ON | ~15mA | 3min idle |
| DROWSY | 50Hz (20ms) | 1% | OFF | OFF | ~8-12mA | 10min idle |
Notes:
- LED0 — Layer color indicator, or caps lock breathing if active
- LED1 — Layer color indicator, requires feedback from tower
- Current draw — Measured on Banana Pi BPI-Leaf-S3 with 500mAh battery
- DROWSY radio — ESP-NOW idle (not fully deinitialized, see limitations)
Architecture
Progressive power states achieved through atomic state machine and lock-free reads.
┌─────────────────────────────────────────────────────────────────┐
│ LYNXcat Tasks │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ finger_matrix│ │ thumb_matrix │ │ scroll_wheel │ ... │
│ │ _task │ │ _task │ │ _task │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └────────────┬────┴────────────────┘ │
│ ▼ │
│ ActivityTracker │
│ (atomic timestamp + signal) │
│ │ │
│ ┌────────────┴────────────┐ │
│ ▼ ▼ │
│ power_task POWER_STATE │
│ (state machine) (atomic global) │
│ │ │ │
│ │ ┌──────────┼──────────┐ │
│ │ ▼ ▼ ▼ │
│ │ scan_interval() led_brightness_factor() │
│ │ │ │ │
│ │ ▼ ▼ │
│ │ Scanner tasks LED task │
│ │ (query rate) (query brightness) │
└─────────┴────────────────────────────────────────────────────────┘
Components:
ActivityTracker — Lock-free activity timestamp tracking
- Stores last activity time as
AtomicU32(milliseconds since boot) - Provides
Signalfor immediate wake notification - Updated by all scanner tasks on input detection
power_task — State machine loop
- Reads
ActivityTrackerto determine target state - Writes transitions to
POWER_STATEatomic - Uses
select()to wake on timeout OR activity signal - No hardware handles, pure state logic
POWER_STATE — Global atomic AtomicU8
- Written only by
power_task - Read by all tasks via
current_power_state() - Relaxed ordering (no synchronization needed)
Helper functions — Query current state behavior
scan_interval()— Matrix scan rate for current stateled_brightness_factor()— LED brightness multiplierled1_enabled()— Whether LED1 should be onradio_enabled()— Whether ESP-NOW should transmit
State Transitions
Progressive timeout cascade with instant wake on activity.
ACTIVE ──(3min idle)──▶ IDLE ──(10min idle)──▶ DROWSY
▲ ▲ │
│ │ │
└──(any input)───────┴──────(any input)──────┘
Transition Logic:
From ACTIVE:
- After 3min idle → IDLE (scan to 100Hz, LEDs to 5%)
From IDLE:
- After 10min cumulative idle → DROWSY (scan to 50Hz, LED0 to 1%, LED1 OFF, radio idle)
- Any input → ACTIVE (instant)
From DROWSY:
- Any input → ACTIVE (instant, radio already initialized)
Wake Behavior:
Activity signal provides immediate transition to ACTIVE, bypassing periodic timeout checks. Scanner tasks call ActivityTracker::record_activity() which:
- Updates atomic timestamp
- Signals
power_taskvia EmbassySignal power_taskwakes fromselect()and transitions to ACTIVE
Implementation Freeze: Light Sleep & Deep Sleep
Light Sleep (Phase 3) and Deep Sleep (Phase 4) implementations are frozen indefinitely.
Known ESP32-S3 Limitations
Embassy timer loss:
- TIMG0 is clock-gated during light sleep
- Embassy doesn't track how much time passed during sleep
- All
Timer::after(),Ticker, and timeout futures have incorrect deadlines - No documented compensation mechanism exists
WiFi/ESP-NOW reinit:
- WiFi must be stopped before light sleep (per ESP-IDF spec)
StaticCellRAII prevents re-initialization after wake- WiFi restarts on channel 1 (esp-rs issue #9855)
- Peer info deleted on WiFi stop, requires re-registration
Known Embedded Rust Limitations
No async sleep pattern:
- No documented example exists for Embassy async + ESP32-S3 light sleep in
no_stdRust - No esp-hal API for light sleep with GPIO wake sources (as of esp-hal 1.0)
- No esp-radio API for WiFi/ESP-NOW stop/restart lifecycle
Required for Unfreeze
One or more of these must exist:
- Embassy light sleep support with automatic time compensation
- esp-radio reinit API without
StaticCellissues - Stable esp-hal sleep API with GPIO wake configuration
- Community example showing working pattern
Practical Floor
The DROWSY state at ~8-12mA is the practical power floor for now. Further reduction requires solving the light sleep integration problem, which is an ecosystem-level challenge beyond this project.
CPU clock reduction (240MHz → 80MHz) could bring DROWSY closer to ~5mA without light sleep complexity, if esp-hal exposes clock configuration APIs.
Code Organization
Location: firmware/cat/src/power/
| File | Purpose |
|---|---|
mod.rs | PowerState enum, POWER_STATE atomic, helper functions |
manager.rs | power_task state machine loop |
config.rs | PowerConfig timeout constants |
activity.rs | ActivityTracker atomic timestamp and signal |
Integration Points:
firmware/cat/src/bin/main.rs— Spawnspower_task, passesActivityTrackerto scanner tasksfirmware/cat/src/feedback/led_controller.rs— Queriesled_brightness_factor()andled1_enabled()firmware/cat/src/comm.rs— Queriesradio_enabled()inradio_task- Scanner tasks (finger, thumb, scroll, mouse) — Call
ActivityTracker::record_activity()on input
See Also
Concept documentation:
docs/power/concept.md— Full power state specifications and battery life calculationsdocs/power/idle.md— Phase 1 implementation plan (ACTIVE ↔ IDLE)docs/power/drowsy.md— Phase 2 implementation plan (DROWSY state)docs/power/light-sleep.md— Phase 3 blocking issues and research notes
Implementation:
firmware/cat/src/power/— Power management source codefirmware/cat/src/comm.rs— Radio lifecycle with power state awarenessfirmware/cat/src/feedback/led_controller.rs— LED rendering with brightness adaptation