20aed903ba
- Voice pipeline: ESP32 WebSocket client → voice bridge → LLM → Piper TTS (Tower :8001) - Hints engine: 3 puzzles (LA_440, LEFOU_PIANO, QR_FINALE), anti-cheat, 3 hint levels - MCP hardware server: 6 tools (puzzle, audio, LED, camera, scenario, status), stdio transport - Analytics: ESP32 module + 6 web endpoints + Dashboard UI with chat interface - Security: auth middleware (Bearer NVS), rate limiting, input validation on 30 endpoints - Frontend: code-split (1.1MB → 210KB initial), ErrorBoundary, API timeout, WS reconnect - Tests: 24 Python + 38 TypeScript + 18 MCP = 80 project tests (+ 19 mascarade) - Specs: AI_INTEGRATION_SPEC, MCP_HARDWARE_SERVER_SPEC, QA_TEST_MATRIX_SPEC - Docs: SECURITY, DEPLOYMENT_RUNBOOK, voice pipeline guide, AI architecture map - 6 AI agent definitions (.github/agents/ai_*.md) - TUI orchestration script (tools/dev/zacus_tui.py) - Docker compose TTS for Tower + KXKM-AI - CHANGELOG, README, mkdocs.yml updated - Cycle detection (DFS) in runtime3 validator - Sprint plan: plans/SPRINT_AI_INTEGRATION.md Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
300 lines
10 KiB
Markdown
300 lines
10 KiB
Markdown
# Voice Pipeline Guide — Professeur Zacus
|
|
|
|
Technical guide for integrating voice interaction into the Zacus ESP32-S3 installation.
|
|
|
|
## Overview
|
|
|
|
The voice pipeline enables visitors to interact with Professeur Zacus using natural speech. The system uses a split architecture: wake word detection runs locally on the ESP32-S3 (via ESP-SR), while speech-to-text, LLM inference, and text-to-speech run server-side via the mascarade bridge.
|
|
|
|
## Hardware Requirements
|
|
|
|
### INMP441 MEMS Microphone
|
|
|
|
| INMP441 Pin | ESP32-S3 GPIO | Notes |
|
|
|-------------|---------------|-------|
|
|
| SCK (BCLK) | GPIO 41 | I2S0 bit clock |
|
|
| WS (LRCLK) | GPIO 42 | I2S0 word select |
|
|
| SD (DATA) | GPIO 2 | I2S0 data in |
|
|
| L/R | GND | Left channel select |
|
|
| VDD | 3.3V | Power supply |
|
|
| GND | GND | Ground |
|
|
|
|
**Important:** The microphone uses I2S0, which is a separate bus from the speaker output on I2S1 (MAX98357A). Both can operate simultaneously for full-duplex audio.
|
|
|
|
### Speaker (already present)
|
|
|
|
The Freenove All-in-One board includes a MAX98357A I2S amplifier on I2S1. No additional wiring needed for audio output.
|
|
|
|
### Bill of Materials (voice-specific)
|
|
|
|
| Part | Qty | Approx. Cost | Source |
|
|
|------|-----|-------------|--------|
|
|
| INMP441 breakout | 1 | 3-5 EUR | AliExpress, Amazon |
|
|
| Dupont wires (F-F) | 5 | included | — |
|
|
|
|
## Software Requirements
|
|
|
|
### ESP-IDF + esp-sr Component
|
|
|
|
The ESP-SR library (WakeNet, MultiNet, AFE) requires the ESP-IDF framework. The current project uses Arduino framework via PlatformIO.
|
|
|
|
**Option A: Full migration to ESP-IDF (recommended for production)**
|
|
|
|
```ini
|
|
; platformio.ini changes
|
|
[env:freenove_allinone]
|
|
framework = espidf
|
|
; All Arduino-style code must be rewritten to ESP-IDF APIs
|
|
```
|
|
|
|
**Option B: Arduino as ESP-IDF component (gradual migration)**
|
|
|
|
```ini
|
|
; platformio.ini changes
|
|
[env:freenove_allinone]
|
|
framework = arduino, espidf
|
|
; Allows mixing Arduino and ESP-IDF code
|
|
; esp-sr can be added as an IDF component
|
|
```
|
|
|
|
### esp-sr Component Registration
|
|
|
|
Create or update `idf_component.yml`:
|
|
```yaml
|
|
dependencies:
|
|
espressif/esp-sr:
|
|
version: "^1.0.0"
|
|
# Includes: WakeNet, MultiNet, AFE (AEC + NS + VAD)
|
|
```
|
|
|
|
### Partition Table
|
|
|
|
ESP-SR models need flash space. Update `partitions.csv`:
|
|
```
|
|
# Name, Type, SubType, Offset, Size, Flags
|
|
model, data, spiffs, , 1500K, ; WakeNet + optional MultiNet models
|
|
```
|
|
|
|
## Voice Flow — Sequence Diagram
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant Mic as INMP441<br/>(I2S0)
|
|
participant AFE as ESP-SR AFE<br/>(AEC+NS+VAD)
|
|
participant WN as WakeNet9<br/>("Prof Zacus")
|
|
participant WS as WebSocket<br/>Client
|
|
participant Bridge as mascarade<br/>bridge:4001
|
|
participant LLM as LLM<br/>(Qwen/GPT)
|
|
participant TTS as Piper TTS<br/>(French)
|
|
participant Spk as Speaker<br/>(I2S1)
|
|
|
|
Note over Mic,AFE: LISTENING state
|
|
Mic->>AFE: 16kHz 16-bit PCM (continuous)
|
|
AFE->>WN: Cleaned audio frames
|
|
WN-->>WN: Scanning for wake word...
|
|
|
|
Note over WN,WS: WAKE_DETECTED state
|
|
WN->>WS: Wake word detected!
|
|
Spk->>Spk: Play chime / LED feedback
|
|
|
|
Note over WS,Bridge: STREAMING state
|
|
WS->>Bridge: {"type":"hello","wakeword":"professeur zacus"}
|
|
loop Until VAD silence
|
|
AFE->>WS: Processed audio frames
|
|
WS->>Bridge: OPUS-encoded binary frames
|
|
Bridge-->>WS: {"type":"stt","text":"..."} (interim)
|
|
end
|
|
WS->>Bridge: {"type":"end_of_speech"}
|
|
|
|
Note over Bridge,LLM: PROCESSING state
|
|
Bridge->>LLM: Final transcription -> LLM prompt
|
|
LLM->>Bridge: Response text (streamed)
|
|
|
|
Note over Bridge,Spk: SPEAKING state
|
|
Bridge->>TTS: Response text
|
|
TTS->>Bridge: OPUS audio stream
|
|
Bridge->>WS: {"type":"tts","state":"start"}
|
|
loop TTS audio chunks
|
|
Bridge->>WS: OPUS-encoded binary frames
|
|
WS->>Spk: Decode OPUS -> PCM -> I2S1
|
|
end
|
|
Bridge->>WS: {"type":"tts","state":"end"}
|
|
|
|
Note over Mic,Spk: Back to LISTENING
|
|
```
|
|
|
|
## XiaoZhi WebSocket Protocol
|
|
|
|
The voice pipeline follows the [XiaoZhi-ESP32](https://github.com/78/xiaozhi-esp32) WebSocket protocol, which is well-documented and battle-tested.
|
|
|
|
### Client -> Server Messages
|
|
|
|
| Type | Format | Description |
|
|
|------|--------|-------------|
|
|
| Hello | `{"type":"hello","version":1,"wakeword":"..."}` | Session start after wake detection |
|
|
| Audio | Binary (OPUS frames) | 20ms OPUS-encoded audio at 16kHz mono |
|
|
| Abort | `{"type":"abort"}` | Cancel current interaction |
|
|
| End of speech | `{"type":"end_of_speech"}` | VAD detected end of utterance |
|
|
|
|
### Server -> Client Messages
|
|
|
|
| Type | Format | Description |
|
|
|------|--------|-------------|
|
|
| STT result | `{"type":"stt","text":"..."}` | Interim/final transcription |
|
|
| LLM text | `{"type":"llm","text":"...","emotion":"..."}` | Streamed LLM response |
|
|
| TTS control | `{"type":"tts","state":"start\|end"}` | TTS playback boundaries |
|
|
| TTS audio | Binary (OPUS frames) | OPUS-encoded speech audio |
|
|
| Emotion | `{"type":"emotion","value":"happy\|thinking\|..."}` | Facial expression hint |
|
|
|
|
### Audio Encoding
|
|
|
|
- **Codec:** OPUS (RFC 6716)
|
|
- **Frame size:** 20ms
|
|
- **Sample rate:** 16kHz
|
|
- **Channels:** Mono
|
|
- **Bitrate:** ~16kbps (voice-optimized)
|
|
- **Library:** `libopus` via ESP-IDF component or bundled in esp-sr
|
|
|
|
## Server-Side Requirements
|
|
|
|
### mascarade Bridge Endpoint
|
|
|
|
The mascarade bridge (running on GrosMac or VM) needs a `/voice` WebSocket endpoint that:
|
|
|
|
1. Receives OPUS audio frames from ESP32
|
|
2. Decodes OPUS -> PCM
|
|
3. Runs STT (Whisper via faster-whisper or whisper.cpp)
|
|
4. Sends transcription to LLM pipeline (existing mascarade flow)
|
|
5. Sends LLM response text to TTS
|
|
6. Encodes TTS output as OPUS
|
|
7. Streams OPUS frames back to ESP32
|
|
|
|
This endpoint does not exist yet in mascarade. It will be added as a new route in the bridge service.
|
|
|
|
### TTS Services
|
|
|
|
Two TTS options are provided via Docker Compose (`tools/dev/docker-compose.tts.yml`):
|
|
|
|
**Piper TTS (recommended for low latency)**
|
|
- CPU-only, runs on VM (192.168.0.119)
|
|
- French voice: `fr_FR-siwis-medium` or `fr_FR-upmc-medium`
|
|
- Latency: ~200ms for short sentences
|
|
- OpenAI-compatible API on port 8000
|
|
|
|
**Coqui XTTS-v2 (for voice cloning)**
|
|
- GPU required, deploy on KXKM-AI (RTX 4090)
|
|
- Can clone Professeur Zacus's voice from ~30s of sample audio
|
|
- Higher quality but higher latency (~1-2s)
|
|
- API on port 5002
|
|
|
|
### STT Service
|
|
|
|
- **Recommended:** faster-whisper with `large-v3` model
|
|
- **Language:** French (`--language fr`)
|
|
- **Deploy on:** KXKM-AI for GPU inference, or VM for CPU (slower)
|
|
- **Alternative:** whisper.cpp for CPU-optimized inference
|
|
|
|
## Custom Wake Word — Espressif Process
|
|
|
|
To get a custom "Professeur Zacus" wake word model for WakeNet9:
|
|
|
|
1. **Contact Espressif** via https://www.espressif.com/en/contact-us
|
|
2. **Provide:**
|
|
- Target phrase: "Professeur Zacus"
|
|
- Language: French
|
|
- 200+ audio recordings of the phrase (diverse speakers, environments)
|
|
- Target platform: ESP32-S3 with WakeNet9
|
|
3. **Timeline:** 2-4 weeks
|
|
4. **Cost:** Varies (may be free for open-source/educational projects)
|
|
5. **Delivery:** `.wn9` model file to flash to partition
|
|
|
|
### Recording Tips for Training Data
|
|
|
|
- Record in the target environment (exhibition space)
|
|
- Include male/female voices, different ages
|
|
- Include background noise variations
|
|
- Record at 16kHz, 16-bit, mono WAV
|
|
- Minimum 200 recordings, ideally 500+
|
|
- Use the INMP441 mic itself for best acoustic match
|
|
|
|
## French Voice Command Strategy
|
|
|
|
**Local MultiNet is NOT recommended for French** because:
|
|
- MultiNet only supports Chinese, English, and a few other languages
|
|
- French phonetics are complex (liaisons, nasals, silent letters)
|
|
- Limited command vocabulary even in supported languages
|
|
|
|
**Strategy: Server-side ASR for all speech recognition**
|
|
|
|
1. Wake word detection: Local (WakeNet9, language-agnostic acoustic model)
|
|
2. All speech-to-text: Server-side via Whisper (excellent French support)
|
|
3. Intent parsing: LLM-based via mascarade (natural language understanding)
|
|
4. No local MultiNet needed — everything after wake word goes to server
|
|
|
|
This simplifies the ESP32 firmware (no MultiNet model in flash) and gives unlimited French vocabulary through the LLM.
|
|
|
|
## Migration Path — Arduino to ESP-IDF
|
|
|
|
### Phase 1: Scaffold (current)
|
|
- Voice pipeline header + stubs in Arduino framework
|
|
- No ESP-SR dependency
|
|
- Compiles and runs (functions return safe defaults)
|
|
|
|
### Phase 2: Arduino-as-Component
|
|
- Switch PlatformIO to `framework = arduino, espidf`
|
|
- Add esp-sr as IDF component
|
|
- Implement I2S mic input and AFE initialization
|
|
- Test wake word detection with "Hi Lexin" placeholder
|
|
- Keep all existing Arduino code unchanged
|
|
|
|
### Phase 3: WebSocket Streaming
|
|
- Add OPUS encoder (IDF component)
|
|
- Implement WebSocket client for mascarade bridge
|
|
- Stream audio on wake detection
|
|
- Receive and play TTS responses
|
|
|
|
### Phase 4: Production
|
|
- Order custom "Professeur Zacus" wake word
|
|
- Full ESP-IDF migration (optional, for memory optimization)
|
|
- AEC tuning (echo cancellation between speaker and mic)
|
|
- LED/display feedback during voice states
|
|
|
|
## Memory Budget
|
|
|
|
| Component | RAM Type | Size | Notes |
|
|
|-----------|----------|------|-------|
|
|
| WakeNet9 | PSRAM | ~340 KB | Single wake word model |
|
|
| AFE (1ch) | PSRAM | ~1.1 MB | AEC + noise suppression + VAD |
|
|
| OPUS codec | Internal | ~50 KB | Encoder + decoder |
|
|
| Audio buffers | PSRAM | ~64 KB | Ring buffers for I2S + WebSocket |
|
|
| **Total voice** | **Mixed** | **~1.5 MB** | |
|
|
| LVGL + UI | PSRAM | ~2 MB | Existing allocation |
|
|
| Camera | PSRAM | ~500 KB | JPEG buffer |
|
|
| **System total** | **PSRAM** | **~4 MB** | Of 8 MB available |
|
|
|
|
## File Structure
|
|
|
|
```
|
|
ESP32_ZACUS/ui_freenove_allinone/
|
|
├── include/voice/
|
|
│ └── voice_pipeline.h # Public API (this scaffold)
|
|
├── src/voice/
|
|
│ └── voice_pipeline.cpp # Stub implementation
|
|
└── ...
|
|
|
|
docs/voice/
|
|
└── VOICE_PIPELINE_GUIDE.md # This document
|
|
|
|
tools/dev/
|
|
└── docker-compose.tts.yml # TTS services (Piper + XTTS-v2)
|
|
```
|
|
|
|
## References
|
|
|
|
- [XiaoZhi-ESP32](https://github.com/78/xiaozhi-esp32) — Open-source voice assistant for ESP32-S3
|
|
- [ESP-SR Programming Guide](https://docs.espressif.com/projects/esp-sr/en/latest/) — WakeNet, MultiNet, AFE
|
|
- [Piper TTS](https://github.com/rhasspy/piper) — Fast local TTS with French models
|
|
- [Coqui XTTS-v2](https://github.com/coqui-ai/TTS) — Voice cloning TTS
|
|
- [faster-whisper](https://github.com/guillaumekln/faster-whisper) — CTranslate2-based Whisper
|
|
- [OPUS codec](https://opus-codec.org/) — Low-latency audio codec
|