写一份赏心悦目的工程文档,是很困难的事情。若想写得完善,不仅得用对工具(use the right tools),注重文笔,还得投入大把时间,真心是一件难度颇高的事情。但,若是真写好了,也是善莫大焉:既可让人明白「为何如此设计」,即「知其然更知其所以然」;也能剥离一些琐碎的细节,让更多没那么多时间与精力、或者背景知识不足的朋友,对核心方法和思路,多一点理解,即,给人提供一种「纲举目张提纲挈领抽丝剥茧」的可能性。
机缘巧合,俺今天就决定抛砖引玉,写一篇不那么好的工程文档。也期望对本文话题感兴趣的朋友,将其扩展或者重构成一篇优秀的工程文档。
背景
Z-Stack 是德州仪器(Texas Instrument)的半开源 Zigbee 协议栈。Z-Stack 2.5.1a 是其发布的最后一个独立发行版;所谓「独立发行版」,即,提供的版本里,既包括了诸如智能家居的 Home Automation profile 相关内容,也包括了诸如集中抄表的 Smart Energy profile 相关内容,通俗地讲,大杂烩。往后的 Z-Stack,则是捆绑在单独的 profile 里,不再提供单独下载。(本信息参考链接,亦可参考 2.5.1a 的 release note)
为何采取这种策略?(以下是个人理解)还得先说说 Zigbee 诞生的初衷。
Zigbee 原本是为了解决低功耗局域网的互操作性问题,而诞生的一个基于 802.15.4 层的协议。假设,你家里的灯泡采取了 SmartBlub
协议(胡诌的名字),空气净化设备使用了私有 AirCleaner 协议,集中控制器则是 SmartController
协议,那么恭喜你,鸡同鸭讲的窘状,在你家的「智能家居」之间发生了。遇上如此窝火的事情,你当然会把这群「智能设备」骂一遍,毕竟阿Q说过,虫豸才不骂人。
为了避免高素质的你受委屈,Zigbee
诞生了:灯泡们,空调们,大伙都使用 Zigbee 协议,大家好才是真的好!
然而,世界是复杂的。除了智能家居,还有很多需要互联互通互操作的领域,如能源管理,医疗,建筑自动化。你会问,如此多迥异的应用场景,彼此也会有不同的拓扑和通信需求,同一套 Zigbee 协议,可以满足全部的场景吗?Zigbee 响亮地吼道「五大受损一个对策」!只可惜,Zigbee 不是欧莱雅,为了应对这些不同的场景,其不得不折腾了若干不同的 profile(配置),比如,上文你看到的 Home Automation 和 Smart Energy。
So,聪明的你已经看完了开头,也应该料到了结局:profile 的分化,是任何试图「不完全开源(槽点)但提供完善服务(优点)」的 Zigbee 协议栈供应商不得不面临的结局……
问题
以下部分,都是针对 Z-Stack 2.5.1a 版本。
Zigbee 角色中,有 Coordinator / Router / End-Device 三种角色。end-device 设备主要是做为传感节点,将采集到的数据信息,以及平时的控制信息(如维持连接的心跳数据包,命令控制数据包,等等)发送到 router / coordinator 设备上。下文称呼:节点 / 终端节点 / 传感节点,都是指代 End-device;路由 / 中继,都是指代 Router;协调器 / AP / Zigbee 网关,都是指代 Coordinator。
本文讲述的部分,主要是针对节点的入网控制部分。网上很多朋友遇到的问题,归纳起来,都类似如下两个典型问题:
1. router / coordinator 不存在时,或者因为信号强度过低而链路断开时,从 sniffer 嗅探器里可观察到,end-device 频繁发送 beacon 导致传感节点的电池电量(往往传感节点都是电池供电)被消耗殆尽。何解?
2. 在只有一群 end-device 和一个 coordinator 的稳定运行网络里,更换 coordinator
后,节点无法再次入网。如何破?
注意:这里故意不牵涉任何关于 router 的问题,因为 router 和 coordinator 在 zigbee
网络里,角色行为有一定的重合度,会使得问题本身复杂化;考虑到本文的重点是 end-device 的入网行为上,故简化问题,去掉 router。
解决办法
对上述两个问题,给出一些解决问题的建议,抛砖引玉,以供参考。
1. 这个问题比较简单。仅从解决方法入手,只需修改两个配置即可:
聪明的你一定知道 f8wConfig.cfg 文件的存在。这其中,涵盖了一众 Zigbee 协议栈的配置信息。
# file: \Projects\zstack\Tools\CC2530DB\f8wConfig.cfg
-DBEACON_REQUEST_DELAY=3000 # from default 100 –> 3000
-DBEACON_REQ_DELAY_MASK=0x0FFF # from default 0x00FF –> 0x0FFF
其中 –D 前缀表示预处理器的 #define 宏定义。而上述两个宏定义,前者,表示 beacon request 之间的延时 delay,后者,表示延时的掩码 mask(引入随机性),即真正的延时是 delay + rand() & mask。
可以通过简单的计算得知,起初 delay 默认是 100 毫秒,mask 默认是 0x00FF,延时的上下限分别是 [100, 355];修改成 3000 毫秒和 0x0FFF 掩码后,上下限则分别变成了 [3000, 7095]。取平均值 228ms 和 5048ms,可见平均延时增加了 20 倍左右。如果一直处于 beacon request 搜网过程里,原本能持续搜索 5 天的电池容量,一下子可以持续支撑 3 个月。很简单,对吧!
2. 根据假设,网络里只有一群 end-device 和 coordinator,并且已经稳定运行ing。突然你的小猫小狗跑过来,把协调器从桌上扔到了地上,不幸翘腿,以至于你不得不更换协调器。问题来了:节点依然在发送 beacon request,新的协调器也一直在回应 superframe(dev.Cap 也是 1,即还有剩余的 end-device capacity、允许节点入网),可节点就是没有 association request,更别提入网了…… 你感到很沮丧,把小猫小狗批评了一顿,尽管它们不懂你在说什么。
要解决这个问题,有两种全然不同的方法。
a) 第一种,简单粗暴,软件重启。
聪明的你肯定知道,sample application 应用的 SampleApp_ProcessEvent 函数中,有一个 ZDO_STATE_CHANGE 的系统消息,是 ZDO (Zigbee Device Object) 层发来的消息称「哥状态有变,兄弟们请根据新状态自行作出处理」。
啥情况下 SampleApp_ProcessEvent 会收到来自 ZDO 的 STATE_CHANGE 消息呢?
在上述问题情况下,由于 end-device 不知 coordinator 已经翘腿,所以一旦传感器检测到某种信息,它依然傻兮兮的给协调器发过去;自然,它无法收到来自协调器的 MAC layer ACK 即确认响应(confirmation acknowledgement);从 sniffer 上观察,你会发现收不到响应的 end-device 多次重传数据包。
多次重传数据包都失败(都没有收到 MAC layer ACK)后,end-device 就会认为已经同父节点(parent-node)失去了联系,遂敦促 ZDO 发送状态变更(先前是 DEV_END_DEVICE 状态,如今和父节点失联,成为了孤儿节点状态,即 DEV_NWK_ORPHAN)。
一旦 SampleApp_ProcessEvent 收到状态变更为孤儿,软件重启开始新一轮搜网入网过程即可。
b) 第一种方法过于简单粗暴,以至于你无法完成一些更完善的操作,以应对更复杂的现实环境。
不妨再假设,你的协调器被小猫小狗扔到地上后,并没有翘腿,而仅仅是电池脱落(暂时休克)。正在厕所里拉屎的你,听到响声后,虽心有余,而力不足,无法立刻冲出厕所,拯救暂时休克的协调器,只能眼睁睁看着 end-device 给协调器发数据而收不到 MAC layer ACK、从而自认为变成了孤儿……
根据第一种方法,end-device 的传感器或许检测到了某个重要的信息,而一旦重启,除非你将数据写入了 NV 即 non-volatile
区域,否则,信息将丢失……
其实,end-device 只需要再等两分钟,等你走出厕所,给协调器安上电池后,它就可以直接通过 orphan
notification(而不是 beacon request),以更少的空中交互次数、更低的能量消耗完成再次入网…… 如何做到这一点?
聪明的你肯定可以搜索到 devStartModes_t 枚举类型,这个枚举状态,决定了节点入网的默认行为。
如,MODE_RESUME 对应于孤儿节点状态,即试图通过 orphan notification 入网;而 MODE_REJOIN 和 MODE_JOIN 虽然都是发送 beacon request 入网,但 REJOIN 希望看到 superframe.extended_PAN_ID 字段(扩展 PAN ID),等于其变成孤儿状态之前的网络的 extended PAN ID。
节点同父节点失联后,MAC / NWK 层自然是最先得知此消息,为了尽快告知其它层的兄弟们,MAC / NWK 通过 ZDO 层的回调函数 ZDO_SyncIndicationCB(),告知说「咱们已经同父节点失联,哥们请负责通知其他兄弟」。此函数遂发送 ZDO_NWK_JOIN_REQ 消息给 ZDApp,通知说「我们已经失联,请尽快处理重新入网事宜」。
接下来的事情,聪明的你应该可以通过阅读 Z-Stack 网络部分的源码自行搞定了。如果想要把 Zigbee primitive 即原语弄得很清楚,可以参考 Zigbee 2007 Specification,戳这里可以参考下俺之前阅读源码时的一些摘要。
整体来讲,理解清楚 request confirmation indication 的含义,应该就可以比较顺利的理解网络层代码了。比如,假设现在是 MODE_JOIN 入网模式,ZDO_StartDevice() 里的 NLME_NetworkDiscoveryRequest() 会请求「发送 beacon 获取周围的父节点们」,对于回应的 superframe(s) 数据帧,会通过 ZDO_beaconNotifyIndCB() 做处理,而 Network Discovery 的结果,则会通过 ZDO_NetworkDiscoveryConfirmCB() 来反馈,等等。这篇博客里,也在理论层面上,描述了整体的入网过程。
福利
你时间少,事情多,今天要陪人打牌,明天要陪人吃饭,后天得去旅游,实在是没时间阅读那么多的网络层源码,如何破?戳这里下载福利。这里设计的入网行为(参考链接里的《入网行为设计》),是相当宽泛的设定,应该可以满足绝大多数场景的需求,而且可简单定制(参考 ZDApp.c 中的 gl_zdo_prepare_init_cnt_timeout 二维数组)。
虽然 z-stack 本身是半开源且能够公开下载的,但考虑到这里对其做了一些裁剪和改动,为了防止和 TI 的发布协议之间有任何冲突,上述链接里的压缩包是加密的。
如果你希望获取解压密码,请给我发送邮件,表明自己使用 z-stack 正在做何种类型的项目(俺也顺便做点小调研,获得一些反馈)。一句话信息诸如「求密码」是会被直接无视的。博客左侧的链接即是邮件地址。谢绝骚扰。
「2014-5-31」Z-Stack - Modification of Zigbee Device Object for better network access management