The problem
Zigbee networks fail in ways the controller can't see. Home Assistant tells you a device is "unavailable" — it can't tell you why. Was the frame never transmitted? Transmitted but not acknowledged? Is the mesh routing around a dying router? Is a neighbor's network stomping your channel — or worse, sitting on a different channel but physically close enough to deafen your coordinator's receiver? Every one of those looks identical from the software side: a device that stopped answering.
The usual answer is "flash a dongle and stare at Wireshark." That gets you frames, but a raw frame stream is the starting point of a diagnosis, not the answer. I wanted the layer above: what was happening on-air when the device dropped, correlated with channel noise, the routing topology, and the controller's own logs.
What it is
A full diagnostic platform built on the ESP32-C6 (chosen for its native 802.15.4 radio and its price — the SuperMini boards make extra radios almost free):
- Live capture and decode — MAC / NWK / APS / ZCL, with AES-CCM* decryption when you provide your network key. Export to Wireshark (802.15.4 TAP pcap with RSSI/LQI/channel), CSV, or JSON.
- Device intelligence — every device heard on-air gets a page: message history, signal trend, first/last seen, and automatic incident logging ("went silent for N minutes, here's the RF context at that moment"). A "why is it going unresponsive?" analysis mines every record for that device — link topology risks, route failures, rejoin events, even matching zigpy/ZHA log entries pulled from Home Assistant.
- Routing tree — a live LQI/RSSI-annotated graph of the mesh, carefully distinguishing the MAC-layer physical hop from the NWK-layer logical source (they are not the same thing, and conflating them draws fictional meshes).
- RF spectrum analyzer — energy-detect sweeps across channels 11–26, peak-hold, a waterfall, and a Wi-Fi overlap row so raised noise maps to the Wi-Fi channel sitting on it.
- Neighbor-network discovery — every PAN heard on-air, tagged yours/foreign, identified by manufacturer OUI, a paired Hue bridge integration (the bridge tells us its channel, so its PAN gets named), and Thread/Matter recognition via 6LoWPAN framing heuristics. Passive survey or active beacon-request scan across all 16 channels.
- Channel airtime analysis — frames/min stacked by network over up to 24 h, per-channel totals, and a per-network RSSI histogram. This is the "who is loud and how close" view — it exists because of a real failure (see below).
- Active testing — optional, explicitly-gated TX: probe a device for a MAC ACK to prove its radio is alive on-channel right now, on demand or on a schedule.
- Multi-radio — up to four C6s with assignable roles: pin one to your network's channel for gapless capture while another sweeps spectrum, hops channels, monitors the neighbor's network, or runs probes.
Architecture
Three PlatformIO firmware builds share one capture core:
>td >satellite
| Build | Transport | Role |
|---|---|---|
tethered | USB serial | |
| SPI | Dumb capture-and-forward to a primary; runs on the $3 SuperMini | |
standalone | Wi-Fi AP/STA | Self-contained web UI, no PC — delegates capture to SPI satellites |
That split exists because of a hardware fact discovered the hard way: the C6's Wi-Fi and 802.15.4 radios share one antenna. Run both and Wi-Fi clients can't even associate. So the rule became: a C6 is either a radio or a UI, never both. The standalone build's own radio stays off; satellites feed it.
The primary host is a single-binary Go application — plug a USB C6 into your PC or HA box, run one executable, get the whole web UI (embedded, zero runtime dependencies) with SQLite history. A custom framing protocol (documented in the repo as the source of truth) carries frames, commands, and status over USB or SPI, including relay commands so the host can retune a satellite hanging off another radio's SPI bus.
Firmware updates flow...
Read more »
michael