feat(espnow): Pi bridge client, binary wire, and espnow-sender firmware
Replace serial/Wi-Fi driver transport paths with WebSocket bridge client, binary espnow_wire delivery, device announce registry, and restructured espnow-sender (AP + broadcast passthrough). Includes docs and tests. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
70
src/util/espnow_registry.py
Normal file
70
src/util/espnow_registry.py
Normal file
@@ -0,0 +1,70 @@
|
||||
"""Handle ESP-NOW ANNOUNCE uplink and push GROUPS to drivers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from models.device import Device, normalize_mac # noqa: F401 — re-export for callers
|
||||
from models.group import Group
|
||||
from models.bridge_ws_client import get_bridge_client
|
||||
from util.espnow_wire import mac_bytes_to_hex, pack_groups, parse_announce
|
||||
from util.groups_for_device import groups_for_mac
|
||||
|
||||
|
||||
async def handle_espnow_announce(peer_mac: bytes, packet: bytes) -> None:
|
||||
info = parse_announce(packet)
|
||||
if not info:
|
||||
return
|
||||
mac_hex = mac_bytes_to_hex(peer_mac)
|
||||
if not mac_hex:
|
||||
return
|
||||
|
||||
devices = Device()
|
||||
did, persisted = devices.upsert_espnow_announced(
|
||||
mac_hex,
|
||||
info["name"],
|
||||
device_type=info.get("device_type", "led"),
|
||||
num_leds=info.get("num_leds"),
|
||||
color_order=info.get("color_order"),
|
||||
startup_mode=info.get("startup_mode"),
|
||||
brightness=info.get("brightness"),
|
||||
)
|
||||
if not did:
|
||||
return
|
||||
if persisted:
|
||||
print(f"[espnow] registered mac={did} name={info['name']!r}")
|
||||
|
||||
groups = Group()
|
||||
gids = groups_for_mac(did, groups)
|
||||
groups_pkt = pack_groups(gids)
|
||||
|
||||
client = get_bridge_client()
|
||||
if client is None:
|
||||
print("[espnow] bridge client not configured; groups not sent")
|
||||
return
|
||||
ok = await client.send_espnow(groups_pkt, peer_mac=peer_mac)
|
||||
if ok:
|
||||
print(f"[espnow] groups -> {did}: {gids}")
|
||||
else:
|
||||
print(f"[espnow] groups send failed for {did}")
|
||||
|
||||
|
||||
async def push_groups_for_group_devices(gdoc: dict) -> None:
|
||||
"""Refresh GROUPS on every MAC listed on a group document."""
|
||||
mac_list = gdoc.get("devices") if isinstance(gdoc.get("devices"), list) else []
|
||||
for mac in mac_list:
|
||||
m = normalize_mac(str(mac))
|
||||
if m:
|
||||
await push_groups_to_mac(m)
|
||||
|
||||
|
||||
async def push_groups_to_mac(mac_hex: str) -> bool:
|
||||
"""Re-send GROUPS packet to one device (after group membership change)."""
|
||||
mac = normalize_mac(mac_hex)
|
||||
if not mac:
|
||||
return False
|
||||
client = get_bridge_client()
|
||||
if client is None:
|
||||
return False
|
||||
groups = Group()
|
||||
gids = groups_for_mac(mac, groups)
|
||||
pkt = pack_groups(gids)
|
||||
return await client.send_espnow(pkt, peer_mac=bytes.fromhex(mac))
|
||||
Reference in New Issue
Block a user