Workbench
I Took an M5StickC Plus from 2.3 Hours to About 12 Hours on Battery

My M5StickC Plus lasted roughly 2.3 hours as a wireless desk display. Relaxing its Bluetooth connection pushed that to about 3.5 hours. The real fix—letting ESP-IDF coordinate light sleep—brought the measured idle runtime to roughly 12 hours without making approval prompts wait for a manual wake-up.
The code is public in my claude-desktop-buddy fork. The device is an M5StickC Plus running the screen-side half of my Buddy Bridge setup. It shows activity from coding sessions and can approve or deny an opted-in request from two physical buttons.
That sounds like a light workload: a 135-by-240 display, a Bluetooth Low Energy connection, and an animation that mostly sleeps. The first battery result made it clear that “mostly” was doing too much work.
The display was off, but the processor and radio were not
The firmware already dimmed and shut off the screen when it was idle. It also reduced animation, display, and motion-sensor work. None of those changes gave the device the kind of runtime I expected from a desk companion.
The original low-power pass lowered the screen-off CPU clock, throttled the idle loop, sampled the IMU less often, and stopped advertising after a Bluetooth client connected. It also increased the stale-data window so the slower idle heartbeat would not make the firmware declare the bridge dead.
Those are sensible reductions, but BLE still has timing requirements. The radio must wake for connection events, and the processor cannot be pushed below a clock floor that quietly disables that path.
Forty megahertz made the sleeping device unreachable
I tried dropping the ESP32 core to 40 MHz while the screen was off. The battery number improved, but the device stopped behaving like a remote. Buddy Bridge could connect, then the link would disappear roughly 70 seconds into screen-off. Re-advertising also vanished, so the bridge could not find the stick again until I woke it by hand.
The problem was not the approval logic. The ESP32 Bluetooth radio needed the APB clock at 80 MHz or higher. The older board library had happened to keep that clock domain alive. Moving to M5Unified exposed the too-low floor.
I raised the screen-off clock back to 80 MHz and kept the crystal domain powered for Bluetooth. That restored reconnection and prompt delivery. It also removed an attractive but invalid power-saving number: battery life does not count if the device misses the event it exists to show.
A manual light-sleep loop looked like a brownout
The next attempt called esp_light_sleep_start() directly during idle gaps. About nine seconds after the screen turned off, the ESP32-PICO-D4 rebooted. The first suspicion was a brownout because the failure appeared only in the low-power path.
A debug build made the reset reason visible over serial and on the boot splash. It was an RTC watchdog reset, not a brownout. The manual sleep loop conflicted with the watchdog and the crystal arrangement needed by BLE on this chip revision.
I tried the obvious watchdog controls before abandoning the path. Disabling, feeding, and changing the RTC watchdog did not make the loop stable. A custom bootloader with watchdog changes did not fix it either. The manual call was the wrong level of control.
The supported power manager did what the loop could not
The working design moves the firmware to Arduino built as an ESP-IDF component. That enables CONFIG_PM_ENABLE and tickless idle, then configures automatic light sleep with esp_pm_configure().
ESP-IDF can coordinate the scheduler, radio, dynamic frequency scaling, and watchdogs before entering a sleep interval. The firmware no longer guesses when those subsystems are safe to pause. The from-source build stayed connected for more than 100 seconds of screen-off testing with no reboot, GIF characters intact, and prompts waking directly into the approval screen.
The measured idle result rose from about 2.3 hours to about 12 hours. That is a little over five times the starting runtime. It is not a promise for every M5StickC Plus: screen brightness, battery age, animation, connection quality, and active use all change the result. It is the measured result recorded for this hardware and firmware path.
Bluetooth still needed an active and an idle personality
Automatic light sleep handled most of the power reduction, but the BLE schedule still mattered. A desk display does not need the same radio responsiveness while its screen is dark as it does while a permission prompt is waiting.
The firmware now asks for a relaxed connection while idle and a tight connection after wake-up. The idle request uses a 30–60 ms interval, slave latency of eight, and a ten-second supervision timeout. Waking requests 7.5–22.5 ms with no slave latency and a four-second supervision timeout.
That split keeps the radio out of unnecessary idle events without forcing every prompt through a sleepy schedule. The first BLE-only tuning pass moved runtime from roughly 2.3 to 3.5 hours. After ESP-IDF light sleep carried the larger savings, the idle latency could be reduced from twelve to eight to favor connection reliability.
The battery page had to measure the whole session
An instantaneous current reading is misleading when I open the battery page: the screen has just turned on, the processor is active, and the radio may be busy. That number describes inspection, not idle life.
The M5StickC Plus has an AXP192 power-management chip with a hardware coulomb counter. The firmware snapshots that counter when USB power is removed and integrates the discharge over the battery session. The device page can then show elapsed battery time, average draw, and projected time remaining.
The newer M5StickS3 is supported by the same codebase, but its M5PM1 does not expose the same current-sense and coulomb-counter path. On that board the battery page falls back to a voltage estimate. The firmware keeps that limitation visible instead of inventing precision.
There are two build paths for a reason
The normal PlatformIO environments use the precompiled Arduino framework. They are easier to build and currently cover the M5StickC Plus, M5StickS3, bench, and debug targets in GitHub Actions. The latest main-branch CI run passed all four of those builds at commit 8b7eb0f.
The approximately 12-hour path is the separate ESP-IDF environment. It builds Arduino and LittleFS from source, pulls an ESP-IDF component, and takes longer. The repository’s CI file deliberately excludes that network-dependent build; the project history records it as built and measured locally. I did not rerun the PlatformIO build or repeat the battery discharge test for this article.
The published 0.1.0 release also predates the 12-hour work. Its prebuilt M5StickC Plus images remain useful, but downloading that release does not install the current power-management path. Anyone who wants the longer-runtime build must currently build the main branch from source.
What I would buy for a new build
I would not hunt down an old M5StickC Plus just to reproduce my battery number. M5Stack now marks the M5StickC Plus2 as end-of-life and points new buyers to the M5StickS3. The S3 is the current retail board, with a 250 mAh battery, native USB-C, 8 MB of flash, and 8 MB of PSRAM.
The firmware builds for the S3 and the project history records a hardware check, but its power hardware is different. It has no AXP192 coulomb counter, and I have not repeated the approximately 12-hour discharge test on it. I would buy the S3 for a new desk display, not assume that the old board’s runtime measurement transfers unchanged.
Use the right image when updating an already-paired stick
The release has a merged image for a blank device and an app-only image for an update. They are not interchangeable.
# Blank device: bootloader, partitions, and app
esptool.py --chip esp32 write_flash 0x0 claude-desktop-buddy-<version>-merged.bin
# Existing paired device: application only
esptool.py --chip esp32 write_flash 0x10000 claude-desktop-buddy-<version>-app.bin
The merged image starts at 0x0 and overwrites NVS at 0x9000. That removes the BLE bond and saved settings. If the computer still holds the old bond, the new connection can drop about one second after connecting because the two sides no longer share the same keys.
For a routine update, the app image at 0x10000 preserves NVS. If I intentionally use the merged image, I also remove the old Bluetooth device from Windows before pairing again. The problem is stale identity, not weak signal.
What I would keep from this work
The largest battery improvement did not come from turning one knob lower. It came from giving power management to the layer that understands the scheduler and radio together.
The debugging order mattered just as much. I made the reset reason visible before changing more hardware settings. I rejected a 40 MHz result when the radio stopped working. I separated an instantaneous current display from a whole-session measurement. I kept a simpler build path when the best runtime required a more complicated toolchain.
A twelve-hour desk device is still not a week-long sensor. It is enough to spend a workday away from USB while remaining available for the one event that matters.
Disclosure: I maintain this fork and Buddy Bridge. The firmware began as Anthropic’s Hardware Buddy example and credits ToxicOrca for battery-related work merged into this fork. The projects are unofficial and not affiliated with or endorsed by Anthropic. Repository and hardware links are not affiliate links. The hero is a project photograph from the firmware repository.