# Cosmo Voice Satellite Домашний голосовой ассистент. Слушает wake word, распознаёт речь, ходит в OpenClaw gateway, проигрывает ответ через ElevenLabs. Два агента: **Cosmo** (владельца) и **Люся** (жены) — каждый со своим wake word и своим gateway. ## Архитектура ``` mic ─► wake word (openwakeword) ─► STT (Groq) ─► OpenClaw gateway ─► TTS (ElevenLabs) ─► mpv ─► speakers ``` - **Wake word:** openwakeword (обучается на своих записях, см. ниже). Раньше планировался Porcupine — отказались. - **STT:** Groq API, `whisper-large-v3-turbo`, ru. - **LLM:** OpenClaw gateway на N100 (`192.168.31.103:18789` для cosmo, `:18790` для lusya), `openai/gpt-5.4-mini`. - **TTS:** ElevenLabs `eleven_flash_v2_5` стримом через mpv stdin. ## Структура ``` home-voice-assistant/ ├── satellite.py # entry-обёртка ├── satellite/ # рантайм │ ├── __main__.py # python -m satellite [--wake] │ ├── config.py, text.py │ ├── stt.py, audio.py, tts.py, llm.py │ └── modes.py # run_with_enter / run_with_porcupine (wake word) ├── record_wav.py # запись датасета для wake word ├── remove_silent.py # чистка тихих + перенумерация ├── training/ # пайплайн обучения wake word │ ├── step_1.py … step_5.py │ ├── training_config.json │ ├── own_samples//{positive,negative}/*.wav │ ├── openwakeword/ # форк │ └── my_custom_model// # фичи + .onnx ├── data/models/ # готовые .onnx wake word моделей └── deploy/ # setup.sh + systemd unit для Pi ``` ## Запуск ```bash python -m venv .venv && source .venv/bin/activate pip install -r requirements.txt cp .env.example .env # заполнить ключи python satellite.py # режим Enter (без wake word, для отладки) python satellite.py --wake # режим wake word (нужна обученная модель в data/models/) ``` Системные зависимости: - Python 3.12+ - `portaudio` — `brew install portaudio` - `mpv` — `brew install mpv` ## Обучение своего wake word OpenWakeWord обучает DNN-модель на твоих записях слова. Пайплайн в `training/`: | Шаг | Что делает | |-----|-----------| | `step_1.py` | Установка зависимостей (piper, openwakeword) | | `step_2.py` | Создаёт `training_config.json` (параметры обучения) | | `step_3.py` | Скачивает датасеты (audioset, fma, RIRs, ACAV features) — ~17 GB | | `step_4.py` | Аугментация → тренировка → экспорт `.onnx` в `data/models/` | | `step_5.py` | Проверка моделей и подсказки для `.env` | ### Запись датасета ```bash # по одной записи (Enter → 2 секунды → сохраняем) python record_wav.py cosmo positive python record_wav.py cosmo negative # непрерывно N секунд → нарезаем по 2с, тишину выкидываем python record_wav.py cosmo negative long 300 INPUT_DEVICE=1 python record_wav.py cosmo negative long 600 ``` `record_wav.py` отбраковывает тихие записи по `MIN_RMS=300` (изменить можно константой в начале файла). ### Чистка ```bash python remove_silent.py ``` Удаляет файлы с RMS ниже порога и переименовывает оставшиеся в `001.wav … NNN.wav`. ### Тренировка 1. В `training/training_config.json` укажи `wake_word_list`, `use_own_samples: true`, параметры: ```json { "wake_word_list": ["cosmo"], "use_own_samples": true, "false_activation_penalty": 100, "target_false_positives_per_hour": 3.0, "target_recall": 0.5, "number_of_training_steps": 3000, "layer_size": 64 } ``` 2. В `training/openwakeword/examples/custom_model.yml` подними `augmentation_rounds: 10` (или больше). 3. Снеси кэш если был старый запуск: ```bash rm -rf training/my_custom_model/ data/models/.onnx ``` 4. Запусти: ```bash python training/step_4.py ``` 5. Пропиши в `.env`: `WAKE_WORD_COSMO=data/models/cosmo.onnx`. ### Сколько данных нужно | Positive | Negative | Recall | |---|---|---| | 100–200 | 200+ | 0.1–0.3 (плохо) | | 300–500 | 500+ | 0.4–0.6 (минимум) | | 800–1500 | 1000+ | 0.7–0.85 | | 2000+ | 2000+ | 0.9+ | Главное — **разнообразие**: разные дистанции до микрофона, интонации, время дня, фоны. Аугментация (`augmentation_rounds`) умножит твой датасет в N раз во время обучения. Негативы должны включать **фонетически близкие** слова ("космос", "косо", "просто"), обычную речь, имена других ассистентов ("алиса", "сири"), бытовые звуки. ## Архитектурные решения - **Одна сессия диалога на день** на агента (`Conversation` в `llm.py`). История хранится клиентом, отправляется целиком. Сброс — фразой "сбрось историю" или сменой даты. - **Keep-alive HTTP** (`requests.Session`) — переиспользует TCP/TLS. - **Streaming TTS** — ElevenLabs пайпится в `mpv` через stdin, играет пока генерируется. - **STT без диска** — PCM → WAV в `BytesIO` → Groq. - **Barge-in** — `stop_speaking()` убивает mpv при новой активации. - **Ошибки не роняют сервис** — каждый слой ловит `Exception`, пишет в `errors.log`. ## .env (ключевые переменные) | Переменная | Что | |---|---| | `GATEWAY_URL`, `LUSYA_GATEWAY_URL` | OpenClaw gateways | | `GATEWAY_TOKEN`, `LUSYA_GATEWAY_TOKEN` | Авторизация | | `AGENT`, `LUSYA_AGENT` | `openclaw/main`, `openclaw/wife` | | `VOICE_MODEL` | LLM для голоса (передаётся в `x-openclaw-model`) | | `GROQ_API_KEY` | STT | | `ELEVENLABS_API_KEY`, `COSMO_TTS_VOICE`, `LUSYA_TTS_VOICE` | TTS | | `WAKE_WORD_COSMO`, `WAKE_WORD_LUSYA` | Пути к `.onnx` моделям | | `SILENCE_THRESHOLD`, `SILENCE_DURATION` | VAD | | `MAX_HISTORY` | Лимит сообщений в сессии | | `AUDIO_SINK` | На Pi: `bluez_sink.XX_XX_XX.a2dp_sink` | ## Деплой на Raspberry Pi ```bash sudo bash deploy/setup.sh sudo systemctl start cosmo-satellite sudo journalctl -u cosmo-satellite -f ``` ## Roadmap - [x] Модулизация satellite - [x] ElevenLabs streaming + barge-in - [x] Сессии диалога с автосбросом - [x] Пайплайн тренировки wake word на своих записях - [ ] Обучить рабочую модель cosmo (нужно ~500+ позитивов) - [ ] Подключить Люсю в `run_with_porcupine` (сейчас грузится только cosmo) - [ ] Проверить systemd autostart на Pi в проде - [ ] Home Assistant tool в OpenClaw - [ ] Real-time barge-in (прерывание голосом во время TTS) - [ ] Контекст окружения в system prompt - [ ] Speaker identification - [ ] Проактивные уведомления через WebSocket