feat(controller): udp hello discovery and remove tcp registration

Made-with: Cursor
This commit is contained in:
pi
2026-04-06 21:28:13 +12:00
parent fd618d7714
commit 7179b6531e
2 changed files with 58 additions and 11 deletions

View File

@@ -35,6 +35,7 @@ _tcp_device_lock = threading.Lock()
# Wi-Fi drivers send one hello line then stay quiet; periodic outbound data makes dead peers # Wi-Fi drivers send one hello line then stay quiet; periodic outbound data makes dead peers
# fail drain() within this interval (keepalive alone is often slow or ineffective). # fail drain() within this interval (keepalive alone is often slow or ineffective).
TCP_LIVENESS_PING_INTERVAL_S = 12.0 TCP_LIVENESS_PING_INTERVAL_S = 12.0
DISCOVERY_UDP_PORT = 8766
# Keepalive or lossy Wi-Fi can still surface OSError(110) / TimeoutError on recv or wait_closed. # Keepalive or lossy Wi-Fi can still surface OSError(110) / TimeoutError on recv or wait_closed.
_TCP_PEER_GONE = ( _TCP_PEER_GONE = (
@@ -108,7 +109,7 @@ async def _tcp_liveness_ping_loop(writer, peer_ip: str) -> None:
return return
def _register_tcp_device_sync( def _register_udp_device_sync(
device_name: str, peer_ip: str, mac, device_type=None device_name: str, peer_ip: str, mac, device_type=None
) -> None: ) -> None:
with _tcp_device_lock: with _tcp_device_lock:
@@ -119,13 +120,65 @@ def _register_tcp_device_sync(
) )
if did: if did:
print( print(
f"TCP device registered: mac={did} name={device_name!r} ip={peer_ip!r}" f"UDP device registered: mac={did} name={device_name!r} ip={peer_ip!r}"
) )
except Exception as e: except Exception as e:
print(f"TCP device registry failed: {e}") print(f"UDP device registry failed: {e}")
traceback.print_exception(type(e), e, e.__traceback__) traceback.print_exception(type(e), e, e.__traceback__)
async def _handle_udp_discovery(sock) -> None:
while True:
try:
data, addr = await asyncio.get_running_loop().sock_recvfrom(sock, 2048)
except asyncio.CancelledError:
raise
except Exception as e:
print(f"[UDP] recv failed: {e!r}")
continue
peer_ip = addr[0] if addr else ""
line = data.split(b"\n", 1)[0].strip()
if line:
try:
parsed = json.loads(line.decode("utf-8"))
if isinstance(parsed, dict):
dns = str(parsed.get("device_name") or "").strip()
mac = parsed.get("mac") or parsed.get("device_mac") or parsed.get(
"sta_mac"
)
device_type = parsed.get("type") or parsed.get("device_type")
if dns and normalize_mac(mac):
_register_udp_device_sync(dns, peer_ip, mac, device_type)
except (UnicodeError, ValueError, TypeError):
pass
try:
await asyncio.get_running_loop().sock_sendto(sock, data, addr)
except Exception as e:
print(f"[UDP] echo send failed: {e!r}")
async def _run_udp_discovery_server() -> None:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setblocking(False)
try:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
except (AttributeError, OSError):
pass
try:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
except (AttributeError, OSError):
pass
sock.bind(("0.0.0.0", DISCOVERY_UDP_PORT))
print(f"UDP discovery listening on 0.0.0.0:{DISCOVERY_UDP_PORT}")
try:
await _handle_udp_discovery(sock)
finally:
try:
sock.close()
except Exception:
pass
async def _handle_tcp_client(reader, writer): async def _handle_tcp_client(reader, writer):
"""Read newline-delimited JSON from Wi-Fi LED drivers; forward to serial bridge.""" """Read newline-delimited JSON from Wi-Fi LED drivers; forward to serial bridge."""
peer = writer.get_extra_info("peername") peer = writer.get_extra_info("peername")
@@ -173,13 +226,6 @@ async def _handle_tcp_client(reader, writer):
pass pass
continue continue
if isinstance(parsed, dict): if isinstance(parsed, dict):
dns = str(parsed.get("device_name") or "").strip()
mac = parsed.get("mac") or parsed.get("device_mac") or parsed.get("sta_mac")
device_type = parsed.get("type") or parsed.get("device_type")
if dns and normalize_mac(mac):
_register_tcp_device_sync(
dns, peer_ip, mac, device_type=device_type
)
addr = parsed.pop("to", None) addr = parsed.pop("to", None)
payload = json.dumps(parsed) if parsed else "{}" payload = json.dumps(parsed) if parsed else "{}"
if sender: if sender:
@@ -356,6 +402,7 @@ async def main(port=80):
await asyncio.gather( await asyncio.gather(
app.start_server(host="0.0.0.0", port=port), app.start_server(host="0.0.0.0", port=port),
_run_tcp_server(settings), _run_tcp_server(settings),
_run_udp_discovery_server(),
) )
except OSError as e: except OSError as e:
if e.errno == errno.EADDRINUSE: if e.errno == errno.EADDRINUSE: