Recognize simple device commands in the transcript and act on them without
a Tatlock round-trip. commands.match() maps volume up/down, mute/unmute,
"set volume to N", and "goes to eleven"/max to a "command" message sent
straight to the device; the utterance never reaches the LLM. Matching is
deliberately precise so real requests ("set an alarm for a quarter to
eleven") are not hijacked. Adds the "command" message to the device
protocol in docs/architecture.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKPbR6DY2JygHbyLjxm7Uu
Four controlled experiments characterize it: 72-byte frame times out
at CMD53 after the slave advertised credits; bus width/clock/power
save have no effect; removing dhcpc stretches MTBF ~5min -> ~13min.
Slave-side peripheral stall, upstream bug. Device self-heals in ~15s.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The device now runs the complete face contract on glass: seven states
with glyph expressions (custom 140px face font), matrix rain as pooled
label streams (22px DejaVu+Noto katakana font), rage kaomoji frames
(64px), orbit arc + elapsed counter wait cues, blink/talk/thought
animations, idle clock (SNTP, Europe/Amsterdam), and the power ladder
(active/ambient 35%/dormant 5% with rain parked).
Voice path: ES7210 mic capture task streams 16k PCM over
esp_websocket_client to the gateway; reply PCM buffers to PSRAM and
plays via the audio task; touch-to-talk (tap to speak, tap to send).
Protocol mapping per docs: thinking->effort, audio->speaking,
error event->rage (auto-composes after 2 loops), WS loss->error face.
Bring-up fixes: 8MB factory partition (fonts overflowed 1.5M),
bsp_display_lock(0) is try-lock in this adapter (use UINT32_MAX),
gong synth yields to feed IDLE0, wifi scan diagnostics (found SSID
case mismatch), esp_websocket_client pinned ~1.3 (1.4 needs IDF>5.5).
Verified on hardware: boots, connects to Wi-Fi and holds an open
WebSocket to the gateway. Sauron (iMac TTY endpoint + ops console)
planned in architecture.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
User verdict: audible and butler-non-intrusive on the bare 2W speaker.
Recorded as the house sound signature in architecture.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- firmware boots to a rendered LVGL face in 1.57s (eyes + smile + tag)
- sdkconfig: CONFIG_IDF_EXPERIMENTAL_FEATURES=y unlocks SPIRAM 200MHz
(silently degraded to 20MHz before -> MIPI-DSI underrun -> LVGL lock
starvation -> task watchdog); mirrors official 08_lvgl_demo_v9 config
- architecture.md: power management section (user prime concern) —
active/ambient/dormant/night ladder, levers, hard edges, <=300ms wake
- AGENTS/CLAUDE: replace stale bring-up warnings with verified commands
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The gateway service now lives in system-admin-toj/containers/stacks/
tatlock-ui.yml (shared docker-dataplane network: speaches by service
name, host-run Tatlock by LAN IP). Drop deploy/desklock-gateway.yml.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Settles the Python version question: floor >=3.11, no ceiling.
Container moves to python:3.13-slim, CI tests on 3.13. The embedded
Piper fallback resamples with numpy (already present via [speech]).
Verified with a live round trip at 16 kHz.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Face: new 'rage' state — 3-frame kaomoji loop (stare, flip the table,
put it back) for in-flight request failures; 'error' stays the quiet
persistent face for a dead link. Sim + artifact + design doc updated.
- Gateway: stt.py/tts.py are now pluggable backends. Default 'speaches'
talks OpenAI-format HTTP to the live container on :8601
(faster-whisper-small STT, Kokoro bm_george TTS with 24->16 kHz
audioop resample); 'embedded' fallback kept behind the [speech] extra.
Verified with a live TTS->STT round trip (warm: STT 0.27s, TTS 1.9s).
Docker image is now slim (no CUDA/ML deps). Python pinned to 3.12
(system 3.8 too old, audioop gone in 3.13).
- CI: .gitea/workflows/build.yml — lint+test on main pushes; on v* tags
test, build gateway image, push to registry, release, and trigger
Watchtower (tatlock pattern; needs REGISTRY_USER/REGISTRY_PASSWORD/
WATCHTOWER_TOKEN secrets). Runtime stack in deploy/desklock-gateway.yml.
- architecture.md: measured speech latencies, deployed-Speaches status,
CI & deployment section.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- sim/face/index.html: browser simulator of the 800x800 round panel with
six states (idle/listening/pensive/effort/speaking/error), state-driven
rain density, blink/talk/thought animations, idle clock
- effort state gets hard-required wait cues: orbiting bezel arc, elapsed
counter, max rain (Tatlock turns run 10-25s)
- rain driven by setInterval, not rAF: renders under the screenshot
tool's --virtual-time-budget and mirrors LVGL lv_timer
- architecture.md: Face design contract (state table, protocol mapping,
LVGL port notes) + latency table updated to GPU-era benchmarks
(Steward ~6s warm, full flow 11-25s; old CPU-era figures retired)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- STT/TTS move to a shared Speaches container (OpenAI-format, GPU, port
8601 proposed); gateway becomes a thin orchestrator with pluggable
speech backends (speaches default, embedded fallback)
- Record the on-device ceiling: WakeNet wake word, VAD, ES7210 AEC,
optional MultiNet fixed commands; open-vocabulary STT permanently out
- Record the real latency bottleneck (Tatlock ~2 min full local flow):
gateway must stream chat tokens and synthesize sentence-by-sentence
- Plan reply_delta + barge-in protocol additions
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>