zigbee終端無法重連的問題解決
1.zigbee重連的原因
(1)zigbee由於各種原因的干擾導致信號太差而掉線。
(2)協調器重啟。
2.zigbee終端重連的處理
(1)zigbee掉線后會進入回調函數:void ZDO_SyncIndicationCB( uint8 type, uint16 shortAddr );
產生ZDO_NWK_JOIN_REQ,之后會重新初始化網絡:
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事件 }
(2)接着會重新啟動設備,按重連的方式初始化網絡。
if ( events & ZDO_NETWORK_INIT ) { // Initialize apps and start the network devState = DEV_INIT; osal_set_event( ZDAppTaskID, ZDO_STATE_CHANGE_EVT ); ZDO_StartDevice( (uint8)ZDO_Config_Node_Descriptor.LogicalType, devStartMode,DEFAULT_BEACON_ORDER, DEFAULT_SUPERFRAME_ORDER ); // Return unprocessed events return (events ^ ZDO_NETWORK_INIT); }
(3)啟動設備的時候,終端節點是已孤兒節點(Orphan)來入網的。
void ZDO_StartDevice( byte logicalType, devStartModes_t startMode, byte beaconOrder, byte superframeOrder ) { ........... else if ( startMode == MODE_RESUME ) { if ( logicalType == NODETYPE_ROUTER ) { ZMacScanCnf_t scanCnf; devState = DEV_NWK_ORPHAN; /* if router and nvram is available, fake successful orphan scan */ scanCnf.hdr.Status = ZSUCCESS; scanCnf.ScanType = ZMAC_ORPHAN_SCAN; scanCnf.UnscannedChannels = 0; scanCnf.ResultListSize = 0; nwk_ScanJoiningOrphan(&scanCnf); ret = ZSuccess; } else { devState = DEV_NWK_ORPHAN; ret = NLME_OrphanJoinRequest( zgDefaultChannelList, zgDefaultStartingScanDuration ); } } else { } } ..... }
(4)加入以孤兒節點的方式還是入網失敗,則協議棧會讓zigbee的入網模式改為MODE_JOIN或者MODE_REJOIN。
void ZDApp_ProcessNetworkJoin( void ) { ........ else if ( devState == DEV_NWK_ORPHAN || devState == DEV_NWK_REJOIN ) { // results of an orphaning attempt by this device if (nwkStatus == ZSuccess) { // Verify NWK key is available before sending Device_annce if ( ZG_SECURE_ENABLED && ( ZDApp_RestoreNwkKey() == false ) ) { osal_set_event( ZDAppTaskID, ZDO_STATE_CHANGE_EVT ); // wait for auth from trust center devState = DEV_END_DEVICE_UNAUTH; // Start the reset timer for MAX UNAUTH time ZDApp_ResetTimerStart( MAX_DEVICE_UNAUTH_TIMEOUT ); } else { devState = DEV_END_DEVICE; osal_set_event( ZDAppTaskID, ZDO_STATE_CHANGE_EVT ); // setup Power Manager Device // The receiver is on, turn network layer polling off. if ( ZDO_Config_Node_Descriptor.CapabilityFlags & CAPINFO_RCVR_ON_IDLE ) { { NLME_SetPollRate( 0 ); NLME_SetQueuedPollRate( 0 ); NLME_SetResponseRate( 0 ); } } if ( ZSTACK_ROUTER_BUILD ) { // NOTE: first two parameters are not used, see NLMEDE.h for details if ( ZDO_Config_Node_Descriptor.LogicalType != NODETYPE_DEVICE ) { NLME_StartRouterRequest( 0, 0, false ); } } ZDApp_AnnounceNewAddress(); } } else { if ( devStartMode == MODE_RESUME ) { if ( ++retryCnt <= MAX_RESUME_RETRY ) { //如果nwkPanId沒有設置,則讓設備入網模式設為第一次入網;如果nwkPanId有設置過則入網模式設為重新入網 if ( _NIB.nwkPanId == 0xFFFF || _NIB.nwkPanId == INVALID_PAN_ID ) devStartMode = MODE_JOIN;//第一次入網 else { devStartMode = MODE_REJOIN;//重新入網 _tmpRejoinState = true; } } // Do a normal join to the network after certain times of rejoin retries else if( AIB_apsUseInsecureJoin == true ) { devStartMode = MODE_JOIN; } } // Clear the neighbor Table and network discovery tables. nwkNeighborInitTable(); NLME_NwkDiscTerm(); // setup a retry for later... ZDApp_NetworkInit( (uint16)(NWK_START_DELAY + (osal_rand()& EXTENDED_JOINING_RANDOM_MASK)) ); } } ....... } }
(4)接着重新再次初始化設備,這次是以rejoin的方式來初始化設備的。其實在這里只是啟動一個網絡掃描而已: NLME_NetworkDiscoveryRequest( zgDefaultChannelList, zgDefaultStartingScanDuration );
void ZDO_StartDevice( byte logicalType, devStartModes_t startMode, byte beaconOrder, byte superframeOrder ) { .......... if ( ZG_BUILD_JOINING_TYPE && (logicalType == NODETYPE_ROUTER || logicalType == NODETYPE_DEVICE) ) { if ( (startMode == MODE_JOIN) || (startMode == MODE_REJOIN) ) //根據當前啟動模式是連接或者重連模式則啟動一個網絡掃描 { devState = DEV_NWK_DISC; #if defined( MANAGED_SCAN ) ZDOManagedScan_Next(); ret = NLME_NetworkDiscoveryRequest( managedScanChannelMask, BEACON_ORDER_15_MSEC ); #else ret = NLME_NetworkDiscoveryRequest( zgDefaultChannelList, zgDefaultStartingScanDuration );//啟動網絡掃描請求 #if defined ( ZIGBEE_FREQ_AGILITY ) if ( !( ZDO_Config_Node_Descriptor.CapabilityFlags & CAPINFO_RCVR_ON_IDLE ) && ( ret == ZSuccess ) && ( ++discRetries == 4 ) ) { // For devices with RxOnWhenIdle equals to FALSE, any network channel // change will not be recieved. On these devices or routers that have // lost the network, an active scan shall be conducted on the Default // Channel list using the extended PANID to find the network. If the // extended PANID isn't found using the Default Channel list, an scan // should be completed using all channels. zgDefaultChannelList = MAX_CHANNELS_24GHZ; } #endif // ZIGBEE_FREQ_AGILITY #if defined ( ZIGBEE_COMMISSIONING ) if (startMode == MODE_REJOIN && scanCnt++ >= 5 ) { // When ApsUseExtendedPanID is commissioned to a non zero value via // application specific means, the device shall conduct an active scan // on the Default Channel list and join the PAN with the same // ExtendedPanID. If the PAN is not found, an scan should be completed // on all channels. // When devices rejoin the network and the PAN is not found from zgDefaultChannelList = MAX_CHANNELS_24GHZ; } #endif // ZIGBEE_COMMISSIONING #endif } ....... if ( ret != ZSuccess ) { osal_start_timerEx(ZDAppTaskID, ZDO_NETWORK_INIT, NWK_RETRY_DELAY ); } }
(5)有設備接入則會調用回調函數:ZDO_NetworkDiscoveryConfirmCB(uint8 status),在這個回調函數會產生ZDO_NWK_DISC_CNF事件。然后在void ZDApp_ProcessOSALMsg( osal_event_hdr_t *msgPtr )里面處理該事件,重連過程中會從NV中讀取panid等數據, 最后發出重新連接的申請NLME_ NLME_ReJoinRequest( ZDO_UseExtendedPANID, pChosenNwk->logicalChannel);
void ZDApp_ProcessOSALMsg( osal_event_hdr_t *msgPtr )
{
…….
switch ( msgPtr->event )
{
………..
case ZDO_NWK_DISC_CNF:
if (devState != DEV_NWK_DISC)
break;
if ( ZG_BUILD_JOINING_TYPE && ZG_DEVICE_JOINING_TYPE ) { // Process the network discovery scan results and choose a parent // device to join/rejoin itself networkDesc_t *pChosenNwk; if ( ( (pChosenNwk = ZDApp_NwkDescListProcessing()) != NULL ) && (zdoDiscCounter > NUM_DISC_ATTEMPTS) ) { if ( devStartMode == MODE_JOIN ) { devState = DEV_NWK_JOINING; ZDApp_NodeProfileSync( pChosenNwk->stackProfile); if ( NLME_JoinRequest( pChosenNwk->extendedPANID, pChosenNwk->panId, pChosenNwk->logicalChannel, ZDO_Config_Node_Descriptor.CapabilityFlags, pChosenNwk->chosenRouter, pChosenNwk->chosenRouterDepth ) != ZSuccess ) { ZDApp_NetworkInit( (uint16)(NWK_START_DELAY + ((uint16)(osal_rand()& EXTENDED_JOINING_RANDOM_MASK))) ); } } // if ( devStartMode == MODE_JOIN ) else if ( devStartMode == MODE_REJOIN )//重連的處理 { ZStatus_t rejoinStatus; devState = DEV_NWK_REJOIN; // Before trying to do rejoin, check if the device has a valid short address // If not, generate a random short address for itself if ( _NIB.nwkDevAddress == INVALID_NODE_ADDR ) { uint16 commNwkAddr; //從NV里面讀取網絡信息 // Verify if the Network address has been commissioned by external tool if ( ( osal_nv_read( ZCD_NV_COMMISSIONED_NWK_ADDR, 0, sizeof(commNwkAddr), (void*)&commNwkAddr ) == ZSUCCESS ) && ( commNwkAddr != INVALID_NODE_ADDR ) ) { _NIB.nwkDevAddress = commNwkAddr; // clear Allocate address bit because device has a commissioned address _NIB.CapabilityFlags &= ~CAPINFO_ALLOC_ADDR; } else { _NIB.nwkDevAddress = osal_rand(); } ZMacSetReq( ZMacShortAddress, (byte*)&_NIB.nwkDevAddress ); } // Check if the device has a valid PanID, if not, set it to the discovered Pan if ( _NIB.nwkPanId == INVALID_PAN_ID ) { _NIB.nwkPanId = pChosenNwk->panId; ZMacSetReq( ZMacPanId, (byte*)&(_NIB.nwkPanId) );//設置新的panID到NV里面 } tmp = true; ZMacSetReq( ZMacRxOnIdle, &tmp ); // Set receiver always on during rejoin // Perform Secure or Unsecure Rejoin depending on available configuration if ( ZG_SECURE_ENABLED && ( ZDApp_RestoreNwkKey() == TRUE ) ) { rejoinStatus = NLME_ReJoinRequest( ZDO_UseExtendedPANID, pChosenNwk->logicalChannel); //發出重新連接的申請 } else { rejoinStatus = NLME_ReJoinRequestUnsecure( ZDO_UseExtendedPANID, pChosenNwk->logicalChannel); //發出重新連接的申請 } if ( rejoinStatus != ZSuccess ) { ZDApp_NetworkInit( (uint16)(NWK_START_DELAY + ((uint16)(osal_rand()& EXTENDED_JOINING_RANDOM_MASK))) ); } } // else if ( devStartMode == MODE_REJOIN ) // The receiver is on, turn network layer polling off. ........... } } else { if ( continueJoining ) { zdoDiscCounter++; ZDApp_NetworkInit( (uint16)(BEACON_REQUEST_DELAY + ((uint16)(osal_rand()& BEACON_REQ_DELAY_MASK))) ); #endif } } } break; ..........
}
}
(6)設備入網成功會調用入網成功的回調函數ZDO_JoinConfirmCB(uint16 PanId, ZStatus_t Status),接着聲明本設備的短地址(這樣協調器才知道設備入網成功),並產生ZDO_STATE_CHANGE_EVT網絡改變的事件。devState 的狀態是在void ZDO_JoinConfirmCB( )里面改變的,ZDApp_ProcessNetworkJoin( void )里面才會處理入網成功。
void ZDO_JoinConfirmCB( uint16 PanId, ZStatus_t Status ) { (void)PanId; // remove if this parameter is used. nwkStatus = (byte)Status; if ( Status == ZSUCCESS ) { if ( ZSTACK_END_DEVICE_BUILD || (ZSTACK_ROUTER_BUILD && ((_NIB.CapabilityFlags & ZMAC_ASSOC_CAPINFO_FFD_TYPE) == 0))) { neighborEntry_t *pItem; // We don't need the neighbor table entries. // Clear the neighbor Table to remove beacon information nwkNeighborInitTable(); // Force a neighbor table entry for the parent pItem = nwkNeighborFindEmptySlot(); if ( pItem != NULL ) { osal_memset( pItem, 0x00, sizeof ( neighborEntry_t ) ); pItem->neighborAddress = _NIB.nwkCoordAddress; osal_cpyExtAddr( pItem ->neighborExtAddr, _NIB. nwkCoordExtAddress ); pItem->panId = _NIB. nwkPanId; pItem->linkInfo.rxLqi = DEF_LQI; pItem->linkInfo.txCounter = DEF_LINK_COUNTER; pItem->linkInfo.txCost = DEF_LINK_COST; } } if ( (devState == DEV_HOLD) ) { // Began with HOLD_AUTO_START devState = DEV_NWK_JOINING;//改變devState狀態,代表入網已經成功 } if ( !ZG_SECURE_ENABLED ) { // Notify to save info into NV ZDApp_NVUpdate(); } } else { } // Pass the join confirm to higher layer if callback registered if (zdoCBFunc[ZDO_JOIN_CNF_CBID] != NULL ) { zdoJoinCnf_t joinCnf; joinCnf.status = Status; joinCnf.deviceAddr = _NIB.nwkDevAddress; joinCnf.parentAddr = _NIB.nwkCoordAddress; zdoCBFunc[ZDO_JOIN_CNF_CBID]( (void*)&joinCnf ); } // Notify ZDApp ZDApp_SendMsg( ZDAppTaskID, ZDO_NWK_JOIN_IND, sizeof(osal_event_hdr_t), (byte*)NULL ); } //入網成功的處理 void ZDApp_ProcessNetworkJoin( void ) { if ( (devState == DEV_NWK_JOINING) || ((devState == DEV_NWK_ORPHAN) && (ZDO_Config_Node_Descriptor.LogicalType == NODETYPE_ROUTER)) ) { }else if( devState == DEV_NWK_ORPHAN || devState == DEV_NWK_REJOIN ){ ......... else //該處暗含:if(devState == DEV_NWK_JOINING) { // Assume from address conflict if ( _NIB.nwkAddrAlloc == NWK_ADDRESSING_STOCHASTIC ) { // Notify the network ZDApp_AnnounceNewAddress();//聲明本設備的短地址 // Notify apps osal_set_event( ZDAppTaskID, ZDO_STATE_CHANGE_EVT );//告知網絡狀態改變 } } }
4.zigbee重連失敗分析。
(1)重連成功與重連失敗首先要用抓包工具Packet Sniffer來分析
下面是重連成功的圖:能看的的數據包是===》Beancon Request==>協調器的回應包==》終端的NWK Rejoin Request 數據包 ===》終端的Data Request ==》協調器回應 NWK Rejoin Response包
下面是重連失敗的圖:能看到的數據包是 ===》Beancon Request==>協調器的回應包==》終端的NWK Rejoin Request 數據包 ===》 Beancon Request
*從上面的數據包對比可知,重連失敗的數據包沒有=》終端的Data Request《=, 這樣導致終端無法得知之前連接的設備是否還在線,那么終端只能認為該網絡是全新的網絡,需要重新連接,不能重連舊設備。
接下來要查明為什么不發Data Request的包,查看代碼發現某個地方設置不查詢數據NLME_SetPollRate(0);當時寫這行代碼是為了zigbee設備能夠更好的休眠,盡量減少數據的發送,故關閉數據輪詢。把NLME_SetPollRate(0)注釋掉即可解決問題,重啟協調器,終端能夠正常地重連到協調器上。