本文首先介绍Zigbee的若干基本概念,然后针对TI Zigbee解决方案中的Zigbee网络处理器(ZNP)应用形式,介绍如何创建一个Zigbee网络,并与已有的节点进行通信。
基本概念
Zigbee
Zigbee是一种短距离、低速率、低功耗的无线传感器网络技术,包含了从底层网络到上层应用的完整定义,标准由Zigbee联盟制定,最新规格为Zigbee 3.0。
多个IC厂商提供包含Zigbee技术的产品,本文只讨论TI的解决方案。
信道
Zigbee可以在Sub-1GHz、2.4GHz等多个频率范围工作,一般选择在2.4GHz频率范围,这个范围中又划分出16个具体的频点(信道),对应编号从11(0x0B)到26(0x1A)。当同一个或相邻近的物理空间中有多个Zigbee网络时,需要错开信道。
设备类型
Zigbee网络中的设备分为三种类型:协调器(Coordinator)、路由(Router)和终端节点(End Device)。其中,协调器负责建立网络,并为新加入的节点分配地址(当网络建立、节点入网后,即使去掉协调器,网络也能正常工作);路由负责将收到数据转发给其他节点,以拓展网络的覆盖范围;终端节点只能接收和发送自身的数据,不能转发。
需要说明的是:(1)路由本身也具有终端节点的功能,可以收发自身的数据;(2)终端节点可以不依赖路由,直接通过协调器入网。所以,一个完整的Zigbee网络,可以没有终端节点,只有协调器和路由;也可以没有路由,只有协调器和终端节点。
网络地址
Zigbee设备在出厂时都有一个唯一的8字节MAC地址,但在加入网络时,会被协调器分配一个两字节的网络地址,后续以这个网络地址通信。协调器的网络地址是0x0000。0xFFFF是广播地址,如果将通信的目的地址设为0xFFFF,那么所有节点都会收到。
PANID
PANID是Zigbee网络的标识,即使同一信道里有多个Zigbee网络,只要它们的PANID不同,也可以共存并独立工作。
Z-Stack
Z-Stack是TI的Zigbee协议栈,包含文档、源代码和预编译的库,可以从TI网站上下载。Z-Stack使用IAR编译和调试,生成的hex文件通过烧写器烧写进Zigbee芯片当中。
ZNP
ZNP,即Zigbee网络处理器,是TI的Zigbee解决方案的一种应用形式。在这种形式下,Zigbee芯片只负责处理底层网络的功能,而将上层应用交给主机处理,ZNP与主机之间通过USB或UART连接。Zigbee芯片实现ZNP功能所需的固件及其源码,以及接口API的说明都包含在Z-Stack中。所谓接口API,指主机通过USB或UART与ZNP通信的帧格式,以及命令和参数的定义。在主机侧,TI提供了对接口API的包装,即znp-host-framework,C语言实现,开源,代码托管在TI的Git网站上,另外在GitHub上还有适用于Node.js的版本。
安全
Zigbee网络支持安全加密机制,通信数据采集AES加密。密钥的分发有两种方法:一种是预先配置在所有需要加入网络的节点上;另一种是只配置在协调器上,节点入网时分发到节点。
配置ZNP
TI有多款芯片可以用作ZNP,我们选择CC2538,通过其自带的USB接口连接主机,硬件dongle可以从淘宝采购。对应的工程文件在Z-Stack的Projects\zstack\ZNP\CC2538
目录当中。
Z-Stack中默认开启了安全加密,可以通过修改代码禁用:
Projects/zstack/Tools/CC2538DB/f8wConfig.cfg
21(修改宏定义): DSECURE=0
Components/stack/bdb/bdb_touchlink.h
50(注释掉此行): //#error SECURE must be globally defined to TRUE.
如果按照TI提供的参考设计,配置了CC2592作为PA,还需要使能它:
Components/hal/target/CC2538ZNP/hal_board_cfg.h
84(增加一行): #define HAL_PA_LNA_CC2592
使用ZNP创建网络
将CC2538 USB dongle连接到主机,就可以从主机上发命令控制它了。CC2538的USB接口是一个CDC设备,在Windows上无需安装驱动,会被识别成一个串口;在Linux系统上,需要确保内核包含USB CDC设备驱动,会被识别成/dev/ttyACMx(x是序号,从0开始)。
Z-Tool
Z-Tool是Z-Stack附带的一个测试工具,可以用它给ZNP发送指令。在安装Z-Stack以后,在安装目录下找到Tools\Z-Tool\Z-Tool.exe
,双击打开,它会自动搜索并连接ZNP。
如上图所示,界面左上角是Z-Tool支持的各种命令,分为System、AF等多个类别。下面介绍使用这些命令控制ZNP创建网络的步骤。
- 配置启动选项,在下次复位前清除网络状态和所有配置
命令:SimpleAPI/ZB_WRITE_CONFIGURATION
参数:ConfigId: 3(ZCD_NV_STARTUP_OPTION),Len: 1,Value: [3] - 复位
命令:SimpleAPI/ZB_SYSTEM_RESET
- 配置设备类型
命令:SimpleAPI/ZB_WRITE_CONFIGURATION
参数:ConfigId: 135(ZCD_NV_LOGICAL_TYPE),Len: 1,Value: [0](协调器) - 设置PANID
命令:Util/UTIL_SET_PANID
参数:PanId: 6 - 设置信道(复位后生效)
命令:Util/UTIL_SET_CHANNELS
参数:Channels: 0x00020000(即17信道,计算方法:1<<17 = 0x00020000) - 设置信道(立即生效)
命令:AppConfig/APP_CNF_BDB_SET_CHANNEL
参数:isPrimary: TRUE, Channels: 0x00020000(计算方法同上一条命令) - 注册应用程序(对于创建网络来说,这一步不是必需的,但后面收发数据需要)
命令:AF/AF_REGISTER
参数:
EndPoint: 11
AppProfID: 3845
AppDeviceId: 256
AppDevVer: 0
LatencyReq: 0
AppNumInClusters: 1
AppInClusterList: [1]
AppNumOutClusters: 1
AppOutClusterList: [1] - 启动网络
命令:ZDO/ZDO_STARTUP_FROM_APP
- 配置启动选项,在下次复位后保持网络状态和配置
命令:ZB_WRITE_CONFIGURATION
参数:ConfigId: 3(ZCD_NV_STARTUP_OPTION),Len: 1, Value: 0
Node.js
以上步骤可以通过Node.js脚本实现:
const znp = require('cc-znp')
znp.request('SAPI', 'writeConfiguration', {
configid: 3,
len: 1,
value: [0x03]
})
znp.request('SAPI', 'systemReset', {})
znp.request('SAPI', 'writeConfiguration', {
configid: 0x87,
len: 1,
value: [0] //coordinator
})
znp.request('UTIL', 'setPanid', {
panid: 6
})
znp.request('UTIL', 'setChannels', {
channels: 0x00020000
})
znp.sendCmd(1, 'RPC_SYS_DEBUG', 0x08, new Buffer([1, 0, 0, 2, 0]))
znp.request('AF', 'register', {
endpoint: 0x0B,
appprofid: 0xF05,
appdeviceid: 0x100,
appdevver: 0,
latencyreq: 0,
appnuminclusters: 1,
appinclusterlist: [1],
appnumoutclusters: 1,
appoutclusterlist: [1]
})
znp.request('ZDO', 'startupFromApp', {
startdelay: 0
}
znp.request('SAPI', 'writeConfiguration', {
configid: 3,
len: 1,
value: [0]
})
发送数据(待发送的数据放在buffer变量中):
znp.request('AF', 'dataRequest', {
dstaddr: 0xFFFF,
destendpoint: 0x0B,
srcendpoint: 0x0B,
clusterid: 0x0001,
transid: 1,
options: 0,
radius: 0x7,
len: buffer.length,
data: buffer
})
接收数据:
znp.on('AREQ', function (msg) {
if(msg.ind === 'incomingMsg') {
var srcaddr = msg.data.srcaddr;
var data = msg.data.data;
}
});
C语言
在Linux上,可以使用TI提供的znp-host-framework来操作ZNP。其中提供了一个cmdLine例程,可以在命令行中交互式地设置创建网络的参数,主要步骤与上文大体一致,缺少了AppConfig/APP_CNF_BDB_SET_CHANNEL
这条命令(设置信道并立即生效),可以自行添加:
/*
* mtAppCnf.c
*
* This module contains the API for the MT App Config Interface
*
*/
#include
#include
#include "mtAppCnf.h"
#include "mtParser.h"
#include "rpc.h"
#include "dbgPrint.h"
extern uint8_t srspRpcBuff[RPC_MAX_LEN];
uint8_t appCnfBdbSetChannel(mtBdbSetChannelFormat_t *req)
{
uint8_t status;
uint8_t cmInd = 0;
uint32_t cmdLen = 5;
uint8_t *cmd = malloc(cmdLen);
if (cmd)
{
cmd[cmInd++] = req->isPrimary;
memcpy((cmd + cmInd), req->channelMask, 4);
cmInd += 4;
status = rpcSendFrame((MT_RPC_CMD_SREQ | MT_RPC_SYS_APP_CNF),
MT_APP_CNF_BDB_SET_CHANNEL, cmd, cmdLen);
if (status == MT_RPC_SUCCESS)
{
rpcWaitMqClientMsg(50);
status = srspRpcBuff[2];
}
free(cmd);
return status;
}
else
{
dbg_print(PRINT_LEVEL_WARNING, "Memory for cmd was not allocated\n");
return 1;
}
}
TODO
- 新节点入网:需要在Zigbee协调器上控制是否允许新节点入网。