寫一份賞心悅目的工程文檔,是很困難的事情。若想寫得完善,不僅得用對工具(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. 這個問題比較簡單。僅從解決方法入手,只需修改兩個配置即可:
# file: \Projects\zstack\Tools\CC2530DB\f8wConfig.cfg -DBEACON_REQUEST_DELAY=3000 # from default 100 -> 3000 -DBEACON_REQ_DELAY_MASK=0x0FFF # from default 0x00FF -> 0x0FFF
聰明的你一定知道 f8wConfig.cfg 文件的存在。這其中,涵蓋了一眾 Zigbee 協議棧的配置信息。
其中 –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 正在做何種類型的項目(俺也順便做點小調研,獲得一些反饋)。一句話信息諸如「求密碼」是會被直接無視的。博客左側的鏈接即是郵件地址。謝絕騷擾。