This repository contains ESP-IDF firmware for the M5Stack StickS3. The current firmware is not an OS-native Bluetooth audio endpoint. It is a StickS3-targeted local-rule automation and configuration application with BLE status/rule events, Wi-Fi/web configuration, GPIO/button triggers, and selected action outputs.
Bluetooth transport note: StickS3 uses an ESP32-S3 controller, which supports Bluetooth LE but does not support Classic Bluetooth / BR/EDR. The firmware's Bluetooth path is a custom BLE GATT status/rule-event service only; it cannot pair as a Classic Bluetooth audio endpoint, headset, speaker, serial-port, or audio device.
Flash image note:
build/m5sticks3_local_automation_app.binis the ESP-IDF application partition only. Do not flash that file at offset0x0; useidf.py flashor the merged factory image generated bytools/make_factory_image.py. The firmware uses the checked-inpartitions.csvwith a factory app slot expanded to the end of the 8 MB flash (0x7F0000bytes from offset0x10000); the ESP-IDF default 1 MiB factory slot is too small for the current BLE/Wi-Fi/LCD/rule-automation image and will reset-loop before the LCD can turn on.
This section is a direct status overlay from the current C implementation, not from older comments or planning text. Use the status labels below when judging what is actually wired into the default firmware:
- ✅ Implemented and wired on default boot: executed from
app_main()or reachable through tasks/callbacks it starts. - 🟡 Kconfig-controlled / conditionally wired: source support exists and is wired when its Kconfig option is enabled; maintainers can still disable it for smaller or bring-up builds.
- ⛔ Not implemented / fail-closed: validation rejects it, the dispatcher returns unsupported, or no driver path exists.
- 🧪 Hardware calibration/bench check needed: the code path is implemented and covered by host/static checks, but measurements on a real StickS3 are still needed before product claims.
- 🚫 Not planned / not supported by StickS3 hardware: intentionally out of scope for this firmware or impossible on ESP32-S3/StickS3 hardware.
On the default esp32s3 build the app currently does the following:
- ✅ Initializes NVS, app time, ESP-NETIF, and the default event loop.
- ✅ Starts the StickS3 status UI, LCD/menu path when enabled, and the two documented buttons.
- ✅ Starts Wi-Fi station/setup-AP support used by Web UI and HTTP actions.
- ✅ Loads the local automation rule configuration from NVS, or safe defaults if the stored blob is missing/invalid.
- ✅ Starts the rule runtime, action worker, GPIO polling, network-state polling, and BLE-state polling tasks; the Web UI HTTP server is deferred until the Web UI service is enabled from the on-device menu.
- ✅ Starts the custom BLE GATT status/rule-event service, publishes BLE status, and keeps the device alive in an error state if BLE startup fails.
- ✅ Includes sound-level capture in the default build through
CONFIG_APP_SOUND_LEVEL_TRIGGERS=y:app_main()initializes and monitors the capture-only StickS3 ES8311/I2S path only while shared sound-capture demand is active: enabledsound.*automation rules or Web UI telemetry. Maintainers can still disable the feature through Kconfig for audio-free builds.
Firmware size is only one reason for Kconfig gates, and not the main reason for every gate. Runtime automation config controls whether a rule consumes a compiled source, but it cannot remove SDK components, background tasks, boot-time initialization, or hardware side effects from the firmware image. Kconfig gates are kept for features that pull in optional stacks or touch shared board resources before a user rule can safely use them. Examples: sound triggers and speaker actions add the ES8311/I2S/audio path and can affect shared microphone/speaker ownership; BLE transport selection changes Bluetooth components and unsupported Bluetooth audio transports are absent from the StickS3 build; the LCD gate controls panel bring-up; and battery/USB/BMI270/ADC gates let bring-up or release builds exclude polling of M5PM1/BMI270/ADC hardware until those paths are desired. For supported sources that are compiled in, runtime rule config still controls actual automation use; disabled or unsupported sources continue to fail closed through capability reporting and validation.
The code-derived capability overlay is grouped by how the rule runtime uses each item: trigger sources that emit comparable facts, rule actions that can be dispatched when rules fire, and supporting services/transports or deliberately disabled product capabilities that are not rule endpoints by themselves.
| Trigger/source | Current status | Code-derived note |
|---|---|---|
| Button triggers | ✅ Implemented/wired | KEY1/KEY2 short events emit automation facts. |
| BLE/Wi-Fi state triggers | ✅ Implemented/wired | Background tasks emit ble.connected and wifi.connected facts on state changes. |
| GPIO digital/edge triggers | ✅ Implemented/wired | Enabled rules create validated GPIO triggers and the runtime polls them every 20 ms. |
| Sound-level triggers | ✅ Implemented/default available | CONFIG_APP_SOUND_LEVEL_TRIGGERS=y is enabled in the project defaults and links the audio sources. Runtime capture starts only while shared demand is active: enabled sound.* automation rules or Web UI telemetry. The capture service computes metrics and feeds sound.rms_dbfs, sound.peak_dbfs, and sound.clipped facts into the rule runtime. |
| HAT sources | ⛔ Not implemented / fail-closed | Capabilities report HAT sources disabled and the HAT probe returns unsupported. |
| GPIO pulse/frequency sources | ⛔ Not implemented | Profiles are reported disabled and source support is false. |
| Battery percent fact | 🟡 Implemented/default available; hardware calibration/bench check needed | CONFIG_APP_BATTERY_FACTS=y is enabled in the project defaults, so power.battery_percent is compiled into the default firmware and emitted from M5PM1 VBAT reads unless disabled in menuconfig; it uses firmware LiPo interpolation and still requires hardware comparison against the official M5.Power battery API before product conformance claims. |
| USB/external-power fact | 🟡 Implemented/default available; hardware calibration/bench check needed | CONFIG_APP_USB_POWER_FACTS=y is enabled in the project defaults, so power.usb_present is compiled into the default firmware and emitted from M5PM1 VIN/5V reads unless disabled in menuconfig; VIN/5V reads tolerate VBAT read failures, absence is emitted only when both rails read below threshold, and the firmware does not switch EXT_5V mode or infer current direction. |
| BMI270 motion fact | 🟡 Implemented/default available; hardware calibration/bench check needed | CONFIG_APP_BMI270_FACTS=y is enabled in the project defaults, so bmi270.motion is compiled into the default firmware and emitted from polling-only BMI270 accelerometer reads unless disabled in menuconfig; interrupt routing through M5PM1 GPIO4/PYG4 remains unused until hardware bench checks are complete. |
| Safe ADC voltage facts | 🟡 Implemented/default available; hardware calibration/bench check needed | CONFIG_APP_ADC_FACTS=y is enabled in the project defaults, so adc.voltage_mv is compiled into the default firmware and emitted for safe ADC1 sources unless disabled in menuconfig; ADC exposure is limited to the source-backed safe ADC1 allowlist and excludes boot, button, USB-JTAG, LCD, I2C, audio, IR, power-sensitive, and non-ADC pins. |
| Rule action | Current status | Code-derived note |
|---|---|---|
| BLE rule-event notifications | ✅ Implemented/wired | ble_message actions dispatch through transport_ble_send_rule_event() when the BLE transport is built. |
| HTTP POST action | ✅ Implemented/wired | Dispatch uses esp_http_client when network readiness is true. |
| NEC IR send action | ✅ Implemented/wired | Dispatch uses the RMT TX path on the configured IR TX GPIO. |
| Local UI action | ✅ Implemented/wired | Dispatch sets the status UI ready state. |
| Speaker tone action | ✅ Implemented/Kconfig-gated | speaker_tone validates bounded frequency/duration/volume parameters, uses the playback-only ES8311/I2S speaker path and temporarily stops microphone capture before playback, and enables the M5PM1 PYG3 speaker amplifier only while playback is active. |
| HAT actions | ⛔ Not implemented / fail-closed | Capabilities report HAT actions disabled and HAT actions are rejected/unsupported by the dispatcher. |
| Capability | Current status | Code-derived note |
|---|---|---|
| BLE GATT status | ✅ Implemented/wired | Default transport starts the custom BLE GATT service when CONFIG_APP_TRANSPORT_BLE_GATT_RULE_EVENTS=y; this service exposes status and rule-event notifications only. |
| Web UI sound telemetry | ✅ Implemented/default available | Web UI status reads the same sound-level service last-metrics path used by sound triggers; telemetry demand can keep capture active without creating a second I2S reader. |
| PCM streaming endpoint | ⛔ Not implemented / not exposed | No BLE, Wi-Fi, USB, or debug endpoint streams raw microphone PCM. Raw PCM streaming would be a transport/service capability, not a trigger source or rule action. |
| Onboard speaker hardware / AW8737 firmware control | ✅ Implemented/Kconfig-gated | StickS3 has onboard ES8311/AW8737 speaker hardware, and speaker output is exposed as the bounded 16 kHz square-tone speaker_tone rule action. The firmware follows the official single-owner mic/speaker pattern, caps configured volume below 75%, uses the source-backed M5PM1 PYG3 amplifier enable sequence, and disables the amplifier after each tone. |
| Classic Bluetooth audio profiles | 🚫 Not planned / not supported by StickS3 hardware | StickS3 uses ESP32-S3, which does not support Bluetooth Classic / BR/EDR; no unsupported Bluetooth audio transport source or Kconfig option remains in this project. |
| BLE Audio class device | 🚫 Not planned / not supported by current StickS3 hardware target | The firmware uses a custom BLE GATT rule-event/status service; it does not expose an OS-native BLE Audio microphone/speaker class and no BLE Audio transport is planned for this ESP32-S3 StickS3 target. |
| USB Audio class device | ⛔ Not implemented / deferred, not marked hardware-unsupported | ESP32-S3 has USB device capability, but this firmware has no USB Audio Class/TinyUSB audio transport path wired; USB Audio remains a separate product decision rather than a current rule-runtime capability. |
| Wi-Fi station/setup AP | ✅ Implemented/wired | Boot starts Wi-Fi support; station/AP mode, scan/connect/forget/AP/mode APIs are implemented. |
| HTTP Web UI server | ✅ Implemented/on demand | Server starts only when the on-device Web UI service enables it and stops when the service is disabled. |
| Web time endpoint | ✅ Implemented | /api/time GET/POST is registered and handled. |
| Rule config storage | ✅ Implemented/wired | Config loads from NVS, falls back to defaults, and /api/config can save/replace the running config. |
| Rule engine | ✅ Implemented/wired | Source matching, comparators, false-to-true transitions, sustain, cooldown, and action fan-out are active through the runtime. |
| Audio board init / capture-only I2S / ES8311 | ✅ Implemented/wired by default | CONFIG_APP_SOUND_LEVEL_TRIGGERS=y links the audio sources and invokes the capture-only initializer; Kconfig can still disable it. |
The default transport is a custom Bluetooth LE GATT rule-event service advertised as M5StickS3-Control. It exposes service UUID 0xFFF0 with:
| Characteristic | Purpose | Notes |
|---|---|---|
0xFFF4 |
Status | M5TS status packet read/notify. |
0xFFF5 |
Rule events | M5RE notifications when configured automation rules fire BLE-message actions. |
The default firmware includes capture-only sound-level telemetry for local automation and Web UI status, but it starts monitoring only while enabled sound.* rules or Web UI telemetry demand exist and still does not expose PCM debug streaming. The launcher/menu UI is the product UI on boot. Sound trigger enums, metric helpers, and the capture-only sound-level service are available through CONFIG_APP_SOUND_LEVEL_TRIGGERS=y, which is enabled in the checked-in defaults and can be disabled for audio-free builds.
Transport decision: Unsupported Bluetooth audio profiles are rejected because StickS3 uses ESP32-S3-PICO-1-N8R8 and ESP32-S3 does not support Bluetooth Classic / BR/EDR. BLE Audio class-device work is also marked not-planned for the current ESP32-S3 StickS3 target. USB Audio is different: it is not labeled hardware-unsupported, but it remains deferred until a USB Audio Class design, roles, memory budget, host behavior, and product requirements are verified.
CONFIG_APP_WIFI_ENABLE=y starts Wi-Fi during boot. The firmware attempts saved station credentials and can fall back to the configured setup AP name, which defaults to M5StickS3-Setup. CONFIG_APP_WIFI_KEYBOARD_PROVISIONING defaults to n so the LCD stays in the normal dashboard/menu hierarchy at boot; when enabled, the LCD keyboard can collect Wi-Fi credentials before AP fallback.
To conserve RAM, the HTTP Web UI server is not started automatically at boot. It starts only from the on-device Web UI entry: Wi-Fi Mode enables it after station Wi-Fi is connected, and AP Mode enables it after AP startup succeeds. It is stopped again when the user backs out of the Web UI Wi-Fi result screen or AP URL screen to Main; stopping the server releases the ESP-IDF HTTP server task, handler table, stack, and related heap allocations. The standalone Connect to Wi-Fi flow only saves station credentials and does not start or stop the Web UI service.
When enabled, the web server exposes a small local configuration UI at / plus JSON endpoints:
| Endpoint | Method | Current purpose |
|---|---|---|
/api/status |
GET | Rule/web status, last action result, HTTP network readiness, and Wi-Fi status. |
/api/time |
GET/POST | Read or update the configured timezone used by the status UI clock. |
/api/capabilities |
GET | Supported/disabled trigger sources, actions, GPIO profiles, HAT placeholders, and pin-conflict classes. |
/api/config |
GET/POST | Export/import the automation config, save it to NVS, and replace the running rule engine config. |
/api/wifi/status |
GET | Wi-Fi station/AP state, AP name, AP channel/max connections, and web URL. |
/api/wifi/scan |
POST | Scan nearby Wi-Fi networks. |
/api/wifi/connect |
POST | Connect and persist station credentials. |
/api/wifi/forget |
POST | Forget saved station credentials. |
/api/wifi/ap |
POST | Start AP mode with a configurable AP name, optional password, and channel. |
/api/wifi/mode |
POST | Select Wi-Fi, AP, AP+Wi-Fi, or off mode. |
/api/rules/test |
POST | Inject a test fact through the current rule runtime. |
/api/gpio/test |
POST | Validate a candidate GPIO rule/config against safe-pin rules. |
/api/hat/probe |
POST | Fail-closed HAT capability probe placeholder; HAT drivers are not enabled yet. |
The onboard LCD menu uses the canonical product hierarchy for local setup:
Main
├── Web UI
│ ├── Wi-Fi Mode
│ │ ├── Scan Nearby Wi-Fi
│ │ │ └── choose network → try saved password if available → password input only on missing/bad password → auto connect/save
│ │ └── Hidden / Manual SSID → SSID input → try saved password if available → password input only on missing/bad password → auto connect/save
│ └── AP Mode
│ ├── Set AP Name
│ ├── Set AP Password
│ ├── Set Channel
│ ├── Start AP Mode
│ └── Show AP URL
├── Connect to Wi-Fi
│ ├── Scan Nearby Wi-Fi
│ │ └── choose network → try saved password if available → password input only on missing/bad password → auto connect/save
│ └── Hidden / Manual SSID → SSID input → try saved password if available → password input only on missing/bad password → auto connect/save
├── Connect to Bluetooth
│ └── Show BLE Status
├── All automations
│ ├── Automation 1
│ │ ├── Enable Flag
│ │ ├── Trigger → Key1 Short / Key2 Short / BLE Connected / Wi-Fi Connected
│ │ └── Action → BLE Event / HTTP POST / Local UI / IR
│ └── Automation 2
│ ├── Enable Flag
│ ├── Trigger → Key1 Short / Key2 Short / BLE Connected / Wi-Fi Connected
│ └── Action → BLE Event / HTTP POST / Local UI / IR
└── Settings
├── Display
│ └── Edit Timezone [editable]
├── Device
│ ├── Device Status [read-only]
│ └── About [read-only]
├── Connectivity
│ ├── Wi-Fi Setup [editable flow]
│ ├── BLE Status [read-only]
│ └── Web UI [editable flow]
├── Automation
│ ├── Automation 1 [editable]
│ └── Automation 2 [editable]
├── Hardware
│ ├── Power Status [read-only]
│ ├── Buttons [read-only]
│ └── LCD [read-only]
├── Maintenance
│ └── Restart Device [destructive]
└── About [read-only]
Settings is organized as shallow branch menus so the LCD navigation stack stays within its fixed depth while still linking back to existing setup flows. Read-only Settings entries only display live firmware or hardware information and do not save state. Editable entries either open the existing 9-key edit overlay, run an existing setup flow, or toggle a persisted/runtime setting. Destructive entries take immediate device-level action; currently Settings > Maintenance > Restart Device restarts the firmware.
| Settings entry | Type | Behavior |
|---|---|---|
Display > Edit Timezone |
Editable | Opens the 9-key text editor and saves the timezone through the same time configuration used by /api/time. |
Device > Device Status |
Read-only | Shows UI mode, Wi-Fi state, uptime, and free heap. |
Device > About and top-level About |
Read-only | Shows project/version/ESP-IDF build information and product description. |
Connectivity > Wi-Fi Setup |
Editable flow | Enters the existing station Wi-Fi scan/manual credential flow. |
Connectivity > BLE Status |
Read-only | Opens the BLE status page and refresh action already used from Main. |
Connectivity > Web UI |
Editable flow | Opens the same Web UI entry used from Main; the HTTP server is enabled only by the Web UI Wi-Fi/AP result flows and is disabled when exiting those result screens. |
Automation > Automation 1/2 |
Editable | Links to the existing enable/trigger/action editors for the two LCD-visible automation slots. |
Hardware > Power Status, Buttons, LCD |
Read-only | Shows battery/status-bar availability, the fixed KEY1/KEY2 control scheme, and LCD readiness. |
Maintenance > Restart Device |
Destructive | Immediately requests a firmware restart. |
Web UI > Wi-Fi Mode first checks the current station state. If the firmware is already connected to Wi-Fi, it enables the HTTP Web UI server and shows the station Web UI URL. If the firmware is not connected, it stays inside the Web UI > Wi-Fi Mode branch so the user can scan or enter a network specifically for Web UI access; the server is enabled only after that Web UI Wi-Fi connection succeeds.
The standalone Connect to Wi-Fi station flow supports the same scan/manual credential entry and persistence, but it does not enable the HTTP Web UI server. Selecting a highlighted SSID tries the stored password when the selected SSID matches saved credentials; the bottom 9-key password input overlay is shown only when no password is stored or the saved password fails with a password-related authentication/handshake reason. Hidden networks use Hidden / Manual SSID, which opens SSID input first and then follows the same saved-password/enter-password logic. Successful manual password entry persists the credentials.
AP setup for the Web UI is under Web UI > AP Mode, where the device can set AP name/password/channel, start AP mode, enable the HTTP Web UI server, and show the AP URL. Long-press/back from the Web UI Wi-Fi result screen or AP URL screen returns to Main and disables the Web UI service, stopping the HTTP server to free its RAM resources.
The browser Web UI still exposes the HTTP Wi-Fi endpoints listed above for station/AP setup from a phone or desktop browser while the Web UI service is enabled. On boot, saved station credentials are tried first; if station connection fails, the firmware can start AP mode for setup, but the HTTP server remains deferred until the user explicitly enters Web UI Wi-Fi/AP mode. Short browser form fields center-align their contents so SSIDs, passwords, AP names, channels, and timezone selections remain readable on phone-sized screens; large text/status blocks remain left-aligned for JSON and logs.
LCD flow uses the onboard buttons when the menu is open:
| Input | Menu behavior |
|---|---|
| KEY1 short | Select the highlighted item or scanned Wi-Fi network. |
| KEY2 short | Move to the next menu item or next scanned Wi-Fi network. |
| KEY2 double | Move to the previous menu item or previous scanned Wi-Fi network. |
| KEY2 long | Back. |
| KEY1 long from the idle status view | Open Main. |
When the 9-key input overlay is active, the LCD shows a 4x4 phone-style pad (1, 2ABC, 3DEF, - / 4GHI, 5JKL, 6MNO, . / 7PQRS, 8TUV, 9WXYZ, DEL / *#(, 0+, space, Next). KEY1 short selects the current key, KEY2 short moves to the next key, and KEY2 long moves to the previous key. Repeated selects on a phone key cycle through the digit, lowercase letters, and uppercase letters, for example 2, a, b, c, A, B, C; after 2 seconds without another select the current character is committed. Secret fields temporarily show only the pending character and mask committed characters as *, while normal fields keep committed characters visible.
The implemented and wired automation foundation includes:
- A fixed schema version and bounded rule config stored in NVS.
- Up to 8 rules and up to 3 actions per rule.
- Validation for enums, cooldowns, sustain time, action counts, HTTP URL/token sizes, supported sources/actions, and GPIO safety.
- A deterministic rule engine with source matching, comparators, false-to-true transitions, sustain-duration handling, and cooldown enforcement.
- Trigger adapters for KEY1 short press, KEY2 short press, BLE connection state, Wi-Fi connection state, and safe GPIO digital/edge inputs.
- Action dispatch for BLE rule-event notifications, HTTP POST, NEC IR send, and local UI signaling.
- Host tests for the rule engine, config store, runtime, web handlers, trigger adapters, and action modules.
Currently wired default-boot trigger sources are button.key1.short, button.key2.short, ble.connected, wifi.connected, gpio.digital, gpio.edge, sound.rms_dbfs, sound.peak_dbfs, sound.clipped, power.battery_percent, power.usb_present, bmi270.motion, and adc.voltage_mv; the hardware fact Kconfig gates for battery, USB/external-power, BMI270, and safe ADC are enabled in config/sdkconfig.defaults unless an opt-out build disables them. Sound facts and Web UI sound metrics are produced by the single capture-only sound-level service when audio initialization succeeds and at least one enabled automation rule uses a sound.* source or Web UI telemetry demand is active. Hardware facts are produced by the polling-only hardware fact service. gpio.pulse_count, gpio.frequency_hz, and all HAT sensor sources remain defined but disabled until drivers and hardware behavior are verified.
Currently supported actions are ble_message, http_post, ir_send, and local_ui. HAT operations are defined but disabled until source-backed drivers are implemented.
- Replaced the misleading Classic Bluetooth audio-device direction with a StickS3-targeted BLE rule-event transport. ESP32-S3 does not support Bluetooth Classic / BR/EDR, so the unsupported Bluetooth audio transport source and Kconfig option have been removed rather than carried as dormant code.
- Implemented source-backed StickS3 board constants, shared I2C ownership, M5PM1 L3B audio-rail enable helpers, capture-only I2S/ES8311 initialization helpers, LCD status UI, documented key polling, and audio safety checks. The sound-level service invokes the audio helpers when
CONFIG_APP_SOUND_LEVEL_TRIGGERS=y, which is enabled by default. - Removed the old sound-meter UI path and kept BLE status/rule-event packets for automation.
- Added Wi-Fi station/setup-AP provisioning, LCD keyboard provisioning, a local web UI, JSON APIs, NVS-backed automation config storage, and capability reporting. The Web UI resource budget is documented in
docs/web_ui_resource_budget.md, the phased redesign status is tracked indocs/web_ui_redesign_status.md, and generated static assets are checked bytools/check_web_ui_budget.pyduring host validation. - Added the local rule automation core: schema, validation, engine, runtime, button/BLE/Wi-Fi/GPIO/sound facts, default-enabled and separately Kconfig-gated battery, USB/external-power, BMI270, and ADC hardware facts, HTTP/IR/BLE/UI actions, safe GPIO/ADC validation, and host/static test coverage.
- Added factory-image generation and documentation/static checks so the release image can be flashed at offset
0x0instead of flashing the app partition by mistake.
- Run full ESP-IDF hardware validation on a real StickS3: boot, BLE telemetry, Wi-Fi setup, web UI, NVS save/reload, GPIO fixture tests, IR frame tests, and oscilloscope/logic-analyzer audio clock checks.
- Improve the web rule editor beyond the current compact setup page and JSON import/export flow.
- Add authenticated or local-only deployment guidance for the web UI before treating it as a user-facing network service.
- Implement and validate more external sources only after hardware routes are verified: GPIO pulse/frequency and selected M5Stack HAT sensors. Battery percent, USB/external-power present, BMI270 motion, and safe ADC1 paths are enabled in the default firmware through separate Kconfig gates, but still require hardware bench validation before release claims.
- Implement HAT actions only with source-backed protocols and tests.
- Bench-validate the Kconfig-gated
speaker_toneaction on StickS3 hardware, including M5PM1 PYG3 amplifier enable/disable, I2SG14_I2S_DDACoutput, and restoration of demand-driven microphone capture after playback. - Revisit whether the product needs a standard USB Audio class; BLE Audio class-device support is not planned for the current ESP32-S3 StickS3 target, and until USB Audio is explicitly designed and implemented this firmware should be described as a custom BLE rule-event and local automation device, not an OS-native audio endpoint.
The automation plan is now summarized here instead of in a separate planning README/checklist pair. The implemented runtime includes:
- Rule model and validation: schema version 1, up to 8 rules, up to 3 actions per rule, bounded names/source keys/HTTP fields, cooldown and sustain limits, source/action validation, and safe defaults.
- Rule engine: deterministic source matching, comparators, false-to-true transition detection, sustain-duration tracking, cooldown enforcement, event sequencing, and fire counts.
- Trigger runtime: KEY1/KEY2 facts, BLE connection facts, Wi-Fi readiness facts, safe GPIO digital/edge facts, and sound-level facts are wired by default; sound sensor monitoring starts only when shared sound demand exists (enabled
sound.*rules or Web UI telemetry) and audio initialization succeeds. - Capability gating:
/api/capabilitiesreports supported and disabled sources/actions. Battery, USB/external-power, BMI270, and ADC source runtime availability follows separate Kconfig gates that are enabled inconfig/sdkconfig.defaults; validation still rejects unsupported HAT, GPIO pulse/frequency, unsafe ADC/GPIO routes, and HAT-action features. - Action dispatch: BLE rule-event notifications, HTTP POST events, NEC IR send, and local UI feedback are implemented; HAT actions return unsupported.
- Web/storage path:
/serves the compact setup UI,/api/configimports/exports/saves bounded NVS configs,/api/timereads/updates timezone state, and invalid stored configs fall back to safe defaults.
The remaining automation roadmap is hardware/product work: validate the runtime on real StickS3 hardware, improve the web editor UX, document network-security assumptions, add a host BLE client example, and only then implement additional source-backed HAT, GPIO pulse/frequency, or speaker features beyond the default-enabled, separately Kconfig-gated battery, USB/external-power, BMI270, and safe ADC fact service.
tools/make_factory_image.py reads build/flasher_args.json when present, falls back to build/flash_args, verifies the referenced bootloader, partition table, app, and any other ESP-IDF flash artifacts exist, and writes build/m5sticks3_local_automation.bin. This merged factory image is the only single-file artifact intended for offset 0x0; the application-partition image is named build/m5sticks3_local_automation_app.bin and must only be flashed at the app offset shown in build/flash_args.
The factory-image tool validates the 0x0 boot image before merging and rejects a plan that would put the application artifact at offset 0x0. If the ROM banner reaches Invalid image block, can't boot, erase and reflash the generated factory image rather than any app-only .bin file.
GitHub Actions also creates this same build/m5sticks3_local_automation.bin factory image during the ESP-IDF build job and uploads it with build/m5sticks3_local_automation.bin.sha256 plus build/FLASH_THIS_FACTORY_IMAGE.txt. The release artifact intentionally does not upload the top-level ESP-IDF *_app.bin application partition image, because flashing that app-only binary at offset 0x0 produces the ROM Invalid image block, can't boot reset loop shown by the bootloader.
Flash the merged image with the same ESP-IDF/esptool environment used for the build:
esptool.py --chip esp32s3 --port <PORT> erase_flash
esptool.py --chip esp32s3 --port <PORT> write_flash 0x0 build/m5sticks3_local_automation.binBefore claiming end-to-end hardware validation, run these checks on a real StickS3 and any required external fixtures:
| Item | Hardware validation steps |
|---|---|
| Boot | Flash the merged ESP-IDF image or use idf.py flash, power-cycle StickS3, and confirm the app reaches the launcher UI without error state. |
| Status/events | Read or subscribe to 0xFFF4, subscribe to 0xFFF5, and confirm status plus automation rule-event packets update. |
| Rule event | Configure a BLE-message rule, subscribe to 0xFFF5, fire the rule, and confirm an M5RE event packet. |
| Wi-Fi and web | Verify saved station credentials, setup AP fallback, LCD keyboard provisioning when enabled, Web UI Wi-Fi/AP entry starts / and /api/status, standalone Connect to Wi-Fi does not start the server, and long-press/back from Web UI result/URL screens stops the Web UI server. |
| Config save/reload | Save a sound or button rule through POST /api/config, reboot, and confirm GET /api/config returns the saved rule with secrets masked. |
| HTTP POST action | Configure an HTTP POST action to a local test endpoint, fire /api/rules/test, and confirm one bounded JSON event arrives. |
| IR send action | Configure a NEC IR action, trigger /api/rules/test, and confirm a matching NEC frame on an IR receiver or logic analyzer. |
| GPIO trigger | Configure a safe GPIO digital/edge rule on a validated pin, toggle the input after debounce, and confirm exactly one normalized GPIO fact fires the action. |
| Fail-closed HAT probe | Call /api/hat/probe for unsupported HAT sources and confirm the response remains unsupported until a real HAT driver is implemented. |
| Audio clocks | Measure GPIO18 fixed MCLK at 12.288 MHz, GPIO17 BCLK at the documented 512 kHz target, and GPIO15 LRCK at 16 kHz. |
| Capture-only safety | Confirm default capture boot does not drive I2S TX, unmute the ES8311 DAC, or enable the speaker amplifier unless a speaker_tone action is actively running. |
| Speaker action | Configure speaker_tone at 7000 Hz / 100 ms / 50% volume, trigger it, and confirm GPIO14 (G14_I2S_DDAC) activity, M5PM1 PYG3 high only during playback, PYG3 low after completion, and sound-level capture restart when still demanded. |
The project can claim working StickS3 firmware only when these checks match the current custom BLE rule-event and local automation product:
- Static checks: run the eight validation scripts in the development-checks section below.
- Host checks: run
tests/host/run_host_tests.sh; host tests cover pure helpers, display-text sanitizing/layout, audio pipeline/metric helpers, app mode cycling, fake-bus ES8311 sequencing, bit-preserving M5PM1 GPIO helpers, board-audio ordering, failure cleanup, BLE protocol helpers, rule validation, engine transitions/sustain/cooldown, config storage, trigger adapters, GPIO safety, web handlers, and action modules. - ESP-IDF checks: build the default
esp32s3BLE rule-event/automation transport; confirm unsupported Bluetooth audio transports remain absent foresp32s3; generate the merged factory image withpython3 tools/make_factory_image.py. - Automation checks: verify
/api/capabilities,/api/config,/api/gpio/test, sound/button/BLE/Wi-Fi/GPIO facts, cooldown/sustain behavior, HTTP POST actions, and NEC IR actions against the current supported feature set. - Failure checks: if shared I2C, BLE startup fails, later dependent work must not run and the app must remain alive in an error/diagnostic state instead of guessing unsafe hardware cleanup or reset-looping.
Keep the documentation set intentionally small:
docs/README.mdis the single entry point for product status, transport decision, automation status/roadmap, smoke checks, development checks, and change policy.docs/hardware/sticks3/sticks3.mdis the main StickS3 hardware entry point, with detailed source-backed topic files indocs/hardware/sticks3/.docs/implementation_inventory.mdlists everysrc/**/*.cimplementation file one by one, with default-link status and host-test coverage checked bytools/check_source_inventory.py.
For every hardware, transport, web, or automation change, update or explicitly confirm docs/README.md, docs/hardware/sticks3/sticks3.md, docs/hardware/sticks3/, docs/implementation_inventory.md, docs/hardware/sticks3/sticks3.board.json when board facts change, touched source comments, Kconfig/default config, CMake/source layout, static validation scripts, host tests, and factory-image flow as applicable.
Every new hardware write sequence must document or cite the source document or source code, device address, register address, bit mask, intended value, reset/default behavior if known, read-modify-write requirements, unrelated fields that must be preserved, and host tests proving bit preservation for shared registers. If required hardware behavior is unknown, keep it blocked or feature-gated; do not guess M5PM1 L3B polarity, M5PM1 speaker-amplifier pulses, ES8311 volatile readback, BMI270 interrupt routing, HAT protocols, ADC paths, or safe external GPIO routes.
Documentation must describe the current product as a custom BLE rule-event and local automation device, must not call it an unsupported Bluetooth or OS-native audio endpoint, and must clearly label each capability with the status categories in this document, including implemented, deferred/not implemented, not planned/hardware-unsupported, or deliberately disabled.
The StickS3 hardware can support a custom USB Audio Class firmware profile through the ESP32-S3 native USB device pins and the ES8311 I2S/I2C audio codec, but the checked-in default image remains the local automation firmware and keeps CONFIG_APP_USB_UAC_DEVICE=n. UAC work and its managed usb_device_uac dependency are implemented behind explicit Kconfig gates for microphone, speaker, combined descriptor, and simultaneous mic+speaker experiments; it must not be treated as enabled in the default BLE/Wi-Fi/rule-automation image.
The currently supported software target is 16 kHz mono 16-bit PCM; other rates remain rejected until matching ES8311/I2S clock profiles are implemented and tested. When explicitly enabled, app_main() starts the UAC service after rule-runtime initialization; the service resolves Kconfig, starts the selected board-audio owner, initializes Espressif UAC, and launches bridge tasks. The UAC callback bridge uses fixed ring buffers so USB callbacks move bytes only; codec configuration, logging, allocation, and blocking I2S work stay outside the callback path. Combined descriptors without the simultaneous owner enumerate with callbacks but intentionally do not start audio bridge tasks; the simultaneous mic+speaker profile is experimental and requires real StickS3 hardware validation before any product claim.
Keep detailed board pins, electrical constraints, source-backed hardware facts, and hardware acceptance notes under docs/hardware/sticks3/, with docs/hardware/sticks3/sticks3.md as the main entry point. This README stays focused on product behavior, user-facing firmware functions, validation flow, and change policy.
The focused status UI button/input architecture review is recorded in docs/status_ui_input_review.md. It documents the StickS3 hardware conformance check, global input routing checklist, host-test coverage, and ESP-IDF validation limitation for the refactor.
Run these checks after changes when the local environment supports them:
python3 tools/check_board_map.py
python3 tools/check_transport_config.py
python3 tools/check_docs_consistency.py
python3 tools/check_audio_clock.py
python3 tools/check_audio_safety.py
python3 tools/check_source_inventory.py
python3 tools/check_partition_table.py
python3 tools/check_naming_consistency.py
tests/host/run_host_tests.shESP-IDF build/flash validation still requires an ESP-IDF environment and attached hardware.
- M5Stack StickS3 documentation and pin map: https://docs.m5stack.com/en/core/StickS3
- M5Stack StickS3 Arduino programming documentation: https://docs.m5stack.com/en/arduino/m5sticks3/program
- M5Stack StickS3 Battery Arduino documentation: https://docs.m5stack.com/en/arduino/m5sticks3/battery
- M5Stack StickS3 Button Arduino documentation: https://docs.m5stack.com/en/arduino/m5sticks3/button
- M5Stack StickS3 Display Arduino documentation: https://docs.m5stack.com/en/arduino/m5sticks3/display
- M5Stack StickS3 IMU Arduino documentation: https://docs.m5stack.com/en/arduino/m5sticks3/imu
- M5Stack StickS3 IR NEC Arduino documentation: https://docs.m5stack.com/en/arduino/m5sticks3/ir_nec
- M5Stack StickS3 Microphone Arduino documentation: https://docs.m5stack.com/en/arduino/m5sticks3/mic
- M5Stack StickS3 Speaker Arduino documentation: https://docs.m5stack.com/en/arduino/m5sticks3/speaker
- M5Stack StickS3 Wakeup Arduino documentation: https://docs.m5stack.com/en/arduino/m5sticks3/wakeup
- M5Stack StickS3 M5PM1 Arduino documentation: https://docs.m5stack.com/en/arduino/m5sticks3/m5pm1
- M5Stack M5PM1 source repository: https://github.com/m5stack/M5PM1
- M5Stack M5Unified source repository: https://github.com/m5stack/M5Unified
- M5Stack M5GFX source repository: https://github.com/m5stack/M5GFX
- M5Stack M5GFX StickS3 initialization source: https://github.com/m5stack/M5GFX/blob/master/src/M5GFX.cpp
- StickS3 schematic PDF: https://m5stack-doc.oss-cn-shenzhen.aliyuncs.com/1207/K150_Stick_S3_PRJ_V0.6_20251111_2025_11_17_16_10_24.pdf
- ES8311 datasheet: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/atom/Atomic%20Echo%20Base/ES8311.pdf
- BMI270 datasheet: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/datasheet/core/K128%20CoreS3/BMI270.PDF
- ESP32-S3 technical reference manual: https://m5stack-doc.oss-cn-shenzhen.aliyuncs.com/477/esp32-s3_technical_reference_manual_cn.pdf
- Espressif Bluetooth architecture documentation: https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/bt-architecture/overview.html
- Repository hardware notes:
docs/hardware/sticks3/sticks3.md
Global sensor-monitoring rule: firmware must initialize and monitor a sensor only while an explicit demand source needs it. For GPIO this is enabled-rule source usage; for sound capture this is the union of enabled sound.* automation rules and Web UI telemetry. If neither trigger nor telemetry demand is active, the producer must stay stopped and release its runtime RAM/task resources. Future sensor producers should keep demand sources explicit and avoid duplicate readers.
Runtime capture starts only while shared demand is active. Sound-level triggers are enabled in the checked-in defaults with CONFIG_APP_SOUND_LEVEL_TRIGGERS=y. This links audio_metrics.c, board_audio.c, board_audio_clock.c, board_audio_power.c, board_i2s.c, es8311.c, and sound_level_service.c. To honor demand-driven sensor monitoring, the firmware allocates the sound service state and initializes the StickS3 ES8311 microphone path with BOARD_AUDIO_PROFILE_CAPTURE_ONLY only while shared sound demand is active. Enabled sound.* rules consume live sound.rms_dbfs, sound.peak_dbfs, and sound.clipped facts through the existing automation runtime, and Web UI telemetry reads the same service status/last-metrics path without starting a second I2S reader. Maintainers can still turn the Kconfig option off for audio-free builds.