Files
T
Michal ZaniewiczandClaude Opus 4.8 fc32afc3c4 Docs: make the README substitution table match waveshare-va.yaml
boot_sound_file was listed as living in the thin config but was only defined in
core.yaml; add it to waveshare-va.yaml as a commented optional override, and show
the friendly_name default.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 19:12:23 +02:00

150 lines
7.7 KiB
Markdown

![ESPHome and Home Assistant voice assistant on the Waveshare ESP32-S3-AUDIO-Board](docs/hero.jpg)
# ESPHome Voice Assistant for the Waveshare ESP32-S3-AUDIO-Board
A **Home Assistant voice satellite** running on the
[Waveshare ESP32-S3-AUDIO-Board](https://www.waveshare.com/esp32-s3-audio-board.htm),
the little AI smart-speaker devkit with a dual-mic array, an ES8311 codec, three
buttons and a 7-LED RGB ring. Pure ESPHome, no custom C firmware: an always-on
core you pull as a package, plus one thin config file you actually edit.
> **Status: stable (v1.0.0).** Wake word, STT/TTS, clean playback and the LED
> ring are confirmed on-device. Full docs are in the
> [Wiki](https://github.com/MichalZaniewicz/esphome-waveshare-esp32-s3-audio-va/wiki);
> the release history is in [CHANGELOG.md](CHANGELOG.md).
## What it does
- **Voice assistant**: on-device wake word (`alexa`, `okay_nabu`) via
`micro_wake_word`, the full Home Assistant Assist pipeline (STT / LLM / TTS),
a wake beep and music ducking while it listens.
- **Simultaneous music and announcements**: a mixer speaker blends the media and
announcement pipelines, so a doorbell announcement ducks the music instead of
fighting it. Both are exposed to Music Assistant.
- **LED ring**: one state machine drives it. Boot, no-Wi-Fi, no-HA, listening,
thinking, replying, timer counting, ringing, volume changed - each a distinct
colour/effect. Brightness and the animation for the listening / thinking /
replying phases are pickable from HA, from 15 effects: solid, pulses, breathe,
wipe, scan, spinner, comet, twinkle, fireworks, fire, rainbows.
- **Timers**: set by voice, with an on-ring countdown and a "Next timer" sensor
in HA. (A daily-alarm engine is present but its entities are hidden by default.)
- **Buttons**: the three onboard keys do volume down, play-pause, volume up.
- **Boot chime**: a short "ready" sound once the device connects to HA
(toggleable, and it also settles the amp so the ring boots silent).
- **Tunable live from HA**: microphone mute, ES7210 mic gain, LED brightness and
wake-word sensitivity are all entities, so there's no reflashing to tune it.
## Quick start
> Requires **ESPHome 2025.8.0+**.
1. Copy `secrets.example.yaml` to `secrets.yaml` and fill in your Wi-Fi. The
native API is unencrypted by default; enable encryption in `base/core.yaml`
if you want it (see the commented block there).
2. Copy **`waveshare-va.yaml`** next to it and edit the `substitutions:` at the
top (device name, timezone, volume limits). That thin file is the only
firmware file you keep. The core is **pulled from GitHub at compile time**,
see its `packages:` block.
3. **First flash over USB**, then updates go wireless:
```
esphome run waveshare-va.yaml
```
Or drop both files into the ESPHome dashboard's `/config/esphome/` and hit
Install.
4. In Home Assistant: the new ESPHome device appears, open **Configure** and
assign an Assist pipeline.
5. Say "Alexa" (or "OK Nabu"). The ring should go violet.
The example config pins the `v1.0.0` release tag, so a build is reproducible. To
move to a newer release, bump `ref:` in the `packages:` block to a later tag (or
`main` to track the latest), then `esphome clean waveshare-va.yaml` (clears the
package cache) and `esphome run waveshare-va.yaml`.
## Documentation
The [Wiki](https://github.com/MichalZaniewicz/esphome-waveshare-esp32-s3-audio-va/wiki)
has the full guide:
- **[Installation](https://github.com/MichalZaniewicz/esphome-waveshare-esp32-s3-audio-va/wiki/Installation)**: first flash, Home Assistant setup, updating.
- **[Configuration](https://github.com/MichalZaniewicz/esphome-waveshare-esp32-s3-audio-va/wiki/Configuration)**: every substitution and every Home Assistant entity.
- **[Audio architecture](https://github.com/MichalZaniewicz/esphome-waveshare-esp32-s3-audio-va/wiki/Audio-architecture)**: the shared-I2S two-bus design, in depth.
- **[LED ring](https://github.com/MichalZaniewicz/esphome-waveshare-esp32-s3-audio-va/wiki/LED-ring)**: the state machine and all 15 effects.
- **[Hardware](https://github.com/MichalZaniewicz/esphome-waveshare-esp32-s3-audio-va/wiki/Hardware)**: pinout, I2C map, and sourced gotchas.
- **[Troubleshooting](https://github.com/MichalZaniewicz/esphome-waveshare-esp32-s3-audio-va/wiki/Troubleshooting)** and **[FAQ](https://github.com/MichalZaniewicz/esphome-waveshare-esp32-s3-audio-va/wiki/FAQ)**.
## How the shared I2S bus is handled
The board wires the **ES8311 (DAC) and the ES7210 (ADC) to the same BCLK/LRCLK
pins**, and only one device can drive those clocks. ESPHome also cannot run a
single I2S bus full-duplex: a microphone and a speaker on one bus each try to
init the port, and the second fails with "Parent bus is busy".
The layout that works, all on **stock ESPHome components**:
- **Two I2S buses** (two ports) over the shared pins. The **mic bus is the I2S
master**: it is always capturing for the wake word, so it drives BCLK/LRCLK/MCLK
continuously. The **speaker bus is a slave** that reads the mic's clock, so it
never needs a port of its own to master.
- The ES8311 and ES7210 are stock and slave to the mic's clock.
- The mic is pinned to **16-bit** (the i2s_audio default is 32-bit); since the
mic is master it sets the frame's slot width, and a 32-bit frame against the
16-bit DAC comes out as noise.
This gives simultaneous capture + playback with no custom component. The
annotated config is in the audio section of `base/core.yaml`.
## Repository layout
```
waveshare-va.yaml # YOUR config: copy + edit this (pulls the rest from GitHub)
secrets.example.yaml # copy to secrets.yaml
base/
core.yaml # the always-on core, pulled as a remote package
docs/
HARDWARE.md # pinout, I2C map, gotchas
scripts/
validate.py # offline YAML check (syntax, substitutions, duplicate ids)
esplog.py # stream device logs over the native API
skill/
waveshare-esp32-s3-audio/ # Claude Code skill: pinout + hard-won gotchas
```
## Configuration
Everything worth changing day to day is a Home Assistant entity, not a config
edit: mic gain, LED brightness, the ring animation per assistant phase
(Listening / Thinking / Replying effect), wake-word sensitivity, wake sound,
boot sound, microphone mute.
What lives in `waveshare-va.yaml`:
| Substitution | Default | What it does |
|---|---|---|
| `name` / `friendly_name` | `waveshare-va` / `Waveshare Voice` | Device name. Changing `name` re-creates every entity in HA. |
| `posix_timezone` | `CET-1CEST,...` | Clock zone in POSIX form (the device has no IANA database). DST automatic. |
| `volume_min` / `volume_max` | `0.4` / `0.8` | Media player clamps, because the onboard amp distorts near the top. |
| `hidden_ssid` | `false` | `true` enables `fast_connect` for a hidden SSID. |
| `boot_sound_file` | repo `startup.mp3` | The connect-to-HA chime. Any URL or local MP3/FLAC/WAV. |
Pins and the audio format are substitutions too (in `base/core.yaml`), but you
should not need them unless you are porting to another board.
## Claude Code skill
This repo ships a [Claude Code](https://claude.com/claude-code) skill at
[`skill/waveshare-esp32-s3-audio/`](skill/waveshare-esp32-s3-audio/SKILL.md):
the pinout, the shared-I2S constraint, and the gotchas that cost real debugging
time. Install it user-wide so any session picks it up:
```bash
cp -r skill/waveshare-esp32-s3-audio ~/.claude/skills/
```
## Credits
- **[jensenbox](https://github.com/jensenbox/waveshare-esp32-s3-audio)**: the
ESP-master I2S layout for this board that the audio setup is based on.
- **ESPHome**: everything the firmware is built out of.
- **[Home Assistant Voice PE](https://github.com/esphome/home-assistant-voice-pe)**:
the sounds, and the phase/ducking model the LED state machine follows.