【转】ZigBee终端入网方式深入分析
前述
继之前对终端Direct Join的分析,发现很多东西还很模糊,存在很多问题。终于找到时间继续深入挖下去,这次应该比较完整地搞清了终端的入网机制,并纠正之前的几个认识偏差。
由于Z-Stack网络层并不开源,所以一些地方是靠的推测,很多地方的结论也没有实验验证,谨留给诸君参考和斧正。
ZigBee2007协议规范分析
先来看看ZigBee2007协议规范是怎样规定入网请求的:
The semantics of this primitive are as follows:
NLME-JOIN.request
{
ExtendedPANId,
RejoinNetwork,
ScanChannels,
ScanDuration,
CapabilityInformation,
SecurityEnable
}The next higher layer of a device generates this primitive to request to:
- Join a network using the MAC association procedure.
- Join or rejoin a network using the orphaning procedure.
- Join or rejoin a network using the NWK rejoin procedure.
- Switch the operating channel for a device that is joined to a network.
就此原语的描述可以看出,前三种情况均为设备入网的方式,最后一个是为设备切换信道所用,暂不考虑。所以ZigBee设备入网有三种方式,我们分别称之为Join、Orphan Join、Rejoin。三种方式RejoinNetwork参数分别设置为0x00、0x01、0x02。
Join入网过程。首先发起Network Discovery,返回所有应答的节点的信息。在发现的结果中找出符合要求(这里的要求是一些最基本的条件,详见ZigBee协议规范3.6.1.4.1.1节,下同)的父节点,向它发送入网请求。父节点分配16位网络地址。
Rejoin入网过程。发起Network Discovery,在应答的节点中挑选出和自己的ExtendedPANID相同的节点,在这些节点中找出符合要求的父节点,发送入网请求,并且使用自己已拥有的16位网络地址(若没有,则随机生成一个)。
Orphan Join过程。发起Orphan Scan,寻找邻居表中保存有本设备IEEE地址的父节点,在返回结果中找出符合要求的父节点,发送入网请求。父节点返回邻居表中保存的16位网络地址。
可以看出三种入网的过程都可以归纳为网络扫描+选择目标。三者的选择的筛选条件是递增的:任何节点—>指定PANID的节点—>邻居表中有自己信息的节点。
Z-Stack协议栈分析
版本号:ZStack-CC2530-2.5.1a
1. 第一步 扫描
下面是设备启动的函数ZDO_StartDevice,它是设备入网流程的入口,这个函数仅在ZDApp_event_loop事件轮询函数中发生ZDO_NETWORK_INIT事件的时候被调用,而ZDApp_NetworkInit函数就是用来延时发送ZDO_NETWORK_INIT事件的,所以ZDApp_NetworkInit函数也是设备入网过程的触发,这个函数下面将被用到。
这里我只把与终端启动的相关代码贴了出来:
/*********************************************************************
* @fn ZDO_StartDevice
*
* @brief This function starts a device in a network.
*
* @param logicalType - Device type to start
* startMode - indicates mode of device startup
*
* @return none
*/
void ZDO_StartDevice( byte logicalType, devStartModes_t startMode, byte beaconOrder, byte superframeOrder )
{
ZStatus_t ret;
ret = ZUnsupportedMode;
if ( (startMode == MODE_JOIN) || (startMode == MODE_REJOIN) )
{
devState = DEV_NWK_DISC;
ret = NLME_NetworkDiscoveryRequest( zgDefaultChannelList, zgDefaultStartingScanDuration );
}
else if ( startMode == MODE_RESUME ) //Orphan Join
{
devState = DEV_NWK_ORPHAN;
ret = NLME_OrphanJoinRequest( zgDefaultChannelList,
zgDefaultStartingScanDuration );
}
if ( ret != ZSuccess )
{
osal_start_timerEx(ZDAppTaskID, ZDO_NETWORK_INIT, NWK_RETRY_DELAY );
}
}
从上面可以看出,终端的入网第一步就是调用了这两个函数NLME_NetworkDiscoveryRequest、
NLME_OrphanJoinRequest(放到第3步再看),而Join和Rejoin方式的这一部分是完全相同的。从TI的API手册中可以查到:
NLME_NetworkDiscoveryRequest()
此函数请求网络层寻找相邻路由器。这个函数应该在加入并执行网络扫描前调用。扫描确认结果将被返回到ZDO_NetworkDiscoveryConfirmCB()回调函数中。……
2. 扫描结果
在ZDO_NetworkDiscoveryConfirmCB()回调函数中发现,就做了一件事,就是向ZDApp_event_loop发送ZDO_NWK_DISC_CNF事件,直接找到ZDO_NWK_DISC_CNF事件的处理函数(为了方便分析,只留下了关键的函数名):
case ZDO_NWK_DISC_CNF:
if (devState != DEV_NWK_DISC)
break;
if ( ZG_BUILD_JOINING_TYPE && ZG_DEVICE_JOINING_TYPE )
{
networkDesc_t *pChosenNwk;
if ( ( (pChosenNwk = ZDApp_NwkDescListProcessing()) != NULL ) &&
(zdoDiscCounter > NUM_DISC_ATTEMPTS) )
{
if ( devStartMode == MODE_JOIN )
{
devState = DEV_NWK_JOINING;
if ( NLME_JoinRequest( pChosenNwk->…… ) != ZSuccess )
{
ZDApp_NetworkInit(…… );
}
}
else if ( devStartMode == MODE_REJOIN )
{
devState = DEV_NWK_REJOIN;
if ( _NIB.nwkDevAddress == INVALID_NODE_ADDR )
{
// 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.nwkPanId == INVALID_PAN_ID )
{
// Check if the device has a valid PanID,
// if not, set it to the discovered Pan
}
if ( NLME_ReJoinRequest( ……) != ZSuccess )
{
ZDApp_NetworkInit( …… );
}
}
}
else
{
if ( continueJoining )
{
zdoDiscCounter++;
ZDApp_NetworkInit( …… );
}
}
}
break;
通过简化了的代码可以看出,对于扫描结果的处理是这样一个流程:首先需进行至少NUM_DISC_ATTEMPTS次扫描,每次都调用ZDApp_NetworkInit进行重新扫描,如果找到了合格的父节点(pChosenNwk = ZDApp_NwkDescListProcessing()) != NULL),就依照MODE_JOIN或 MODE_REJOIN 分别调用NLME_JoinRequest或NLME_ReJoinRequest向目标父节点发送请求。由于后者的请求中要附带自己的PANID和ShortAddress,所以要事先检查和处理。
从这里可以看出,不管是Join还是Rejoin,如果找不到可用的父节点,将持续调用ZDApp_NetworkInit扫描网络,陷入死循环。
3. 加入父节点
着眼到NLME_JoinRequest和NLME_ReJoinRequest,以及前面的NLME_OrphanJoinRequest上,从TI的API手册中可以查到:
NLME_OrphanJoinRequest()
此函数请求网络层孤立地连接到网络上。此函数是一个默示加入形式的扫描。此函数的结果(状态值)返回到ZDO_JoinConfirmCB()回调函数中。……NLME_JoinRequest ()
此函数允许相邻的更高层请求设备将自己加入到一个网络中。此函数的结果(状态)返回到ZDO_JoinConfirmCB()回调函数中。……NLME_ReJoinRequest ()
使用此函数重新加入一个设备已经加入过的网络。此函数的结果(状态)返回到ZDO_JoinConfirmCB()回调函数中。……
ZDO_JoinConfirmCB()一样只做了一件事,就是向ZDApp_event_loop发送事件ZDO_NWK_JOIN_IND。
下面是ZDO_NWK_JOIN_IND事件的处理函数ZDApp_ProcessNetworkJoin(已简化):
void ZDApp_ProcessNetworkJoin( void )
{
if ( (devState == DEV_NWK_JOINING) ||
((devState == DEV_NWK_ORPHAN) &&
(ZDO_Config_Node_Descriptor.LogicalType == NODETYPE_ROUTER)) )
{
// Result of a Join attempt by this device.
if ( nwkStatus == ZSuccess )
{
osal_set_event( ZDAppTaskID, ZDO_STATE_CHANGE_EVT );
if ( devState == DEV_NWK_JOINING )
{
ZDApp_AnnounceNewAddress();
}
devState = DEV_END_DEVICE;
}
else
{
if ( (devStartMode == MODE_RESUME) &&
(++retryCnt >= MAX_RESUME_RETRY) )
{
if ( _NIB.nwkPanId == 0xFFFF || _NIB.nwkPanId == INVALID_PAN_ID )
devStartMode = MODE_JOIN;
else
{
devStartMode = MODE_REJOIN;
_tmpRejoinState = true;
}
}
/******************************/
/*some process*/
/******************************/
zdoDiscCounter = 1;
ZDApp_NetworkInit( …… );
}
}
else if ( devState == DEV_NWK_ORPHAN || devState == DEV_NWK_REJOIN )
{
// results of an orphaning attempt by this device
if (nwkStatus == ZSuccess)
{
devState = DEV_END_DEVICE;
osal_set_event( ZDAppTaskID, ZDO_STATE_CHANGE_EVT );
ZDApp_AnnounceNewAddress();
}
else
{
if ( devStartMode == MODE_RESUME )
{
if ( ++retryCnt <= MAX_RESUME_RETRY )
{
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( …… );
}
}
}
至此终端就完成了入网的全部流程,如果被父节点接受,那么入网成功;如果失败,则重新开始入网流程。
4. 提出问题
可以看出,函数中没有对失败时的Join方式或Rejoin方式做任何的处理,毫无疑问,两种方式下都将无限重试直到入网成功。并没有实现所谓的:
// Do a normal join to the network after certain times of rejoin retries
那么分析Orphan Join,而根据源代码的逻辑,如果是路由器(NODETYPE_ROUTER)执行Orphan Join,那么当重试次数超过MAX_RESUME_RETRY时,将根据是否搜索到了父节点(_NIB.nwkPanId == 0xFFFF || _NIB.nwkPanId == INVALID_PAN_ID),将入网方式重置为Join方式或Rejoin方式。那么针对Rejoin方式和终端(NODETYPE_DEVICE)的Orphan Join方式呢,很令人费解:
if ( devStartMode == MODE_RESUME )
{
if ( ++retryCnt <= MAX_RESUME_RETRY )
{
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;
}
}
不管怎样,失败的Orphan Join都将直接被置为Join或Rejoin,在这里条件 (++retryCnt <= MAX_RESUME_RETRY)好像总是成立的。那么有没有可能是其他地方对retryCnt进行了修改,搜索遍整个工程,除了这个函数中有对retryCnt的+操作外,只有两处地方对retryCnt进行了赋值,一处是定义时的初始化,一处是断网重连,执行Orphan Join前对retryCnt的清零。
所以,对于终端来说,都只能执行一次Orphan Join,与宏定义MAX_RESUME_RETRY毫无关系。
这到底是TI有意为之,还是逻辑的Bug呢?这个问题有待日后解决。
【转】ZigBee终端入网方式深入分析的更多相关文章
- ZigBee设备入网流程之关联方式
ZigBee设备入网流程 ZigBee设备入网有关联方式和直接方式两种,我所熟悉的是关联方式,这也是最常用的方式. 关联方式 step1 设备发出Beacon Request 设备会在预先设置的几个信 ...
- 【转】zigbee终端无法重连的问题解决
zigbee终端无法重连的问题解决 1.zigbee重连的原因 (1)zigbee由于各种原因的干扰导致信号太差而掉线. (2)协调器重启. 2.zigbee终端重连的处理 (1)zigbee掉线后会 ...
- LoRaWAN协议(五)--OTAA入网方式详述
前言 OTAA(Over-The-Air Activation),是LoRaWAN的一种空中入网方式.当node在上电的时候处于非入网状态时,需要先入网才能和服务器进行通信.其操作就是node发送jo ...
- LoRaWAN协议(四)--入网方式概述
前言 在LoRaWAN中,node最终和服务器能够正常数据交互,需要先入网,入网的本质,也就是获得一些通信相关的参数,有以下几个: NwkSKey AppSKey DevAddr DevEui 其中 ...
- ubuntu-12.10-server中打开终端的方式
ubuntu-12.10-server系统在图形界面的任务栏上找不到终端的踪影,可以使用以下两种方式调出 1.在图形界面中点击Dash Home 点击后搜索terminal即可 2.可以通过快捷键CT ...
- linux shell终端打开方式
前言 Linux操作系统没有Window操作系统界面友好,使用者需要使用命令与系统进行交互,交互媒介为shell终端. 有三种方式可以打开终端: 方法一: 打开新的窗口并打开shell终端,快捷键:c ...
- 记录一个终端入网小助手的bug
背景:技术leader拿到一台超薄笔记本,系统标准化安装,笔记本一开机风扇嗡嗡响,键盘也开始发烫,资源占用排名前三的进程都是终端管理软件,一下子就找上门了.处理:进程分析发现异常,卸载入网小助手后恢复 ...
- ZigBee 入网详解
本文将根据Sniffer来详细解释ZigBee终端设备入网的整个流程,原创博文. 当协调器建立好网络后,终端设备执行zb_startrequest函数,准备入网时,他们两者之间详细的流程如下.
- TI Zigbee Light Link 参考设计
TI Zigbee Light Link 参考设计 原文出处: http://processors.wiki.ti.com/index.php/Category:ZigBee_Light_Link ...
随机推荐
- Android Studio:Failed to resolve ***
更换电脑后,也更新了所有的SDK的tool,仍然报错:Failed to resolve 各种jar包,出现这种问题主要是因为在Android studio中默认不允许在线更新,修改方法如下:
- Atitit.研发团队与公司绩效管理的原理概论的attilax总结
Atitit.研发团队与公司绩效管理的原理概论的attilax总结 1. 四个理念 1 1.1. 绩效管理的三个目的.四个环节.五个关键2 1.2. 绩效目标smart2 2. 考核对象2 3. 绩效 ...
- 定时Job在IIS中潜在危险-IIS 定期回收
引言 有时我们会在IIS中启用一些定时服务,但是你必须清楚IIS会定期回收Asp.net的应用程序的.首先来看IIS啥时候回收APPDomain. APPDomain 回收时机 There are ...
- Windows平台分布式架构实践 - 负载均衡(下)
概述 我们在上一篇Windows平台分布式架构实践 - 负载均衡中讨论了Windows平台下通过NLB(Network Load Balancer) 来实现网站的负载均衡,并且通过压力测试演示了它的效 ...
- DTO – 服务实现中的核心数据
在一个Web服务的实现中,我们常常需要访问数据库,并将从数据库中所取得的数据显示在用户页面中.这样做的一个问题是:用于在用户页面上展示的数据和从数据库中取得的数据常常具有较大区别.在这种情况下,我们常 ...
- [Intel Edison开发板] 03、Edison开发IDE入门及跑官方提供的DEMO
一.启动Eclipse爱迪生开发板IDE eclipse开发环境在iss-iot-win_03-14-16中,但是一定每次都是点bat脚本启动,否则就会少东西(windows->preferen ...
- ABP源码分析二十九:ABP.MongoDb
这个Module通过建立一个MongoDbRepositoryBase<TEntity> 基类,封装了对MongoDb数据库的操作. 这个module通过引用MongoDB.Driver, ...
- HTML (超文本标记语言)
<html> --开始标签 <head> 网页上的控制信息 <title>页面标题</title> </head> <body> ...
- java中判断list是否为空的用法
1.如果想判断list是否为空,可以这么判断: if(null == list || list.size() ==0 ){ //为空的情况 }else{ //不为空的情况 } 2.list.isEmp ...
- Android开发学习之路-Android中使用RxJava
RxJava的核心内容很简单,就是进行异步操作.类似于Handler和AsyncTask的功能,但是在代码结构上不同. RxJava使用了观察者模式和建造者模式中的链式调用(类似于C#的LINQ). ...