feat(bridge): add wifi/serial bridge runtime and UI

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-28 00:38:21 +12:00
parent 2cf019079e
commit 78dc8ffc77
92 changed files with 5679 additions and 1790 deletions

View File

@@ -7,10 +7,12 @@ from typing import Any, Dict, Optional
from models.device import Device, normalize_mac # noqa: F401 — re-export for callers
from models.group import Group
from models.transport import get_current_sender
from models.transport import get_current_bridge
from util.bridge_envelope import build_groups_envelope
from util.espnow_ping import record_ping_rsp
from util.espnow_wire import (
MSG_ANNOUNCE,
MSG_PING_RSP,
WIRE_MAGIC,
mac_bytes_to_hex,
parse_announce,
@@ -24,8 +26,11 @@ async def handle_bridge_uplink(peer_mac: bytes, payload: bytes) -> None:
if not payload:
return
if payload[0] == WIRE_MAGIC:
if wire_msg_type(payload) == MSG_ANNOUNCE:
mt = wire_msg_type(payload)
if mt == MSG_ANNOUNCE:
await handle_espnow_announce(peer_mac, payload)
elif mt == MSG_PING_RSP:
record_ping_rsp(peer_mac, payload)
return
if payload[:1] == b"{":
try:
@@ -128,17 +133,47 @@ async def push_groups_broadcast() -> bool:
return False
async def push_groups_all_espnow_devices() -> Dict[str, Any]:
"""Push ``set_groups`` envelopes to every ESP-NOW device in the registry."""
devices_model = Device()
macs: list[str] = []
skipped = 0
for did, doc in devices_model.items():
if str(doc.get("transport") or "espnow").strip().lower() != "espnow":
continue
mac = normalize_mac(str(did)) or normalize_mac(str(doc.get("address") or ""))
if not mac:
skipped += 1
continue
macs.append(mac)
sent = 0
failed = 0
for mac in macs:
if await push_groups_to_mac(mac):
sent += 1
else:
failed += 1
ok = bool(macs) and failed == 0
return {
"ok": ok,
"sent": sent,
"failed": failed,
"skipped": skipped,
"total": len(macs),
}
async def push_groups_to_mac(mac_hex: str) -> bool:
"""Unicast groups envelope to one driver (set_groups true)."""
mac = normalize_mac(mac_hex)
if not mac:
return False
gids = groups_for_mac(mac, Group())
sender = get_current_sender()
if sender is None:
bridge = get_current_bridge()
if bridge is None:
return False
envelope = build_groups_envelope(mac, gids)
ok = await sender.send(envelope)
ok = await bridge.send(envelope)
if ok:
print(f"[espnow] groups sent mac={mac} groups={gids!r}")
return bool(ok)