New features: - wyoming_satellite/xvf_usb.py — прямой USB доступ к XVF3800 без subprocess - docs/development/multi-event-server-plan.md — план разработки event server - examples/respeaker_usb_mic_array.py — исходный файл с LED анимациями Documentation updates: - DEPLOY.md — добавлена секция про LED управление и event server план - docs/integration/mqtt-led.md — обновлена документация по USB и event server - docs/deployment/systemd-services.md — добавлен event server раздел Refactoring: - examples/wyoming-led.py — замена subprocess на XVF3800Controller - install_service.sh — удалён (устарел, используется install_systemd_services.sh) Bug fixes: - wyoming-led.py — яркость DoA эффекта через масштабирование цвета - systemd сервис — добавлена поддержка wyoming_satellite.xvf_usb модуля
898 lines
27 KiB
Markdown
898 lines
27 KiB
Markdown
# План: Множественные подключения event сервисов к Wyoming Satellite
|
||
|
||
**Дата:** 2026-05-20
|
||
**Статус:** Черновик
|
||
**Ветка:** `feature/multi-event-server`
|
||
|
||
---
|
||
|
||
## Стратегия разработки
|
||
|
||
### Ветка
|
||
|
||
**Создать:** `feature/multi-event-server` (от `master`)
|
||
|
||
- ✅ `master` остаётся стабильным
|
||
- ✅ Тестирование в изоляции
|
||
- ✅ Возможность отката
|
||
|
||
### Резервные копии
|
||
|
||
Перед началом работ:
|
||
|
||
```bash
|
||
cp wyoming_satellite/satellite.py wyoming_satellite/satellite.py.backup
|
||
cp wyoming_satellite/settings.py wyoming_satellite/settings.py.backup
|
||
cp wyoming_satellite/__main__.py wyoming_satellite/__main__.py.backup
|
||
```
|
||
|
||
---
|
||
|
||
## Архитектура
|
||
|
||
### Текущее состояние
|
||
|
||
```
|
||
┌─────────────────┐ ┌─────────────────┐
|
||
│ Home Assistant │ ──────► │ Satellite │
|
||
│ (Wyoming │ 10700 │ (Wyoming │
|
||
│ Client) │ │ Server) │
|
||
└─────────────────┘ └────────┬────────┘
|
||
│
|
||
│ 10500 (один клиент!)
|
||
▼
|
||
┌─────────────────┐
|
||
│ Event Service │
|
||
│ (клиент) │
|
||
└─────────────────┘
|
||
```
|
||
|
||
**Проблема:** Satellite поддерживает **только одного** event клиента через `event.uri`
|
||
|
||
### Целевое состояние
|
||
|
||
```
|
||
┌─────────────────┐ ┌─────────────────┐
|
||
│ Home Assistant │ ──────► │ Satellite │
|
||
│ (Wyoming │ 10700 │ (Wyoming │
|
||
│ Client) │ │ Server) │
|
||
└─────────────────┘ └────────┬────────┘
|
||
│
|
||
┌───────────────┼───────────────┐
|
||
│ │ │
|
||
│ 10600 (event │ │
|
||
│ server) │ │
|
||
▼ ▼ ▼
|
||
┌─────────┐ ┌──────────┐ ┌──────────┐
|
||
│ LED │ │ MQTT │ │ Logger │
|
||
│ Service │ │ Service │ │ Service │
|
||
└─────────┘ └──────────┘ └──────────┘
|
||
```
|
||
|
||
**Решение:** Event сервер на satellite для множественных подключений
|
||
|
||
---
|
||
|
||
## Этапы реализации
|
||
|
||
### Этап 1: Подготовка (30 мин)
|
||
|
||
#### 1.1 Создать ветку
|
||
|
||
```bash
|
||
cd ~/projects/Satellite
|
||
git checkout master
|
||
git pull
|
||
git checkout -b feature/multi-event-server
|
||
```
|
||
|
||
#### 1.2 Создать резервные копии
|
||
|
||
```bash
|
||
cp wyoming_satellite/satellite.py wyoming_satellite/satellite.py.backup
|
||
cp wyoming_satellite/settings.py wyoming_satellite/settings.py.backup
|
||
cp wyoming_satellite/__main__.py wyoming_satellite/__main__.py.backup
|
||
```
|
||
|
||
#### 1.3 Создать тестовый скрипт event-клиента
|
||
|
||
**Файл:** `examples/test_event_client.py`
|
||
|
||
```python
|
||
#!/usr/bin/env python3
|
||
"""Test event client для подключения к satellite."""
|
||
|
||
import argparse
|
||
import asyncio
|
||
import logging
|
||
from wyoming.event import Event
|
||
from wyoming.client import AsyncClient
|
||
|
||
logging.basicConfig(level=logging.INFO)
|
||
logger = logging.getLogger("test_event_client")
|
||
|
||
async def main(args):
|
||
logger.info(f"Connecting to {args.uri}...")
|
||
client = AsyncClient.from_uri(args.uri)
|
||
await client.connect()
|
||
logger.info("Connected!")
|
||
|
||
while True:
|
||
event = await client.read_event()
|
||
if event is None:
|
||
logger.warning("Server disconnected")
|
||
break
|
||
logger.info(f"Event: {event}")
|
||
|
||
if __name__ == "__main__":
|
||
parser = argparse.ArgumentParser()
|
||
parser.add_argument("--uri", default="tcp://127.0.0.1:10600")
|
||
args = parser.parse_args()
|
||
asyncio.run(main(args))
|
||
```
|
||
|
||
---
|
||
|
||
### Этап 2: Модификация `wyoming_satellite/settings.py` (1 час)
|
||
|
||
**Добавить настройки event сервера:**
|
||
|
||
```python
|
||
@dataclass(frozen=True)
|
||
class EventSettings(ServiceSettings):
|
||
"""External event service settings."""
|
||
|
||
startup: Optional[List[str]] = None
|
||
streaming_start: Optional[List[str]] = None
|
||
streaming_stop: Optional[List[str]] = None
|
||
detect: Optional[List[str]] = None
|
||
detection: Optional[List[str]] = None
|
||
played: Optional[List[str]] = None
|
||
transcript: Optional[List[str]] = None
|
||
stt_start: Optional[List[str]] = None
|
||
stt_stop: Optional[List[str]] = None
|
||
synthesize: Optional[List[str]] = None
|
||
tts_start: Optional[List[str]] = None
|
||
tts_stop: Optional[List[str]] = None
|
||
error: Optional[List[str]] = None
|
||
connected: Optional[List[str]] = None
|
||
disconnected: Optional[List[str]] = None
|
||
|
||
# === НОВЫЕ ПОЛЯ: Event server ===
|
||
server_enabled: bool = False
|
||
"""True если event сервер должен быть запущен."""
|
||
|
||
server_host: str = "0.0.0.0"
|
||
"""Host для event сервера."""
|
||
|
||
server_port: int = 10600
|
||
"""Port для event сервера."""
|
||
```
|
||
|
||
**Итого изменений:** ~20 строк
|
||
|
||
---
|
||
|
||
### Этап 3: Модификация `wyoming_satellite/satellite.py` (3-4 часа)
|
||
|
||
#### 3.1 Добавить атрибуты для event сервера
|
||
|
||
В `SatelliteBase.__init__()` после существующих атрибутов:
|
||
|
||
```python
|
||
# Event server (новый)
|
||
self._event_server: Optional[asyncio.Server] = None
|
||
self._event_clients: List[asyncio.StreamWriter] = []
|
||
self._event_clients_lock = asyncio.Lock()
|
||
```
|
||
|
||
**Импорт:** `List` из `typing` (если ещё не импортирован)
|
||
|
||
#### 3.2 Метод для рассылки событий всем клиентам
|
||
|
||
После существующих методов добавить:
|
||
|
||
```python
|
||
async def _broadcast_to_clients(self, event: Event) -> None:
|
||
"""
|
||
Отправить событие всем подключённым event клиентам.
|
||
|
||
Мёртвые подключения автоматически удаляются.
|
||
"""
|
||
async with self._event_clients_lock:
|
||
if not self._event_clients:
|
||
return
|
||
|
||
dead_clients = []
|
||
for i, writer in enumerate(self._event_clients):
|
||
try:
|
||
await async_write_event(event, writer)
|
||
except Exception:
|
||
_LOGGER.debug(
|
||
f"Failed to send event to client {i}, removing"
|
||
)
|
||
dead_clients.append(i)
|
||
|
||
# Удалить мёртвые подключения
|
||
for i in reversed(dead_clients):
|
||
self._event_clients.pop(i)
|
||
```
|
||
|
||
#### 3.3 Заменить `forward_event()`
|
||
|
||
**Было:**
|
||
|
||
```python
|
||
async def forward_event(self, event: Event) -> None:
|
||
"""Forward an event to the event service."""
|
||
if self._event_queue is not None:
|
||
self._event_queue.put_nowait(event)
|
||
```
|
||
|
||
**Стало:**
|
||
|
||
```python
|
||
async def forward_event(self, event: Event) -> None:
|
||
"""
|
||
Forward an event to all event consumers.
|
||
|
||
Sends to:
|
||
- Legacy event service (if configured via event.uri)
|
||
- All connected event server clients
|
||
"""
|
||
# Legacy event service (через TCP клиент)
|
||
if self._event_queue is not None:
|
||
self._event_queue.put_nowait(event)
|
||
|
||
# Event server клиенты
|
||
await self._broadcast_to_clients(event)
|
||
```
|
||
|
||
#### 3.4 Event сервер для подключений
|
||
|
||
Добавить методы после `_event_task_proc()`:
|
||
|
||
```python
|
||
async def start_event_server(self) -> None:
|
||
"""
|
||
Запустить сервер для event клиентов.
|
||
|
||
Клиенты могут подключаться к порту server_port и получать все события.
|
||
"""
|
||
async def handle_client(
|
||
reader: asyncio.StreamReader,
|
||
writer: asyncio.StreamWriter
|
||
) -> None:
|
||
client_addr = writer.get_extra_info('peername')
|
||
_LOGGER.info(f"Event client connected: {client_addr}")
|
||
|
||
async with self._event_clients_lock:
|
||
self._event_clients.append(writer)
|
||
|
||
try:
|
||
# Ждать отключения клиента
|
||
await reader.read(1)
|
||
except asyncio.IncompleteReadError:
|
||
pass # Нормальное отключение
|
||
except Exception as exc:
|
||
_LOGGER.debug(f"Event client error: {exc}")
|
||
finally:
|
||
async with self._event_clients_lock:
|
||
if writer in self._event_clients:
|
||
self._event_clients.remove(writer)
|
||
|
||
try:
|
||
writer.close()
|
||
await writer.wait_closed()
|
||
except Exception:
|
||
pass
|
||
|
||
_LOGGER.info(f"Event client disconnected: {client_addr}")
|
||
|
||
self._event_server = await asyncio.start_server(
|
||
handle_client,
|
||
self.settings.event.server_host,
|
||
self.settings.event.server_port
|
||
)
|
||
|
||
_LOGGER.info(
|
||
f"Event server listening on "
|
||
f"{self.settings.event.server_host}:{self.settings.event.server_port}"
|
||
)
|
||
|
||
async def stop_event_server(self) -> None:
|
||
"""
|
||
Остановить event сервер и закрыть все подключения.
|
||
"""
|
||
_LOGGER.info("Stopping event server...")
|
||
|
||
if self._event_server:
|
||
self._event_server.close()
|
||
await self._event_server.wait_closed()
|
||
self._event_server = None
|
||
|
||
# Закрыть все подключения
|
||
async with self._event_clients_lock:
|
||
for writer in self._event_clients:
|
||
writer.close()
|
||
try:
|
||
await writer.wait_closed()
|
||
except Exception:
|
||
pass
|
||
self._event_clients.clear()
|
||
|
||
_LOGGER.info("Event server stopped")
|
||
```
|
||
|
||
#### 3.5 Интеграция в жизненный цикл
|
||
|
||
**В `_start()`:**
|
||
|
||
```python
|
||
async def _start(self) -> None:
|
||
"""Connect to services."""
|
||
self.state = State.STARTING
|
||
await self._connect_to_services()
|
||
|
||
# Запустить event сервер (новый)
|
||
if self.settings.event.server_enabled:
|
||
await self.start_event_server()
|
||
|
||
self.state = State.STARTED
|
||
await self.started()
|
||
```
|
||
|
||
**В `_stop()`:**
|
||
|
||
```python
|
||
async def _stop(self) -> None:
|
||
"""Disconnect from services."""
|
||
self.server_id = None
|
||
self._writer = None
|
||
|
||
# Остановить event сервер (новый)
|
||
if self._event_server:
|
||
await self.stop_event_server()
|
||
|
||
await self._disconnect_from_services()
|
||
self._disable_ping()
|
||
self.state = State.STOPPED
|
||
```
|
||
|
||
**Итого изменений:** ~200 строк
|
||
|
||
---
|
||
|
||
### Этап 4: Модификация `wyoming_satellite/__main__.py` (30 мин)
|
||
|
||
#### 4.1 Добавить CLI аргументы
|
||
|
||
После существующих аргументов (перед `args = parser.parse_args()`):
|
||
|
||
```python
|
||
# Event server
|
||
parser.add_argument(
|
||
"--event-server-enabled",
|
||
action="store_true",
|
||
help="Enable event server for multiple event clients (default: disabled)"
|
||
)
|
||
parser.add_argument(
|
||
"--event-server-host",
|
||
default="0.0.0.0",
|
||
help="Host for event server (default: 0.0.0.0)"
|
||
)
|
||
parser.add_argument(
|
||
"--event-server-port",
|
||
type=int,
|
||
default=10600,
|
||
help="Port for event server (default: 10600)"
|
||
)
|
||
```
|
||
|
||
#### 4.2 Передать настройки в SatelliteSettings
|
||
|
||
В существующем `EventSettings(...)`:
|
||
|
||
```python
|
||
event=EventSettings(
|
||
uri=args.event_uri,
|
||
command=split_command(args.event_command),
|
||
startup=split_command(args.startup_command),
|
||
streaming_start=split_command(args.streaming_start_command),
|
||
streaming_stop=split_command(args.streaming_stop_command),
|
||
detect=split_command(args.detect_command),
|
||
detection=split_command(args.detection_command),
|
||
played=split_command(args.tts_played_command),
|
||
transcript=split_command(args.transcript_command),
|
||
stt_start=split_command(args.stt_start_command),
|
||
stt_stop=split_command(args.stt_stop_command),
|
||
synthesize=split_command(args.synthesize_command),
|
||
tts_start=split_command(args.tts_start_command),
|
||
tts_stop=split_command(args.tts_stop_command),
|
||
error=split_command(args.error_command),
|
||
connected=split_command(args.connected_command),
|
||
disconnected=split_command(args.disconnected_command),
|
||
|
||
# === НОВЫЕ ПОЛЯ ===
|
||
server_enabled=args.event_server_enabled,
|
||
server_host=args.event_server_host,
|
||
server_port=args.event_server_port,
|
||
),
|
||
```
|
||
|
||
**Итого изменений:** ~30 строк
|
||
|
||
---
|
||
|
||
### Этап 5: Тестовый event клиент (1 час)
|
||
|
||
#### 5.1 Создать `examples/test_event_client.py`
|
||
|
||
```python
|
||
#!/usr/bin/env python3
|
||
"""
|
||
Test event client для подключения к Wyoming Satellite.
|
||
|
||
Подключается к event server и получает все события.
|
||
Используется для тестирования и отладки.
|
||
|
||
Пример использования:
|
||
python3 test_event_client.py --uri tcp://127.0.0.1:10600
|
||
"""
|
||
|
||
import argparse
|
||
import asyncio
|
||
import logging
|
||
import signal
|
||
import sys
|
||
from datetime import datetime
|
||
|
||
from wyoming.event import Event
|
||
from wyoming.client import AsyncClient
|
||
|
||
logging.basicConfig(
|
||
level=logging.INFO,
|
||
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s"
|
||
)
|
||
logger = logging.getLogger("test_event_client")
|
||
|
||
stop_event = asyncio.Event()
|
||
|
||
def signal_handler(sig, frame):
|
||
logger.info(f"Получен сигнал {sig}, завершаю...")
|
||
stop_event.set()
|
||
|
||
async def main(args):
|
||
logger.info(f"Connecting to {args.uri}...")
|
||
|
||
client = AsyncClient.from_uri(args.uri)
|
||
|
||
try:
|
||
await client.connect()
|
||
logger.info("✅ Connected!")
|
||
|
||
event_count = 0
|
||
while not stop_event.is_set():
|
||
try:
|
||
event = await asyncio.wait_for(
|
||
client.read_event(),
|
||
timeout=5.0
|
||
)
|
||
|
||
if event is None:
|
||
logger.warning("⚠️ Server disconnected")
|
||
break
|
||
|
||
event_count += 1
|
||
event_type = event.get("type", "unknown")
|
||
logger.info(f"📨 [{event_count}] {event_type}")
|
||
|
||
if args.verbose:
|
||
logger.debug(f" Data: {event}")
|
||
|
||
except asyncio.TimeoutError:
|
||
# Проверка stop_event
|
||
continue
|
||
except Exception as exc:
|
||
logger.error(f"❌ Error reading event: {exc}")
|
||
break
|
||
|
||
logger.info(f"Всего получено событий: {event_count}")
|
||
|
||
except Exception as exc:
|
||
logger.error(f"❌ Error: {exc}")
|
||
finally:
|
||
try:
|
||
await client.disconnect()
|
||
except Exception:
|
||
pass
|
||
logger.info("👋 Disconnected")
|
||
|
||
if __name__ == "__main__":
|
||
parser = argparse.ArgumentParser(
|
||
description="Test event client для Wyoming Satellite"
|
||
)
|
||
parser.add_argument(
|
||
"--uri",
|
||
default="tcp://127.0.0.1:10600",
|
||
help="URI для подключения (default: tcp://127.0.0.1:10600)"
|
||
)
|
||
parser.add_argument(
|
||
"--verbose",
|
||
action="store_true",
|
||
help="Выводить полные данные событий"
|
||
)
|
||
args = parser.parse_args()
|
||
|
||
signal.signal(signal.SIGINT, signal_handler)
|
||
signal.signal(signal.SIGTERM, signal_handler)
|
||
|
||
try:
|
||
asyncio.run(main(args))
|
||
except KeyboardInterrupt:
|
||
pass
|
||
```
|
||
|
||
#### 5.2 Сделать исполняемым
|
||
|
||
```bash
|
||
chmod +x examples/test_event_client.py
|
||
```
|
||
|
||
---
|
||
|
||
### Этап 6: Тестирование (2-3 часа)
|
||
|
||
#### 6.1 Запустить satellite с event сервером
|
||
|
||
```bash
|
||
cd ~/projects/Satellite
|
||
source ~/.venv_satellite/bin/activate
|
||
|
||
python3 -m wyoming_satellite \
|
||
--uri tcp://0.0.0.0:10700 \
|
||
--event-server-enabled \
|
||
--event-server-port 10600 \
|
||
--debug
|
||
```
|
||
|
||
**Ожидаемый вывод:**
|
||
```
|
||
INFO: Ready
|
||
INFO: Event server listening on 0.0.0.0:10600
|
||
INFO: Слушаем tcp://0.0.0.0:10700
|
||
```
|
||
|
||
#### 6.2 Проверить подключения
|
||
|
||
**Терминал 1: Home Assistant (порт 10700)**
|
||
```bash
|
||
# Эмуляция подключения HA (или реальное HA)
|
||
# HA должен подключиться автоматически через Zeroconf
|
||
```
|
||
|
||
**Терминал 2: test_event_client #1 (порт 10600)**
|
||
```bash
|
||
source ~/.venv_satellite/bin/activate
|
||
python3 examples/test_event_client.py --uri tcp://127.0.0.1:10600
|
||
```
|
||
|
||
**Терминал 3: test_event_client #2 (порт 10600)**
|
||
```bash
|
||
source ~/.venv_satellite/bin/activate
|
||
python3 examples/test_event_client.py --uri tcp://127.0.0.1:10600
|
||
```
|
||
|
||
**Терминал 4:原有 wyoming-led.py (порт 10500)**
|
||
```bash
|
||
source ~/.venv_satellite/bin/activate
|
||
cd examples
|
||
python3 wyoming-led.py --host 127.0.0.1 --port 10500
|
||
```
|
||
|
||
#### 6.3 Проверить логи
|
||
|
||
**В логе satellite:**
|
||
```
|
||
INFO: Event client connected: ('127.0.0.1', XXXX) # клиент 1
|
||
INFO: Event client connected: ('127.0.0.1', XXXX) # клиент 2
|
||
INFO: Connected to server # Home Assistant
|
||
```
|
||
|
||
**Проверить:**
|
||
- ✅ Все 3 клиента получают события
|
||
- ✅ Home Assistant продолжает работать (порт 10700)
|
||
- ✅ При отключении клиента satellite не падает
|
||
- ✅ wyoming-led.py работает через старый порт 10500
|
||
|
||
#### 6.4 Тест на устойчивость
|
||
|
||
```bash
|
||
# Отключить клиента 1 (Ctrl+C)
|
||
# Проверить что клиент 2 продолжает работать
|
||
|
||
# Отключить wyoming-led.py
|
||
# Проверить что event клиенты работают
|
||
|
||
# Отключить Home Assistant
|
||
# Проверить что event клиенты работают
|
||
```
|
||
|
||
---
|
||
|
||
### Этап 7: Обновление документации (30 мин)
|
||
|
||
#### 7.1 `docs/integration/mqtt-led.md`
|
||
|
||
Добавить секцию после "Архитектура USB контроллера":
|
||
|
||
```markdown
|
||
## Event Server (множественные подключения)
|
||
|
||
Начиная с версии 2026.06, Wyoming Satellite поддерживает event server для
|
||
множественных подключений.
|
||
|
||
### Преимущества
|
||
|
||
- ✅ **Масштабируемость:** Неограниченное количество event клиентов
|
||
- ✅ **Надёжность:** Падение клиента не влияет на satellite
|
||
- ✅ **Гибкость:** Каждый клиент выбирает какие события обрабатывать
|
||
|
||
### Запуск
|
||
|
||
```bash
|
||
python3 -m wyoming_satellite \
|
||
--uri tcp://0.0.0.0:10700 \
|
||
--event-server-enabled \
|
||
--event-server-port 10600
|
||
```
|
||
|
||
### Подключение клиента
|
||
|
||
```python
|
||
from wyoming.client import AsyncClient
|
||
|
||
client = AsyncClient.from_uri("tcp://127.0.0.1:10600")
|
||
await client.connect()
|
||
|
||
while True:
|
||
event = await client.read_event()
|
||
# Обработка события
|
||
```
|
||
|
||
### Обратная совместимость
|
||
|
||
Старый `wyoming-led.py` продолжает работать через `--event-uri`:
|
||
|
||
```bash
|
||
python3 -m wyoming_satellite \
|
||
--event-uri tcp://127.0.0.1:10500 \
|
||
--event-server-enabled \
|
||
--event-server-port 10600
|
||
```
|
||
|
||
Оба метода работают параллельно!
|
||
```
|
||
|
||
#### 7.2 `docs/deployment/systemd-services.md`
|
||
|
||
Обновить секцию `wyoming-event.service`:
|
||
|
||
```markdown
|
||
### Event Server (новый)
|
||
|
||
Для поддержки множественных event клиентов добавьте в сервис:
|
||
|
||
```ini
|
||
ExecStart=/home/sirmrak/projects/.venv_satellite/bin/python3 \
|
||
/home/sirmrak/projects/Satellite/wyoming_satellite/__main__.py \
|
||
--uri tcp://0.0.0.0:10700 \
|
||
--event-server-enabled \
|
||
--event-server-port 10600
|
||
```
|
||
|
||
Клиенты подключаются к порту 10600:
|
||
|
||
```bash
|
||
python3 examples/test_event_client.py --uri tcp://127.0.0.1:10600
|
||
```
|
||
```
|
||
|
||
#### 7.3 `DEPLOY.md`
|
||
|
||
Добавить секцию после "LED управление":
|
||
|
||
```markdown
|
||
## Event Server (множественные подключения)
|
||
|
||
Для подключения нескольких event сервисов одновременно:
|
||
|
||
```bash
|
||
python3 -m wyoming_satellite \
|
||
--event-server-enabled \
|
||
--event-server-port 10600
|
||
```
|
||
|
||
**Примеры использования:**
|
||
|
||
- LED управление + MQTT уведомления + логирование в базу
|
||
- Несколько независимых обработчиков событий
|
||
- Тестовые клиенты для отладки
|
||
|
||
**Документация:** [Event Server](docs/integration/mqtt-led.md#event-server)
|
||
```
|
||
|
||
---
|
||
|
||
### Этап 8: Merge в master (после успешного тестирования)
|
||
|
||
```bash
|
||
# Проверка что всё работает
|
||
git status
|
||
git diff master
|
||
|
||
# Merge
|
||
git checkout master
|
||
git pull origin master
|
||
git merge feature/multi-event-server
|
||
|
||
# Push
|
||
git push origin master
|
||
|
||
# Удалить ветку (опционально)
|
||
git branch -d feature/multi-event-server
|
||
```
|
||
|
||
---
|
||
|
||
## Итоговые изменения
|
||
|
||
| Файл | Тип | Изменения | Строк |
|
||
|------|-----|-----------|-------|
|
||
| `wyoming_satellite/settings.py` | Изменён | EventSettings + server настройки | ~20 |
|
||
| `wyoming_satellite/satellite.py` | Изменён | Event server + broadcast | ~200 |
|
||
| `wyoming_satellite/__main__.py` | Изменён | CLI аргументы | ~30 |
|
||
| `examples/test_event_client.py` | Новый | Тестовый клиент | ~80 |
|
||
| `docs/integration/mqtt-led.md` | Изменён | Документация event server | ~40 |
|
||
| `docs/deployment/systemd-services.md` | Изменён | Обновление сервисов | ~20 |
|
||
| `DEPLOY.md` | Изменён | Quick start | ~15 |
|
||
| **ВСЕГО** | | | **~405 строк** |
|
||
|
||
---
|
||
|
||
## Риски и план Б
|
||
|
||
| Риск | Вероятность | Влияние | Решение |
|
||
|------|-------------|---------|---------|
|
||
| Конфликт портов (10600 занят) | Низкая | Среднее | Проверка `lsof -i :10600`, смена порта |
|
||
| Утечка памяти (клиенты) | Средняя | Высокое | Логирование подключений, таймауты |
|
||
| Падение satellite | Низкая | Критичное | Тестирование в ветке, backup файлов |
|
||
| Home Assistant отключится | Низкая | Критичное | Порт 10700 не меняется, обратная совместимость |
|
||
| Event клиенты не получают события | Средняя | Среднее | Проверка `_broadcast_to_clients()`, логирование |
|
||
|
||
### План Б: Откат
|
||
|
||
Если что-то идёт не так:
|
||
|
||
```bash
|
||
cd ~/projects/Satellite
|
||
git checkout master
|
||
|
||
# Восстановить файлы
|
||
cp wyoming_satellite/satellite.py.backup wyoming_satellite/satellite.py
|
||
cp wyoming_satellite/settings.py.backup wyoming_satellite/settings.py
|
||
cp wyoming_satellite/__main__.py.backup wyoming_satellite/__main__.py
|
||
|
||
# Перезапустить сервисы
|
||
sudo systemctl daemon-reload
|
||
sudo systemctl restart wyoming-satelite-vosk.service
|
||
```
|
||
|
||
---
|
||
|
||
## Контрольный список
|
||
|
||
### Перед началом
|
||
|
||
- [ ] Создана ветка `feature/multi-event-server`
|
||
- [ ] Созданы backup файлов
|
||
- [ ] Текущая версия работает стабильно
|
||
|
||
### После этапа 3
|
||
|
||
- [ ] satellite.py изменён без синтаксических ошибок
|
||
- [ ] `python3 -m py_compile wyoming_satellite/satellite.py` проходит
|
||
- [ ] Импорт `from wyoming_satellite.satellite import SatelliteBase` работает
|
||
|
||
### После этапа 4
|
||
|
||
- [ ] `python3 -m wyoming_satellite --help` показывает новые аргументы
|
||
- [ ] `--event-server-enabled` распознаётся
|
||
|
||
### После этапа 5
|
||
|
||
- [ ] `examples/test_event_client.py --help` работает
|
||
- [ ] Клиент подключается к порту 10600
|
||
|
||
### После этапа 6
|
||
|
||
- [ ] Satellite запускается с `--event-server-enabled`
|
||
- [ ] Multiple clients can connect
|
||
- [ ] Все клиенты получают события
|
||
- [ ] Home Assistant работает (порт 10700)
|
||
- [ ] wyoming-led.py работает (порт 10500)
|
||
- [ ] При отключении клиента satellite не падает
|
||
|
||
### После этапа 7
|
||
|
||
- [ ] Документация обновлена
|
||
- [ ] Примеры в документации работают
|
||
|
||
### Перед merge
|
||
|
||
- [ ] Все тесты пройдены
|
||
- [ ] Документация актуальна
|
||
- [ ] Backup файлы удалены (опционально)
|
||
|
||
---
|
||
|
||
## Приложения
|
||
|
||
### A. Пример события в event клиенте
|
||
|
||
```json
|
||
{
|
||
"type": "voice-started",
|
||
"data": {}
|
||
}
|
||
```
|
||
|
||
```json
|
||
{
|
||
"type": "voice-stopped",
|
||
"data": {}
|
||
}
|
||
```
|
||
|
||
```json
|
||
{
|
||
"type": "transcript",
|
||
"data": {
|
||
"text": "привет дроид"
|
||
}
|
||
}
|
||
```
|
||
|
||
### B. Диагностика
|
||
|
||
```bash
|
||
# Проверка портов
|
||
lsof -i :10600
|
||
lsof -i :10700
|
||
lsof -i :10500
|
||
|
||
# Проверка подключений
|
||
netstat -tlnp | grep python
|
||
|
||
# Логи satellite
|
||
journalctl -u wyoming-satelite-vosk.service -f
|
||
|
||
# Тест подключения
|
||
telnet 127.0.0.1 10600
|
||
```
|
||
|
||
### C. Сравнение подходов
|
||
|
||
| Аспект | Старый (push) | Новый (pull) |
|
||
|--------|---------------|--------------|
|
||
| Подключения | 1 satellite → 1 event | N clients → 1 satellite |
|
||
| Отказоустойчивость | Падение event → satellite работает | Падение client → satellite работает |
|
||
| Масштабируемость | Ограничено 1 сервисом | Неограниченно клиентов |
|
||
| Сложность | Проще | Сложнее (управление клиентами) |
|
||
| Гибкость | Низкая | Высокая (каждый клиент выбирает события) |
|
||
| Обратная совместимость | ✅ Работает | ✅ Работает через event.uri |
|
||
|
||
---
|
||
|
||
**Конец документа**
|