docs: добавлен протокол WebSocket команд (set_volume, mute, play_sound, etc.)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
@@ -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` аргумент добавлен
|
||||
|
||||
Reference in New Issue
Block a user