87 lines
2.5 KiB
Python
87 lines
2.5 KiB
Python
"""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,
|
|
}
|