Skip to content

Releases: tostmann/esp-coordinator

v1.5.73 — Coordinator IEEE byte-order fix (issue #6)

Choose a tag to compare

@tostmann tostmann released this 13 Jul 13:43

v1.5.73 — Coordinator IEEE byte-order fix (issue #6)

What was wrong

GET_LOCAL_IEEE_ADDR (the NCP command a host uses to read the coordinator's own
64-bit IEEE address) returned the address in big-endian byte order. On the
ZBOSS NCP link IEEE addresses are little-endian, so the host stored the
coordinator's IEEE with all 8 bytes reversed.

Consequence: the host (Zigbee2MQTT / zigbee-herdsman, and any ZBOSS-NCP host)
recorded a reversed, phantom coordinator IEEE and used it as the destination
when it bound devices to the coordinator. Any device that then has to resolve the
coordinator by IEEE — a bound switch (e.g. IKEA E2201/RODRET), or a sensor bound
to report to the coordinator — sends Network Address Requests for an address no
device on the mesh owns
, so it never resolves and retries endlessly; its bound
commands/reports are never delivered. Devices addressed by short address 0x0000
(most routine traffic) were unaffected, which is why the symptom looked narrow.

What the fix does

GET_LOCAL_IEEE_ADDR now returns the operational long address in correct
little-endian (wire) order — the same order every other IEEE field already used.
Fixed at the source, so a corrected coordinator IEEE is handed out from now on.

Migration — required for existing installs

The firmware fix prevents the wrong address from being handed out again, but two
copies of the old reversed address live off-chip and must be refreshed:

  1. Flash v1.5.73.
  2. Restart your host (Zigbee2MQTT). It re-reads the coordinator IEEE on start,
    sees it changed, logs Coordinator address changed, updating to '0x…', and
    corrects its own database automatically. No manual database editing needed.
    (Verified against zigbee-herdsman end-to-end.)
  3. Re-pair or re-bind the affected devices. A device keeps the old reversed
    coordinator address in its own binding table until it is re-paired or its
    bindings are removed and recreated. For each device stuck in a request loop or
    not delivering bound reports (bound switches, sensors bound to the
    coordinator): re-pair it, or remove+recreate its bindings / run Reconfigure.

What is NOT affected

The coordinator's actual on-air identity, PAN ID, network keys and device
associations were always correct
— only the value reported to the host was
reversed. No network re-formation is required; the mesh is intact.

ZHA / zigpy-zboss users

zigpy-zboss decodes the IEEE per spec (little-endian, t.EUI64), so the fix is
correct for it as well. Its host-side behavior on a coordinator-IEEE change has
not been bench-verified here; expect the same "re-bind the affected devices" step
and watch the startup log after flashing.

Also in this release

  • APSDE request-slot watchdog: reclaims request-table slots left in the executing
    state (e.g. after an unconfirmed delivery) so the 16-slot table cannot saturate
    and cause network-wide poll timeouts.

v1.5.70 — NWK_LEAVE_IND spec-compliant + ZDO indication trims (#6)

Choose a tag to compare

@tostmann tostmann released this 11 Jul 10:45

v1.5.70 — ZBOSS-NCP indication layout fixes (issue #6)

Fixes the coordinator's ZDO/NWK indication payloads so they match the ZBOSS NCP Serial Protocol byte layout expected by both host stacks (Zigbee2MQTT adapter:zboss / zigbee-herdsman and zigpy-zboss). Reported in #6 (IKEA E2201 / RODRET).

  • NWK_LEAVE_IND (0x040b) — previously sent as the raw ZBOSS signal struct, which carries a leading 16-bit short address the protocol does not define here. The host decoded short_addr ++ ieee[0:6] as the IEEE address and the 7th IEEE byte as the rejoin flag — the "Device leave is from unknown or already deleted device" symptom in #6. Now emitted spec-exact as [ieee(8) | rejoin(1)]. Effect of the old bug: on-air device leaves were never processed (the device stayed in the host database, no leave event).
  • ZDO_DEV_ANNCE_IND (0x020c) and ZDO_DEV_UPDATE_IND (0x0215) — trimmed to the spec-exact 11-byte payloads (drop the struct alignment pad / the non-spec tc_action+parent_short trailing bytes). Both hosts already tolerated the extra bytes, so this is hygiene, not a behaviour change.
  • ZDO_DEV_AUTHORIZED_IND (0x0214) left unchanged (already spec-exact).

Verified end-to-end on test hardware: a real end-device join yields correct DEV_UPDATE/DEV_ANNCE fields, and a device leave yields NWK_LEAVE_IND = [ieee|rejoin] with the true IEEE; the coordinator does not self-wipe across the leave. Firmware rebuilt for all three targets (ESP32-C6 / C5 / H2).

Note: the separate "IKEA E2201 switches stuck in a Network Address Request loop / actions lost" part of #6 is a distinct, sleepy-end-device address-resolution issue still under investigation — not addressed by this release.

v1.5.0 — ESP32-H2 support

Choose a tag to compare

@tostmann tostmann released this 23 Jun 13:18

ESP32-H2 support

v1.5.0 adds the ESP32-H2 as a third build target alongside the ESP32-C6 (reference platform) and the ESP32-C5 — the same firmware, from one codebase.

Validated end-to-end on real ESP32-H2 silicon: boot + NCP over native USB-Serial/JTAG, 802.15.4 network formation, NVRAM resume across a reboot, and on-air pairing of a real Zigbee device (join → authorize → interview). Independently confirmed beforehand on two further boards and host stacks (Zigbee2MQTT and ZHA), see #5.

Flashing

The web flasher auto-detects the connected chip and serves the matching image: https://install.busware.de/zboss/

Notes for ESP32-H2

  • The 1900K application partition requires a 4 MB flash board (e.g. ESP32-H2-MINI-1).
  • The H2 has no Wi-Fi, so the Wi-Fi-coexistence build does not apply — it is a pure USB/UART coordinator.

The ESP32-C6 remains the reference platform.

v1.4.63 — coordinator-wipe hardening

Choose a tag to compare

@tostmann tostmann released this 19 Jun 12:37

v1.4.63 — coordinator-wipe hardening

Critical fix. A routine "remove device" in Zigbee2MQTT could make the coordinator wipe its own network. If you have ever lost your Zigbee network unexpectedly, this is very likely why — please update.

What was wrong

When you remove a device, the host sends a ZDO Mgmt_Leave to the coordinator. The firmware routed that through a raw low-level ZBOSS call which, for an unknown/unresolvable target, made the coordinator perform a network-leave on itself — wiping its live network and NVRAM with no reboot (~2 out of 3 leaves on the bench; independent of the target address or flags). Removing an offline device (which Zigbee2MQTT force-removes) was enough to trigger it.

Fixes in this release

  1. Leave self-wipe (the root cause). Device-leave now goes through the official esp-zigbee-lib API esp_zb_zdo_device_leave_req(), which does not self-wipe; a self-addressed leave (dst=0x0000) is refused outright. Verified end-to-end: a real device removed via Zigbee2MQTT is removed correctly and the coordinator keeps its network.
  2. Restore buffer-before-commit gate. RESTORE_NETWORK now buffers the full 40 KB image in RAM and structurally validates it before writing anything to flash, so a corrupt/truncated restore is side-effect-free — your existing network and credentials are preserved, no reboot.
  3. Poison-backup guard. GET_NETWORK_BACKUP refuses to snapshot a coordinator that is not currently on a network, so a host cannot store an un-restorable backup of an already-blank stack.

Flashing

Web flasher: https://install.busware.de/zboss/ (ESP32-C6 and ESP32-C5). Or esptool write_flash 0x0 with the attached factory image for your chip. Flashing the factory image does not erase your Zigbee network — it lives in a separate flash partition.

Bench-validated end-to-end on ESP32-C6 over USB.

v1.4.0 — ESP32-C5 support (multi-target)

Choose a tag to compare

@tostmann tostmann released this 17 Jun 10:30

v1.4.0 — ESP32-C5 support (multi-target)

The firmware now builds and runs on the ESP32-C5 in addition to the ESP32-C6, from a single codebase. The web flasher auto-detects your chip and serves the matching image.

What's new

  • ESP32-C5 support. Full ZBOSS NCP coordinator on C5 silicon — USB-Serial/JTAG transport, 802.15.4 network formation and NVRAM resume across reboot, validated end-to-end with zigpy-zboss. Memory headroom on the C5 (384 KB SRAM): the coordinator links with ~189 KB of internal RAM free and uses only 34 % of the app partition.
  • Per-target NCP-UART pin defaults — C5: TX=GPIO23, RX=GPIO24 (the C6 defaults GPIO22/23 land on the C5's SPI-flash pins). Override via menuconfigNCP_UART_*.
  • Separate per-chip factory images: the C5's second-stage bootloader sits at flash 0x2000, the C6's at 0x0. Each image is still written from 0x0.
  • Build a target with idf.py set-target esp32c6|esp32c5 (the default config is no longer pinned to one chip; CMake fails loudly if no target is set).

Status / caveats

  • The C6 remains the reference platform. C5 support is validated on one board (rev v1.0) so far.
  • The C5 NCP-UART pin defaults are datasheet-safe on standard Espressif C5 boards but have not been hardware-tested; third-party board pinouts may need an override. The default USB path needs none of this.

Flash

Web installer: https://install.busware.de/zboss/ (auto-detects C6/C5)

CLI:

esptool.py --chip esp32c5 write_flash 0x0 binaries/factory-c5.bin   # ESP32-C5
esptool.py --chip esp32c6 write_flash 0x0 binaries/factory.bin      # ESP32-C6

v1.3.57 — GET_NWK_KEYS for zigpy-zboss 2.0.2 / ZHA compatibility

Choose a tag to compare

@tostmann tostmann released this 16 Jun 12:40

ZHA / zigpy-zboss 2.0.2 compatibility

zigpy-zboss 2.0.2 changed how the network key is read at startup and now hard-aborts the connection when GET_NWK_KEYS returns no usable key — where 2.0.1 silently tolerated the unimplemented command. As a result, ZHA (and zigpy-zboss 2.0.2 generally) could no longer resume an already-formed network against this firmware.

This release implements GET_NWK_KEYS (0x001e), returning a fixed, recognizable placeholder key so the resume path succeeds. The NCP holds the real key and performs all cryptography itself; the host-side key is bookkeeping only — a ZHA network backup is not restorable against this firmware regardless (the frame-counter restore command is not implemented; use GET_NETWORK_BACKUP / RESTORE_NETWORK for a full NVRAM snapshot). Zigbee2MQTT / herdsman never queries GET_NWK_KEYS and is unaffected.

Validated end-to-end: zigpy-zboss 2.0.2 resume + blank auto-form over USB and over a TCP-bridged link, full Home Assistant / ZHA coordinator init, and a live device pairing.

v1.3.56 — host-controlled permit join, truthful PAN getter, boot-guard safe mode

Choose a tag to compare

@tostmann tostmann released this 07 Jun 16:00

Three independent hardenings, each root-caused and validated on real hardware.

Host-controlled permit join (BOOT-1)

The coordinator no longer opens its network for joining without being told to. Previously, BDB network steering ran at every boot and after each successful formation — steering on a formed network broadcasts Mgmt_Permit_Joining(180 s), so the network could be open for joining for 3 minutes after a plain reboot with no host attached. The boot path now runs BDB initialization only (NVRAM resume, or idle when factory-new), and permit join is exclusively driven by the host (NWK_PERMIT_JOINING / ZDO_PERMIT_JOINING_REQ, i.e. zigbee2mqtt's permit_join).

Validated on-air: a continuously steering factory-new probe device could not join for 220 s after a coordinator reboot, then joined 4 seconds after a host permit-join command.

Note: any device already ON the network may still broadcast a 180 s permit-join window after its own steering (e.g. when it rejoins) — that is spec-compliant Zigbee behaviour and unchanged.

Truthful PAN getter (PAN-1)

SET_PAN_ID on a formed network is now rejected (OPERATION_FAILED) instead of silently rewriting only the cache that the getters read. Hosts that write their desired settings after forming (zigpy-zboss does) previously saw their requested PAN echoed back by every getter while the device actually ran — and persisted — a different one; after the next reboot the PAN appeared to have changed, which can trip a host's "network changed, reinitialize" logic. Setting the PAN before formation (zigbee-herdsman's flow) keeps working and is honoured at formation time.

Boot-guard safe mode (WEDGE-1)

New resilience layer for a failure class where a power event can leave the stick with a permanently silent application (suspected hang early in stack init). The firmware now counts boots that die before the Zigbee stack comes up (a 30 s deadline converts silent hangs into countable reboots); after 3 consecutive failures it boots into a sticky safe mode: the serial link comes up without the Zigbee stack, GET_MODULE_VERSION and NCP_RESET are served, and everything else answers GENERIC_BLOCKED — the stick stays reachable and diagnosable instead of appearing dead, and NCP_RESET with the factory-reset option doubles as a remote recovery path. Safe mode exits on a host-commanded reset, a true power cycle, or a 10-minute automatic retry.

Validation

  • On-air permit-gate probe (new test/permit_gate_check.py): closed for 220 s post-boot, join 4 s after host permit
  • Formation PAN now equals post-reboot PAN across repeated reboots; zigpy-zboss 2.0.0 tolerates the rejected post-formation write
  • Safe-mode chain exercised end-to-end with an artificial pre-stack hang build: deadline-reboot accumulation, sticky safe mode across port reopens, blocked getters, supervised exit
  • Regression on the final build: zigpy-zboss 2.0.0 full startup (network resume) and the v1.3.50 duplicate-drop suite, all pass

Flash via the web flasher at install.busware.de/zboss or esptool write_flash 0x0 binaries/factory.bin. Network settings (NVRAM) are preserved across an app update; a backup via the web flasher before any flash is recommended regardless.

v1.3.50 — link-layer duplicate drop

Choose a tag to compare

@tostmann tostmann released this 07 Jun 13:17

Link-layer duplicate drop (DUP-1)

Fixes handling of retransmitted host data frames in the ZBOSS NCP serial protocol layer.

The bug: the header's retransmit bit was treated as NACK on every frame type. A host that re-sent a data frame after an ACK timeout (stable packet_seq + retransmit flag — exactly what zigpy-zboss ≥ 2.0.0 and zigbee-herdsman do) got its retransmission silently discarded: no re-ACK, no execution. A host retry could therefore never succeed — and an unflagged duplicate would have been executed twice. Latent on USB/TCP (frame loss is rare there), real on lossy UART links.

The fix: the firmware now remembers the sequence number of the last accepted host data frame. A flagged retransmission with a matching sequence is re-ACKed but not executed again (lost-ACK case); a flagged frame with an unseen sequence is accepted as a first delivery (lost-frame case).

Validation (on real hardware, ESP32-C6):

  • New on-wire probe test/dup_drop_check.py: 4/4 PASS — duplicate re-ACKed and not re-executed, fresh flagged frame dispatched, link continues normally
  • zigpy-zboss 2.0.0 full-startup runs against this build: restart on a formed network and blank-NCP auto_form, both SUCCESS
  • New test/routing_gate_check.py: 4/4 PASS — isolated validation of the v1.3.45 RX routing gate (interface routing flips only on a frame signature; port-prober garbage, e.g. ModemManager AT probes, is discarded; signature split across reads handled)

Who should update: anyone using the UART transport (TTL header / ser2net setups) or driving the coordinator with zigpy-zboss ≥ 2.0.0 (ZHA path). USB users with Zigbee2MQTT are unaffected in practice but get the hardening for free.

Flash via the web flasher at install.busware.de/zboss or esptool write_flash 0x0 binaries/factory.bin. Network settings (NVRAM) are preserved across an app update; the web flasher's backup feature is recommended before any flash regardless.

v1.3.49 — ZBOSS thread-safety fix

Choose a tag to compare

@tostmann tostmann released this 06 Jun 09:17

v1.3.49 — ZBOSS thread-safety fix (recommended upgrade)

Bugfix release. No new features, no protocol changes — existing setups upgrade by reflashing, network data is preserved.

The fix

The NCP command dispatch called ZBOSS APIs from the application task without holding the ZBOSS port-layer lock (zb_esp_osif_lock_acquire — the same rule esp-zigbee-lib enforces via esp_zb_lock_acquire(): the ZBOSS core is not thread-safe). The two tasks could race inside the stack's interrupt-bracketing primitive, corrupting the FreeRTOS critical-section nesting count and crash-rebooting the coordinator with:

assert failed: vPortExitCritical port.c:618 (port_uxCriticalNesting[0] > 0)

This design flaw is inherited from the original codebase and affects all previous releases. It is timing-dependent: it reproduced reliably under device-interview bursts on the WiFi-coexistence development branch, and is rarer (but equally present) on plain USB/UART builds — if your coordinator ever rebooted spontaneously during device pairing or interviews, this was a likely cause.

The dispatch is now serialised against the stack task with the port-layer lock. Validated with repeated device-interview stress runs (previously ~80% crash rate on the reproducer, now 0 failures) plus an 8-hour continuous-load soak.

Also in this release

  • Boot log line BOOT: reset reason: N (visible on debug builds; aids crash diagnosis on boards whose eFuses suppress the ROM reset banner)
  • Event-queue hardening: dropped-event diagnostics + bounded retry on the host-RX path
  • Release binary built with the documented UART default pins (TX=GPIO22, RX=GPIO23)

Flashing

Web flasher: https://install.busware.de/zboss/ — or esptool --chip esp32c6 write_flash 0x0 factory.bin. Reflashing without erase preserves the formed network and paired devices.

v1.3.45 — UART Host Transport, XIAO ESP32-C6 Support, NCP_RESET Fixes

Choose a tag to compare

@tostmann tostmann released this 05 Jun 18:50

New: UART host transport — run the coordinator without USB

The NCP frame stream is now additionally available on a hardware UART, in parallel to USB-Serial/JTAG (#2, discussion #1):

  • Default pins: coordinator TX = GPIO22, RX = GPIO23 (= D4/D5 on the Seeed XIAO ESP32-C6), 115200 8N1, no flow control — configurable via menuconfigZigbee Network Co-processor.
  • Hook the coordinator straight onto an OpenWRT router's TTL header, a Raspberry Pi GPIO header UART, or a USB-UART adapter / ser2net box. See the README wiring guide.
  • Both interfaces stay live: responses follow whichever interface last delivered a valid NCP frame. The routing flip is signature-gated, so port probers (e.g. ModemManager's AT probes) can neither steal the link nor corrupt the stream.
  • Bonus: on a UART the host's port survives the coordinator's resets — the USB-CDC reset quirks disappear entirely on this path.
  • Validated end-to-end with zigbee2mqtt (adapter: zboss) and zigpy-zboss: network formation, device pairing, full 40 KB NVRAM backup.

New: Seeed XIAO ESP32-C6 external antenna option

CONFIG_NCP_XIAO_EXT_ANTENNA routes RF to the XIAO's external U.FL connector (GPIO3/GPIO14 RF-switch control per Seeed's reference). Default off; the default build now leaves the switch pins in a defined passive INPUT state — internal antenna on the XIAO, no-op on every other board.

Critical fixes

  • NCP_RESET over USB no longer freezes the chip until power-cycle. The legacy USB "phy detach" register writes hard-freeze the ESP32-C6 on current ESP-IDF builds. They are removed; NVRAM/factory erase now runs at the start of the next boot, before the radio is active.
  • NCP_RESET protocol semantics fixed: no more pre-reboot "OK" response. Hosts treated that response as "reset complete" and continued immediately — everything sent before the actual reboot died mid-flight. This was the root cause of the long-standing "Backup failed … after 10000ms" failures after factory-reset / restore / formation. The boot-ready frame after the reboot is now the response, so reset → re-form and restore → resume flows complete deterministically.
  • Full-spec NWK_FORMATION response (NWKAddr + PANID + page + channel) — fixes formation against hosts parsing the complete layout, notably zigpy-zboss ≥ 2.x / Home Assistant ZHA (hardware-validated against kardia-as/zigpy-zboss#73).
  • Link-layer sequence race closed (duplicate packet_seq from concurrent ACK/data paths → sporadic "Unexpected packet sequence" on the host).

Improvements

  • GET_NETWORK_BACKUP serves all chunks from a point-in-time RAM snapshot — consistent 40 KB images, single bulk flash read.
  • README: UART wiring guide, XIAO pin mapping, refreshed ZHA status.

Flashing: Web flasher (updated to v1.3.45) or esptool.py --chip esp32c6 write_flash 0x0 esp-coordinator-esp32c6-factory.bin.