diff --git a/bridge/aria_bridge.py b/bridge/aria_bridge.py index a66140f..a49e673 100644 --- a/bridge/aria_bridge.py +++ b/bridge/aria_bridge.py @@ -461,7 +461,8 @@ class ARIABridge: else: self.rvs_url = "" self.rvs_url_fallback = "" - self.current_mode = Mode.NORMAL + # Mode aus Shared Config laden (persistiert ueber Container-Restarts) + self.current_mode = self._load_persisted_mode() self.running = False # Komponenten (TTS: immer XTTS remote, Piper wurde entfernt) @@ -821,20 +822,17 @@ class ARIABridge: is_critical = metadata.get("critical", False) requested_voice = metadata.get("voice") - # Modus-Wechsel pruefen + # Modus-Wechsel pruefen (Sprachbefehl im Text) new_mode = detect_mode_switch(text) if new_mode is not None: self.current_mode = new_mode + self._persist_mode() logger.info( "[core] Modus → %s %s", self.current_mode.config.emoji, self.current_mode.config.name, ) - await self._send_to_rvs({ - "type": "mode", - "payload": {"mode": self.current_mode.name}, - "timestamp": int(asyncio.get_event_loop().time() * 1000), - }) + await self._broadcast_current_mode() # Eindeutige Message-ID fuer Audio-Cache-Zuordnung message_id = str(uuid.uuid4()) @@ -892,6 +890,47 @@ class ARIABridge: except Exception as e: logger.error("[core] XTTS-Request fehlgeschlagen: %s — kein Audio", e) + # ── Mode Persistence (global, nicht pro Geraet) ────── + _MODE_FILE = "/shared/config/mode.json" + + def _load_persisted_mode(self) -> Mode: + """Laedt den zuletzt aktiven Modus aus Shared Config oder NORMAL.""" + try: + if os.path.exists(self._MODE_FILE): + data = json.loads(Path(self._MODE_FILE).read_text()) + mode_name = data.get("mode", "NORMAL") + for m in Mode: + if m.name == mode_name: + logger.info("[mode] Persistierter Modus geladen: %s", m.config.name) + return m + except Exception as e: + logger.warning("[mode] Laden fehlgeschlagen: %s", e) + return Mode.NORMAL + + def _persist_mode(self) -> None: + """Speichert den aktuellen Modus in Shared Config.""" + try: + os.makedirs("/shared/config", exist_ok=True) + Path(self._MODE_FILE).write_text(json.dumps({"mode": self.current_mode.name})) + except Exception as e: + logger.warning("[mode] Speichern fehlgeschlagen: %s", e) + + async def _broadcast_current_mode(self) -> None: + """Broadcastet den aktuellen Modus an alle RVS-Clients (App + Diagnostic).""" + try: + await self._send_to_rvs({ + "type": "mode", + "payload": { + "mode": self.current_mode.name.lower(), + "name": self.current_mode.config.name, + "emoji": self.current_mode.config.emoji, + "sender": "bridge", # Filter in mode-Handler gegen Loops + }, + "timestamp": int(asyncio.get_event_loop().time() * 1000), + }) + except Exception as e: + logger.debug("[mode] Broadcast fehlgeschlagen: %s", e) + def _fetch_active_session(self) -> None: """Holt die aktive Session vom Diagnostic-Endpoint.""" try: @@ -957,6 +996,10 @@ class ARIABridge: retry_delay = 2 logger.info("[rvs] Verbunden — warte auf App-Nachrichten") + # Aktuellen Modus broadcasten damit gerade verbundene Apps/Diagnostic + # ihren UI-State sofort syncen koennen + await self._broadcast_current_mode() + # Heartbeat senden (RVS erwartet Ping alle 30s) heartbeat_task = asyncio.create_task(self._rvs_heartbeat()) @@ -1169,21 +1212,22 @@ class ARIABridge: elif msg_type == "mode": # Moduswechsel von der App — ID ('normal', 'dnd', ...) ODER Aktivierungsphrase mode_name = payload.get("mode", "") + # Sender kann der Broadcast der Bridge selbst sein — den ignorieren damit + # andere Apps nicht in eine Loop geraten + if payload.get("sender") == "bridge": + return new_mode = mode_from_id(mode_name) or detect_mode_switch(mode_name) - if new_mode is not None: + if new_mode is not None and new_mode != self.current_mode: self.current_mode = new_mode + self._persist_mode() logger.info( "[rvs] Modus → %s %s (von App)", self.current_mode.config.emoji, self.current_mode.config.name, ) - # Bestaetigung an alle Clients (App/Diagnostic) damit UI sync bleibt - await self._send_to_rvs({ - "type": "mode", - "payload": {"mode": mode_name, "name": self.current_mode.config.name}, - "timestamp": int(asyncio.get_event_loop().time() * 1000), - }) - else: + # Broadcast an ALLE Clients (App + Diagnostic) damit UI ueberall sync ist + await self._broadcast_current_mode() + elif new_mode is None: logger.warning("[rvs] Unbekannter Modus: '%s'", mode_name) elif msg_type == "location":