物联网项目——《基于Cat.1的智慧路灯系统设计与实现1》
本项目设计一套基于Cat.1的智慧路灯系统实现对路灯的远程控制路面照度的实时采集等功能并通过自组网方式完成终端节点到网关的信息汇聚通过网关的Cat.1通信方案完成信息上报云平台通过云平台实现远程状态监测与控制指令下发。项目实现的是一个简单的路灯系统主要实现自组网、通信、远程开关灯控制、动态调光、环境数据检测、ZigBee网络状态监测六大功能包括一个协调器两个终端。实验准备硬件三个小熊派开发板STM32L431为主控、三个E53_ZigBee扩展版CC2530、两个E53_SC1扩展版、一个E53_EC800X扩展版使用的是移远的EC800M系列、一个陶晶池串口屏、一个GP2Y粉尘传感器、若干个杜邦线软件Visual Studio Code、IAR、串口调试助手、BearPi-IoT Std 华为云平台的智慧路灯案例的实验源码、Z-stack 2.5协议栈、华为云平台小熊派源码链接华为云平台的智慧路灯案例部分硬件实物图协调器各模块连接图ZigBee模块通过UART2与STM32通信Cat.1模块通过LPUART与STM32通信。终端各模块连接图ZigBee模块同样通过UART2与STM32通信E53_SC1扩展版插在E53 interface 1扩展口上。E53-1扩展接口引脚自组网功能准备好上述实验的软硬件后第一步是实现终端上ZigBee模块与协调器上ZigBee模块的无线通信也就是通过ZigBee实现串口透传功能。项目基于Z-stack 2.5协议栈串口透传工程进行。/******************************** Filename: SerialApp.h *********************************/ #ifndef SERIALAPP_H #define SERIALAPP_H #ifdef __cplusplus extern C { #endif // 自定义全局宏定义 #ifndef DEVICE_TIMEOUT_THRESHOLD #define DEVICE_TIMEOUT_THRESHOLD 30 // 30秒超时阈值 #define COORD_SEND_TIMEOUT 1000 // 协调器发送超时1秒 #define SERIALAPP_TIMEOUT_EVT 0x0004 // 自定义定时超时事件 #endif #include ZComDef.h #include stdlib.h // 用于 abs() 函数 #include math.h #define SERIALAPP_ENDPOINT 11 //应用自定义端点号 #define SERIALAPP_PROFID 0x0F05 #define SERIALAPP_DEVICEID 0x0001 #define SERIALAPP_DEVICE_VERSION 0 #define SERIALAPP_FLAGS 0 #define SERIALAPP_MAX_CLUSTERS 4 #define SERIALAPP_CLUSTERID1 1 #define SERIALAPP_CLUSTERID2 2 #define SERIALAPP_CONNECTREQ_CLUSTER 3 #define SERIALAPP_CONNECTRSP_CLUSTER 4 #define SERIALAPP_SEND_EVT 0x0001 #define SERIALAPP_RESP_EVT 0x0002 // OTA Flow Control Delays #define SERIALAPP_ACK_DELAY 1 #define SERIALAPP_NAK_DELAY 16 // OTA Flow Control Status #define OTA_SUCCESS ZSuccess #define OTA_DUP_MSG (ZSuccess1) #define OTA_SER_BUSY (ZSuccess2) #define Devnumber_Max 9 //设置最大连接设备数 #define DevID 2 //作为终端的设备ID可以设置为0~Devnumber_Max //****在同一个PAN-ID下的终端不能相同 extern byte SerialApp_TaskID; extern void SerialApp_Init( byte task_id ); extern UINT16 SerialApp_ProcessEvent( byte task_id, UINT16 events ); #ifdef __cplusplus } #endif #endif /* SERIALAPP_H *//******************************** Filename: SerialApp.c 核心功能 *1.协调器建网、终端自动入网上报ID与短地址 *2.串口数据 - ZigBee无线透明传输 *3.8位序列号防重包、防乱序、重连自动复位序号 *4.30秒设备离线超时检测、1秒发送超时防卡死 *5.点对点点播广播兼容、应答流控机制 *********************************/ #include stdio.h #include string.h #include AF.h #include OnBoard.h #include OSAL_Tasks.h #include OSAL_Clock.h #include SerialApp.h #include ZDApp.h #include ZDObject.h #include ZDProfile.h #include hal_drivers.h #include hal_key.h #if defined ( LCD_SUPPORTED ) #include hal_lcd.h #endif #include hal_led.h #include hal_uart.h #if !defined( SERIAL_APP_PORT ) #define SERIAL_APP_PORT 0 //使用串口0 #endif #if !defined( SERIAL_APP_BAUD ) //#define SERIAL_APP_BAUD HAL_UART_BR_38400 #define SERIAL_APP_BAUD HAL_UART_BR_115200 #endif #if !defined( SERIAL_APP_THRESH ) #define SERIAL_APP_THRESH 64 #endif #if !defined( SERIAL_APP_RX_SZ ) #define SERIAL_APP_RX_SZ 128 //串口接收缓冲区大小 #endif #if !defined( SERIAL_APP_TX_SZ ) #define SERIAL_APP_TX_SZ 128 //串口发送缓冲区大小 #endif #if !defined( SERIAL_APP_IDLE ) #define SERIAL_APP_IDLE 6 #endif #if !defined( SERIAL_APP_LOOPBACK ) #define SERIAL_APP_LOOPBACK FALSE #endif #if !defined( SERIAL_APP_TX_MAX ) #define SERIAL_APP_TX_MAX 80 #endif #define SERIAL_APP_RSP_CNT 4 // 簇ID列表输入簇、输出簇共用 const cId_t SerialApp_ClusterList[SERIALAPP_MAX_CLUSTERS] { SERIALAPP_CLUSTERID1, // 透传数据簇 SERIALAPP_CLUSTERID2, // 应答流控簇 SERIALAPP_CONNECTREQ_CLUSTER, // 终端入网请求簇 SERIALAPP_CONNECTRSP_CLUSTER // 协调器入网应答簇 }; // 设备简单描述符 注册到ZigBee协议栈 const SimpleDescriptionFormat_t SerialApp_SimpleDesc { SERIALAPP_ENDPOINT, // 应用端点号 SERIALAPP_PROFID, // 协议Profile ID SERIALAPP_DEVICEID, // 设备ID SERIALAPP_DEVICE_VERSION, // 设备版本 SERIALAPP_FLAGS, // 设备标志 SERIALAPP_MAX_CLUSTERS, // 输入簇数量 (cId_t *)SerialApp_ClusterList, // 输入簇列表 SERIALAPP_MAX_CLUSTERS, // 输出簇数量 (cId_t *)SerialApp_ClusterList // 输出簇列表 }; endPointDesc_t SerialApp_epDesc { SERIALAPP_ENDPOINT, SerialApp_TaskID, (SimpleDescriptionFormat_t *)SerialApp_SimpleDesc, noLatencyReqs }; // 在协调器侧添加设备状态跟踪 typedef struct { uint16 shortAddr; // 设备短地址 uint8 lastSeq; // 上次序列号 uint32 lastSeenTime; // 最后活跃时间戳 uint8 isOnline; // 在线状态标志 } DeviceStatus_t; devStates_t SampleApp_NwkState; // 当前设备网络状态协调器/路由器/终端 uint8 SerialApp_TaskID; // 本应用任务ID extern UTCTime osal_getClock(void); static uint8 SerialApp_MsgID; static afAddrType_t SerialApp_TxAddr; static uint8 SerialApp_TxSeq; static uint8 SerialApp_TxBuf[SERIAL_APP_TX_MAX1]; static uint8 SerialApp_TxLen; static afAddrType_t SerialApp_RxAddr; static uint8 SerialApp_RxSeq; static uint8 SerialApp_RspBuf[SERIAL_APP_RSP_CNT]; static uint16 DevAddr[Devnumber_Max] {0}; // 设备短地址存储数组 static uint8 SerialApp_RxSeq_ep[Devnumber_Max] {0xC3}; // 每个设备的序列号 static DeviceStatus_t deviceStatus[Devnumber_Max] {0}; // 设备状态跟踪 /********************************************************************* * LOCAL FUNCTIONS */ static void SerialApp_ProcessMSGCmd( afIncomingMSGPacket_t *pkt ); static void SerialApp_Send(void); static void SerialApp_Resp(void); static void SerialApp_CallBack(uint8 port, uint8 event); static void SerialApp_DeviceConnect(void); static void SerialApp_DeviceConnectRsp(uint8*); static void SerialApp_ConnectReqProcess(uint8*); static void checkDeviceTimeout(void); /********************************************************************* * fn SerialApp_Init * * brief This is called during OSAL tasks initialization. * * param task_id - the Task ID assigned by OSAL. * * return none */ void SerialApp_Init( uint8 task_id ) { uint8 i; halUARTCfg_t uartConfig; SerialApp_TaskID task_id; SerialApp_RxSeq 0xC3; SampleApp_NwkState DEV_INIT; for(i0 ; iDevnumber_Max ; i) { SerialApp_RxSeq_ep[i] 0xc3; // 初始化序列号数组 // 初始化设备状态结构 deviceStatus[i].shortAddr 0; deviceStatus[i].lastSeq 0xC3; deviceStatus[i].lastSeenTime 0; deviceStatus[i].isOnline 0; } afRegister( (endPointDesc_t *)SerialApp_epDesc ); RegisterForKeys( task_id ); //初始化串口 uartConfig.configured TRUE; uartConfig.baudRate SERIAL_APP_BAUD;//默认波特率 uartConfig.flowControl FALSE; uartConfig.flowControlThreshold SERIAL_APP_THRESH; uartConfig.rx.maxBufSize SERIAL_APP_RX_SZ; uartConfig.tx.maxBufSize SERIAL_APP_TX_SZ; uartConfig.idleTimeout SERIAL_APP_IDLE; uartConfig.intEnable TRUE; uartConfig.callBackFunc SerialApp_CallBack; //打开串口 HalUARTOpen (SERIAL_APP_PORT, uartConfig); #if defined ( LCD_SUPPORTED ) HalLcdWriteString( SerialApp, HAL_LCD_LINE_2 ); #endif ZDO_RegisterForZDOMsg( SerialApp_TaskID, End_Device_Bind_rsp ); ZDO_RegisterForZDOMsg( SerialApp_TaskID, Match_Desc_rsp ); osal_start_timerEx( SerialApp_TaskID, SERIALAPP_TIMEOUT_EVT, 1000 ); } /********************************************************************* * fn SerialApp_ProcessEvent * * brief Generic Application Task event processor. * * param task_id - The OSAL assigned task ID. * param events - Bit map of events to process. * * return Event flags of all unprocessed events. */ UINT16 SerialApp_ProcessEvent( uint8 task_id, UINT16 events ) { (void)task_id; // Intentionally unreferenced parameter if ( events SYS_EVENT_MSG ) { afIncomingMSGPacket_t *MSGpkt; while ( (MSGpkt (afIncomingMSGPacket_t *)osal_msg_receive( SerialApp_TaskID )) ) { switch ( MSGpkt-hdr.event ) { case AF_INCOMING_MSG_CMD: SerialApp_ProcessMSGCmd( MSGpkt ); break; case ZDO_STATE_CHANGE: SampleApp_NwkState (devStates_t)(MSGpkt-hdr.status); if ( (SampleApp_NwkState DEV_ZB_COORD) || (SampleApp_NwkState DEV_ROUTER) || (SampleApp_NwkState DEV_END_DEVICE) ) { // Start sending the periodic message in a regular interval. //终端联网成功后发送自己的短地址 if(SampleApp_NwkState ! DEV_ZB_COORD) SerialApp_DeviceConnect(); } else { // Device is no longer in the network } break; default: break; } osal_msg_deallocate( (uint8 *)MSGpkt ); } return ( events ^ SYS_EVENT_MSG ); } if ( events SERIALAPP_SEND_EVT ) { //串口数据无线发送 SerialApp_Send(); return ( events ^ SERIALAPP_SEND_EVT ); } if ( events SERIALAPP_RESP_EVT ) { //串口发送数据的响应 //收到此数据后表示可以无线发送下一个串口数据 SerialApp_Resp(); return ( events ^ SERIALAPP_RESP_EVT ); } if ( events SERIALAPP_TIMEOUT_EVT ) { // 超时强制解锁发送防止卡死 if(SerialApp_TxLen ! 0) { SerialApp_TxLen 0; } // 循环定时 osal_start_timerEx( SerialApp_TaskID, SERIALAPP_TIMEOUT_EVT, COORD_SEND_TIMEOUT ); return ( events ^ SERIALAPP_TIMEOUT_EVT ); } return ( 0 ); // Discard unknown events. } /********************************************************************* * fn SerialApp_ProcessMSGCmd * * brief Data message processor callback. This function processes * any incoming data - probably from other devices. Based * on the cluster ID, perform the intended action. * * param pkt - pointer to the incoming message packet * * return TRUE if the pkt parameter is being used and will be freed later, * FALSE otherwise. */ void SerialApp_ProcessMSGCmd( afIncomingMSGPacket_t *pkt ) { uint8 stat; uint8 seqnb; uint8 delay; uint8 device_id; switch ( pkt-clusterId ) { // A message with a serial data block to be transmitted on the serial port. case SERIALAPP_CLUSTERID1: // 收到发送过来的数据通过串口输出到电脑显示 // Store the address for sending and retrying. osal_memcpy(SerialApp_RxAddr, (pkt-srcAddr), sizeof( afAddrType_t )); // 接收到的数据包的序列号 seqnb pkt-cmd.Data[0]; device_id pkt-cmd.Data[3]; // 提取设备ID if(device_id 0 device_id Devnumber_Max) { device_id device_id % 16; // 确保在有效范围内 } else { device_id 0; // 默认处理 } // 获取该设备对应的序列号记录 SerialApp_RxSeq SerialApp_RxSeq_ep[device_id]; // 容错处理逻辑开始 // 检查设备状态 if (deviceStatus[device_id].isOnline) { uint8 diff (uint8)(seqnb - SerialApp_RxSeq); // 允许序列号回绕 if ((diff 0 diff 0x80) || (diff 0x80)) { // 序列号正确串口输出数据 if ( HalUARTWrite( SERIAL_APP_PORT, pkt-cmd.Data1, (pkt-cmd.DataLength-1) ) ) { // 保存序列号供下次使用 SerialApp_RxSeq_ep[device_id] seqnb; deviceStatus[device_id].lastSeq seqnb; deviceStatus[device_id].lastSeenTime (uint32)osal_getClock(); stat OTA_SUCCESS; } else { stat OTA_SER_BUSY; } } else { // 重复包仍然输出但不更新序列号 HalUARTWrite( SERIAL_APP_PORT, pkt-cmd.Data1, (pkt-cmd.DataLength-1) ); deviceStatus[device_id].lastSeenTime (uint32)osal_getClock(); stat OTA_DUP_MSG; } } else { // 设备不在线状态但收到数据可能是刚连接 // 重置序列号为当前值并标记为在线 SerialApp_RxSeq_ep[device_id] seqnb; deviceStatus[device_id].isOnline 1; deviceStatus[device_id].lastSeq seqnb; deviceStatus[device_id].lastSeenTime (uint32)osal_getClock(); if ( HalUARTWrite( SERIAL_APP_PORT, pkt-cmd.Data1, (pkt-cmd.DataLength-1) ) ) { stat OTA_SUCCESS; } else { stat OTA_SER_BUSY; } } // 容错处理逻辑结束 // 选择适当的OTA流控制延迟 delay (stat OTA_SER_BUSY) ? SERIALAPP_NAK_DELAY : SERIALAPP_ACK_DELAY; // 构建并发送OTA响应消息 SerialApp_RspBuf[0] stat; SerialApp_RspBuf[1] seqnb; SerialApp_RspBuf[2] LO_UINT16( delay ); SerialApp_RspBuf[3] HI_UINT16( delay ); osal_set_event( SerialApp_TaskID, SERIALAPP_RESP_EVT ); osal_stop_timerEx(SerialApp_TaskID, SERIALAPP_RESP_EVT); break; // 发送数据包后接到响应消息 case SERIALAPP_CLUSTERID2: if ((pkt-cmd.Data[1] SerialApp_TxSeq) ((pkt-cmd.Data[0] OTA_SUCCESS) || (pkt-cmd.Data[0] OTA_DUP_MSG))) { SerialApp_TxLen 0; osal_stop_timerEx(SerialApp_TaskID, SERIALAPP_SEND_EVT); } else { SerialApp_TxLen 0; delay BUILD_UINT16( pkt-cmd.Data[2], pkt-cmd.Data[3] ); osal_start_timerEx( SerialApp_TaskID, SERIALAPP_SEND_EVT, delay ); } break; //协调器接收到终端的连接消息 case SERIALAPP_CONNECTREQ_CLUSTER: SerialApp_ConnectReqProcess((uint8*)pkt-cmd.Data); break; //终端接收到协调器连接的消息 case SERIALAPP_CONNECTRSP_CLUSTER: SerialApp_DeviceConnectRsp((uint8*)pkt-cmd.Data); break; default: break; } } /********************************************************************* * fn SerialApp_Send * * brief Send data OTA. * * param none * * return none */ static void SerialApp_Send(void) { uint8 index; uint16 nwAddr; #if SERIAL_APP_LOOPBACK if (SerialApp_TxLen SERIAL_APP_TX_MAX) { SerialApp_TxLen HalUARTRead(SERIAL_APP_PORT, SerialApp_TxBufSerialApp_TxLen1, SERIAL_APP_TX_MAX-SerialApp_TxLen); } if (SerialApp_TxLen) { (void)SerialApp_TxAddr; if (HalUARTWrite(SERIAL_APP_PORT, SerialApp_TxBuf1, SerialApp_TxLen)) { SerialApp_TxLen 0; } else { osal_set_event(SerialApp_TaskID, SERIALAPP_SEND_EVT); } } #else // SerialApp_TxLen 不为 0 时 代表有数 据要发送或者正在发送 // SerialApp_TxLen 为 0 时 代表没有数据发送或者已经发送 完了。发送端接收到接收端的确认信息后 // 确定本次数据已经被接收到会将 SerialApp_TxLen 置 0 为接收下次数据作准备 if (!SerialApp_TxLen (SerialApp_TxLen HalUARTRead(SERIAL_APP_PORT, SerialApp_TxBuf1, SERIAL_APP_TX_MAX))) { SerialApp_TxBuf[0] SerialApp_TxSeq; } //如果是协调器在发送前根据命令中的DevID字段判断向哪个终端发送 //协调器的命令格式为 //[Devid]:[payload] //如果是终端则直接进行透传就可以了 #if ZDO_COORDINATOR //协调器 index SerialApp_TxBuf[1]; if(index0 index9) { index index % 16; } nwAddr DevAddr[index]; //这里的nwAddr如果不为0则以该地址进行发送必须发送否则会出现拥塞 //如果为0协调器则会以广播的形式下发数据其实是DevID有误 if (nwAddr ! 0) { SerialApp_TxAddr.addrMode (afAddrMode_t)Addr16Bit; SerialApp_TxAddr.endPoint SERIALAPP_ENDPOINT; SerialApp_TxAddr.addr.shortAddr nwAddr; } else { SerialApp_TxAddr.addrMode (afAddrMode_t)AddrBroadcast; SerialApp_TxAddr.endPoint SERIALAPP_ENDPOINT; SerialApp_TxAddr.addr.shortAddr 0xFFFF; } #else //终端或路由器 #endif //发送的时候也做相应判断 if (SerialApp_TxLen) { #if ZDO_COORDINATOR //协调器分两种发送方式 if(nwAddr ! 0) { if (afStatus_SUCCESS ! AF_DataRequest(SerialApp_TxAddr, (endPointDesc_t *)SerialApp_epDesc, SERIALAPP_CLUSTERID1, SerialApp_TxLen1, SerialApp_TxBuf, SerialApp_MsgID, 0, AF_DEFAULT_RADIUS)) { osal_set_event(SerialApp_TaskID, SERIALAPP_SEND_EVT); } } else { if (afStatus_SUCCESS ! AF_DataRequest( SerialApp_TxAddr, SerialApp_epDesc, SERIALAPP_CLUSTERID1, SerialApp_TxLen1, SerialApp_TxBuf, SerialApp_MsgID, AF_DISCV_ROUTE, AF_DEFAULT_RADIUS )) { osal_set_event(SerialApp_TaskID, SERIALAPP_SEND_EVT); } } #else //终端和路由器采用点播发送方式 if (afStatus_SUCCESS ! AF_DataRequest(SerialApp_TxAddr, (endPointDesc_t *)SerialApp_epDesc, SERIALAPP_CLUSTERID1, SerialApp_TxLen1, SerialApp_TxBuf, SerialApp_MsgID, 0, AF_DEFAULT_RADIUS)) { osal_set_event(SerialApp_TaskID, SERIALAPP_SEND_EVT); } #endif } #endif } /********************************************************************* * fn SerialApp_Resp * * brief Send data OTA. * * param none * * return none */ static void SerialApp_Resp(void) { if (afStatus_SUCCESS ! AF_DataRequest(SerialApp_RxAddr, (endPointDesc_t *)SerialApp_epDesc, SERIALAPP_CLUSTERID2, SERIAL_APP_RSP_CNT, SerialApp_RspBuf, SerialApp_MsgID, 0, AF_DEFAULT_RADIUS)) { osal_set_event(SerialApp_TaskID, SERIALAPP_RESP_EVT); } } /********************************************************************* * fn SerialApp_CallBack * * brief Send data OTA. * * param port - UART port. * param event - the UART port event flag. * * return none ***************************************************************************/ static void SerialApp_CallBack(uint8 port, uint8 event) { (void)port; if ((event (HAL_UART_RX_FULL | HAL_UART_RX_ABOUT_FULL | HAL_UART_RX_TIMEOUT)) #if SERIAL_APP_LOOPBACK (SerialApp_TxLen SERIAL_APP_TX_MAX)) #else !SerialApp_TxLen) #endif { SerialApp_Send(); } } /********************************************************************* *********************************************************************/ void SerialApp_DeviceConnect() { #if ZDO_COORDINATOR //协调器 #else //终端或路由器 uint16 nwkAddr; uint16 parentNwkAddr; char buff[64] {0}; // HalLedBlink( HAL_LED_2, 3, 50, (1000 / 4) ); nwkAddr NLME_GetShortAddr(); //得到自己的短地址 parentNwkAddr NLME_GetCoordShortAddr(); //得到协调器的短地址一般为0x0000 //sprintf(buff, Trying to connect Coordinator,DevID:%d,ShortAddr:%d,parentAddr:%d\r\n,DevID,nwkAddr,parentNwkAddr); //构建打印信息 //HalUARTWrite ( SERIAL_APP_PORT, (uint8*)buff, strlen(buff)); //打印 /*--------------------下面这一段是将自己的设备ID和短地址传给协调器----------------------*/ //设置终端到协调器的点播参数 SerialApp_TxAddr.addrMode (afAddrMode_t)Addr16Bit; //16位短地址的点播方式 SerialApp_TxAddr.endPoint SERIALAPP_ENDPOINT; //端点编号 SerialApp_TxAddr.addr.shortAddr parentNwkAddr; //协调器短地址一般位0x0000 buff[0] DevID; //设备ID buff[1] HI_UINT16( nwkAddr ); //高八位 buff[2] LO_UINT16( nwkAddr ); //低八位 if ( AF_DataRequest( SerialApp_TxAddr, SerialApp_epDesc, SERIALAPP_CONNECTREQ_CLUSTER, 3, (uint8*)buff, SerialApp_MsgID, 0, AF_DEFAULT_RADIUS ) afStatus_SUCCESS ) { } else { // Error occurred in request to send. } #endif //ZDO_COORDINATOR } //协调器接收到终端上报的“设备ID短地址”后的处理函数 void SerialApp_ConnectReqProcess(uint8 *buf) { uint16 nwkAddr; uint8 index; uint8 device_id buf[0]; char buff[64] {0}; index buf[0]; nwkAddr BUILD_UINT16(buf[2], buf[1]); //重新构建接收到的终端短地址 // 检查设备是否为新连接或重连 if (deviceStatus[device_id].shortAddr ! nwkAddr || !deviceStatus[device_id].isOnline) { // 设备重连或首次连接重置序列号 SerialApp_RxSeq_ep[device_id] 0xC3; // 更新设备状态 deviceStatus[device_id].shortAddr nwkAddr; deviceStatus[device_id].lastSeq 0xC3; deviceStatus[device_id].isOnline 1; } // 记录活跃时间 deviceStatus[device_id].lastSeenTime (uint32)osal_getClock(); DevAddr[device_id] nwkAddr; //保存收到终端的短地址供发送使用 /*-----向发送短地址的终端返回自身短地址其实就是0x0000---------****/ SerialApp_TxAddr.addrMode (afAddrMode_t)Addr16Bit; SerialApp_TxAddr.endPoint SERIALAPP_ENDPOINT; SerialApp_TxAddr.addr.shortAddr nwkAddr; nwkAddr NLME_GetShortAddr(); //得到协调器自己的短地址不出意外就是0x0000 // 原有保存短地址逻辑 DevAddr[device_id] nwkAddr; //sprintf(buff, One Endev is connectted.DevID:%d,ShortAddr:%d\r\n,index,SerialApp_TxAddr.addr.shortAddr); //HalUARTWrite ( SERIAL_APP_PORT, (uint8*)buff, strlen(buff)); buff[0] HI_UINT16( nwkAddr ); buff[1] LO_UINT16( nwkAddr ); if ( AF_DataRequest( SerialApp_TxAddr, SerialApp_epDesc, SERIALAPP_CONNECTRSP_CLUSTER, 2, (uint8*)buff, SerialApp_MsgID, 0, AF_DEFAULT_RADIUS ) afStatus_SUCCESS ) { } else { // Error occurred in request to send. } //sprintf(buff, One endev connect success\n ); //HalUARTWrite ( SERIAL_APP_PORT, buff, strlen(buff)); } //终端节点接收到协调器回传的短地址后进行保存供数据上报时使用 void SerialApp_DeviceConnectRsp(uint8 *buf) { char buff[64] {0}; #if ZDO_COORDINATOR #else SerialApp_TxAddr.addrMode (afAddrMode_t)Addr16Bit; SerialApp_TxAddr.endPoint SERIALAPP_ENDPOINT; SerialApp_TxAddr.addr.shortAddr BUILD_UINT16(buf[1], buf[0]); //sprintf(buff, Connect Coordinator success\n ); //HalUARTWrite ( SERIAL_APP_PORT, buff, strlen(buff)); #endif } void checkDeviceTimeout(void) { uint32 currentTime (uint32)osal_getClock(); for (int i 0; i Devnumber_Max; i) { if (deviceStatus[i].isOnline (currentTime - deviceStatus[i].lastSeenTime) DEVICE_TIMEOUT_THRESHOLD) { deviceStatus[i].isOnline 0; // 标记为离线 // 可选清除短地址映射 // DevAddr[i] 0; } } }一、前言三个核心问题ZigBee 协调器和终端到底是怎么自动组网、互相绑定的串口数据是怎么从上位机→协调器→终端反向又是怎么上报的为什么要设计序列号它怎么防重包、防乱序二、整体网络架构1. 网络角色划分协调器负责创建 ZigBee 网络短地址固定0x0000管理所有终端、保存设备地址、维护在线状态、串口与无线数据转发。终端节点主动搜索并加入协调器网络上报自身 ID 和短地址实现串口数据无线上报、接收协调器下发数据。上位机这里是MCU通过串口连接协调器只需要收发普通串口数据感知不到 ZigBee 无线协议存在。2. 工程核心配置通信端点11自定义簇 ID数据收发簇、连接请求簇、连接应答簇最大支持终端数量9 台设备离线超时30 秒无通信自动判离线串口波特率115200三、组网与设备入网1. 协调器初始化建网协调器上电后执行应用层初始化做几件关键事初始化串口、配置收发缓冲区、注册串口回调注册 ZigBee 应用端点与簇列表向协议栈登记应用初始化设备状态结构体数组把所有终端默认置为离线、序列号初始化开启定时事件用于后续设备离线超时检测和发送防卡死。设备状态核心结构体协调器管理所有终端typedef struct { uint16 shortAddr; // 终端ZigBee短地址 uint8 lastSeq; // 上次正常通信序列号 uint32 lastSeenTime; // 最后一次通信时间戳 uint8 isOnline; // 在线/离线标记 } DeviceStatus_t;协调器通过这个数组记录每一个终端的地址、在线状态、通信序列号。2. 终端入网发起连接请求终端上电自动扫描空气中 ZigBee 网络加入协调器创建的网络入网成功后协议栈抛出网络状态变更事件终端主动触发设备连接请求流程读取自身短地址、协调器固定短地址0x0000封装数据包设备ID 自身短地址高8位 自身短地址低8位通过连接请求簇定点发给协调器。3. 协调器登记设备并应答协调器收到终端连接请求簇数据后进入SerialApp_ConnectReqProcess处理解析出终端设备 ID、终端短地址判断设备是首次入网还是断网重连新设备登记短地址、标记为在线、初始化序列号、刷新活跃时间重连设备重置序列号、刷新在线状态与时间戳把终端短地址存入DevAddr[]映射表后续串口下发按设备 ID 找地址组装应答包通过连接应答簇SERIALAPP_CONNECTRSP_CLUSTER簇回复终端完成入网绑定。4. 终端接收应答组网完成终端收到协调器应答后记录协调器短地址0x0000后续所有上行串口数据都定点发给协调器。 组网一句话总结终端主动找网→上报身份和地址→协调器登记管理→双向应答确认实现一主多从自动组网、自动上线。四、串口无线透传数据透传分两个方向协调器下发给终端、终端上报给协调器底层逻辑完全一致。1. 串口数据触发机制串口配置了接收超时、缓冲区满等中断事件一旦上位机有数据发来立刻触发SerialApp_CallBack回调函数直接调用SerialApp_Send()进入无线发送逻辑。2. 协调器向下发数据协调器读取串口缓冲区数据约定格式[设备ID][透传业务数据]提取设备 ID从DevAddr[]数组查找对应终端16 位短地址若设备 ID 合法、终端已入网采用定点点播发送若设备 ID 非法 / 终端未入网采用广播发送0xFFFF给数据包首部添加自增序列号通过AF_DataRequest无线发送簇 ID 使用SERIALAPP_CLUSTERID1。3. 终端向上上报数据终端逻辑更简单 入网后所有串口数据默认直接定点发给协调器 0x0000同样带上序列号封装发送。4. 接收端处理与串口透传输出任意一方收到无线透传数据包后解析簇 ID识别是普通透传数据取出包头序列号做序列号校验、重复包判断校验通过后把净数据直接写到串口上位机瞬间收到实现透明传输构造应答包携带接收状态、当前序列号、流控延时通过应答簇回发给发送方。5. 流控防卡死机制发送方收到应答后清空发送标记允许发送下一包数据软件设计 1 秒定时超时事件SERIALAPP_TIMEOUT_EVT长时间收不到应答强制解锁发送状态避免透传卡死串口繁忙时自动延时重发保证连续大数据稳定透传。五、序列号设计整套透传最关键的可靠性全靠8 位序列号支撑每个终端独立一套序列号初始固定0xC3每发一包自动自增溢出自动回绕。1. 防重复数据包无线环境容易出现路由转发、信号反射导致重复收包接收端对比当前包序列号和上一次合法序列号判定为重复包依然透传输出数据但不更新序列号防止序号错乱同时刷新设备活跃时间保持在线状态。2. 防乱序、校验数据包合法性通过序列号差值判断是否为合法递增数据包uint8 diff (uint8)(seqnb - SerialApp_RxSeq); if ((diff 0 diff 0x80) || (diff 0x80)) { // 序列号合法正常接收并更新记录 }既能允许正常递增也兼容 8 位字节溢出回绕过滤异常乱序包。3. 设备重连自动复位终端断电重启、断网重连时 协调器检测到设备状态变化自动重置该设备序列号为初始值避免旧序号残留导致通信异常。4. 设备离线超时检测定时任务中调用checkDeviceTimeout()定时轮询所有终端状态对比每个终端lastSeenTime最后通信时间超过 30 秒无任何数据交互自动标记isOnline 0离线离线设备不再响应无效数据包直到重新入网绑定。