|
5 | 5 | import asyncio |
6 | 6 | import logging |
7 | 7 | import re |
| 8 | +from collections.abc import Callable, Coroutine |
| 9 | +from typing import Any |
8 | 10 | from urllib.parse import urljoin, urlparse |
9 | 11 |
|
10 | 12 | import aiohttp |
@@ -126,6 +128,7 @@ def __init__( |
126 | 128 | # WebSocket |
127 | 129 | self._ws_client: RemoteWebSocketClient | None = None |
128 | 130 | self._last_update_type: UpdateType = UpdateType.NONE |
| 131 | + self._state_change_callbacks: list[Callable[[], Coroutine[Any, Any, None]]] = [] |
129 | 132 |
|
130 | 133 | # Entities cache (media players referenced by activities) |
131 | 134 | self._entities: dict[str, MediaPlayerEntity] = {} |
@@ -291,6 +294,10 @@ async def resolve_discovery( |
291 | 294 | # WebSocket |
292 | 295 | # ------------------------------------------------------------------ |
293 | 296 |
|
| 297 | + def on_state_change(self, callback: Callable[[], Coroutine[Any, Any, None]]) -> None: |
| 298 | + """Register a coroutine callback to be called after any WebSocket state update.""" |
| 299 | + self._state_change_callbacks.append(callback) |
| 300 | + |
294 | 301 | async def connect_websocket(self, *, reconnect_delay: float = 10.0) -> None: |
295 | 302 | """Start a WebSocket connection and keep it alive automatically. |
296 | 303 |
|
@@ -343,6 +350,9 @@ async def _handle_ws_message(self, raw: str) -> None: |
343 | 350 | case IRLearningEvent(): |
344 | 351 | self._on_ir_learning(event) |
345 | 352 |
|
| 353 | + for cb in self._state_change_callbacks: |
| 354 | + await cb() |
| 355 | + |
346 | 356 | # WS event handlers (private, synchronous) |
347 | 357 |
|
348 | 358 | def _on_battery(self, event: BatteryEvent) -> None: |
|
0 commit comments