寫一份賞心悅目的工程文檔,是很困難的事情。若想寫得完善,不只得用對工具(use the right tools),注重文筆,還得投入大把時間,真心是一件難度頗高的事情。但,如果真寫好了,也是善莫大焉:既可以讓人明白「爲什麼如此設計」,即「知其然更知其因此然」;也能剝離一些瑣碎的細節,讓更多沒那麼多時間與精力、或者背景知識不足的朋友,對核心方法和思路,多一點理解,即,給人提供一種「綱舉目張提綱挈領抽絲剝繭」的可能性。node
機緣巧合,俺今天就決定拋磚引玉,寫一篇不那麼好的工程文檔。也指望對本文話題感興趣的朋友,將其擴展或者重構成一篇優秀的工程文檔。git
背景github
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 協議,你們好纔是真的好!app
然而,世界是複雜的。除了智能家居,還有不少須要互聯互通互操做的領域,如能源管理,醫療,建築自動化。你會問,如此多迥異的應用場景,彼此也會有不一樣的拓撲和通訊需求,同一套 Zigbee 協議,能夠知足所有的場景嗎?Zigbee 響亮地吼道「五大受損一個對策」!只惋惜,Zigbee 不是歐萊雅,爲了應對這些不一樣的場景,其不得不折騰了若干不一樣的 profile(配置),好比,上文你看到的 Home Automation 和 Smart Energy。less
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 正在作何種類型的項目(俺也順便作點小調研,得到一些反饋)。一句話信息諸如「求密碼」是會被直接無視的。博客左側的連接便是郵件地址。謝絕騷擾。