07_Z-Stack多节点组网及终端消息上报
多节点组网
实现两个终端与协调器组网。
- 在
zcl_samplesw.c
文件中,定义全局变量用于保存连接的设备地址
c
#ifdef ZCL_REPORT
#define ZCLAPP_ENDDEVICE_NUM 2 // 连接设备数量
static uint16 zclSampleSw_EndDeviceAddrs[ZCLAPP_ENDDEVICE_NUM]; // 全局变量保存连接到的设备地址
static uint8 zclSampleSw_ProcessInReportCmd( zclIncomingMsg_t *pInMsg );
#endif // ZCL_REPORT
- 在
zclSampleSw_Init
初始化函数中,协调器创建网络,终端加入网络
c
#ifdef ZDO_COORDINATOR
// Init Uart
zclSampleSw_InitUart();
ZDO_RegisterForZDOMsg ( zclSampleSw_TaskID, Device_annce ); // 注册 Device_annce 簇ID
bdb_StartCommissioning( BDB_COMMISSIONING_MODE_NWK_FORMATION |
BDB_COMMISSIONING_MODE_FINDING_BINDING ); // 协调器建立网络
NLME_PermitJoiningRequest(255);
#else
bdb_StartCommissioning( BDB_COMMISSIONING_MODE_NWK_STEERING |
BDB_COMMISSIONING_MODE_FINDING_BINDING ); // 入网
/* //该部分代码写至zclSampleSw_ProcessCommissioningStatus中
// 当终端设备入网成功后, 开始上报
osal_start_timerEx(zclSampleSw_TaskID,
SAMPLEAPP_REPORT_EVT,
SAMPLEAPP_REPORT_PERIOD); // 初始触发上报数据事件
zclSampleSw_DeviceAnnce(); // 成功加入网络后 广播自己的地址
*/
#endif
- 在回调函数
zclSampleSw_ProcessCommissioningStatus
中,若终端成功加入网络则广播自己的地址,否则触发重连事件
c
case BDB_COMMISSIONING_NWK_STEERING:
if(bdbCommissioningModeMsg->bdbCommissioningStatus == BDB_COMMISSIONING_SUCCESS)
{
// 成功加入网络
//We are on the nwk, what now?
#ifdef ZDO_COORDINATOR
#else
osal_start_timerEx(zclSampleSw_TaskID,
SAMPLEAPP_REPORT_EVT,
SAMPLEAPP_REPORT_PERIOD); // 初始触发上报数据事件
zclSampleSw_DeviceAnnce(); // 加入网络后 广播自己的地址
#endif
}
else
{
#ifdef ZDO_COORDINATOR
#else
// 触发重新连接事件
osal_start_timerEx(zclSampleSw_TaskID,
SAMPLEAPP_REJOIN_EVT,
SAMPLEAPP_REJOIN_PERIOD);
#endif
}
break;
c
/**
* @brief 终端广播自己的网络地址
*/
static void zclSampleSw_DeviceAnnce( void )
{
ZDP_DeviceAnnce(
NLME_GetShortAddr(),//获取本设备的网络地址(短地址)
NLME_GetExtAddr(),//获取本设备的物理地址(通常就是MAC地址)
ZDO_Config_Node_Descriptor.CapabilityFlags,
0
);
}
- 在
zclSampleSw_event_loop
事件处理函数中,终端处理重连事件
c
#ifdef ZDO_COORDINATOR
#else
// Rejoin 重新加入网络
if ( events & SAMPLEAPP_REJOIN_EVT )
{
bdb_StartCommissioning(BDB_COMMISSIONING_MODE_NWK_STEERING |
BDB_COMMISSIONING_MODE_FINDING_BINDING );
return ( events ^ SAMPLEAPP_REJOIN_EVT );
}
#endif
- 在
zclSampleSw_event_loop
事件处理函数中,协调器处理ZDO_CB_MSG
事件,将收到的终端广播的地址保存
c
#ifdef ZDO_COORDINATOR
case ZDO_CB_MSG:
zclSampleSw_processZDOMgs( (zdoIncomingMsg_t *)MSGpkt ); // ZDO消息处理函数
break;
#endif
c
/**
* @brief ZDO消息处理函数
*/
static void zclSampleSw_processZDOMgs(zdoIncomingMsg_t *pMsg)
{
switch ( pMsg->clusterID )//判断消息中的Cluster ID
{
case Device_annce: // 收到终端的宣告广播
{
static uint8 idx = 0;
// 把终端的网络地址保存到全局变量中
if(idx < ZCLAPP_ENDDEVICE_NUM)
{
zclSampleSw_EndDeviceAddrs[idx] = pMsg->srcAddr.addr.shortAddr;
// 打印信息
HalLcdWriteStringValue("EndAddr:", zclSampleSw_EndDeviceAddrs[idx], 16, idx%2+3);
idx++;
}
else if(idx > ZCLAPP_ENDDEVICE_NUM)
{
}
}
break;
default:
break;
}
}
ZCL层消息上报
终端定时上报数据给协调器,协调器收到数据后在屏幕上显示
- 在回调函数
zclSampleSw_ProcessCommissioningStatus
中,终端在成功入网后触发SAMPLEAPP_REPORT_EVT
上报事件
c
case BDB_COMMISSIONING_NWK_STEERING:
if(bdbCommissioningModeMsg->bdbCommissioningStatus == BDB_COMMISSIONING_SUCCESS)
{
// 成功加入网络
//We are on the nwk, what now?
#ifdef ZDO_COORDINATOR
#else
osal_start_timerEx(zclSampleSw_TaskID,
SAMPLEAPP_REPORT_EVT,
SAMPLEAPP_REPORT_PERIOD); // 初始触发上报数据事件
zclSampleSw_DeviceAnnce(); // 加入网络后 广播自己的地址
#endif
}
else
{
...
}
break;
- 在
zclSampleSw_event_loop
事件处理函数中,处理SAMPLEAPP_REPORT_EVT
事件
c
#ifdef ZDO_COORDINATOR
#else
// Report 上报数据
if ( events & SAMPLEAPP_REPORT_EVT )
{
zclSampleSw_ReportTest(); // 上报数据处理函数
osal_start_timerEx(zclSampleSw_TaskID,
SAMPLEAPP_REPORT_EVT,
SAMPLEAPP_REPORT_PERIOD); // 重复触发上报事件
return ( events ^ SAMPLEAPP_REPORT_EVT );
}
#endif
c
/**
* @brief 终端上报数据给协调器
*/
static void zclSampleSw_ReportTest(void)
{
static uint8 seqNum = 0;
zclReportCmd_t *reportCmd;
afAddrType_t destAddr; // 目标地址
destAddr.addrMode = afAddr16Bit; // 模式为短地址
destAddr.endPoint = SAMPLESW_ENDPOINT; // 端点号
destAddr.addr.shortAddr = 0x0000; // 0x0000表示协调器
reportCmd = (zclReportCmd_t *)osal_mem_alloc(sizeof(zclReportCmd_t) +
sizeof(zclReport_t)); // 分配空间
if(reportCmd == NULL)
return;
reportCmd->attrList[0].attrData = (uint8 *)osal_mem_alloc(sizeof(uint8)); // 为上报的数据分配空间
if(reportCmd->attrList[0].attrData == NULL)
return;
reportCmd->numAttr = 1; // 上报属性个数
reportCmd->attrList[0].attrID = ATTRID_ON_OFF_SWITCH_TYPE;
reportCmd->attrList[0].dataType = ZCL_DATATYPE_ENUM8;
*((uint8 *)(reportCmd->attrList[0].attrData)) = seqNum; // 上报的数据=seqNum
// 调用上报函数
zcl_SendReportCmd( SAMPLESW_ENDPOINT, // 源端点号
&destAddr, // 目标设备地址信息
ZCL_CLUSTER_ID_GEN_ON_OFF_SWITCH_CONFIG, // 属性所属Cluster ID
reportCmd, // 上报的属性值
ZCL_FRAME_CLIENT_SERVER_DIR, // 通信方向
TRUE, // 关闭默认相应
seqNum++ ); // 每上报一次 seqNum+1
HalLcdWriteStringValue("Report: ", (seqNum-1), 10, 4);
osal_mem_free(reportCmd->attrList[0].attrData); // 释放内存
osal_mem_free(reportCmd);
}
c
extern ZStatus_t zcl_SendReportCmd(
uint8 srcEP, //源端点号
afAddrType_t *dstAddr, //目标设备地址信息
uint16 realClusterID, //属性所属Cluster ID
zclReportCmd_t *reportCmd,//描述待上报的属性值
uint8 direction, //通信方向
uint8 disableDefaultRsp, //是否关闭默认响应(目标设备的响应)
uint8 seqNum); //数据包标号,由开发者自定义
- 协调器收到上报信息后会触发
ZCL_INCOMING_MSG
事件,在zclSampleSw_event_loop
事件处理函数中调用zclSampleSw_ProcessIncomingMsg
处理该事件,在zclSampleSw_ProcessIncomingMsg
中判断命令ID是否为ZCL_CMD_REPORT
,然后调用zclSampleSw_ProcessInReportCmd
处理终端上报的信息
c
// zclSampleSw_event_loop 调用 zclSampleSw_ProcessIncomingMsg
case ZCL_INCOMING_MSG:
{
zclSampleSw_ProcessIncomingMsg( (zclIncomingMsg_t *)MSGpkt );
}
break;
c
// zclSampleSw_ProcessIncomingMsg 调用 zclSampleSw_ProcessInReportCmd
case ZCL_CMD_REPORT:
zclSampleSw_ProcessInReportCmd( pInMsg );
break;
c
static uint8 zclSampleSw_ProcessInReportCmd( zclIncomingMsg_t *pInMsg )
{
zclReportCmd_t *reportCmd;
uint8 i;
reportCmd = (zclReportCmd_t *)pInMsg->attrCmd;
for ( i = 0; i < reportCmd->numAttr; i++ ) // 解包
{
if( pInMsg->clusterId == ZCL_CLUSTER_ID_GEN_ON_OFF_SWITCH_CONFIG &&
reportCmd->attrList[i].attrID == ATTRID_ON_OFF_SWITCH_TYPE)
{
int8 attrDat = *(reportCmd->attrList[i].attrData); // 接收消息
for(uint8 j=0; j < ZCLAPP_ENDDEVICE_NUM; j++)
{
// 根据 短地址分行打印上报的消息
if(pInMsg->srcAddr.addr.shortAddr == zclSampleSw_EndDeviceAddrs[j])
HalLcdWriteStringValue("Rx Value:", attrDat, 10, 3+j%2);
}
}
}
return ( TRUE );
}
- 预编译选项中开启
ZCL_REPORT_DESTINATION_DEVICE
和ZCL_REPORT
终端断电无法重连问题解决
- 在
Tools
文件夹下f8wConfig.cfg
文件中配置PAN ID
为固定数值
D
// -DZDAPP_CONFIG_PAN_ID=0xFFFF
-DZDAPP_CONFIG_PAN_ID=0x0001
-
预编译选项中开启
NV_INIT=1
和NV_RESTORE=1
, 开启后Zigbee 会将一些网络配置参数和网络状态存储到非易失活存储器中, 以便重连时能迅速恢复网络状态 -
在
ZDO
文件夹下ZDApp.c
文件中的ZDApp_ProcessOSALMsg
函数里加入以下内容(加到最后即可)
c
case ZDO_NWK_JOIN_REQ: //重连事件
if ( ZG_BUILD_JOINING_TYPE && ZG_DEVICE_JOINING_TYPE )
{
retryCnt = 0;
devStartMode = MODE_RESUME; //让设备处于网络恢复模式
_tmpRejoinState = true; //初始化临时状态为重连
osal_cpyExtAddr( ZDO_UseExtendedPANID, _NIB.extendedPANID ); //初始化ZDO为之前的PANID
zgDefaultStartingScanDuration = BEACON_ORDER_60_MSEC; //每60毫秒发送一个信标
ZDApp_NetworkInit( 0 ); //重新初始化网络产生ZDO_NETWORK_INIT事件
}
break;
- 完成以上步骤后,终端断电后也能重新连上协调器了,具体重连策略可看参考资料中官方论坛的帖子
参考资料
第7章:基于ZCL的属性上报实验 - ZigBee 3.0 开发指南
zigbee端末掉线和重连的问题 cc2530协议栈2.5.1a - Zigbee 和 Thread 论坛 - Zigbee 和 Thread - E2E™ 设计支持