feat(controller): udp hello discovery and remove tcp registration
Made-with: Cursor
This commit is contained in:
Submodule led-driver updated: cef9e00819...aaaf660e9d
67
src/main.py
67
src/main.py
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user