feat(bridge): add wifi/serial bridge runtime and UI
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
86
src/util/espnow_ping.py
Normal file
86
src/util/espnow_ping.py
Normal file
@@ -0,0 +1,86 @@
|
||||
"""ESP-NOW broadcast ping: collect PING_RSP from drivers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import secrets
|
||||
import time
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from models.device import Device
|
||||
from models.transport import get_current_bridge
|
||||
from util.espnow_wire import pack_ping_req, parse_ping_rsp
|
||||
|
||||
_active: Dict[int, Dict[str, Any]] = {}
|
||||
|
||||
|
||||
def register_device_from_ping(peer_mac: bytes, name: str) -> bool:
|
||||
"""Add or update registry entry from a PING_RSP (drivers may not have sent ANNOUNCE yet)."""
|
||||
if not peer_mac or len(peer_mac) != 6:
|
||||
return False
|
||||
mac_hex = peer_mac.hex()
|
||||
label = (name or "").strip() or f"led-{mac_hex}"
|
||||
did, persisted = Device().upsert_espnow_announced(mac_hex, label)
|
||||
if did and persisted:
|
||||
print(f"[espnow] registered mac={did} name={label!r} (ping)")
|
||||
return bool(persisted)
|
||||
|
||||
|
||||
def record_ping_rsp(peer_mac: bytes, packet: bytes) -> None:
|
||||
info = parse_ping_rsp(packet)
|
||||
if info is None:
|
||||
return
|
||||
session = _active.get(info["ping_id"])
|
||||
if session is None:
|
||||
return
|
||||
mac_hex = peer_mac.hex()
|
||||
session["responses"][mac_hex] = {
|
||||
"mac": mac_hex,
|
||||
"name": info["name"],
|
||||
"rtt_ms": int((time.monotonic() - session["sent_at"]) * 1000),
|
||||
}
|
||||
if register_device_from_ping(peer_mac, info["name"]):
|
||||
session["registered"] = int(session.get("registered", 0)) + 1
|
||||
|
||||
|
||||
async def run_ping(*, timeout_s: float = 3.0) -> Dict[str, Any]:
|
||||
"""
|
||||
Broadcast PING_REQ and collect PING_RSP until ``timeout_s``.
|
||||
|
||||
Returns ``{ok, ping_id, timeout_s, responses}``; ``responses`` maps MAC hex to
|
||||
``{mac, name, rtt_ms}``.
|
||||
"""
|
||||
bridge = get_current_bridge()
|
||||
if bridge is None:
|
||||
return {"ok": False, "error": "Transport not configured", "responses": {}}
|
||||
|
||||
ping_id = secrets.randbits(32) or 1
|
||||
session: Dict[str, Any] = {
|
||||
"responses": {},
|
||||
"sent_at": time.monotonic(),
|
||||
"registered": 0,
|
||||
}
|
||||
_active[ping_id] = session
|
||||
pkt = pack_ping_req(ping_id)
|
||||
ok = await bridge.send(pkt)
|
||||
if not ok:
|
||||
_active.pop(ping_id, None)
|
||||
return {
|
||||
"ok": False,
|
||||
"error": "Send failed",
|
||||
"ping_id": ping_id,
|
||||
"responses": {},
|
||||
}
|
||||
|
||||
await asyncio.sleep(timeout_s)
|
||||
responses = dict(session["responses"])
|
||||
registered = int(session.get("registered", 0))
|
||||
_active.pop(ping_id, None)
|
||||
|
||||
return {
|
||||
"ok": True,
|
||||
"ping_id": ping_id,
|
||||
"timeout_s": timeout_s,
|
||||
"responses": responses,
|
||||
"registered": registered,
|
||||
}
|
||||
Reference in New Issue
Block a user