"""Push Wi-Fi TCP connect/disconnect updates to browser WebSocket clients.""" import json import threading from typing import Any, Set # Threading lock: safe across asyncio tasks and avoids binding asyncio.Lock to the wrong loop. _clients_lock = threading.Lock() _clients: Set[Any] = set() async def register_device_status_ws(ws: Any) -> None: with _clients_lock: _clients.add(ws) async def unregister_device_status_ws(ws: Any) -> None: with _clients_lock: _clients.discard(ws) async def broadcast_device_tcp_status(ip: str, connected: bool) -> None: from models.tcp_clients import normalize_tcp_peer_ip ip = normalize_tcp_peer_ip(ip) if not ip: return msg = json.dumps({"type": "device_tcp", "ip": ip, "connected": bool(connected)}) with _clients_lock: targets = list(_clients) dead = [] for ws in targets: try: await ws.send(msg) except Exception as exc: dead.append(ws) print(f"[device_status_broadcaster] ws.send failed: {exc!r}") if dead: with _clients_lock: for ws in dead: _clients.discard(ws) async def broadcast_device_tcp_snapshot_to(ws: Any) -> None: from models import tcp_clients as tcp ips = tcp.list_connected_ips() msg = json.dumps({"type": "device_tcp_snapshot", "connected_ips": ips}) try: await ws.send(msg) except Exception as exc: print(f"[device_status_broadcaster] snapshot send failed: {exc!r}")