Skip to content

perf(power): light-sleep power management — BLE-wakeable sleep + 240MHz-when-live (AI FYI) - #121

Open
johnbuckman wants to merge 3 commits into
mainfrom
ai-fyi/ble-wakeable-sleep
Open

perf(power): light-sleep power management — BLE-wakeable sleep + 240MHz-when-live (AI FYI)#121
johnbuckman wants to merge 3 commits into
mainfrom
ai-fyi/ble-wakeable-sleep

Conversation

@johnbuckman

@johnbuckman johnbuckman commented Aug 10, 2026

Copy link
Copy Markdown

⚠️ AI-written, FYI only. This change and its testing were done by an AI agent (Claude) on a hardware-in-the-loop bench (KM003C USB power analyzer + real BLE via the tcl-ble-osx CoreBluetooth driver). Posted for review and discussion, not as something that has to be merged. Numbers are from a single V8.1 unit, full battery, room temperature.

Builds on the automatic light sleep in #119 (its two commits are included here) and turns it into a complete, demand-matched power profile. Three commits:

  1. Tier-2 automatic light sleep (= perf(power): Tier-2 automatic light sleep, -64% idle (AI FYI) #119).
  2. BLE-wakeable low-power sleep (soft-sleep yields 50 ms so the SoC light-sleeps between BLE events).
  3. Pin CPU to 240 MHz when connected + live (extension headroom).

Deep sleep is untouched: double-tap-O and the BLE "off" command (03 0A 02) still fully power down the radio (µA, button/charger wake only).

The resulting power profile (all measured over real BLE)

State Power CPU
Deep sleep (double-tap / BLE "off") µA radio off
Connected + soft-sleep (BLE "sleep" 03 0A 04 01) 31.4 mA released → light-sleep
Not connected, idle (advertising) ~40 mA DFS + light-sleep
Connected + live (weighing / extensions) 64.4 mA 240 MHz pinned
(stock idle-ON, for reference) 110.6 mA 240 fixed

1–2. BLE-wakeable low-power sleep

Makes the "sleep" state (03 0A 04 01 — scale sleeps but stays connected so the app can wake it) genuinely low-power. Stock "sleep" only turns off the OLED + peripheral rails; the CPU stays at 240 MHz and BLE never sleeps. This lets the SoC light-sleep between connection events.

Apples-to-apples, connected + BLE-wakeable sleep: stock 103.8 mA → this 31.4 mA = −70% (3.3×), with wake unchanged (5/5 at ~105 ms, stable connection, 0 disconnects).

3. 240 MHz pin when connected + live

For the upcoming on-scale extension mechanism: hold an ESP_PM_CPU_FREQ_MAX lock while (deviceConnected && !b_softSleep) so extensions get full compute during active connected use; release it otherwise so DFS scales down and light sleep engages. Measured: connected + live 64.4 mA (240 pinned) vs connected + soft-sleep 31.4 mA (released → light-sleep, unchanged — the sleep savings are fully preserved), wake still 5/5. Using a PM lock rather than a raw setCpuFrequencyMhz is deliberate — the latter fights DFS.

How it was verified

Real BLE central (macOS via tcl-ble-osx): connect, stream weight (~10 Hz), 5 sleep→wake cycles — notifications stop in sleep and resume on wake, wake latency a consistent ~105 ms, 0 disconnects / 0 write errors. This also settled the standing risk that the light-sleep firmware (BLE on the internal RC clock — this board has no 32 kHz crystal) might not hold a BLE connection. It does — rock stable. All runtime code is #if CONFIG_PM_ENABLE-guarded, so the stock precompiled build is unchanged. Build: pio run -e esp32s3_pm -t upload.

What I tried that did NOT help (and reverted)

Requesting a slow BLE connection interval / high slave latency on soft-sleep entry (ble_gap_update_params) to cut radio wakeups further. Measured: no power change (wake latency stayed 105 ms → macOS as central rejected the request; CoreBluetooth is documented to clamp/ignore peripheral connection-parameter requests) and it broke wake reliability (1/5 + a disconnect from the per-cycle param toggling). Reverted. It may help with a phone central that honors conn-param requests, but that can't be validated on this bench.

Known gaps / not verified

  • Measured over USB, so all mA figures include the CH340 bridge chip (~a few mA) that powers the bench cable. On battery that's gone, so the true draw is lower and the relative sleep win is even larger than 3.3×. Exact battery-side figures need a shunt / PPK2.
  • The Decent heartbeat must keep flowing during sleep or the scale disconnects (~5 s, b_requireHeartBeat) and can no longer be BLE-woken. Left as-is deliberately (soft-sleep is typically USB-plugged, where the auto-off path is gated off anyway).
  • The 240 mA figure is with the CPU pinned but doing light work — a busy extension draws more on top; if some extensions are light-duty the lock could later be made opt-in per-extension.
  • Relationship to the other AI-FYI PRs: this includes perf(power): Tier-2 automatic light sleep, -64% idle (AI FYI) #119 (Tier-2 light sleep) and is an alternative to perf(power): scale CPU to 80MHz when radios idle (AI FYI, -25% idle) #118 (manual CPU scaling, which DFS subsumes). It needs a from-source framework rebuild (the CONFIG_PM_* flags are compile-time); the [env:esp32s3_pm] env carries them.

decentjohn and others added 3 commits August 10, 2026 16:08
Enable ESP-IDF automatic light sleep (CONFIG_PM_ENABLE + tickless idle +
esp_pm_configure light_sleep=on), with the NimBLE controller's low-power
clock on RTC_SLOW (internal RC) since this board has no external 32kHz
crystal. New opt-in from-source env [env:esp32s3_pm] carries the sdkconfig.

Config flags alone do nothing here: the main loop never blocks, so the
FreeRTOS idle task never runs and tickless idle never enters. The fix is a
blocking yield at the end of loop(); with it, idle drops 110.6 -> 39.7 mA
(-64%) on a V8.1 unit. All runtime bits are guarded by CONFIG_PM_ENABLE, so
the change is inert on the stock precompiled build.

Serial is hardened for light sleep: UART0 wake-on-rx, plus a stay-awake
grace window after serial activity (t_lastSerialActivity) so a command's
bytes aren't lost to a mid-command sleep. Functional serial regression is
consistently 8/8 with idle power unchanged.

AI-written change, posted as an FYI for review; not necessarily to merge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make the soft-sleep state (BLE "sleep" cmd 03 0A 04 01) genuinely low-power:
in soft-sleep the main loop yields 50ms instead of 10ms, so the SoC light-
sleeps in longer chunks between BLE connection events while staying reachable.
Builds on the automatic light sleep from the Tier-2 change; deep sleep is
unchanged (double-tap and BLE "off" 03 0A 02 still fully power down the radio).

Verified over real BLE (macOS central): connected BLE-wakeable soft-sleep drops
from 103.8 mA (stock) to 31.4 mA (-70%, 3.3x), wake stays 5/5 at ~105ms with a
stable connection. Guarded by CONFIG_PM_ENABLE; inert on the stock build.

AI-written change, posted as an FYI for review; not necessarily to merge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On the DFS/light-sleep build, hold an ESP_PM_CPU_FREQ_MAX lock while
(deviceConnected && !b_softSleep) so on-scale extensions (the upcoming
extension mechanism) get full compute during active connected use; release
it otherwise so DFS scales down and light sleep engages. Measured over real
BLE: connected+live 64.4mA (240 pinned) vs connected+soft-sleep 31.4mA
(released -> light sleep, unchanged), wake still 5/5. Guarded by
CONFIG_PM_ENABLE; inert on the stock build.

AI-written change, posted as an FYI for review; not necessarily to merge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@johnbuckman johnbuckman changed the title perf(power): BLE-wakeable low-power sleep, ~70% lower (AI FYI) perf(power): light-sleep power management — BLE-wakeable sleep + 240MHz-when-live (AI FYI) Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants