ESPHome config for an M5Stack Echo Pyramid base running a plain AtomS3 (ESP32-S3, no PSRAM) as a Home Assistant voice satellite, with a custom RGB-LED animation engine, a color status display with a state-reactive skull, and a Kismet security-alert pipeline.
The Echo Pyramid's official ESPHome example targets the AtomS3R (which has PSRAM + on-device wake words). This config makes it work on the cheaper plain AtomS3 with push-to-talk and a few non-obvious memory/streaming fixes (see Gotchas below).
- M5Stack Echo Pyramid base: ES8311 DAC + ES7210 AEC mic array + si5351 MCLK + AW87559 amp, 28× WS2812 RGB LEDs (4 channels × 7) and 2 capacitive touch sliders driven by an onboard STM32G030 co-processor (all on the external I²C bus, SDA=GPIO38 / SCL=GPIO39).
- M5Stack AtomS3 (plain, no PSRAM) — 0.85" GC9107 LCD on internal SPI.
- Push-to-talk voice assistant (no PSRAM → no on-device wake word): tap the AtomS3 button to talk.
- Spoken TTS out the pyramid speaker (Home Assistant Assist pipeline; tested with Google Gemini STT/LLM/TTS).
- Custom LED animations keyed to state: idle cyan breathe · listening blue comet · thinking amber
pulse · speaking green wave · alert red strobe · rainbow. Exposed as a
LED Effectselect +RGB Brightnessnumber. LED writes are paused during audio playback to avoid bus/current contention. - Color status display with a Material Design Icons skull that recolors with the assistant state, a red skull-and-crossbones "KISMET" alert page, and a system page (IP/RSSI/uptime).
- Touch sliders: left swipe = volume, right swipe = LED brightness.
- Kismet alert page fed by a Home Assistant
input_texthelper.
pip install esphomecp secrets.yaml.example secrets.yamland fill in Wi-Fi + generated keys.- First flash over USB:
esphome run m5-voice.yaml --device /dev/ttyACM0(the AtomS3 enumerates as a native USB CDC device, e.g./dev/ttyACM0). - Adopt in Home Assistant (ESPHome integration), then create/assign an Assist pipeline.
- Subsequent flashes are OTA:
esphome run m5-voice.yaml --device <name>.local
- STM32 RGB co-processor clock-stretches hard while driving the WS2812s, so ESPHome I²C
write()logs-> FAILeven though the LEDs light. Seti2c: timeout: 10msand silence thepyramidrgblogger. Touch reads succeed; LED writes "fail"-but-work. - Audio out is the hard part on a no-PSRAM board — the streamed-TTS pipeline is sized for PSRAM:
media_player: speakerdefaultsbuffer_sizeto 1 MB →ESP_ERR_NO_MEM. Set it small (here 72000); too big starves the heap, too small clips long replies.- Decode 48 kHz FLAC needs more RAM than 16 kHz (
-4 = FLAC_DECODER_ERROR_MEMORY_ALLOCATION); usesample_rate: 16000to match the speaker (no resample) and shrink the decode buffer. format: WAVbreaks the streaming reader (ESP_FAIL); FLAC works.- If a reply is larger than
buffer_size, the device pauses reading, HA closes the idle HTTP connection, and the reader loops onESP_FAIL(stuck "playing"). Keep replies short (LLM prompt) or raise the buffer. An AtomS3R (PSRAM) removes the limit. - Set
wifi: power_save_mode: noneand Home Assistant'sinternal_urlto a LAN address the device can route to (not a Tailscale/CGNAT address).
mdi.ttfis the Material Design Icons webfont (skullF068C, skull-crossbonesF0BC6, radarF0437).
Config: MIT. mdi.ttf is redistributed under the SIL Open Font License (Material Design Icons).