Files
desklock/docs/architecture.md
T
jpmschweitzerandClaude Fable 5 576fd7d237 Initial scaffold: ESP32-P4 firmware + voice gateway for Tatlock endpoint
DeskLock gives the Tatlock butler a face and voice on a Waveshare
ESP32-P4-WIFI6-Touch-LCD-3.4C round display in the living room.

- firmware/: ESP-IDF project targeting esp32p4 with the Waveshare XC BSP
- gateway/: FastAPI voice bridge (faster-whisper STT, Tatlock chat, Piper TTS)
- docs/architecture.md: component design and device<->gateway WS protocol

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 16:58:59 +02:00

87 lines
4.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# DeskLock Architecture
## Goal
A always-on, glanceable butler face in the living room. You speak to it; it relays your
words to Tatlock and speaks the reply back, with a face that reflects what it's doing
(idle, listening, thinking, speaking). It is deliberately a *thin* endpoint: all
intelligence lives in Tatlock, all heavy audio processing lives in the gateway.
## Components
### 1. Firmware (`firmware/`) — ESP32-P4
Responsibilities:
- **Face rendering** (LVGL 9 on the 800×800 round MIPI-DSI panel via the
`waveshare/esp32_p4_wifi6_touch_lcd_xc` BSP). Face states:
- `idle` — subtle animation + clock (it's a desk clock when nobody's talking to it)
- `listening` — visual feedback that the mic is hot
- `thinking` — Tatlock is working on a reply
- `speaking` — mouth/waveform animation synced to TTS playback
- **Audio capture**: dual mics through the ES7210 (hardware echo cancellation reference
from the playback path), 16 kHz 16-bit mono PCM.
- **Audio playback**: ES8311 codec → speaker. Plays PCM streamed from the gateway.
- **Transport**: a single WebSocket to the gateway carrying binary PCM frames plus JSON
control events (`state`, `transcript`, `reply_text`, errors). Device reconnects with
backoff; face shows a disconnected state when the gateway is unreachable.
- **Interaction**: phase 1 is touch-to-talk (tap the face). Phase 2 adds esp-sr WakeNet
wake word on the P4 so the interaction is hands-free.
Non-responsibilities: no STT, no TTS, no conversation state. If it's not rendering,
recording, or playing, it doesn't belong in firmware.
### 2. Gateway (`gateway/`) — container on tower-of-joy
A FastAPI service bridging device audio to Tatlock text:
1. Accepts the device WebSocket (`/ws/voice`).
2. Buffers inbound PCM until end-of-utterance (client-signalled in phase 1; VAD later).
3. **STT**: faster-whisper on the RTX 2080 Ti.
4. **Chat**: POSTs the transcript to Tatlock `/v1/chat/completions`
(`http://tatlock.schweitz.internal:8000`, OpenAI-compatible, streaming). Maintains the
conversation id so follow-ups have context.
5. **TTS**: Piper (fast, CPU-friendly, local) synthesizes the reply.
6. Streams reply PCM back to the device along with `reply_text` for on-screen display.
The gateway is stateless apart from in-flight conversations; it can restart freely.
### 3. Tatlock — existing backend (`/mnt/media/Projects/tatlock`)
Untouched by this project. DeskLock consumes its OpenAI-compatible API over the internal
LAN (port 8000, bypassing the Authentik-protected public route). If device auth is needed
later, the gateway holds the credential — never the firmware.
## WebSocket protocol (device ↔ gateway)
Binary frames: raw 16 kHz s16le mono PCM (mic upstream, TTS downstream).
Text frames: JSON control messages.
```
device → gateway: {"type": "utterance_start"}
device → gateway: <binary PCM frames>
device → gateway: {"type": "utterance_end"}
gateway → device: {"type": "state", "value": "thinking"}
gateway → device: {"type": "transcript", "text": "..."}
gateway → device: {"type": "reply_text", "text": "..."}
gateway → device: {"type": "audio_start", "sample_rate": 16000}
gateway → device: <binary PCM frames>
gateway → device: {"type": "audio_end"}
```
Keep this protocol documented here and mirrored in `firmware/` and `gateway/` constants —
it is the one contract between the two halves of the repo.
## Key decisions & rationale
- **ESP-IDF native (not Arduino/ESPHome)**: the P4 + MIPI-DSI + esp-sr stack is only
first-class in ESP-IDF; Waveshare recommends it, and the BSP targets it.
- **Gateway owns STT/TTS (not the device)**: the P4 could run small STT models, but
server-side whisper is dramatically better, and Piper voices beat embedded TTS. The
GPU is already there. Wake word is the only speech task that must be on-device.
- **Separate gateway (not extending Tatlock)**: keeps Tatlock's API text-only and clean;
audio concerns (codecs, VAD, streaming) stay at the edge. The gateway is also where a
future second endpoint (kitchen, office) would connect.
- **Monorepo**: the WS protocol couples firmware and gateway; versioning them together
avoids contract drift.