An open source Zigbee stack implemented in Rust.
Traditional Zigbee coordinator firmware is written in C and is compiled to run self-contained on a microcontroller. Ziggurat splits this architecture in two: we rely on a microcontroller to manage the IEEE 802.15.4 radio (the wireless layer of both Zigbee and Thread) and move all Zigbee processing and logic onto the host computer. This effectively turns the Zigbee coordinator hardware into a simple radio that sends packets, receives packets, and automatically sends IEEE 802.15.4 ACKs. Nothing more. All encryption, decryption, sending, retrying, routing, logic, etc. is done on the host by Ziggurat.
Ziggurat requires low-level access to a Zigbee radio. Thankfully, there already exists firmware that provides this out of the box: OpenThread RCP! The same firmware can now be used for both Zigbee and Thread.
The most powerful Zigbee microcontrollers today have only a few hundred kilobytes of RAM and maybe a megabyte of flash. Their Zigbee firmware is written with this resource-constrained environment in mind: limited packet buffers, simpler routing algorithms, fewer concurrent requests.
Ziggurat moves all of the logic out of the microcontroller and onto the host, which has way more than a few hundred kilobytes of RAM. Routing, discovery, and neighbor tables don't need size limits. We can buffer an unlimited number of requests. We can keep track of multiple concurrent routes to a single device and pick intelligently, reducing latency.
To compile firmware for a given Zigbee/Thread chip, you use a chip vendor's SDK. For example, Silicon Labs Simplicity SDK. These SDKs are a mix of pre-compiled libraries without source code and a bit of source code to glue together the functions exported by the libraries. The interesting parts of a Zigbee stack are locked away (e.g. how does routing work?). If there are bugs, the chip vendor decides if and when a bug is worth fixing.
Ziggurat is entirely open source, both in licensing and source availability, and is developed in the open. There are no closed-source binary blobs. OpenThread RCP uses a standardized serial protocol and is effectively chip-agnostic, allowing Ziggurat to work with any radio that supports OpenThread RCP.
Rust is memory safe and fast. It's also fun to write. Ziggurat is written in fairly performant Rust to reduce the round trip latency when communicating with the radio hardware and to allow it to run on even the most basic of hosts, and the same code runs embedded in the radio firmware itself (see below).
Ziggurat is in beta testing and can be set up as a regular ZHA radio in Home Assistant 2026.7.0 or newer. There are two ways to run it:
The most performant way to run Ziggurat is straight on your host machine, hooked up to an OpenThread RCP radio:
- Flash your Zigbee radio with OpenThread RCP firmware, the same firmware you use for Thread. Radios with Silicon Labs chips (e.g. EFR32MG21, EFR32MG24) are recommended but others work too.
- Install the Ziggurat server app from the Zigpy app repo for Home Assistant.
- Set up ZHA (or start a migration) and select "manual" as the serial port path. Type
in
ws://local-ziggurat:9999as the URL. - When ZHA asks for the radio type, pick Ziggurat and migrate your existing network (or set a new one up).
- Done.
Ziggurat is small enough to fit on larger microcontrollers, there are example firmwares for the Silicon Labs EFR32MG24 or Espressif ESP32-C6. The architecture for this firmware is a little special: we embed Ziggurat inside of OpenThread RCP and tunnel its protocol through a vendor Spinel property (0x3D5A). This special build of OpenThread RCP can be used for a OpenThread border router, Ziggurat on host, and Ziggurat on device.
ZHA can communicate directly with Ziggurat running on a microcontroller via serial. Set it up as you would any other radio in ZHA.
- Flash your Zigbee radio with Ziggurat + OpenThread RCP firmware
- Home Assistant Connect ZBT-2 firmwares are built as part of CI. Find the latest one
from GitHub Actions
and flash the
.gblfile to your ZBT-2 with the OHF Device Toolbox oruniversal-silabs-flasher
- Plug it into your Home Assistant OS box.
- Set up ZHA and pick the Ziggurat serial port.
- ZHA will try to probe the protocol spoken by your radio. This will take a minute and is expected to fail.
- Select
Zigguratas the radio type and then fill in the serial port details. The default baudrate is 460800 and hardware flow control should be enabled. - Done.
Keep in mind the host stack is the recommended way to run Ziggurat, as running it on a microcontroller is slightly less performant than on host and may have fewer features as Ziggurat continues development.
Ziggurat aims to implement the portions of a Zigbee stack used by normal Home Assistant users, not the entire binder of Zigbee specification verbatim. It is nearly feature-complete in terms of functionality exposed by consumer Zigbee devices:
- 802.15.4, Zigbee APS, and Zigbee NWK layers are implemented, cryptography included
- AODV routing (the default)
- Route discovery
- Source routing
- Device joining
- Child device message holding and management
- Zigbee Green Power: direct reception of GPDFs, which are passed unprocessed to zigpy
- Zigbee Green Power: transmission of GPDFs (needed for bidirectional GPDs)
Zigbee R23 and R23.1 support (i.e. Zigbee 4.0) are low on the list but planned for the future. Keep in mind Zigbee is both forward and backward compatible and practically nothing user-visible has changed in these revisions.
Ziggurat's public API is a simple WebSocket server. You can run it locally instead of in the Home Assistant app:
cargo run --bin ziggurat -- --device /dev/cu.SLAB_USBtoUART --listen 0.0.0.0:9999The adapter itself is stateless. ZHA pushes all network configuration to it on startup.
- Ziggurat statically allocates most protocol-level parsing buffers. Only a few places in
the stack (e.g.
HashMap) still rely on dynamic allocation. - For testing purposes (especially CI) it would be convenient to allow Ziggurat to have a dynamically configurable role on startup. Adding the ability to join a network as a router would allow for some useful end-to-end testing scenarios, like a synthetic Spinel layer that can connect multiple Ziggurat instances together.
- Zigbee Green Power transmission: a GPD only listens 20-25 ms after it transmits, too short for a round trip to zigpy, so Ziggurat has to queue the response and send it itself.
- Exploring more routing algorithms.
An earlier iteration of this concept in Python is available as zigpy-spinel.