ESP-IDF v6.0 firmware for an open-source BLE button transmitter designed to be indistinguishable from a Shelly BLU Button from the point of view of BluButtonBridge.
AGENTS.mddocs/WORKFLOW.mddocs/COMPATIBILITY_NOTES.mddocs/PAGES_INSTALLER_CONTRACT.mddocs/BOOTSTRAP_PLAN.md
Only for Codex runtime or review-phase work:
.codex/config.toml.codex/agents/*.toml
This repository starts intentionally small:
- no web UI on the device
- no Wi-Fi or AP mode
- no OTA runtime flow on the device
- no MQTT or GPIO action engine
- USB-powered experimental phase first
- board-aware multi-target build setup from day zero
- browser-based installer hosted on GitHub Pages
The first goal is to prove the compatibility path:
- stable device identity
- persisted AES key
- persisted monotonic counter
- 4 button events
- BLE encrypted advertising compatible with the existing bridge receiver
- deep-sleep idle with button-triggered wake
BluButton does not initialize or use Wi-Fi in the v0 runtime.
On ESP32-class targets, ESP-IDF still forces CONFIG_ESP_WIFI_ENABLED=y
for SOC_WIFI_SUPPORTED chips in Kconfig, so some Wi-Fi and LWIP components
remain in the build graph even though the firmware does not provide any Wi-Fi
feature or control surface.
- ESP32 DevKit V1
- ESP32-C3 SuperMini
- Seeed XIAO ESP32-C3
Terms used in this repo:
board id: canonical profile id fromconfig/boards.json, for examplexiao-esp32-c3board alias: short token accepted by helper scripts, for examplexiao-c3target: the ESP-IDF chip target such asesp32oresp32c3
For backward compatibility, the generic esp32c3 alias still resolves to esp32c3-supermini.
Use xiao-esp32-c3 or xiao-c3 explicitly for the XIAO profile.
- ESP-IDF v6.0 environment export at
~/esp/esp-idf-v6.0/export.sh jqesptool- a USB data cable
ESPPORT=/dev/...when flashing or opening the monitor
To install the recommended ESP-IDF layout:
mkdir -p ~/esp && cd ~/esp
git clone --recursive --branch v6.0 https://github.com/espressif/esp-idf.git esp-idf-v6.0
cd esp-idf-v6.0
PYTHON=$(which python3.12) ./install.sh esp32 esp32c3Use the helper script so each board keeps its own generated sdkconfig in its build directory while the repo stores target defaults plus board overlays in configs/:
source ~/esp/esp-idf-v6.0/export.sh
scripts/idf-target.sh esp32 build
scripts/idf-target.sh esp32c3-supermini build
scripts/idf-target.sh xiao-esp32-c3 buildYou can also use the board aliases:
scripts/idf-target.sh esp32-devkit-v1 build
scripts/idf-target.sh esp32c3-supermini build
scripts/idf-target.sh xiao-c3 buildFor flashing and serial monitoring, provide the port explicitly:
source ~/esp/esp-idf-v6.0/export.sh
ESPPORT=/dev/cu.usbmodem3101 scripts/idf-target.sh esp32c3-supermini flash
ESPPORT=/dev/cu.usbmodem3101 scripts/idf-target.sh esp32c3-supermini monitor
ESPPORT=/dev/cu.usbmodem3101 scripts/idf-target.sh xiao-esp32-c3 flash
ESPPORT=/dev/cu.usbmodem3101 scripts/idf-target.sh xiao-esp32-c3 monitorThe repository includes a GitHub Pages installer based on ESP Web Tools.
- Expected installer site:
https://robertoamd90.github.io/blu-button/ - Installer source:
site/index.html - Shared board catalog:
config/boards.json - Pages workflow:
.github/workflows/pages.yml
The browser installer flashes the mirrored board-specific split install set:
bootloader.binpartition-table.binota-data.bin- app image
using the board-specific offsets from config/boards.json.
That keeps ordinary browser reinstalls aligned with the NVS persistence contract.
When the installer offers a clean-erase choice, that path is for explicit reprovisioning, not the default reflash flow.
Each release should include the board-specific full images for manual recovery and release completeness:
BluButton-esp32-devkit-v1-full.binBluButton-esp32c3-supermini-full.binBluButton-xiao-esp32-c3-full.bin
For local release packaging from existing build output:
source ~/esp/esp-idf-v6.0/export.sh
scripts/package-release.sh allThat script also produces the board-specific app images:
BluButton-esp32-devkit-v1.binBluButton-esp32c3-supermini.binBluButton-xiao-esp32-c3.bin
The packaging step also emits the split artifacts used by the browser installer:
BluButton-<board>-bootloader.binBluButton-<board>-partition-table.binBluButton-<board>-ota-data.bin
The merged full.bin remains useful for manual recovery and low-level reprovisioning, but it is not the default browser-install path.
In the current phase, the maintenance surface is intentionally simple:
- the normal idle state is deep sleep
- a button press wakes the device, opens a short gesture-capture session for single, double, triple, long, and maintenance hold detection, and returns to deep sleep after the local action or BLE advertisement completes
- BLE startup overlaps with the gesture session so the wake-to-advertise path stays bounded instead of serializing all startup cost after classification
- on first boot, the device generates and stores an AES key if one is not already present
- the firmware may print device identity and registration credentials on the serial log, but that is no longer the primary operator path for key retrieval
- a wake-triggering hold is credited for the boot time already spent before classification, so long and maintenance holds are not shortened by the deep-sleep resume path
- a 10-second hold remains reserved for local maintenance behavior such as reprinting credentials
After flashing, open a serial monitor at 115200 baud. From this repo the simplest path is:
source ~/esp/esp-idf-v6.0/export.sh
ESPPORT=/dev/cu.usbmodem3101 scripts/idf-target.sh xiao-esp32-c3 monitorFor reliable AES-key extraction, prefer the dedicated script instead of depending on runtime log timing or USB serial continuity across deep sleep:
bash scripts/read-device-aes-key.sh --port /dev/cu.usbserial-0001That script reads the nvs partition through the ROM bootloader path and prints the stored identity:aes_key value as lowercase hex. This is the recommended maintenance path on boards such as the ESP32-C3 where the native USB serial link may drop during deep sleep.
The bare XIAO ESP32-C3 does not expose a firmware-controlled user LED, so local LED feedback patterns are a no-op on that board profile unless a later hardware-specific profile adds an external LED.
For a mobile enclosure build with XIAO ESP32-C3, battery, and 4 buttons, see the printable case project: https://www.printables.com/model/1698587-blu-button-case
- board catalog in
config/boards.json - target helper scripts in
scripts/ - per-target
sdkconfigbases plus board overlays inconfigs/ - board profile abstraction in
components/board_config/ - version derivation from
git describe - GitHub Pages browser installer in
site/ - Pages deployment workflow in
.github/workflows/pages.yml
- web UI hosted by the device
- Wi-Fi / AP runtime
- MQTT manager
- OTA manager and staged OTA flow
- console SSE plumbing
- bridge-side BLE receiver implementation
docs/WORKFLOW.md: development cycle and multi-agent review expectationsdocs/BOOTSTRAP_PLAN.md: current v0 status, validation gaps, and likely next slicesdocs/COMPATIBILITY_NOTES.md: BLE compatibility contract toward BluButtonBridge and generic BTHome listenersdocs/PAGES_INSTALLER_CONTRACT.md: browser installer and Pages deploy contractAGENTS.md: contributor and coding-agent workflow guardrails.codex/config.tomland.codex/agents/*.toml: project-scoped Codex runtime settings and review-phase subagent contracts