diff --git a/README.md b/README.md index 5e759dc..76bb4bd 100644 --- a/README.md +++ b/README.md @@ -137,10 +137,77 @@ PY Don't send `wake` from the smoke — it would open a real OpenAI session and burn budget. Plan 3 exercises that end-to-end. +## Plan 3: Pi client + +The Python client lives in `client/`. It is shipped to the Pi inside +`client.tar.gz` (built into the backend Docker image) and installed by +`install.sh`. Modules: + +- `main.py` — entry point. Sets `PA_ALSA_PLUGHW=1` before importing + sounddevice. Wires audio, state, wakeword, session, and playback. +- `state.py` — `IDLE → WAKE_PENDING → LISTENING ↔ ASSISTANT_SPEAKING → + IDLE_PENDING → IDLE`. `wakeword_enabled` is true only in `IDLE`; + `uplink_enabled` is true only in `LISTENING`. +- `audio.py` — USB device discovery, persistent 24 kHz/mono/int16 InputStream + and OutputStream, and the 24 → 16 kHz resample for the wakeword feed. +- `wakeword.py` — openwakeword `alexa` model, threshold + cooldown. +- `playback.py` — single worker thread that owns the OutputStream and + implements `set_volume` as a software gain (the USB Speaker Phone has no + hardware playback volume control; only a `PCM Playback Switch`). +- `session.py` — backend WS client, dispatch, tool round-trip, + exponential-backoff reconnect. +- `config.py` — `~/assistant/state/config.json` (mode 0600). +- `pair.py` — `python -m client.pair --backend [--code ]`. + +### Updating the Pi + +For a fast iteration loop: + +```sh +sshpass -p 'assistant' rsync -az --delete --exclude __pycache__ --exclude tests \ + client/ pi@192.168.50.115:assistant/code/client/ +sshpass -p 'assistant' ssh pi@192.168.50.115 'systemctl --user restart assistant' +sshpass -p 'assistant' ssh pi@192.168.50.115 \ + 'journalctl --user -u assistant -n 50 --no-pager' +``` + +For a production update (after a backend deploy): + +```sh +sshpass -p 'assistant' ssh pi@192.168.50.115 \ + 'curl -fsSL https://assistant.volcanic.tes.gd/install.sh | bash' +``` + +### Local tests + +```sh +python3 -m venv .venv +.venv/bin/pip install -r requirements-dev.txt requests websockets numpy scipy +.venv/bin/python -m pytest client/tests +``` + +Hardware-touching modules (`audio.py` streams, `wakeword.py`, `playback.py`, +`session.run_forever`) are smoke-tested via the live deploy. Deterministic +modules (`state.py`, `config.py`, `pair.py`, the resample helper, the session +dispatcher) are covered by the pytest suite. + +### End-to-end smoke + +With the unit running on the Pi: + +```sh +sshpass -p 'assistant' ssh pi@192.168.50.115 'journalctl --user -u assistant -f' +``` + +…then say `alexa` near the Speaker Phone. Expected log sequence: `wakeword +fired`, `state IDLE -> WAKE_PENDING`, `session_started conversation_id=…`, +`state WAKE_PENDING -> LISTENING`. Speak a question; the assistant replies +through the speaker (`state LISTENING -> ASSISTANT_SPEAKING`, then +`assistant_done`). Say "bye" → `session_ended reason=tool`, then `state +LISTENING -> IDLE_PENDING -> IDLE`. The conversation row + turns are visible +via the deployed backend's DB (admin UI in Plan 4). + ## What's next -- **Plan 3** — full Python client on the Pi (wakeword, VAD, audio plumbing, - state machine, install + pair CLIs). This is the production caller of - `/device` and the integration test for everything above. - **Plan 4** — React management UI for the admin dashboard, per-device config editor, and conversation history viewer.