Files
L'électron rare 20aed903ba feat: AI integration — voice pipeline, hints engine, MCP server, analytics, security
- 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>
2026-03-22 13:52:45 +01:00

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