diff --git a/docs/development/websocket-server-plan.md b/docs/development/websocket-server-plan.md index c32ed54..c98db6f 100644 --- a/docs/development/websocket-server-plan.md +++ b/docs/development/websocket-server-plan.md @@ -225,10 +225,22 @@ async def start_websocket_server(self) -> None: self._websocket_clients.add(websocket) try: - # Ждать отключения клиента + # Ждать команды от клиента async for message in websocket: - # Игнорировать входящие сообщения (только рассылка) - _LOGGER.debug(f"Received from WebSocket: {message}") + try: + data = json.loads(message) + command = data.get('command') + command_data = data.get('data', {}) + + if command: + _LOGGER.debug(f"Received command: {command}") + await self.handle_websocket_command(command, command_data) + else: + _LOGGER.debug(f"Received message (no command): {data}") + except json.JSONDecodeError: + _LOGGER.warning(f"Invalid JSON from WebSocket: {message}") + except Exception as exc: + _LOGGER.error(f"Error processing command: {exc}") except websockets.exceptions.ConnectionClosed: pass # Нормальное отключение except Exception as exc: @@ -270,7 +282,83 @@ async def stop_websocket_server(self) -> None: _LOGGER.info("WebSocket server stopped") ``` -#### 2.4 Интеграция в жизненный цикл +#### 2.4 Обработчик команд + +```python +async def handle_websocket_command(self, command: str, data: dict) -> None: + """ + Обработать команду от WebSocket клиента. + + Поддерживаемые команды: + - set_volume: Изменить громкость + - mute: Заглушить микрофон + - unmute: Включить микрофон + - play_sound: Воспроизвести WAV файл + - set_led_brightness: Яркость LED + - set_led_color: Цвет LED + - trigger_event: Отправить событие в Home Assistant + """ + import json + + try: + if command == "set_volume": + # Изменить громкость (0.0 - 1.0) + level = float(data.get("level", 1.0)) + level = max(0.0, min(1.0, level)) # Clamp 0-1 + + # Сохранить в settings (потребуется добавить mutable settings) + _LOGGER.info(f"Setting volume to {level}") + # Примечание: settings frozen, нужно будет изменить архитектуру + + elif command == "mute": + _LOGGER.info("Muting microphone") + self.microphone_muted = True + + elif command == "unmute": + _LOGGER.info("Unmuting microphone") + self.microphone_muted = False + + elif command == "play_sound": + # Воспроизвести WAV файл + wav_path = data.get("path") + if wav_path: + _LOGGER.info(f"Playing sound: {wav_path}") + await self._play_wav(wav_path, mute_microphone=False) + else: + _LOGGER.warning("play_sound command missing 'path'") + + elif command == "set_led_brightness": + # Отправить команду в LED сервис (через event.uri) + brightness = int(data.get("brightness", 128)) + _LOGGER.info(f"Setting LED brightness to {brightness}") + # Отправить событие в event service + + elif command == "set_led_color": + # Установить цвет LED + r = int(data.get("r", 0)) + g = int(data.get("g", 0)) + b = int(data.get("b", 0)) + _LOGGER.info(f"Setting LED color to RGB({r}, {g}, {b})") + # Отправить событие в event service + + elif command == "trigger_event": + # Отправить событие в Home Assistant + event_type = data.get("event_type") + event_data = data.get("data", {}) + if event_type: + _LOGGER.info(f"Triggering event: {event_type}") + # Отправить RunPipeline или другое событие + else: + _LOGGER.warning("trigger_event missing 'event_type'") + + else: + _LOGGER.warning(f"Unknown command: {command}") + + except Exception as exc: + _LOGGER.error(f"Error handling command {command}: {exc}") +``` + +#### 2.5 Интеграция в жизненный цикл **В `_start()`:** @@ -305,7 +393,7 @@ async def _stop(self) -> None: self.state = State.STOPPED ``` -#### 2.5 Обновить `forward_event()` +#### 2.6 Обновить `forward_event()` ```python async def forward_event(self, event: Event) -> None: @@ -328,6 +416,81 @@ async def forward_event(self, event: Event) -> None: --- +### Этап WS-2A: Протокол WebSocket команд (30 мин) ⏳ ОЖИДАЕТСЯ + +**Формат сообщений:** + +```json +{ + "command": "имя_команды", + "data": { + "ключ": "значение" + } +} +``` + +**Поддерживаемые команды:** + +| Команда | Данные | Описание | +|---------|--------|----------| +| `set_volume` | `{"level": 0.0-1.0}` | Изменить громкость | +| `mute` | `{}` | Заглушить микрофон | +| `unmute` | `{}` | Включить микрофон | +| `play_sound` | `{"path": "/path/to.wav"}` | Воспроизвести WAV | +| `set_led_brightness` | `{"brightness": 0-255}` | Яркость LED | +| `set_led_color` | `{"r": 255, "g": 0, "b": 0}` | Цвет LED (RGB) | +| `trigger_event` | `{"event_type": "...", "data": {...}}` | Событие в HA | + +**Примеры:** + +```javascript +// Установить громкость 70% +ws.send(JSON.stringify({ + "command": "set_volume", + "data": {"level": 0.7} +})); + +// Заглушить микрофон +ws.send(JSON.stringify({ + "command": "mute", + "data": {} +})); + +// Воспроизвести звук +ws.send(JSON.stringify({ + "command": "play_sound", + "data": {"path": "/sounds/awake.wav"} +})); + +// Установить цвет LED (красный) +ws.send(JSON.stringify({ + "command": "set_led_color", + "data": {"r": 255, "g": 0, "b": 0} +})); + +// Отправить событие в Home Assistant +ws.send(JSON.stringify({ + "command": "trigger_event", + "data": { + "event_type": "wyoming_satellite.custom", + "data": {"custom_data": "value"} + } +})); +``` + +**Ответы сервера:** + +Сервер отправляет события в формате: + +```json +{ + "type": "voice-started", + "data": {} +} +``` + +--- + ### Этап WS-3: Модификация `wyoming_satellite/__main__.py` (20 мин) ⏳ ОЖИДАЕТСЯ #### 3.1 Добавить CLI аргументы @@ -497,11 +660,11 @@ python3 -m wyoming_satellite \ | Файл | Тип | Изменения | Строк | |------|-----|-----------|-------| | `wyoming_satellite/settings.py` | Изменён | EventSettings + WebSocket | ~10 | -| `wyoming_satellite/satellite.py` | Изменён | WebSocket server + broadcast | ~150 | +| `wyoming_satellite/satellite.py` | Изменён | WebSocket server + broadcast + commands | ~200 | | `wyoming_satellite/__main__.py` | Изменён | CLI аргументы | ~20 | | `examples/websocket-service/*` | Существующий | Готовые клиенты | — | -| `docs/integration/mqtt-led.md` | Изменён | Документация WebSocket | ~50 | -| **ВСЕГО** | | | **~230 строк** | +| `docs/integration/mqtt-led.md` | Изменён | Документация WebSocket + commands | ~80 | +| **ВСЕГО** | | | **~310 строк** | --- @@ -564,6 +727,16 @@ sudo systemctl restart wyoming-satelite-vosk.service - [ ] Интеграция в `_stop()` выполнена - [ ] `python3 -m py_compile wyoming_satellite/satellite.py` проходит +### ⏳ Этап WS-2A: Протокол команд (ожидается) + +- [ ] `handle_websocket_command()` реализован +- [ ] Команда `set_volume` работает +- [ ] Команда `mute`/`unmute` работает +- [ ] Команда `play_sound` работает +- [ ] Команда `set_led_*` работает +- [ ] Команда `trigger_event` работает +- [ ] Обработка ошибок реализована + ### ⏳ Этап WS-3: __main__.py (ожидается) - [ ] `--websocket-enabled` аргумент добавлен