docs(espnow): update docs and tests for p2p merge

Align API, architecture, and help with devices envelope transport,
bridge wifi/serial settings, and MAC-keyed device registry. Fix
endpoint tests for envelope identify payloads; remove obsolete p2p.py.
Bump led-tool for --serial-usb bridge provisioning.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-06 21:10:06 +12:00
parent d682753e42
commit cfdd6de291
11 changed files with 190 additions and 183 deletions

View File

@@ -29,15 +29,56 @@ from microdot.websocket import with_websocket # noqa: E402
class DummyBridge:
def __init__(self):
self.sent: list[tuple[str, Optional[str]]] = []
self.sent: list[tuple[Any, Optional[str]]] = []
async def send(self, data: Any, addr: Optional[str] = None):
if isinstance(data, (bytes, bytearray)):
if isinstance(data, dict):
from util.bridge_envelope import ( # noqa: E402
BROADCAST_MAC,
build_devices_envelope,
format_mac_key,
is_broadcast_mac,
normalize_mac_key,
)
from util.v1_wire import compact_envelope # noqa: E402
if data.get("v") == "1" and ("devices" in data or "dv" in data):
data = compact_envelope(data)
elif addr is not None:
s = str(addr).strip().lower()
if is_broadcast_mac(s):
mac_key = BROADCAST_MAC
else:
h = normalize_mac_key(s)
mac_key = format_mac_key(h) if h else None
if mac_key:
body = {k: v for k, v in data.items() if k != "v"}
data = build_devices_envelope({mac_key: body})
else:
data = json.dumps(data, separators=(",", ":"))
else:
data = json.dumps(data, separators=(",", ":"))
elif isinstance(data, (bytes, bytearray)):
data = bytes(data).decode(errors="ignore")
self.sent.append((data, addr))
return True
def _bridge_sent_envelope(bridge: DummyBridge, index: int) -> Dict[str, Any]:
data, _addr = bridge.sent[index]
if isinstance(data, dict):
return data
return json.loads(data)
def _device_body_from_envelope(envelope: Dict[str, Any], mac: str) -> Dict[str, Any]:
from util.bridge_envelope import format_mac_key, normalize_mac_key # noqa: E402
devs = envelope.get("dv") or envelope.get("devices") or {}
key = format_mac_key(normalize_mac_key(mac))
return devs[key]
def _json(resp: requests.Response) -> Dict[str, Any]:
# Many endpoints already set Content-Type; but be tolerant for now.
return resp.json() # pragma: no cover
@@ -672,17 +713,19 @@ def test_groups_sequences_scenes_palettes_patterns_endpoints(server):
assert resp.status_code == 200
assert resp.json().get("message")
assert len(bridge.sent) >= 1
first = json.loads(bridge.sent[0][0])
assert "presets" in first and "select" in first
assert first["presets"]["__identify"]["p"] == "blink"
assert first["presets"]["__identify"]["d"] == 50
assert first["select"] == ["__identify"]
first = _bridge_sent_envelope(bridge, 0)
assert first["v"] == "1"
first_body = _device_body_from_envelope(first, dev_id)
assert first_body["p"]["__identify"]["p"] == "blink"
assert first_body["p"]["__identify"]["d"] == 50
assert first_body["s"] == ["__identify"]
deadline = time.monotonic() + 2.0
while len(bridge.sent) < 2 and time.monotonic() < deadline:
time.sleep(0.02)
assert len(bridge.sent) >= 2
second = json.loads(bridge.sent[1][0])
assert second.get("select") == ["off"]
second = _bridge_sent_envelope(bridge, 1)
second_body = _device_body_from_envelope(second, dev_id)
assert second_body["s"] == ["off"]
resp = c.post(
f"{base_url}/devices",
@@ -702,7 +745,7 @@ def test_groups_sequences_scenes_palettes_patterns_endpoints(server):
resp = c.get(f"{base_url}/devices/{wid}")
assert resp.status_code == 200
assert resp.json().get("connected") is False
assert resp.json().get("connected") is None
resp = c.post(
f"{base_url}/devices",