SmartestHome/firmware/esp32-s3-touch-lcd-1.85c/README.md

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` (`hosts/thin-client/scripts/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.