178 lines
9.8 KiB
Markdown
178 lines
9.8 KiB
Markdown
# ESP32-S3-Touch-LCD-1.85C-V2 — voice + status display
|
|
|
|
Phase 14 of [`docs/project-plan.md`](../../docs/project-plan.md). A DIY voice
|
|
satellite + round-screen status display built on the Waveshare
|
|
ESP32-S3-Touch-LCD-1.85C-V2, evaluated in this project as a possible alternative to
|
|
the Home Assistant Voice PE.
|
|
|
|
**Media status always takes priority on screen, and it's always THIS room's
|
|
media status.** Whenever the configured `media_player` entity is `playing`, the
|
|
display shows its cover art, title and artist. Otherwise it idles, cycling every 8
|
|
seconds between a clock (time + date) and the weather. A ring visualizer overlays
|
|
whichever of those is showing whenever the assistant is listening, thinking, or
|
|
replying — it never replaces the page underneath, it just sits on top of it. See
|
|
[Multiple rooms](#multiple-rooms) below for what actually guarantees the "this
|
|
room's" part, since ESPHome has no built-in concept of "the media player in my
|
|
room" — that has to be set up deliberately, per unit.
|
|
|
|
## ⚠️ This needs the V2 hardware revision specifically
|
|
|
|
Waveshare shipped two hardware revisions of this board. **V1 has no echo-
|
|
cancellation circuit and different audio pins** — this config's I2S pin numbers,
|
|
the ES7210/ES8311 audio path, and the display's reset wiring are all V2-specific
|
|
and will not work on a V1 unit. Confirm which you have before flashing (see
|
|
`docs/project-plan.md`'s Phase 14 hardware note for how): PCB silkscreen showing
|
|
"Rev2.0", factory firmware reporting "Rev2.0", or a "V2" QC sticker on the case.
|
|
|
|
## Before you flash
|
|
|
|
1. Install ESPHome (`pip install esphome`, or use the Home Assistant ESPHome
|
|
add-on / dashboard instead — either works, this repo doesn't assume which).
|
|
2. Copy the secrets template and fill it in:
|
|
```sh
|
|
cp secrets.yaml.example secrets.yaml
|
|
```
|
|
`secrets.yaml` is git-ignored (repo `.gitignore` covers it) — never commit a
|
|
real one. Two of its values are **not filled in by anyone yet, on purpose**:
|
|
- `media_player_entity_id` — which Home Assistant `media_player.*` entity this
|
|
unit mirrors. **This must be the entity that reflects THIS unit's own
|
|
room** — see [Multiple rooms](#multiple-rooms) below, it's not just a
|
|
"pick any one" placeholder.
|
|
- `weather_entity_id` — whichever `weather.*` entity your Home Assistant
|
|
already has.
|
|
Both entities need to be exposed to the Home Assistant API (the default for
|
|
most entities; check Settings → Voice assistants → Expose if either doesn't
|
|
show up).
|
|
3. `api_encryption_key` — generate one:
|
|
```sh
|
|
python3 -c "import base64, os; print(base64.b64encode(os.urandom(32)).decode())"
|
|
```
|
|
4. This device plugs into the **existing** Phase 3 Assist pipeline (Wyoming
|
|
faster-whisper + Piper) — there's nothing new to stand up on the Home
|
|
Assistant side beyond adding the device itself (Settings → Devices → Add
|
|
Device → ESPHome, after the first flash below) and picking an Assist
|
|
pipeline for it, same as any other ESPHome voice satellite.
|
|
|
|
## Flashing
|
|
|
|
```sh
|
|
esphome run voice-display.yaml
|
|
```
|
|
|
|
First flash needs USB-C (OTA needs the device already on Wi-Fi, which needs the
|
|
first flash). Subsequent updates can go over OTA (`esphome run` auto-detects a
|
|
device already on the network).
|
|
|
|
## Multiple rooms
|
|
|
|
This is a per-room device, exactly like the thin client's per-image
|
|
`THINCLIENT_NAME`/`DIGEST_WEB_URL` (`tools/build-thin-client-iso.sh`).
|
|
Two separate things both have to be set correctly, per physical unit, for "media
|
|
status always on the specific room the device is in" to actually hold:
|
|
|
|
1. **`room` in `voice-display.yaml`'s `substitutions:`** — sets the device's own
|
|
hostname/AP name/friendly name (`voice-display-<room>`), so it's identifiable
|
|
in Home Assistant's device list and on the Wi-Fi fallback AP. This is purely
|
|
identity — changing it doesn't change what the screen shows.
|
|
2. **`media_player_entity_id` in `secrets.yaml`** — this is the one that actually
|
|
controls what's on screen, and it's the one you can genuinely get wrong. Build
|
|
a **separate `secrets.yaml` per unit** (or a separate entry per device if
|
|
you're using the ESPHome dashboard), never one shared file — flashing two
|
|
units from the same secrets means both show the same room's media.
|
|
|
|
- **If a room only ever has one real audio source** (e.g. just the thin
|
|
client's own `media_player`, see
|
|
`hosts/thin-client/agent/thinclient_agent/mqtt_discovery.py`'s
|
|
`register_media_player`), point straight at that entity's ID and you're
|
|
done.
|
|
- **If a room can have more than one active source** (the thin client *and* a
|
|
Spotify Connect speaker *and* a cast device, say), a single hardcoded
|
|
`entity_id` can't stay correct on its own — nothing tells this device
|
|
"prefer whichever one is actually making sound." Build a Home Assistant
|
|
[Universal Media Player](https://www.home-assistant.io/integrations/universal/)
|
|
for that room that aggregates all of its real `media_player` entities, and
|
|
point `media_player_entity_id` at the universal one instead of any single
|
|
real device:
|
|
```yaml
|
|
media_player:
|
|
- platform: universal
|
|
name: "Living room media"
|
|
children:
|
|
- media_player.living_room_thinclient
|
|
- media_player.living_room_spotify_connect
|
|
```
|
|
Default child-selection behaviour is "whichever child isn't idle/off" —
|
|
see the integration's own docs (`active_child_template`) if a room needs
|
|
more deliberate priority between its sources. That Home Assistant config
|
|
is not built by this repo, same "nothing here builds the HA side"
|
|
convention as everywhere else.
|
|
|
|
Nothing in this firmware can detect or correct a wrongly-configured
|
|
`media_player_entity_id` — an entity from the wrong room is a config mistake this
|
|
device has no way to notice, not a bug it degrades gracefully from. Double-check
|
|
it against Developer Tools → States in Home Assistant before trusting the display.
|
|
|
|
## Mounting upside down
|
|
|
|
Two places to change, both commented `# FLIP DISPLAY`-style in
|
|
`voice-display.yaml`: `display.rotation: 180°` and `touchscreen.transform.mirror_x`
|
|
/ `mirror_y: true`. Both must change together or the touch coordinates and the
|
|
image will disagree.
|
|
|
|
## Nothing here builds the Home Assistant side
|
|
|
|
Same convention as the rest of this repo (`admin-canvas/README.md`,
|
|
`hosts/thin-client/README.md`'s own note): this device shows up in Home Assistant
|
|
automatically once flashed and adopted, like any ESPHome device — there's no HA
|
|
YAML to write for it. It **is** a new consumer of the existing Phase 3 Assist
|
|
pipeline, so if that pipeline has never been used by a device before, follow the
|
|
Phase 3 setup in `docs/project-plan.md` first.
|
|
|
|
## Manual verification still outstanding
|
|
|
|
None of this has been run on real hardware — only validated with `esphome config
|
|
voice-display.yaml` (ESPHome's own schema/config validator), which the file
|
|
currently passes cleanly. In rough order of what to check first on a real unit:
|
|
|
|
1. **Display bring-up.** The full register init sequence is adapted from a
|
|
community config for this exact board
|
|
([ulsmith/home-assistant-esphome-esp32-s3-touch-lcd-185c](https://github.com/ulsmith/home-assistant-esphome-esp32-s3-touch-lcd-185c))
|
|
and cross-checked against Waveshare's own V2-specific notes (EXIO2 reset,
|
|
80MHz, `invert_colors`), but has not been flashed here. If it boots to static
|
|
or a blank screen, that sequence is the first place to look.
|
|
2. `GPIO46`/`GPIO45` strapping-pin warnings from `esphome config` are **expected**
|
|
— they're the QSPI display's own data pins, not a mistake in this config; the
|
|
community reference has the identical warnings.
|
|
3. **Audio / AEC path.** The mic (ES7210) and speaker (ES8311) I2S pins are
|
|
adapted from the same reference; whether the board's echo-cancellation
|
|
reference-signal routing actually suppresses the speaker bleeding into the mic
|
|
during TTS playback hasn't been tested.
|
|
4. **`micro_wake_word`'s "okay_nabu" model.** Wake sensitivity/false-positive rate
|
|
is whatever ESPHome's shipped model gives you — untuned, unlistened-to.
|
|
5. **Cover-art fetch.** `entity_picture` is assumed to be a path relative to Home
|
|
Assistant (`ha_base_url` gets prepended unless it already starts with
|
|
`http`) — true for HA's own local media sources, but some integrations
|
|
(Spotify's own card, certain streaming integrations) may return an already-
|
|
absolute URL, or one on a different host entirely. If cover art never
|
|
appears, check what `entity_picture` actually contains for your specific
|
|
`media_player` entity (Developer Tools → States in HA) before assuming the
|
|
display is broken.
|
|
6. Optional: drop a `no_art.png` into Home Assistant's `config/www/` (served at
|
|
`/local/no_art.png`) for a clean idle image instead of a broken-image glyph
|
|
before the first track ever plays. Not required — the device works without
|
|
it, just shows nothing there until either that file exists or a real track
|
|
with artwork plays.
|
|
7. **Idle-cycle and priority timing.** The 8-second weather/clock cycle interval
|
|
and the "switch to media_page the instant state becomes playing" transition
|
|
are both unverified against how promptly Home Assistant's own `media_player`
|
|
state actually updates in practice (some integrations lag a second or two).
|
|
8. Settings-page mute toggle and the physical restart button are both simple,
|
|
low-risk carryover from the reference config, not specifically requested —
|
|
remove them if you'd rather have a leaner build.
|
|
9. **Multi-unit room correctness.** Only ever built/tested against a single
|
|
`secrets.yaml`. If you deploy more than one of these, double-check each
|
|
unit's `media_player_entity_id` against Developer Tools → States before
|
|
trusting it — see [Multiple rooms](#multiple-rooms). This firmware has no
|
|
way to detect a copy-pasted `secrets.yaml` pointing two units at the same
|
|
entity.
|