Repository navigation
Conversation
Throwaway exploration, not for merge: - ziggurat-phy-trel: RadioPhy wrapper carrying 802.15.4 frames over UDP between Ziggurat nodes, mDNS (_zigtrel._udp) / static / learned peers, LQI pinning for backbone peers, and a test-only deny list - server: --backbone-* flags, --satellite-of to start in router role from the coordinator's get_network_info, periodic [diag] logging Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Pick mDNS addresses deterministically (LAN before container bridges and loopback) and stop overriding a peer's address once it has sent us datagrams - --test-network/--test-channel bring a coordinator up on a private test network without ZHA, for bench runs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Name the backbone ZLOE (Zigbee Link Over Ethernet) and add --zloe-interface to confine it to one network interface: the UDP socket binds that interface's IPv4 address, mDNS runs only there and advertises only that address, and same-subnet peer addresses win. --backbone-* flags remain as aliases of the new --zloe-* names; the mDNS service type is now _zloe._udp. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- zloe-ctl: open joins and send On/Off through a node's API, no ZHA - --test-network answers Node_Desc_req for the coordinator itself (zigpy's job normally); ZBOSS joiners leave without it - Deliver to router children directly, the zigpy#88 workaround (fix proper is zigpy#89), so ESP32-C6 ZBOSS routers stay joined Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Treat the ZLOE backbone as trusted, like TREL: coordinators advertise role=coordinator over mDNS, and a satellite started with --satellite asks one for the network settings over ZLOE (NETINFO_REQ/RSP carrying the NetworkInfoPayload, network key included) instead of needing the coordinator's WebSocket URL. --satellite-pan picks one network when several coordinators share the LAN; --satellite-of stays as a fallback. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
--satellite, --satellite-pan, --satellite-nwk and --satellite-of are now --zloe-satellite, --zloe-satellite-pan, --zloe-satellite-nwk and --zloe-satellite-of. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- zloe-ctl: onoff takes an endpoint, sends with radius 30 (radius 0 was clamped to 1 and dropped at the first relay), stops at the APS ack confirm and prints elapsed time; new `lqi` decodes Mgmt_Lqi_rsp - SPIKE.md: findings of the bench runs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Scope peers by extended PAN ID: mDNS TXT xa/xp as in TREL, header carries the epan; --zloe-satellite-pan becomes --zloe-satellite-epan - Dual-stack UDP socket; prefer IPv6 link-local on --zloe-interface, --zloe-ipv4 to prefer IPv4; rotate through advertised addresses when sends go unanswered - 32-bit packet number, "ZL" magic, wire version 2 - Backbone LQI from ack success; drop over-the-air copies from live backbone peers instead of pinning their LQI to 255 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Multicast as the primary broadcast transport, a hub ("hybrid star")
fallback through the coordinator, direct fan-out as the last resort,
and optional suppression of re-sends into the backbone. Lists the gaps
of each part against the stack's passive-ack, route-request and
neighbour-ageing behaviour. Design only; nothing is built.
Co-Authored-By: Claude <noreply@anthropic.com>
- Join the network's group ff12::5a4c:<low 32 bits of the epan> on the backbone interface (--zloe-mcast-port, default 19789, needs --zloe-interface) and announce it as TXT mc=[group]:port; a node that cannot receive multicast announces none - Store each peer's announced address, show it in [diag] - Keep mDNS results received before the node has an identity and apply them once it has one (a satellite missed the coordinator's record) - Datagrams arriving on the group go through the normal receive path; sending broadcasts by multicast is not built yet Co-Authored-By: Claude <noreply@anthropic.com>
- Broadcasts go out as one multicast datagram to the peers whose multicast is confirmed, and as a unicast copy (same packet number) to every other peer - Probe the group every 5 s; peers answer by unicast; a peer without an ack for 12 s gets unicast copies again - Only 802.15.4 data frames go to the backbone - [diag] datagram counters and a test hook that drops group datagrams while a file exists - Bench: fallback after 15 s of blocked multicast, recovery 10 s after unblocking, the backbone neighbour link held throughout Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Join 239.255.<last two epan bytes> on the backbone interface's IPv4 address next to the IPv6 group and announce it as TXT mc4 - Send IPv4 multicast from the unicast socket (IP_MULTICAST_IF) so peers keep learning the unicast port - Probe each group separately; reach each peer through its first confirmed group, IPv6 first or IPv4 first with --zloe-ipv4 - BROADCAST.md: decision to drop the hub and suppression; Wi-Fi peers opt out of multicast with --zloe-mcast-port 0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The multicast block file blocks one group when it contains "v4" or "v6", both when empty - --zloe-test-ipv4-only behaves like a host without IPv6; such a host ignores IPv6 peer addresses and groups - Bench: per-family failover kept the peer on multicast through the other group; an IPv4-only satellite bootstraps and is reached through the IPv4 group Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A board that rejoins as a satellite under a new NWK address kept its old address in the other nodes' neighbor tables (keyed by EUI64), so its Link Status never mapped and its links stayed at cost 0. Announce like any router after joining. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Benchmark (bench run 2026-11-10)Bench: one Linux host, backbone on a wired NIC ( 1. Backbone datagrams per broadcast (3 backbone nodes, 120 s window)
The backbone mesh stayed at link cost 1/1 in both modes. Caveat: at this traffic level (mostly Link Status and many-to-one route requests, about 0.1 broadcasts/s per node), the probes cost more than multicast saves. That's 144 probes plus about 288 unicast acks, against 38 data datagrams saved. Each node probes each announced group (IPv6 and IPv4) every 5 s. Options before this is worth it on small networks:
2. Coordinator outage (two satellites, 60 s)
3. Out-of-range device through the backboneThe coordinator is told to ignore one ZBOSS router over the air (
4. Multicast failover
Detection takes 12–17 s, inside the 45 s neighbour ageing. Issues found on the way
|
Probe each peer's preferred group (IPv6, or IPv4 with --zloe-ipv4) and the other group only while the preferred one is absent or unconfirmed. Halves probe traffic (144 -> 72 per 120 s on the 3-node bench); failover to the other family takes one more probe interval. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Nice! Mapping 802.15.4 straight to UDP datagrams I think is the most performant option, it reuses the existing retry machinery in the upper layers and completely sidesteps the issues caused by TCP (and mostly resolved by QUIC). This is really awesome! I'll try this out with my home network some time this week to see if the unicast/multicast split becomes more noticeable with 100+ devices and group commands. The part that worries me about encapsulation alone is that it strips away the one layer keeping Zigbee's weak security from being more of a problem: isolation. If Zigbee and IP are bridged via a satellite that allows unauthenticated traffic, you basically move the exploitation boundary. Before, it was physically present attackers with an 802.15.4 radio or a misconfigured HA server on the local network. Now, anything on the local network can inject packets straight into the Zigbee network. There are many insecure installations out there (Z2M hardcoded a single network key up until a few years ago, deCONZ used insecure PRNGs and made their network key derivable from the PAN ID for some installations, etc.). I wonder what could be done for encryption and authentication? Using the network key as a shared secret and rejecting weak keys might be a reasonable compromise, as the alternative would be some sort of key exchange, sessions, etc. |
zigpy interviews a satellite after its Device_annce, but a satellite has no client to answer ZDP, so the interview never finished and ZHA did not list it. Answer Node_Desc_req (router descriptor) and Active_EP_req (no endpoints) for the node's own address; the test-network coordinator uses the same responder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Yeah, we can definitively add it. I've just skipped it for PoC. |
- Deliver frames addressed to our own NWK address locally: zigpy scans the coordinator's neighbor and routing tables with ZDP to 0x0000, which Ziggurat used to route-discover and time out - Mgmt_Lqi_rsp reports 0x0000 as the coordinator at depth 0 (entries learned from Link Status defaulted to Router, depth 1) - BROADCAST.md: secure ZLOE over QUIC, keys derived from the network key, enrollment codes, sealed multicast; gap analysis and behaviour Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Real ZHA + Home Assistant: topology scanBench:
ZHA topology scan, after the fixes below:
LQI 255 is a backbone link. The low values are radio links: ESP RCPs report low LQI even for boards a few centimetres apart. ZHA's network visualization shows all four nodes. It draws only each node's link to the coordinator, not the satellite-to-satellite or satellite-to-router links, although they are in the scanned neighbour data. Problems found and fixed on the spike branch:
Not fixed: when the ZBOSS router joined, the coordinator logged Soak: On/Off to the ZBOSS router every 30 s through the live ZHA network, running until stopped. First results: 5 of 5 acknowledged, 40–42 ms to APS ack. Next: a design for secure ZLOE over QUIC, in
|
--zloe-security quic: peer links over QUIC with mutual TLS against a network CA derived from the network key (stateless, revoked by key rotation), ZLOE datagrams as QUIC DATAGRAM frames, broadcasts as one datagram per peer. Plain UDP data, multicast and the unauthenticated settings hand-out are off in this mode. Satellites enroll over QUIC with a one-time 128-bit code, proofs bound to the TLS session (--zloe-enroll <code>), or with --zloe-enroll auto while the coordinator is in pairing mode (permit-join open). Loopback tests: secure exchange, other network key refused, code enrollment (once, wrong code refused), auto-enrollment gated on pairing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Soak (ZHA, 3 backbone nodes, multicast)Setup:
Window: 17:31–18:23, 52.5 minutes.
Observations:
|
SocketAddr::new(ip, port) drops an IPv6 scope id, so enrolling with or dialing a link-local peer on its QUIC port timed out on the bench. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Four backbone transports for a satellite running Ziggurat on the radio chip: UART relay through the host, Wi-Fi coexistence, SPI Ethernet and esp-hosted. Shared no_std ZloeCore extracted from TrelPhy; the three lwIP transports share one C glue. Design only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
I didn't notice Claude started spamming comments here. Sorry. My next test: segmentation. |
Spike for #84, not for merge. Exploration of TREL-like satellite routers: Ziggurat nodes linked over an IP backbone ("ZLOE", Zigbee Link Over Ethernet), configured out-of-band from the coordinator. Opened as a draft to share the code and findings.
What it does
ziggurat-phy-trel: aRadioPhywrapper that adds the backbone as a second link. 802.15.4 frames travel in UDP datagrams; unicasts to backbone peers go over UDP with ack/retry (falling back to the air), broadcasts go over the air and the backbone. No NWK changes: backbone links show up as ordinary neighbours with low link cost._zloe._udp, TXTxa/xpas in TREL, plusnwk,role,mc/mc4), scoped by extended PAN ID. Dual-stack socket, IPv6 link-local preferred, IPv4 supported.--zloe-satellite: a node finds the coordinator on the (trusted) backbone, fetches the network settings over ZLOE and starts in router role. No joining, no persistence.--zloe-mcast-port 0, e.g. Wi-Fi) get unicast copies.--test-network(coordinator without ZHA),zloe-ctl(permit joins, On/Off, Mgmt_Lqi),--deny(fake out-of-range), multicast/IPv4-only test hooks.Findings (ESP32-C6 OpenThread RCPs, two esp-zigbee-lib routers)
crates/ziggurat-phy-trel/SPIKE.mdandcrates/ziggurat-phy-trel/BROADCAST.md.Not upstream material as-is
--test-networkanswersNode_Desc_reqitself (zigpy's job normally).ZLOE link degradation (TREL-like)
NWK does not see two links. ZLOE folds the backbone's health into the per-frame LQI, so a degrading backbone shows up as rising link cost, and Zigbee routes away from it on its own.
How a backbone peer's link degrades and recovers:
Compared with TREL (OpenThread multi-radio):
Gaps: