A highly optimized ZBOSS NCP Serial Protocol implementation for ESP32-C6/H2 modules, tailored specifically for Zigbee2MQTT.
This firmware transforms any cheap, standard ESP32-C6 development board into an enterprise-grade Zigbee Coordinator that rivals or exceeds established commercial adapters.
The ESP32-C6 disrupts the traditional Zigbee Coordinator market (dominated by Texas Instruments Z-Stack and Silicon Labs EZSP) by offering massive hardware capabilities at a fraction of the cost.
Virtually ANY ESP32-C6 board on the market (including generic $3 boards from China) works out-of-the-box as a high-end Coordinator. You do not need specialized "coordinator" dongles anymore.
-
Massive Capacity (200 Nodes) Older chips like the TI CC2531 max out at 20-40 direct children due to tiny 8KB RAM. The ESP32-C6 boasts 512KB SRAM. This firmware is hardcoded to support 200 direct nodes natively, allowing you to build massive, star-shaped networks without memory exhaustion, directly competing with the expensive TI CC2652 range.
-
Native "Long-Range" Transmit Power (+20 dBm) While standard Zigbee chips output +5 dBm and require expensive external Power Amplifiers (the "P" in CC2652P) to reach high range, the ESP32-C6 has an integrated power amplifier on the silicon. It natively transmits at up to +20 dBm out of the box, making it a true Long-Range coordinator.
-
Native Bare-Metal Backups (New in v1.1.0) Unlike Z-Stack or EZSP, which require hundreds of lines of complex parsing scripts to extract individual network keys and tables from RAM, this firmware implements a Chunked Raw NVRAM Transfer. Zigbee2MQTT can seamlessly read and restore the entire 40KB Flash memory (including frame counters, trust center keys, and routes) via standard NCP commands. You can swap a broken ESP32-C6 for a new one, hit restore, and the network will resume instantly.
-
All-in-One SoC (Wi-Fi 6 + Zigbee) Traditional network coordinators require two chips: a Zigbee radio (TI/SiLabs) and a Wi-Fi bridge (ESP32). The ESP32-C6 has both radios built into a single chip, drastically reducing hardware complexity for networked gateways.
-
Modern ZBOSS Stack The commercial, highly-certified ZBOSS stack provides an extremely robust alternative to the often complex and legacy-burdened EZSP protocol.
This project is an actively maintained, heavily optimized evolution of the original esp-coordinator. As of 2026-05, the active upstream lives in this fork; the original andryblack/esp-coordinator repository is archived and read-only and its README points here. If you arrived via a search hit on one of the archived andryblack issues, see LEGACY_ANDRYBLACK_ISSUES.md for the current status of each report.
- Cold-boot panID/channel race FIXED (#5 / #19 / z2m#26152): The ZBOSS dispatch task is now started at boot from
app::start_int, not lazily fromNWK_FORMATION/NWK_START_WITHOUT_FORMATION. Without this, z2m's firstGET_JOINED/GET_PAN_IDqueries returned the uninitialised 0xFFFF / 0xFF defaults, z2m saw a "different" network than its configured options and calledformNetwork()β silently wiping every paired device. Verified live: persistedpanID,extendedPanID, andchannelcome back correctly on every cold boot. - NCP_RESET deferred task + USB phy detach: Long-running parts of factory-reset (
zb_nvram_erase,zb_bdb_reset_via_local_action) moved off the request-handling task so the matching-tsn response goes out on the wire immediately. Beforeesp_restart(), the firmware now forcibly disablesUSB_SERIAL_JTAG_CONF0_REG'sDP_PULLUP+USB_PAD_ENABLEfor 800 ms β the host CDC layer sees a real disconnect, which is what herdsman'sonPortCloseneeds to release itsinResetflag. (For end-to-end factory-reset, the host also needs the matching change intostmann/zigbee2mqttscripts/patch_zboss.js; see ZIGBEE2MQTT.md.) - Tuya Simple_Desc_rsp intercept (esp-zigbee-sdk#485): The prebuilt ZBOSS stack does a strict frame-length check on ZDP Simple_Desc_rsp and silently drops responses with trailing bytes, breaking interview for some Tuya devices (OUI prefix
0x70b3d5..., e.g. TS011F). The firmware now intercepts cluster 0x8004 indications, tolerantly re-parses them, and synthesises a clean response to the host. First known userspace workaround of #485. Live-verified on_TZ3000_w0qqde0g. - Audit pass: 17 fixes across critical/high/medium severity β APSDE bound checks, ZDO request slot lifecycle, RESTORE_NETWORK chunked-transfer hardening, real firmware-version reporting via
GET_MODULE_VERSION, etc. Seegit log.
- Native NVRAM Backup & Restore: Complete firmware-side implementation of custom NCP Commands
0x0099and0x009A, supporting chunked Z2M backups (40 KB image = nvs + zb_storage partitions). - NVRAM Persistence Fix: Coordinator accurately resumes its network from NVRAM on boot, maintaining pairings and frame counters across restarts.
- Manufacturer Code Workaround:
ZDO_SET_NODE_DESC_MANUF_CODEimplementation allows Z2M to emulate the manufacturer code dynamically. - Network Scaling: Tables and memory dynamically optimized for 200 nodes.
- Dynamic TX Power: Supports dynamic adjustment of the transmission power up to 20 dBm.
- Permit Join Handling: Added full support for the
NWK_PERMIT_JOINING (0x0404)command. - Modern ZBOSS SDK: Fully ported to ZBOSS SDK v1.6.x.
While this coordinator works perfectly with the standard Zigbee2MQTT release, we highly recommend using our customized Docker image (ghcr.io/tostmann/zigbee2mqtt-esp32:latest).
Our custom image unlocks Native NVRAM Snapshots, allowing Zigbee2MQTT to automatically stream the ESP32's complete NVRAM over the serial protocol. This means if your hardware breaks, you can just plug in a new ESP32 and Z2M will automatically transfer your latest network state (including frame counters) to the new chip without having to re-pair any devices.
| Setup | Standard koenkk/zigbee2mqtt |
ghcr.io/tostmann/zigbee2mqtt-esp32 |
|---|---|---|
| Fresh install (empty chip, first start) | β Works | β Works |
Existing network, configuration.yaml matches device's persisted channel / panID / extendedPanID |
β Works β devices stay paired across reboots (cold-boot panID race fix) | β Works |
Existing network, configuration.yaml channel/panID differs from device β e.g. migrating between coordinators, or re-aiming at a different network |
reset(FactoryReset) and hangs on a 10 s timeout; the chip's NVRAM gets erased in the process, so paired devices are effectively lost on next start |
β
Reset β factory-reset β re-form flow completes end-to-end, then devices can be re-paired (or restored from a backup coordinator_backup.json) |
| Coordinator hardware migration (swap ESP32 chip, keep network) | β No native backup/restore support | β Raw-NVRAM transfer over the wire β paired devices come back without re-pairing if the snapshot is recent |
TL;DR: if your configuration.yaml already matches the device's persisted network, the standard z2m works fine. If you need to change network parameters, migrate hardware, or just want the safety net of automated NVRAM snapshots, use the tostmann/zigbee2mqtt-esp32 Docker image.
The relevant host-side patches (RESTORE_NETWORK adapter integration plus the onPackage-during-inReset fix that closes the factory-reset hang) live in scripts/patch_zboss.js on the tostmann/zigbee2mqtt fork and are applied automatically via npm postinstall. See patches/herdsman-ncp-reset-fix.md in this repo for the technical rationale.
π Read the full Zigbee2MQTT Setup & Migration Guide
If you would rather drive the coordinator from Home Assistant's native ZHA
integration (i.e. avoid running Z2M at all), the
tostmann/zha-zboss-esp
custom-component is the path. It is HACS-installable, bundles
zigpy-zboss as a Python requirement, runtime-patches the known library
compat gaps against current zigpy / serialx, and extends ZHA's
RadioType enum so ZBOSS appears as a selectable radio type in the
add-integration flow.
Status as of v1.1.23: setup gets through firmware probe / radio-type
pick / network formation cleanly. The full end-to-end add-integration flow
beyond that still hits additional zigpy-zboss bit-rot against zigpy 1.4
that is outside this firmware's scope to fix β those gaps are tracked in
kardia-as/zigpy-zboss#19
and PR #74 is
addressing the first two. For production use, the Z2M path above remains
the recommended option. The ZHA path is shaping up but not yet a no-rough-
edges experience.
Use the stable JTAG USB serial port in your configuration.yaml and configure the transmit power to fully utilize the ESP32-C6 amplifier:
serial:
port: /dev/serial/by-id/usb-Espressif_USB_JTAG_serial_debug_unit_...
adapter: zboss
advanced:
transmit_power: 20You can flash the firmware directly from your browser using our Web Serial Flasher tool. This is the easiest method and requires no software installation.
π Launch ESP-Coordinator Web Flasher
(Supported Browsers: Chrome, Edge, Opera)
Alternatively, you can flash the provided factory binary directly to the 0x0 offset of your ESP32-C6. This single binary includes the bootloader, partition table, OTA data, and the app.
esptool.py -p /dev/ttyACM0 --chip esp32c6 write_flash 0x0 binaries/factory.binYou can compile the firmware yourself using the standard Espressif IoT Development Framework (ESP-IDF v5.5+):
idf.py build
idf.py -p /dev/ttyACM0 flash