4. Zigbee绑定表管理
任务目标
在Zigbee网络中,绑定(Binding)允许源设备无需通过协调器转发即可直接向目标设备发送控制命令。本案例通过协调器发送ZCL命令,完成以下闭环:建立绑定表项(将开关绑定到灯)、验证绑定生效、删除绑定表项、验证删除结果。
环境与依赖
- 硬件:任意支持Zigbee 3.0的协调器(如CC2530/CC2652 + Z-Stack 3.x),两个终端设备(一个开关/按键设备作为源,一个灯/插座设备作为目标)
- 软件:Z-Stack 3.0.2或更高版本,使用ZNP(Zigbee Network Processor)接口通过UART与主机通信
- 主机端:Python 3.6+,安装
pyserial库(pip install pyserial)
实现原理
绑定表存储在协调器(或支持集中绑定表的设备)中。协调器通过ZDO(Zigbee Device Object)的Bind_Req和Unbind_Req命令管理绑定表。本案例使用Z-Stack的ZNP串口协议,直接构造AF(Application Framework)数据请求来发送ZDO命令。
关键完整代码
以下Python脚本通过串口与协调器通信,完成绑定建立、验证、删除的完整流程。代码中已包含ZNP帧封装、校验和计算及ZDO命令构造。
import serial
import struct
import time
# ---------- ZNP 帧封装 ----------
def znp_frame(cmd0, cmd1, data):
"""构造ZNP串口帧: SOF(0xFE) + Len + Cmd0 + Cmd1 + Data + FCS"""
length = len(data) + 2 # Cmd0 + Cmd1
fcs = 0
for b in [length, cmd0, cmd1] + list(data):
fcs ^= b
frame = bytes([0xFE, length, cmd0, cmd1]) + bytes(data) + bytes([fcs])
return frame
def send_znp(ser, cmd0, cmd1, data):
ser.write(znp_frame(cmd0, cmd1, data))
time.sleep(0.2)
# 读取并丢弃响应(简化处理,实际应解析状态)
while ser.in_waiting:
ser.read(ser.in_waiting)
time.sleep(0.1)
# ---------- 地址解析 ----------
def ieee_to_bytes(ieee_str):
"""将 '01:02:03:04:05:06:07:08' 转为小端字节序"""
parts = ieee_str.split(':')
return bytes(int(x, 16) for x in reversed(parts))
def short_to_bytes(short_addr):
return struct.pack('<H', short_addr)
# ---------- ZDO 绑定命令 ----------
def zdo_bind_req(ser, src_ieee, src_ep, dst_ieee, dst_ep, cluster):
"""
发送 ZDO Bind_Req (0x0021)
参数: 源设备IEEE地址、源端点、目标IEEE地址、目标端点、集群ID
"""
cluster_bytes = struct.pack('<H', cluster)
dst_addr_mode = 0x03 # 64位IEEE地址
data = (ieee_to_bytes(src_ieee) + bytes([src_ep]) +
cluster_bytes + bytes([dst_addr_mode]) +
ieee_to_bytes(dst_ieee) + bytes([dst_ep]))
# ZDO 命令通过 AF 数据请求发送,使用 SREQ: AF_DATA_REQUEST (0x24, 0x01)
# DstAddr=协调器短地址0x0000, DstEndpoint=0x00 (ZDO), ClusterId=0x0021
af_data = (short_to_bytes(0x0000) + bytes([0x00, 0x00]) +
struct.pack('<H', 0x0021) + bytes([0x00]) + # ClusterId, TxnId
bytes([len(data)]) + data)
send_znp(ser, 0x24, 0x01, af_data)
def zdo_unbind_req(ser, src_ieee, src_ep, dst_ieee, dst_ep, cluster):
"""发送 ZDO Unbind_Req (0x0022),参数格式同 Bind_Req"""
cluster_bytes = struct.pack('<H', cluster)
dst_addr_mode = 0x03
data = (ieee_to_bytes(src_ieee) + bytes([src_ep]) +
cluster_bytes + bytes([dst_addr_mode]) +
ieee_to_bytes(dst_ieee) + bytes([dst_ep]))
af_data = (short_to_bytes(0x0000) + bytes([0x00, 0x00]) +
struct.pack('<H', 0x0022) + bytes([0x00]) +
bytes([len(data)]) + data)
send_znp(ser, 0x24, 0x01, af_data)
# ---------- 主流程 ----------
if __name__ == '__main__':
PORT = 'COM5' # 根据实际串口修改
BAUD = 115200
ser = serial.Serial(PORT, BAUD, timeout=1)
# 设备参数(需替换为实际设备IEEE地址)
SWITCH_IEEE = '00:12:4B:00:12:34:56:78' # 开关(源)
SWITCH_EP = 1
LIGHT_IEEE = '00:12:4B:00:12:34:56:79' # 灯(目标)
LIGHT_EP = 1
ONOFF_CLUSTER = 0x0006 # On/Off 集群
# 1. 建立绑定:开关 → 灯
print("建立绑定...")
zdo_bind_req(ser, SWITCH_IEEE, SWITCH_EP, LIGHT_IEEE, LIGHT_EP, ONOFF_CLUSTER)
time.sleep(1)
# 2. 验证绑定(通过按键触发或发送On/Off命令,观察灯是否直接响应)
# 此处通过协调器向开关发送"识别"命令,确认绑定表已写入
# 实际验证:按下开关物理按键,灯应亮/灭(无需协调器转发)
print("请按下开关按键,观察灯是否直接动作(绑定生效)")
input("确认后按回车继续...")
# 3. 删除绑定
print("删除绑定...")
zdo_unbind_req(ser, SWITCH_IEEE, SWITCH_EP, LIGHT_IEEE, LIGHT_EP, ONOFF_CLUSTER)
time.sleep(1)
# 4. 验证删除:再次按键,灯不应响应
print("再次按下开关按键,灯应不再动作(绑定已删除)")
ser.close()
运行与验证
- 将协调器通过USB连接电脑,确认串口号(如COM5)
- 确保两个终端设备已入网,记录其IEEE地址(可通过协调器主动扫描或从设备标签获取)
- 运行脚本:
python zigbee_binding.py - 验证步骤:
- 绑定建立:脚本执行后,按下开关物理按键,灯应立即亮/灭,且协调器串口无转发数据(可在协调器端抓包确认无APS数据转发)
- 绑定删除:脚本执行删除后,再次按键,灯不响应
通过条件:绑定建立后按键灯动作、删除后按键灯不动作,且协调器日志中无AF中继转发记录,即证明绑定表管理成功。
常见问题
- 绑定失败:确认源/目标IEEE地址正确、设备在线;部分设备需先使能
PERMIT_JOIN并等待入网稳定 - 按键无反应:检查开关端点号(通常为1),确认集群ID匹配(On/Off为0x0006)
- 串口无响应:核对波特率(ZNP默认115200),检查协调器固件是否为ZNP模式