Add comprehensive README: hardware, flashing, configs, BLZ protocol, troubleshooting
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
# 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:
|
||||
|
||||
```python
|
||||
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:
|
||||
|
||||
```yaml
|
||||
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
|
||||
|
||||
```bash
|
||||
# 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](https://git.notsosm.art/david/nr-flow-validator) — Declarative Node-RED flow generation + conftest/Rego validation
|
||||
- [ThirdReality Zigbee Dongle](https://github.com/ThirdReality/ThirdReality-Zigbee-3.0-USB-dongle) — Firmware images and flashing tools
|
||||
- [zigpy-blz](https://github.com/bouffalolab/zigpy-blz) — Python BLZ protocol library
|
||||
- [Pine64 Zigbee2MQTT](https://github.com/pine64/zigbee2mqtt) — BLZ fork of Zigbee2MQTT
|
||||
Reference in New Issue
Block a user