转载自:http://www.deyisupport.com/question_answer/wireless_connectivity/zigbee/f/104/t/104629.aspx
本文主要介绍了TI ZigBee协议栈Z-Stack中,关于终端设备End Device工作过程中,不同状态之间切换的详细说明,并且通过分析空中交互的数据包进一步了解TI ZigBee协议栈Z-Stack的工作流程。
在Z-Stack Home 1.2.2a协议栈的C:\Texas Instruments\Z-Stack Home 1.2.2a.44539\Components\stack\zdo\ZDApp.h文件中有定义设备的不同状态,分别如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
typedef
enum
{
DEV_HOLD,
// Initialized - not started automatically
DEV_INIT,
// Initialized - not connected to anything
DEV_NWK_DISC,
// Discovering PAN's to join
DEV_NWK_JOINING,
// Joining a PAN
DEV_NWK_SEC_REJOIN_CURR_CHANNEL,
// ReJoining a PAN in secure mode scanning in current channel, only for end devices
DEV_END_DEVICE_UNAUTH,
// Joined but not yet authenticated by trust center
DEV_END_DEVICE,
// Started as device after authentication
DEV_ROUTER,
// Device joined, authenticated and is a router
DEV_COORD_STARTING,
// Started as Zigbee Coordinator
DEV_ZB_COORD,
// Started as Zigbee Coordinator
DEV_NWK_ORPHAN,
// Device has lost information about its parent..
DEV_NWK_KA,
// Device is sending KeepAlive message to its parent
DEV_NWK_BACKOFF,
// Device is waiting before trying to rejoin
DEV_NWK_SEC_REJOIN_ALL_CHANNEL,
// ReJoining a PAN in secure mode scanning in all channels, only for end devices
DEV_NWK_TC_REJOIN_CURR_CHANNEL,
// ReJoining a PAN in Trust center mode scanning in current channel, only for end devices
DEV_NWK_TC_REJOIN_ALL_CHANNEL
// ReJoining a PAN in Trust center mode scanning in all channels, only for end devices
} devStates_t;
|
对于不同的设备类型会有不同的设备状态,那么对于终端设备来说可能使用到的设备如下。
1
2
3
4
5
6
7
8
9
10
11
12
|
DEV_HOLD,
// Initialized - not started automatically
DEV_INIT,
// Initialized - not connected to anything
DEV_NWK_DISC,
// Discovering PAN's to join
DEV_NWK_JOINING,
// Joining a PAN
DEV_NWK_SEC_REJOIN_CURR_CHANNEL,
// ReJoining a PAN in secure mode scanning in current channel, only for end devices
DEV_END_DEVICE_UNAUTH,
// Joined but not yet authenticated by trust center
DEV_END_DEVICE,
// Started as device after authentication
DEV_NWK_ORPHAN,
// Device has lost information about its parent..
DEV_NWK_BACKOFF,
// Device is waiting before trying to rejoin
DEV_NWK_SEC_REJOIN_ALL_CHANNEL,
// ReJoining a PAN in secure mode scanning in all channels, only for end devices
DEV_NWK_TC_REJOIN_CURR_CHANNEL,
// ReJoining a PAN in Trust center mode scanning in current channel, only for end devices
DEV_NWK_TC_REJOIN_ALL_CHANNEL
// ReJoining a PAN in Trust center mode scanning in all channels, only for end devices
|
终端设备在运行过程中,状态机切换示意图如下,该示意图来自C:\Texas Instruments\Z-Stack Home 1.2.2a.44539\Documents\Z-Stack Developer's Guide.pdf文档中的Figure 5.
终端设备在上电开始运行以后,首先会运行到ZDApp_Init( uint8 task_id )来初始化ZDO层的Task。 然后通过下面的代码麻烦来判断节点的初始状态,
1
2
3
4
5
6
7
8
9
10
11
|
// Start the device?
if
( devState != DEV_HOLD )
{
ZDOInitDevice( 0 );
}
else
{
ZDOInitDevice( ZDO_INIT_HOLD_NWK_START );
// Blink LED to indicate HOLD_START
HalLedBlink ( HAL_LED_4, 0, 50, 500 );
}
|
初始化的状态的定义是通过下面代码来实现,HOLD_AUTO_START在IAR工程的Project->Options里面可以定义。DEV_HOLD表示设备处于Hold状态,协议栈不会自动的启动设备,而是通过其他方式去触发比方说按键触发。如果初始状态是DEV_INIT,那么节点就自动启动设备了。
1
2
3
4
5
|
#if defined( HOLD_AUTO_START )
devStates_t devState = DEV_HOLD;
#else
devStates_t devState = DEV_INIT;
#endif
|
通过ZDOInitDevice( uint16 startDelay)函数来启动设备,参数startDelay表示立即启动还是延时以后再启动。
在ZDOInitDevice( uint16 startDelay)函数中最终过触发ZDApp_NetworkInit( extendedDelay );来启动设备,相关的代码是ZDApp_event_loop( uint8 task_id, UINT16 events )函数中
1
2
3
4
5
6
7
8
9
10
11
|
if
( events & ZDO_NETWORK_INIT )
{
// Initialize apps and start the network
ZDApp_ChangeState( DEV_INIT );
ZDO_StartDevice( (uint8)ZDO_Config_Node_Descriptor.LogicalType, devStartMode,
DEFAULT_BEACON_ORDER, DEFAULT_SUPERFRAME_ORDER );
// Return unprocessed events
return
(events ^ ZDO_NETWORK_INIT);
}
|
通过调用ZDO_StartDevice函数,如果设备类型是终端设备,并且是一个未加过网的设备,那么会调用到ZDO_StartDevice中的下面代码,此时设备就进入DEV_NWK_DISC状态,去发现周围的网络。
1
2
3
4
5
|
if
( ZG_BUILD_JOINING_TYPE && (logicalType == NODETYPE_ROUTER || logicalType == NODETYPE_DEVICE) )
{
if
( (startMode == MODE_JOIN) || (startMode == MODE_REJOIN) )
{
ZDApp_ChangeState( DEV_NWK_DISC );
|
节点通过调用NLME_NetworkDiscoveryRequest( managedScanChannelMask, BEACON_ORDER_15_MSEC );函数去启动网络搜索的过程,根据事先定义的信道列表(在f8wConfig.cfg的DEFAULT_CHANLIST中定义的)依次的去每个信道上发送beacon request来发现网络,发送beacon request是通过ZMacScanReq( &scanReq )函数来触发的。如果定义了多个信道,那么就在多个信道上通过发送beacon request来搜索网络。
在完成搜索以后会通过scan confirm的方式告诉应用扫描已经完成了,NLME_NetworkDiscoveryConfirm(uint8 status)-> ZDO_NetworkDiscoveryConfirmCB(status)-> ZDApp_SendMsg( ZDAppTaskID, ZDO_NWK_DISC_CNF, sizeof(osal_event_hdr_t), (uint8 *)&msg );-> ZDApp_ProcessOSALMsg( osal_event_hdr_t *msgPtr )下面的case ZDO_NWK_DISC_CNF:
在ZDO_NWK_DISC_CNF下面,节点作一个判断
1
|
if
( ( (pChosenNwk = ZDApp_NwkDescListProcessing()) != NULL ) && (zdoDiscCounter > NUM_DISC_ATTEMPTS) )
|
需要满足两个条件,一个是从通过搜索到的网络列表里面挑选一个设备作为自己的父设备,另外一个搜索网络的次数要大于默认的NUM_DISC_ATTEMPTS=2,也就是至少要搜索3次网络。
如果不满足搜索3次的要求,就会跳到下面的代码,再次进行网络搜索,知道搜索的次数到3次或者更多了。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
else
{
if
( continueJoining )
{
#if defined ( MANAGED_SCAN )
ZDApp_NetworkInit( MANAGEDSCAN_DELAY_BETWEEN_SCANS );
#else
zdoDiscCounter++;
ZDApp_NetworkInit( (uint16)(BEACON_REQUEST_DELAY
+ ((uint16)(osal_rand()& BEACON_REQ_DELAY_MASK))) );
#endif
}
}
|
关于上面提到的通过搜索到的网络列表里面挑选一个设备作为自己的父设备ZDApp_NwkDescListProcessing调用到nwk_getNwkDescList()获取网络设备数量,其实就是从NwkDescList链表中挑选了合适的网络和设备出来。
那么什么时候把网络和设备放到这个NwkDescList链表里面的去呢?就是在搜索的过程中。 节点通过发送beacon request去搜索网络,该信道中的协调器或者路由器会在收到beacon request以后回beacon出来,带有自己的设备和网络信息。节点收到beacon以后就会触发
ZDO_beaconNotifyIndCB( NLME_beaconInd_t *pBeacon )这个callback函数,然后把网络设备的信息保存起来,并作一定的挑选。
如果节点满足了if ( ( (pChosenNwk = ZDApp_NwkDescListProcessing()) != NULL ) && (zdoDiscCounter > NUM_DISC_ATTEMPTS) )两个条件以后,就开始加网的状态,通过NLME_JoinRequest函数去向父设备发送Associate Request请求分配设备地址。
这个时候的状态已经切换成了DEV_NWK_JOIN的状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
if
( ( (pChosenNwk = ZDApp_NwkDescListProcessing()) != NULL ) && (zdoDiscCounter > NUM_DISC_ATTEMPTS) )
{
if
( devStartMode == MODE_JOIN )
{
ZDApp_ChangeState( 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 )
|
节点通过调用NLME_JoinRequest发送了Associate Request以后,父设备会通过Associate Response为其分配一个16位的短地址。节点在拿到这个短地址以后回会调用到ZDO_JoinConfirmCB( uint16 PanId, ZStatus_t Status ) 这个callback函数,最后会到ZDApp_ProcessOSALMsg( osal_event_hdr_t *msgPtr ) 函数的case ZDO_NWK_JOIN_IND: 下面。
在ZDApp_ProcessNetworkJoin( void ) 函数里面,如果网络使能了SECURE的情况下,设备进入了DEV_END_DEVICE_UNAUTH状态,这个状态其实就是等待Trust Center授权分配秘钥的状态。 如果网络没有使能SECURE的情况下,就直接调用
ZDApp_AnnounceNewAddress();告诉网络里面的设备,已经入网成功了,并且把状态改成了ZDApp_ChangeState( DEV_END_DEVICE );状态,这个时候就是入网已经成功了。
那么在使能SECURE的情况下,节点做了什么?
节点在进入DEV_END_DEVICE_UNAUTH状态以后就会开启一个认证失败的timer事件,ZDApp_ResetTimerStart( MAX_DEVICE_UNAUTH_TIMEOUT ); 如果在MAX_DEVICE_UNAUTH_TIMEOUT 时间里面还没有收到父设备发送的秘钥,或者收到的秘钥是错误的,那么就是(18)重新回到DEV_INIT,则复位重新开始加网的过程。
如果在timer 运行期间收到父设备发送过来的秘钥,ZDSecMgrTransportKeyInd( ZDO_TransportKeyInd_t* ind )-> ZDSecMgrAuthNwkKey()->ZDApp_DeviceAuthEvt();->ZDApp_ChangeState( DEV_END_DEVICE ), 并且这个时候把之前开启的timer停止掉了。最终切换到状态DEV_END_DEVICE状态。
终端设备在成功入网以后,状态成为了DEV_END_DEVICE。正常情况下该终端设备可以顺利的发送数据给父设备,也可以通过Data Request的方式向父设备获取发该子设备的数据。在实际应用层,可能出现终端设备移动,父设备掉电,因为干扰跟父设备通信多次失败,从而导致终端设备成为一个孤立的设备。如果连续发送数据没有收到父设备的MAC ACK,则触发设备进入孤立设备状态,调用到ZDO_SyncIndicationCB( uint8 type, uint16 shortAddr )函数,最后在ZDApp_ProcessOSALMsg( osal_event_hdr_t *msgPtr )触发下面的程序。
1
2
3
4
5
6
7
8
9
10
11
|
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 );
zgDefaultStartingScanDuration = BEACON_ORDER_60_MSEC;
ZDApp_NetworkInit( 0 );
}
break
;
|
启动的状态已经是MODE_RESUME,程序有重新执行到ZDApp_event_loop( uint8 task_id, UINT16 events )中的下面代码,再次开启启动设备的过程。
1
2
3
4
5
6
7
8
9
10
11
|
if
( events & ZDO_NETWORK_INIT )
{
// Initialize apps and start the network
ZDApp_ChangeState( DEV_INIT );
ZDO_StartDevice( (uint8)ZDO_Config_Node_Descriptor.LogicalType, devStartMode,
DEFAULT_BEACON_ORDER, DEFAULT_SUPERFRAME_ORDER );
// Return unprocessed events
return
(events ^ ZDO_NETWORK_INIT);
}
|
最后执行到ZDO_StartDevice中的下面代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
else
if
( startMode == MODE_RESUME )
{
if
( logicalType == NODETYPE_ROUTER )
{
ZMacScanCnf_t scanCnf;
ZDApp_ChangeState( 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
{
ZDApp_ChangeState( DEV_NWK_ORPHAN );
//set timer for scan and rejoin
osal_start_timerEx( ZDAppTaskID, ZDO_REJOIN_BACKOFF, zgDefaultRejoinScan );
ret = NLME_OrphanJoinRequest( runtimeChannel,
zgDefaultStartingScanDuration );
}
}
|
此时设备已经把状态从DEV_END_DEVICE状态切换到DEV_END_ORPHAN状态。通过调用NLME_OrphanJoinRequest函数向信道发送Orphan Notification告诉网络里面的设备,声明自己已经是一个孤立设备,这个Orphan Notification会在定义的每个信道上发送一次。
节点在发出Orphan Notification以后,节点的状态从DEV_END_ORPHAN切换到了NWK_JOINING_ORPHAN状态,在这个状态下节点会在之前定义的信道上发送Orphan Notification消息,如果有原来的父设备回复了Coord Realignment,节点认为原先的父设备重新找到了,则会调用到ZDO_JoinConfirmCB( PanId, Status );再调用到ZDApp_ProcessOSALMsg( osal_event_hdr_t *msgPtr )中的
1
2
3
4
5
6
|
case
ZDO_NWK_JOIN_IND:
if
( ZG_BUILD_JOINING_TYPE && ZG_DEVICE_JOINING_TYPE )
{
ZDApp_ProcessNetworkJoin();
}
break
;
|
然后再次到调用到ZDApp_AnnounceNewAddress();告诉网络里面的其他设备,再次加网成功了。这个时候节点的状态又切换到了DEV_END_DEVICE状态。
节点从End Device状态到Orphan状态,并不是每次都能通过Orphan Scan的方式,重新找到原先的父设备。比方说父设备断电,被移动走,或因为网内其他数据干扰导致父设备回复的Coord Realignment没有收到。
在这种情况下,节点开始重新搜索网络,而不是仅仅只找自己原先的父设备了。 如果在Orphan状态下,join失败的,也会调用到void ZDO_JoinConfirmCB( uint16 PanId, ZStatus_t Status )这个call back函数。再调用到ZDApp_ProcessOSALMsg( osal_event_hdr_t *msgPtr )
只不过这个时候的status变成了失败的状态。进入下面的代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
void
ZDApp_ProcessNetworkJoin(
void
)
......
else
if
(
devState == DEV_NWK_SEC_REJOIN_CURR_CHANNEL ||
devState == DEV_NWK_TC_REJOIN_CURR_CHANNEL ||
devState == DEV_NWK_TC_REJOIN_ALL_CHANNEL ||
devState == DEV_NWK_SEC_REJOIN_ALL_CHANNEL )
......
else
{
if
(
{
if
(
{
if
( _NIB.nwkPanId == 0xFFFF )
devStartMode = MODE_JOIN;
else
{
_tmpRejoinState =
true
;
prevDevState = DEV_NWK_SEC_REJOIN_CURR_CHANNEL;
}
}
// Do a normal join to the network after certain times of rejoin retries
else
if
( AIB_apsUseInsecureJoin ==
true
)
{
devStartMode = MODE_JOIN;
}
}
else
if
(devStartMode == MODE_REJOIN)
|
此时设备的状态devState=DEV_NWK_ORPHAN,并且devStartMode=MODE_RESUME,所以程序会运行到devStartMode = MODE_REJOIN。
最后在ZDApp_ProcessNetworkJoin里面运行到下面代码,开始重新搜索网络了。
1
2
|
ZDApp_NetworkInit( (uint16)(NWK_START_DELAY
+ (osal_rand()& EXTENDED_JOINING_RANDOM_MASK)) );
|
程序再次执行到ZDApp_event_loop( uint8 task_id, UINT16 events )下面代码,此时的状态就变成了DEV_INIT状态。
1
2
3
4
5
6
7
8
9
10
11
|
if
( events & ZDO_NETWORK_INIT )
{
// Initialize apps and start the network
ZDO_StartDevice( (uint8)ZDO_Config_Node_Descriptor.LogicalType, devStartMode,
DEFAULT_BEACON_ORDER, DEFAULT_SUPERFRAME_ORDER );
// Return unprocessed events
return
(events ^ ZDO_NETWORK_INIT);
}
|
在进入DEV_INIT状态以后,进入ZDO_StartDevice()函数,因为设备的startmode是MODE_REJOIN,所以按照下面的代码,进入discover状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
if
( ZG_BUILD_JOINING_TYPE && (logicalType == NODETYPE_ROUTER ||
{
if
( (startMode == MODE_JOIN) || (
{
ZDApp_ChangeState( DEV_NWK_DISC );
#if defined( MANAGED_SCAN )
ZDOManagedScan_Next();
ret = NLME_NetworkDiscoveryRequest( managedScanChannelMask, BEACON_ORDER_15_MSEC );
#else
ret =
#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.
runtimeChannel = MAX_CHANNELS_24GHZ;
}
#endif // ZIGBEE_FREQ_AGILITY
|
跟之前的状态2中,进入discovery状态以后做的事情一样,去信道上扫描原先的网络。但是这个时候不会去所有信道扫描了,只在刚才网络的信道扫描,因为默认认为网络的信道时不会改变的。
在扫描过程结束以后,程序同样执行到了NLME_NetworkDiscoveryConfirm(uint8 status)-> ZDO_NetworkDiscoveryConfirmCB(status)-> ZDApp_SendMsg( ZDAppTaskID, ZDO_NWK_DISC_CNF, sizeof(osal_event_hdr_t), (uint8 *)&msg );-> ZDApp_ProcessOSALMsg( osal_event_hdr_t *msgPtr )下面的case ZDO_NWK_DISC_CNF:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
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) )
{
.....................
else
if
(
{
ZStatus_t rejoinStatus;
// Transition state machine to correct rejoin state based on previous state before network discovery
if
( ZDApp_RestoreNwkKey( FALSE )== TRUE )
............................
// Perform Secure or Unsecure Rejoin depending on available configuration
if
( ZG_SECURE_ENABLED && ( ZDApp_RestoreNwkKey( TRUE ) == TRUE ) )
{
}
else
{
rejoinStatus = NLME_ReJoinRequestUnsecure( ZDO_UseExtendedPANID, pChosenNwk->logicalChannel);
}
if
( rejoinStatus != ZSuccess )
{
ZDApp_NetworkInit( (uint16)(NWK_START_DELAY
+ ((uint16)(osal_rand()& EXTENDED_JOINING_RANDOM_MASK))) );
}
|
由于此次扫描前start mode的状态,会开始进入rejoin状态,通过调用NLME_ReJoinRequest( ZDO_UseExtendedPANID, pChosenNwk->logicalChannel);来发送Rejoin Request消息。
DEV_NWK_REJOIN状态到DEV_END_DEVICE(15)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
else
if
( devState == DEV_NWK_ORPHAN ||
devState == DEV_NWK_TC_REJOIN_CURR_CHANNEL ||
devState == DEV_NWK_TC_REJOIN_ALL_CHANNEL ||
devState == DEV_NWK_SEC_REJOIN_ALL_CHANNEL )
{
......................
// results of an orphaning attempt by this device
// results of an orphaning attempt by this device
{
//When the device has successfully rejoined then reset retryCnt
retryCnt = 0;
// Verify NWK key is available before sending Device_annce
if
( ZG_SECURE_ENABLED && ( ZDApp_RestoreNwkKey( TRUE ) ==
false
) )
{
// wait for auth from trust center
ZDApp_ChangeState( DEV_END_DEVICE_UNAUTH );
// Start the reset timer for MAX UNAUTH time
ZDApp_ResetTimerStart( MAX_DEVICE_UNAUTH_TIMEOUT );
}
else
{
osal_stop_timerEx( ZDAppTaskID, ZDO_REJOIN_BACKOFF );
// setup Power Manager Device
#if defined ( POWER_SAVING )
osal_pwrmgr_device( PWRMGR_BATTERY );
#endif
// The receiver is on, turn network layer polling off.
if
( ZDO_Config_Node_Descriptor.CapabilityFlags & CAPINFO_RCVR_ON_IDLE )
{
// if Child Table Management process is not enabled
if
( zgChildAgingEnable == FALSE )
{
NLME_SetPollRate( 0 );
NLME_SetQueuedPollRate( 0 );
NLME_SetResponseRate( 0 );
}
}
if
( ZSTACK_ROUTER_BUILD )
{
|