From 584e21923cef9a1ba35f0344f090558548deb7fb Mon Sep 17 00:00:00 2001 From: Cosmo Date: Thu, 23 Apr 2026 12:52:34 +0000 Subject: [PATCH] feat(notifier): route TTS to tablet when TABLET_TTS_ENABLED MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When TABLET_URL and VOICE_API_KEY are set, the tablet handles TTS via its ElevenLabs proxy — local speak() is skipped. Controlled by TABLET_TTS_ENABLED (default true when tablet is configured). - notifier.speak_locally() — gate used by all local speech paths - llm._maybe_speak — no-op when tablet plays the voice - modes._handle_reset — emits response event and skips local speak when tablet TTS is on; keeps spoken fallback otherwise Tablet side in smart-home-tablet repo: /api/voice/tts endpoint + VoiceOverlay audio playback (commit ba2e… pending). Co-Authored-By: Claude Opus 4.7 (1M context) --- .env.example | 5 +++++ satellite/llm.py | 3 ++- satellite/modes.py | 5 ++++- satellite/notifier.py | 16 +++++++++++++++- 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index 3c237c5..0601df3 100644 --- a/.env.example +++ b/.env.example @@ -54,3 +54,8 @@ LUSYA_SESSION_KEY=agent:wife:voice:home # Если не настроено, просто пропускается, ассистент работает как раньше. TABLET_URL=https://tablet.digital-home.site VOICE_API_KEY=your_voice_api_key_here + +# TABLET_TTS_ENABLED=true (по умолчанию true когда TABLET_URL/KEY заданы) — +# голос ассистента проигрывается на планшете через ElevenLabs proxy, +# локальный mpv/speak пропускается. false = говорим локально как раньше. +TABLET_TTS_ENABLED=true diff --git a/satellite/llm.py b/satellite/llm.py index faa9f31..684f9fc 100644 --- a/satellite/llm.py +++ b/satellite/llm.py @@ -67,7 +67,8 @@ def _post_with_retry(session, url, headers, payload): def ask_agent_stream(text: str, agent_id: str = "cosmo") -> str: """Отправляет запрос к OpenClaw gateway и озвучивает ответ.""" def _maybe_speak(t: str): - if t.strip(): + # Если TTS на планшете — пропускаем локальный звук, планшет зачитает по response event. + if t.strip() and notifier.speak_locally(): speak(t, agent_id) cfg = AGENTS.get(agent_id, AGENTS["cosmo"]) diff --git a/satellite/modes.py b/satellite/modes.py index e192da0..082201d 100644 --- a/satellite/modes.py +++ b/satellite/modes.py @@ -34,7 +34,10 @@ def _handle_reset(text: str, agent_id: str) -> bool: msg = "Начинаю новую сессию." print(f"🔄 {msg}") - speak(msg, agent_id) + # Отправляем как response event — tablet зачитает, локально говорим только если TTS на этой машине. + notifier.response(msg, agent_id) + if notifier.speak_locally(): + speak(msg, agent_id) return True diff --git a/satellite/notifier.py b/satellite/notifier.py index b91c7e7..6895d54 100644 --- a/satellite/notifier.py +++ b/satellite/notifier.py @@ -16,16 +16,30 @@ 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: - print(f"🔔 Notifier: планшет {TABLET_URL}") + 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