Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
fd55dcf
Remove tokio dependency from `ziggurat-phy`
puddly Jun 22, 2026
918c44f
stdio API for server
puddly Jun 22, 2026
216f0a6
Migrate to new abstract-bits `presence_from` syntax
puddly Jun 19, 2026
93d33bc
Make ziggurat-ieee-802154 `no_std`
puddly Jun 22, 2026
1ff0101
Propagate `no_std` into the main stack??
puddly Jun 22, 2026
a5439e7
Test: minimal driver ESP32
puddly Jun 22, 2026
3b3127b
Test: async runtime abstraction
puddly Jun 23, 2026
d9a7481
Test: explicit stack-owned TX tasks
puddly Jun 23, 2026
a15ccc5
Test: begin migrating to event-based drivers for routing
puddly Jun 23, 2026
ca64844
Test: event-based broadcasts, with ACKs
puddly Jun 23, 2026
f976dcf
`Signal` primitive to wrap Mutex + Notify and replace `oneshot::Sender`
puddly Jun 23, 2026
dab7f35
Abstract away `broadcast`
puddly Jun 23, 2026
7de3546
Test: migrate network scanning
puddly Jun 23, 2026
23fabcb
Test: abstract parking_lot Mutex and drop deadlock canary
puddly Jun 23, 2026
9bd5d56
Abstract the task spawner
puddly Jun 23, 2026
71f6c66
Abstract the mutex as well
puddly Jun 23, 2026
b6f85ac
Test: embassy on host??
puddly Jun 23, 2026
2ec3d03
Clean up deps
puddly Jun 28, 2026
eb510b5
Bridge embassy with tokio for testing on host
puddly Jun 28, 2026
6e40154
Test: finish MCU target compilation
puddly Jun 28, 2026
f476807
Test: hacky ESP32 JSON API
puddly Jun 28, 2026
56e42e6
Fix ESP32 radio reset
puddly Jun 28, 2026
c5650b3
ESP32 network scan API
puddly Jun 28, 2026
748495a
Task cancellation
puddly Jun 28, 2026
3f67d03
Energy scan
puddly Jun 28, 2026
74c12ea
Fix RX
puddly Jun 28, 2026
2694f8b
Implement ESP32 radio exclusivity
puddly Jun 28, 2026
d30752d
Packet capture API for ESP32
puddly Jun 28, 2026
fda843d
Reset request
puddly Jun 29, 2026
32e2144
Mostly hardware accelerated AES for the ESP32-C6
puddly Jun 29, 2026
4b47014
Much faster hardware accelerated AES
puddly Jun 29, 2026
1dec851
Boost the CPU from 80MHz to 160MHz
puddly Jun 29, 2026
5ca4fc2
Gracefully handle no-ACK frames that error due to no ACK (why?)
puddly Jun 29, 2026
d44b78a
Hack: beacon spray during permit window
puddly Jun 29, 2026
9abbf2a
Migrate to development branch of `esp-hal`
puddly Jun 29, 2026
5d00581
Do not stall the USB UART FIFO
puddly Jun 29, 2026
ff08cd0
Implement source match table support in the ESP32 HAL
puddly Jun 29, 2026
b6b82fc
Ensure APS sends run in concurrent tasks
puddly Jun 29, 2026
03350ec
Mark some more TODOs
puddly Jun 29, 2026
92a2d79
Tunnel logging frames over the WebSocket API?
puddly Jun 30, 2026
54ebb50
Return `TxResult::Acked` for frames that do not need an ACK
puddly Jun 30, 2026
3887137
Test: CSMA backoff for ESP32
puddly Jun 30, 2026
5497f48
Test: cap executor stack size and drop unconsumed notifications
puddly Jun 30, 2026
b2018c0
Properly decouple frame sending from retrying
puddly Jun 30, 2026
19ee600
Add retry jitter
puddly Jun 30, 2026
7c17c05
Switch from Seeed XIAO ESP32-C6 to the ESP32-C6-DevKitM-1
puddly Jun 30, 2026
75e8e71
Log to UART
puddly Jun 30, 2026
6edebd4
Ensure we encrypt command replies with the same key as the request
puddly Jun 30, 2026
4fd75d6
Await-less APS send API
puddly Jul 1, 2026
82a28a2
Increase heap allocation to 240K
puddly Jul 1, 2026
d4876b9
Ensure large logging lines can be handled
puddly Jul 1, 2026
8023d8a
Migrate ZDP away from APS tasks
puddly Jul 1, 2026
97cdaf4
Format
puddly Jul 1, 2026
daceed8
Three stage send API to remove fire-and-forget send tasks
puddly Jul 1, 2026
c01f617
Ignore the ESP32 radio LQI and derive it from RSSI
puddly Jul 1, 2026
31a88e9
Absorb large writes in memory
puddly Jul 1, 2026
e8be092
Bump `esp-hal` fork to fix AES mutex crash
puddly Jul 1, 2026
6cb0759
Reformat
puddly Jul 1, 2026
898f973
Document ESP32 instructions in README
puddly Jul 1, 2026
bbca0e5
Statically type `request_id`
puddly Jul 1, 2026
065d7ed
Split APS ACK confirmation out into a new message
puddly Jul 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
309 changes: 302 additions & 7 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
[workspace]
resolver = "2"
members = ["crates/*"]
exclude = ["fuzz"]
# ziggurat-phy-esp and ziggurat-esp only build for an ESP32-C6 (riscv32imac) with esp-hal;
# excluded so host `cargo build` over the workspace doesn't try (and fail) to compile them.
exclude = ["fuzz", "crates/ziggurat-phy-esp", "crates/ziggurat-esp"]

