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

4.1 KiB
Raw Blame History

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.