Files
rpi-zigbee-stack/README.md
T

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.

  1. Clone the firmware repo:

    git clone https://github.com/ThirdReality/ThirdReality-Zigbee-3.0-USB-dongle
    
  2. 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
  3. 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
    
  4. 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:

  1. Unplug
  2. Press-and-hold bootloader button, plug in, hold 2s, release
  3. The ROM bootloader is always accessible via the button
  4. 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, 0x07 escaped with 0x07 prefix + 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