[workspace.package]
version = "0.1.0"
Expand All @@ -11,6 +13,7 @@ license = "Apache-2.0"
authors = []
repository = "https://github.com/zigpy/ziggurat"


[workspace.dependencies]
ziggurat-ieee-802154 = { path = "crates/ziggurat-ieee-802154", version = "0.1.0" }
ziggurat-phy = { path = "crates/ziggurat-phy", version = "0.1.0" }
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,19 @@ Ziggurat can be set up as a regular ZHA radio in **Home Assistant 2026.7.0** or
4. When ZHA asks for the radio type, pick **Ziggurat** and migrate your existing network (or set a new one up).
5. Done.

### ESP32-C6 firmware
Ziggurat can also run on-chip on an ESP32-C6, using its built-in 802.15.4 radio and
exposing the same JSON API over USB-Serial-JTAG: no separate RCP radio or host.

```bash
cd crates/ziggurat-esp
cargo build --release
espflash flash --no-stub --chip esp32c6 --port <PORT> \
target/riscv32imac-unknown-none-elf/release/ziggurat-esp
```

- USB-Serial-JTAG carries the JSON API; UART0 on GPIO16 @ 460800 carries logs, the debug heartbeat, and panic backtraces.

### 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
Expand Down
47 changes: 41 additions & 6 deletions crates/ziggurat-driver/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,45 @@ ziggurat-ieee-802154.workspace = true
ziggurat-phy.workspace = true
ziggurat-zigbee.workspace = true

abstract-bits = "0.2.0"
abstract-bits = { git = "https://github.com/yara-blue/abstract-bits.git", version = "0.2.0" }
arbitrary-int = "2.1.1"
tracing = "0.1"
parking_lot = "0.12.4"
rand = "0.10.1"
thiserror = "2.0.12"
tokio = { version = "1.43.0", features = ["rt", "macros", "time", "sync", "io-util"] }
futures = { version = "0.3", default-features = false }
tracing = { version = "0.1", default-features = false }
thiserror = { version = "2.0.12", default-features = false }

# Host (tokio) runtime backend.
parking_lot = { version = "0.12.4", optional = true }
rand = { version = "0.10.1", optional = true }
tokio = { version = "1.43.0", features = [
"rt",
"macros",
"time",
"sync",
"io-util",
], optional = true }

# Embassy runtime adapter. The MCU binary provides the executor + arch (via esp-rtos), so
# the base dependency pulls no arch feature; the `embassy-host` feature adds the std
# executor so the same adapter can run on the host bridged to tokio.
embassy-executor = { version = "0.10", optional = true }
embassy-time = { version = "0.5", optional = true }
embassy-sync = { version = "0.8", optional = true }
spin = { version = "0.9", default-features = false, features = [
"spin_mutex",
], optional = true }

[features]
default = ["tokio"]
# The host runtime: tokio executor, parking_lot locks, OS randomness.
tokio = ["dep:tokio", "dep:parking_lot", "dep:rand"]
# The embassy runtime adapter (and its no_std-friendly sync primitives), for the MCU.
# Mutually exclusive with `tokio` at the `sync`/`runtime`/`rng` seam.
embassy = ["dep:embassy-executor", "dep:embassy-time", "dep:embassy-sync", "dep:spin"]
# Run the embassy adapter on the host, bridged to a tokio reactor (for tests).
embassy-host = [
"embassy",
"dep:tokio",
"embassy-executor/platform-std",
"embassy-executor/executor-thread",
"embassy-time/std",
]
12 changes: 12 additions & 0 deletions crates/ziggurat-driver/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
#![no_std]

extern crate alloc;

// The host bridge spawns an OS thread for the embassy executor; that path alone needs std.
#[cfg(feature = "embassy-host")]
extern crate std;

pub mod rng;
pub mod runtime;
pub mod signal;
pub mod sync;
pub mod zigbee_stack;

pub use ziggurat_ieee_802154;
Expand Down
53 changes: 53 additions & 0 deletions crates/ziggurat-driver/src/rng.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//! Randomness crate.

/// A uniform `f32` in `[0, 1)`, for jitter scaling.
pub fn random_f32() -> f32 {
let mut bytes = [0u8; 4];
fill_bytes(&mut bytes);
// 24-bit mantissa worth of entropy mapped into [0, 1)
(u32::from_le_bytes(bytes) >> 8) as f32 / (1u32 << 24) as f32
}

/// A uniform `u16`, for stochastic address allocation.
pub fn random_u16() -> u16 {
let mut bytes = [0u8; 2];
fill_bytes(&mut bytes);
u16::from_le_bytes(bytes)
}

/// `N` random bytes, for key material.
pub fn random_array<const N: usize>() -> [u8; N] {
let mut bytes = [0u8; N];
fill_bytes(&mut bytes);
bytes
}

#[cfg(feature = "tokio")]
fn fill_bytes(buf: &mut [u8]) {
use rand::RngExt;
rand::rng().fill(buf);
}

#[cfg(all(feature = "embassy", not(feature = "tokio")))]
pub use embassy_rng::{fill_bytes, install};

#[cfg(all(feature = "embassy", not(feature = "tokio")))]
mod embassy_rng {
use crate::sync::Mutex;
use alloc::boxed::Box;

type FillFn = Box<dyn FnMut(&mut [u8]) + Send>;

static FILL: Mutex<Option<FillFn>> = Mutex::new(None);

/// Install the byte source. The MCU binary backs this with the SoC hardware RNG.
pub fn install(fill: FillFn) {
*FILL.lock() = Some(fill);
}

pub fn fill_bytes(buf: &mut [u8]) {
let mut guard = FILL.lock();
let fill = guard.as_mut().expect("rng::install was never called");
fill(buf);
}
}
Loading