A clean, modular Zigbee coordinator and bridge system for the ESP32-C6, designed to bridge Zigbee devices to MQTT for integration with home automation systems like Home Assistant.
- Zigbee Coordinator: Forms and manages a Zigbee 3.0 network
- Device Management: Automatic device discovery, interview, and provisioning
- Capability Abstraction: Maps Zigbee clusters to stable, typed capabilities
- MQTT Integration: Publishes device states and receives commands via MQTT
- Persistence: Device registry survives reboots without re-pairing
- Interactive Shell: UART-based shell for debugging and configuration
- Event-Driven Architecture: Clean, decoupled services via internal event bus
- Requirements
- Quick Start
- Building
- Running Tests
- Architecture
- Shell Commands
- MQTT Topics
- Configuration
- Development
- Project Status
- License
- ESP32-C6 development board (for target deployment)
- USB cable for programming and UART console
- GCC compiler (C11 support)
- GNU Make
- POSIX-compatible system (Linux, macOS, WSL)
- ESP-IDF v5.0+ (with Zigbee support)
git clone https://github.com/peternicholls/ESP32-Zigbee-Bridge.git
cd ESP32-Zigbee-Bridgemake testmake
./build/bridge# Set up ESP-IDF environment first
. $IDF_PATH/export.sh
# Build for ESP32-C6
idf.py set-target esp32c6
idf.py build
idf.py flash monitorThe project includes a host build for testing and development without ESP32 hardware:
# Clean build artifacts
make clean
# Build main application
make
# Build and run tests
make test
# Run the bridge (simulated)
make run| Target | Description |
|---|---|
make or make all |
Build the main bridge application |
make test |
Build and run unit tests |
make run |
Build and run the bridge |
make clean |
Remove all build artifacts |
The build uses strict compiler flags for code quality:
-Wall -Wextra -Werror: All warnings as errors-std=c11: C11 standard-g -O0: Debug symbols, no optimization (development)-DOS_PLATFORM_HOST=1: Host platform indicator
The test suite validates all core OS and service components:
make testExpected output:
=== ESP32-C6 Zigbee Bridge OS Unit Tests ===
Type tests:
Testing types... PASS
Event bus tests:
Testing event_init... PASS
...
=== Results ===
Passed: 30
Failed: 0
- Type tests: Size assumptions and macros
- Event bus tests: Publish, subscribe, filter, dispatch
- Log tests: Levels, formatting, queue
- Persistence tests: Put, get, flush, schema version
- Registry tests: Node, endpoint, cluster, attribute management
- Interview tests: Device discovery simulation
- Capability tests: Cluster-to-capability mapping
┌──────────────────────────────────────────────────┐
│ Northbound Adapters │
│ - MQTT adapter │
│ - HA Discovery (planned) │
├──────────────────────────────────────────────────┤
│ Domain Services │
│ - Device registry + lifecycle │
│ - Interview/provisioner │
│ - Capability mapper │
├──────────────────────────────────────────────────┤
│ Tiny OS │
│ - Fibre scheduler (cooperative multitasking) │
│ - Event bus │
│ - Logging/tracing │
│ - Persistence service │
│ - UART shell │
├──────────────────────────────────────────────────┤
│ Substrate (ESP32 only) │
│ - ESP-IDF / FreeRTOS │
│ - Vendor Zigbee stack │
│ - Wi-Fi stack │
│ - NVS │
└──────────────────────────────────────────────────┘
Specs and project plans live in docs/spec/ (mirrored from Prompts/).
The project follows a consistent hybrid structure:
- Core OS and shared service APIs keep separated headers/sources
- Specialized modules (adapters, drivers, services) co-locate headers with sources in their own subdirectories
├── docs/ # Documentation
├── main/ # Application entry point
│ └── src/
│ └── main.c
├── os/ # Tiny OS components
│ ├── include/ # Public OS API headers
│ │ ├── os.h
│ │ ├── os_config.h
│ │ ├── os_event.h
│ │ ├── os_fibre.h
│ │ ├── os_log.h
│ │ ├── os_persist.h
│ │ └── os_shell.h
│ └── src/ # OS implementation
├── services/ # Domain services
│ ├── include/ # Shared service type headers (reg_types.h, capability.h, etc.)
│ │ ├── registry.h
│ │ ├── interview.h
│ │ └── capability.h
│ ├── src/ # Core service implementations
│ ├── ha_disc/ # Self-contained service module
│ └── local_node/ # Self-contained service module
├── adapters/ # Northbound protocol adapters (each in its own subdirectory)
│ └── mqtt_adapter/
│ ├── mqtt_adapter.h
│ └── mqtt_adapter.c
├── drivers/ # Hardware driver modules (each in its own subdirectory)
│ ├── zigbee/
│ ├── gpio_button/
│ └── i2c_sensor/
├── apps/ # Demo applications
│ └── src/
│ └── app_blink.c
└── tests/ # Test suite
└── unit/
└── test_os.c
Note: Future adapters and drivers should follow the same co-located pattern (create adapters/new_adapter/ or drivers/new_driver/ with headers and sources in the same directory).
The device model follows the Zigbee canonical structure:
Node (ieee_eui64, nwk_short_addr)
├── metadata (manufacturer, model, sw_build)
├── telemetry (lqi, rssi, power_source)
└── Endpoints[]
├── endpoint_id, profile_id, device_id
└── Clusters[]
├── cluster_id, direction (server|client)
└── Attributes[]
└── attribute_id, type, value, last_updated_ts
Capabilities provide stable abstractions mapped to Zigbee clusters:
| Capability | Type | Description |
|---|---|---|
switch.on |
bool | On/Off switch state |
light.on |
bool | Light on/off state |
light.level |
int (0-100) | Light brightness percentage |
light.color_temp |
int (mireds) | Color temperature |
sensor.temperature |
float (°C) | Temperature reading |
sensor.humidity |
float (%) | Humidity percentage |
sensor.contact |
bool | Contact sensor state |
sensor.motion |
bool | Motion detection |
power.watts |
float (W) | Power consumption |
energy.kwh |
float (kWh) | Energy usage |
The interactive shell is accessible via UART (115200 baud) or stdin when running on host.
| Command | Description |
|---|---|
help |
Show available commands |
ps |
Show running tasks/fibres |
uptime |
Show system uptime |
loglevel [level] |
Get/set log level (ERROR, WARN, INFO, DEBUG, TRACE) |
stats |
Show event bus statistics |
| Command | Description |
|---|---|
devices |
List all registered Zigbee devices |
device <addr> |
Show detailed device information |
=== ESP32-C6 Zigbee Bridge Shell ===
Type 'help' for available commands.
> help
Available commands:
help - Show available commands
ps - Show running tasks
uptime - Show system uptime
loglevel - Get/set log level [level]
stats - Show event bus statistics
devices - List all registered devices
device - Show device details <addr>
> ps
ID NAME STATE STACK USED RUNS
---- ------------ ---------- -------- -------- ----------
0 idle READY 512 0 5
1 shell RUNNING 4096 128 10
2 blink SLEEPING 2048 64 50
3 dispatch SLEEPING 2048 32 25
> uptime
Uptime: 00:05:23.456 (323456 ticks)
> stats
Event Bus Statistics:
Published: 156
Dispatched: 156
Dropped: 0
Queue size: 0
High water: 12
The MQTT adapter uses a structured topic scheme:
| Purpose | Topic Pattern | Example |
|---|---|---|
| State | bridge/<node_id>/<capability>/state |
bridge/00112233AABBCCDD/light.on/state |
| Command | bridge/<node_id>/<capability>/set |
bridge/00112233AABBCCDD/light.on/set |
| Metadata | bridge/<node_id>/meta |
bridge/00112233AABBCCDD/meta |
| Status | bridge/status |
bridge/status |
All payloads use JSON with a value and timestamp:
{"v": true, "ts": 123456}Light state update:
// Topic: bridge/00112233AABBCCDD/light.on/state
{"v": true, "ts": 123456}Temperature sensor:
// Topic: bridge/00112233AABBCCDD/sensor.temperature/state
{"v": 22.5, "ts": 123789}Device metadata:
// Topic: bridge/00112233AABBCCDD/meta
{"ieee": "00112233AABBCCDD", "manufacturer": "IKEA", "model": "TRADFRI bulb"}Configuration is done via os/include/os_config.h:
/* Scheduler configuration */
#define OS_MAX_FIBRES 16 /* Maximum concurrent tasks */
#define OS_DEFAULT_STACK_SIZE 2048 /* Default stack per task */
/* Event bus configuration */
#define OS_EVENT_QUEUE_SIZE 256 /* Event queue depth */
#define OS_MAX_SUBSCRIBERS 32 /* Max event subscribers */
/* Logging configuration */
#define OS_LOG_QUEUE_SIZE 64 /* Log buffer size */
#define OS_LOG_DEFAULT_LEVEL OS_LOG_LEVEL_INFO
/* Persistence configuration */
#define OS_PERSIST_FLUSH_MS 5000 /* Auto-flush interval */From services/include/reg_types.h:
#define REG_MAX_NODES 32 /* Max Zigbee devices */
#define REG_MAX_ENDPOINTS 8 /* Endpoints per device */
#define REG_MAX_CLUSTERS 16 /* Clusters per endpoint */
#define REG_MAX_ATTRIBUTES 32 /* Attributes per cluster */- Language: C11 with GCC extensions
- Style: Explicit sizes (
uint32_t, etc.), no hidden globals - Error handling: Return explicit error codes
- Module prefixes:
os_,reg_,cap_,mqtt_, etc.
#include "os.h"
static int cmd_mycommand(int argc, char *argv[]) {
printf("My command executed!\n");
return 0;
}
// Register during init
static const os_shell_cmd_t cmd = {
"mycommand", "Description", cmd_mycommand
};
os_shell_register(&cmd);-
Add the capability ID in
capability.h:typedef enum { // ... CAP_MY_NEW_CAP, CAP_MAX } cap_id_t;
-
Add the info entry in
capability.c:static const cap_info_t cap_info_table[] = { // ... {CAP_MY_NEW_CAP, "my.capability", CAP_VALUE_INT, "unit"}, };
-
Add the cluster mapping if applicable:
static const cluster_cap_map_t cluster_map[] = { // ... {CLUSTER_ID, ATTR_ID, CAP_MY_NEW_CAP}, };
LOG_E("MODULE", "Error: %s", msg); // Errors only
LOG_W("MODULE", "Warning: %s", msg); // Warnings
LOG_I("MODULE", "Info: %s", msg); // Informational (default)
LOG_D("MODULE", "Debug: %s", msg); // Debug details
LOG_T("MODULE", "Trace: %s", msg); // Very verbose- M0: Project structure and documentation
- M1: Tiny OS core (fibres, event bus, logging, console, shell)
- M2: Persistence service (NVS wrapper with buffered writes)
- M3: Device registry with lifecycle state machine
- M5: Interview/provisioner service (simulated)
- M6: Capability mapping for lights (OnOff, Level clusters)
- M7: MQTT adapter (simulated for host testing)
- M8: Home Assistant discovery service
- Light merging (on/off + level → single light entity)
- Sensor discovery (temperature, humidity, contact, motion)
- Device quirks table for non-standard devices
- M9: Expand device classes (sensors, switches, etc.)
- M4: Zigbee adapter integration with ESP-IDF Zigbee stack
- M10: Matter bridge spike (bonus)
- Boots reliably
- Shell responsive
- Fibres switching (≥3 tasks)
- Event bus stable under load
- Logging queue stable
- Persistence stable
- Interview/provisioner works (simulated)
- Registry persists and restores
- MQTT connects and publishes (simulated)
- HA Discovery service works
- Device quirks system works
- Zigbee coordinator up
- Permit join works
- Light control end-to-end
- MQTT commands trigger Zigbee actions
The bridge supports Home Assistant MQTT discovery for automatic device integration.
| Entity Type | Topic Pattern |
|---|---|
| Light | homeassistant/light/<unique_id>/config |
| Switch | homeassistant/switch/<unique_id>/config |
| Sensor | homeassistant/sensor/<unique_id>/config |
| Binary Sensor | homeassistant/binary_sensor/<unique_id>/config |
When a device supports both on/off and brightness, they are merged into a single HA light entity:
light.oncapability → state controllight.levelcapability → brightness control
<bridge_id>_<node_eui64>_<capability_sanitized>
Example: zigbee_bridge_00112233AABBCCDD_light
The bridge includes a quirks system to handle non-standard Zigbee devices.
| Action | Description |
|---|---|
clamp_range |
Clamp values to min/max range |
invert_boolean |
Invert true/false values |
scale_numeric |
Apply multiplier and offset |
Quirks are defined in services/src/quirks.c:
{
.manufacturer = "VENDOR",
.model = "MODEL-123",
.prefix_match = false,
.actions = {
{
.type = QUIRK_ACTION_CLAMP_RANGE,
.target_cap = CAP_LIGHT_LEVEL,
.params.clamp = { .min = 1, .max = 100 }
}
},
.action_count = 1
}This project is licensed under the MIT License - see the LICENSE file for details.
Contributions are welcome! Please follow these guidelines:
- Fork the repository
- Create a feature branch
- Ensure all tests pass (
make test) - Follow the existing code style
- Submit a pull request
For issues and feature requests, please use the GitHub Issues tracker.
Note: This project is under active development. The Zigbee integration requires ESP32-C6 hardware and the ESP-IDF Zigbee stack for full functionality. Host builds provide simulation for development and testing