Files
home-voice-assistant/satellite/notifier.py
Cosmo c7df540c0b feat(voice): emit listening event between followup turns
После ответа Python сразу уходит в record() ждать follow-up
(FOLLOWUP_TIMEOUT), но планшет об этом не знал — оверлей тихо
скрывался и пользователю казалось что Cosmo его не слышит без
повторного wake-word.

Теперь между итерациями _conversation_loop шлётся notifier.listening() —
планшет показывает мягко пульсирующий орб с 'жду' + сохранённым
текстом прошлого ответа. Закрывается только по notifier.idle()
(таймаут тишины) или если пользователь что-то сказал (command).
2026-04-23 13:55:39 +00:00

81 lines
2.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Tablet notifier — пересылает состояния ассистента в Smart Home Tablet
(https://tablet.digital-home.site/api/voice/event).
Планшет показывает оверлей (Siri-blob, распознанный текст, ответ).
Не критичный слой: любые сетевые ошибки глотаются, ассистент продолжает
работать даже если планшет оффлайн / не настроен.
Активируется только когда заполнены TABLET_URL и VOICE_API_KEY в .env.
"""
import os
import requests
from .config import log
TABLET_URL = os.getenv("TABLET_URL", "").rstrip("/")
VOICE_API_KEY = os.getenv("VOICE_API_KEY", "")
# Когда True — локальный speak() пропускается, голос идёт через планшет.
# По умолчанию включено если TABLET_URL и VOICE_API_KEY заполнены;
# явно отключить: TABLET_TTS_ENABLED=false
TABLET_TTS_ENABLED = (
bool(TABLET_URL and VOICE_API_KEY)
and os.getenv("TABLET_TTS_ENABLED", "true").lower() in ("true", "1", "yes", "on")
)
# Переиспользуем HTTP сессию (keep-alive) для минимума latency
_session = requests.Session()
_ENABLED = bool(TABLET_URL and VOICE_API_KEY)
if _ENABLED:
tts_where = "планшет" if TABLET_TTS_ENABLED else "локально"
print(f"🔔 Notifier: события → {TABLET_URL}, TTS: {tts_where}")
else:
print("🔕 Notifier: отключён (нет TABLET_URL или VOICE_API_KEY в .env)")
def speak_locally() -> bool:
"""True если локальный speak() должен работать (TTS на этой машине)."""
return not TABLET_TTS_ENABLED
def _send(event: str, **payload):
if not _ENABLED:
return
try:
_session.post(
f"{TABLET_URL}/api/voice/event",
json={"event": event, **payload},
headers={"Authorization": f"Bearer {VOICE_API_KEY}"},
timeout=1.5,
)
except requests.RequestException:
log.debug("Tablet notify failed (non-fatal)", exc_info=True)
def wake(agent_id: str):
_send("wake", agent=agent_id)
def command(text: str, agent_id: str):
_send("command", text=text, agent=agent_id)
def response(text: str, agent_id: str):
_send("response", text=text, agent=agent_id)
def idle():
_send("idle")
def error(text: str, agent_id: str = "cosmo"):
_send("error", text=text, agent=agent_id)
def listening(agent_id: str):
"""Голосовой ассистент слушает follow-up (после ответа) — планшет показывает
мягкую пульсацию, сохраняя текст предыдущего ответа."""
_send("listening", agent=agent_id)