7.6 KiB
RPi Zigbee Stack
Docker Compose stack running on a Raspberry Pi 3B+ (DietPi 64-bit) with a ThirdReality Zigbee 3.0 USB dongle.
Hardware
| Component | Detail |
|---|---|
| Board | Raspberry Pi 3B+ |
| OS | DietPi 64-bit |
| Zigbee Dongle | ThirdReality Zigbee 3.0 USB (USB-C, use with USB-A adapter) |
| Dongle Chip | BL702 (Bouffalo Lab) — not BL706 as the vendor README states |
| USB Bridge | CH340 (QinHeng, VID 1a86, PID 7523) |
| Flash | 2MB SPI (jedec c84015) |
| Dongle Port | Powered USB hub required — the CH340 drops off unpowered hubs |
Use lsusb -t on the RPi to verify the dongle is on a powered hub, not daisy-chained behind multiple unpowered hubs.
Services
| Service | Image | Port | Purpose |
|---|---|---|---|
| Mosquitto | eclipse-mosquitto:latest |
1883 | MQTT message broker |
| Zigbee2MQTT | ghcr.io/pine64/zigbee2mqtt:latest-dev |
8080 | Zigbee coordinator (BLZ fork) |
| Node-RED | nodered/node-red:latest |
1880 | Flow-based automation + dashboard |
All containers communicate via Docker's default bridge network using service names (e.g., mosquitto:1883).
Zigbee Dongle Setup
Flashing the Firmware
The dongle ships with factory firmware that must be replaced with the BLZ coordinator firmware. This requires an x86 machine — the Bouffalo Lab DevCube tool does not run on ARM.
-
Clone the firmware repo:
git clone https://github.com/ThirdReality/ThirdReality-Zigbee-3.0-USB-dongle -
Enter bootloader mode on the dongle:
- Press and hold the pin hole button on the dongle body
- While holding, plug the dongle into your x86 machine's USB port
- Hold for 2 more seconds, then release
-
Flash the BL702 firmware (v1.00.12):
ThirdReality-Zigbee-3.0-USB-dongle/Flashing-Tool/BouffaloLabDevCube-v1.9.0/bflb_iot_tool-ubuntu \ --chipname BL702 \ --interface uart \ --port /dev/ttyUSB0 \ --baudrate 2000000 \ --firmware Image/Coordinator/blz/v1.00.12/R3_706_dongle_v1.00.12/R3_bl702_dongle.bin \ --pt Image/Coordinator/blz/v1.00.12/R3_706_dongle_v1.00.12/partition_cfg_1M.toml -
Power cycle: unplug and replug the dongle. The red LED should illuminate, confirming the firmware is running.
Firmware Version Compatibility
Do not use these firmware versions:
- v1.00.01 (
R3_706_dongle.bin) — built for BL706, wrong chip → no boot - v1.00.07 (
whole_flash_data.bin) — flashes but firmware doesn't boot on BL702
| Firmware | Chip | Partition | Works? |
|---|---|---|---|
v1.00.12 R3_bl702_dongle.bin |
BL702 | partition_cfg_1M.toml |
✅ |
v1.00.12 + v1.00.01 partition_cfg_2M.toml |
BL702 | (mismatched) | ❌ |
v1.00.01 R3_706_dongle.bin |
BL706 | partition_cfg_2M.toml |
❌ (wrong chip) |
v1.00.07 whole_flash_data.bin |
(any) | (none) | ❌ (no boot) |
Recovery from Bad Flash
If the dongle gets into a bad state:
- Unplug
- Press-and-hold bootloader button, plug in, hold 2s, release
- The ROM bootloader is always accessible via the button
- Re-flash with the correct firmware
Verifying the Dongle Works
On the RPi, stop zigbee2mqtt and test with pyserial:
import serial, time, struct
def calc_crc16(data):
crc16 = 0xFFFF
for byte in data:
crc16 = ((crc16 >> 8) | (crc16 << 8)) & 0xFFFF
crc16 ^= byte
crc16 ^= (crc16 & 0xFF) >> 4
crc16 ^= ((crc16 << 8) << 4) & 0xFFFF
crc16 ^= (((crc16 & 0xFF) << 5) | ((crc16 & 0xFF) >> 3) << 8) & 0xFFFF
return bytes([(crc16 >> 8) & 0xFF, crc16 & 0xFF])
def escape(data):
o = bytearray()
for b in data:
if b in (0x42,0x4C,0x07): o.extend([0x07,b^0x10])
else: o.append(b)
return bytes(o)
def build(fc, seq, fid, payload=b''):
frm = bytes([fc,seq]) + fid.to_bytes(2,'little') + payload
return b'\x42' + escape(frm + calc_crc16(frm)) + b'\x4C'
ser = serial.Serial('/dev/ttyUSB0', 2000000, timeout=2)
time.sleep(0.3)
ser.write(build(0, 0, 0x0003)) # BLZ RESET
time.sleep(0.5)
resp = ser.read(200)
# Expect: RESET_ACK (frameId 0x0004) with reset_reason byte
print('Dongle OK' if resp else 'Dongle not responding')
ser.close()
A working dongle responds to the BLZ RESET command (frame ID 0x0003) with a RESET_ACK (frame ID 0x0004).
Configuration Files
Mosquitto (mosquitto.conf)
Bare-bones config for local MQTT. No auth, no TLS — only suitable for a trusted LAN.
listener 1883
allow_anonymous true
persistence true
persistence_location /mosquitto/data
log_dest file /mosquitto/log/mosquitto.log
Zigbee2MQTT (zigbee2mqtt.yaml)
Critical settings for the BLZ dongle:
serial:
port: /dev/ttyUSB0
adapter: blz # must be "blz", NOT "zstack" or "ember"
baudrate: 2000000 # must be exactly 2M (not 115200!)
rtscts: false # CH340 doesn't support hardware flow control
The BLZ protocol requires exactly 2,000,000 baud. Any other value causes frameId:21 after 1000ms timeout errors — the dongle and host talk at different speeds.
Node-RED (nodered-package.json)
Additional nodes installed:
node-red-dashboard— provides the UI dashboard at/ui
BLZ Protocol Summary
The Bouffalo Lab Zigbee (BLZ/BZSP) protocol v2.0 is used for host↔dongle communication:
- Baudrate: 2,000,000
- Frame format:
START(0x42) | data | CRC16-CCITT | STOP(0x4C) - Byte stuffing: Bytes
0x42,0x4C,0x07escaped with0x07prefix +XOR 0x10 - Key frame IDs: RESET=
0x0003, RESET_ACK=0x0004, ADD_ENDPOINT=0x0015, ACK=0x0001
The zigpy-blz Python library (from bouffalolab/zigpy-blz) can be used for direct protocol interaction.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Dongle USB disconnects/reconnects repeatedly | Unpowered USB hub or weak port | Use a powered USB hub |
| Red LED not lit | Wrong firmware or bad flash | Re-flash with correct firmware |
zh:blz:uart: frameId:21 after 1000ms |
Baudrate mismatch | Set baudrate: 2000000 in zigbee2mqtt config |
Chip type is not correct (flashing) |
Wrong --chipname |
Use BL702 for this dongle |
| Bootloader won't respond | Firmware corruption | Use bootloader button + reflash |
BFLB LOAD HELP BIN FAIL (flashing) |
Flash in bad state | Power cycle, re-enter bootloader mode |
| YAML validation error on restart | Malformed config | Use inline arrays [1,2,3] not multi-line in advanced section |
| Node-RED "circular config dependency" | Broker node conflict | Deploy as full flow with Node-RED-Deployment-Type: full |
Daily Management
# Clone this repo
git clone https://git.notsosm.art/david/rpi-zigbee-stack.git
cd rpi-zigbee-stack
# Stack control
just up # start all services
just restart # restart all
just logs # view all logs
just zb-status # zigbee2mqtt status
# Zigbee device pairing
just zb-permit # allow joining for 60s
just zb-deny # disable joining
# Config management
just backup # pull current configs from RPi
just sync # push local configs to RPi
just redeploy # sync + restart
# Web interfaces
just nr-editor # Node-RED editor (1880)
just nr-dashboard # Node-RED dashboard (1880/ui)
Zigbee2MQTT frontend: http://192.168.9.147:8080
Related Repos
- nr-flow-validator — Declarative Node-RED flow generation + conftest/Rego validation
- ThirdReality Zigbee Dongle — Firmware images and flashing tools
- zigpy-blz — Python BLZ protocol library
- Pine64 Zigbee2MQTT — BLZ fork of Zigbee2MQTT