物聯網系統中的網關內部程序應該如何設計?物聯網系統中,設備之間是如何通信的;網關中的進程之間消息總線通信模型;網關內部消息總線上的數據如何與服務器進行通信;作為消遣,了解一下物聯網系統中的一些基本知識。
物聯網這個詞語的范疇太廣,似乎所有的硬件設備,只要能夠接入網絡,就可以稱之為物聯網產品,似乎物聯網這個詞可以把一切都納入到其中。
在設計一個應用程序的架構時,可以通過多線程來實現,也可以通過多進程來實現,每個人的習慣都不一樣,各有各的好處。我們這里不去討論孰優孰劣,因為我對多進程這樣的設計思想比較偏愛,所以就直接按照多進程的程序架構來討論。
3.1 網關中需要哪些進程
網關中需要執行的所有進程,是根據網關的功能來決定的,假設包括如下的功能:
(1)連接外網的進程 Proc_Bridge
網關需要連接到云端的服務器,需要一個進程與服務器之間保持長連接,這樣就可以及時接收到服務器發來的控制指令,以及把系統內部數據及時上報給服務器。
這個進程需要把從服務器接收到的指令轉發到網關系統內部,把從系統內部接收到的信息轉發給服務器,類似于橋接的功能,因此命名為 Proc_Bridge。
(2)設備管理進程 Proc_DevMgr
這個進程用來執行設備管理功能,設備的添加(入網)、刪除(退網),都由此進程來管理。
(3)協議轉換進程 Proc_Protocol
下行:把應用層的統一通信協議,轉換成不同類型無線通信協議,發送給相應的無線模塊。
上行:把設備上報的、不同類型的無線通信協議,轉換成應用層的統一通信協議。
(4)邊沿計算進程(自動化控制) Proc_Auto
很明顯,這需要一個獨立的進程來處理各種計算,這個進程就相當于系統的大腦。
(5)無線通信協議相關的進程 Proc_ZigBee, Proc_RF, Proc_ZWave
在硬件上,每一種無線通信模塊通過串口或其他硬件連接方式與到網關的 CPU 進行通信,因此,每一種無線通信模塊都需要一個相應的進程來處理。
(6)其他“軟設備”進程 Proc_Xxx
在之前的項目中,還遇到一些硬件設備,它們與門磁、插座等設備在邏輯上處于同一個層次,但是與網關之間是通過 TCP 來連接。對于這樣的設備,也可以使用一個獨立的進程來進行管理。
上面的這些進程,在網關中的運行模型如下:
3.2 MQTT消息總線
以上這些進程之間需要相互通信,不是簡單的點對點通信,而是一個網狀的通信模型。比如:
設備管理進程 Proc_DevMgr:當任何一種設備被添加到系統中時,都需要處進行處理,因此它需要與 Proc_ZigBee, Proc_RF, Proc_ZWave 這些進程進行通信;當某個設備上報數據時(例如:Proc_ZigBee),Proc_Protocol 進程需要把數據進行協議轉換,然后 Proc_Bridge 進程把轉換后的數據上報給服務器,同時 Proc_Auto 進程需要檢查這個設備上報的數據是否觸發了其他相關聯的設備;
也就是說,這些進程中間的通信是相互交叉的,如果通過傳統的 IPC 方式(共享內存、命名管道、消息隊列、Socket)等,處理起來比較復雜。
引入了 MQTT 消息總線之后,每個進程只需要掛載到總線上。每個進程只需要監聽自己感興趣的 topic,就可以接收到相應的數據。
既然這些進程之間的通信關系比較復雜,那么一個良好的 topic 設計規范就顯得很重要了!
3.3 Topic 的設計
MQTT 的通信模型是基于訂閱/發布的模式,一個客戶端(進程)接入到消息總線之后,需要注冊自己感興趣的 主題 topic,其他客戶端(進程)往這個 topic 發送消息,即可被訂閱者接收到。
主題 topic 是一個以反斜線(/)分割的字符串,用來表示多層的分級結構,例如下面的這 2 個 topic,是亞馬遜 AWS 平臺中在線升級(OTA)相關的 topic:
$aws/things/MyThing/jobs/get/accepted$aws/things/MyThing/jobs/get/rejected
在我們的示例場景中,可以按照下面這樣來設計主題 topic:
(1) Proc_DevMgr
訂閱主題:
$iot/v1/ZigBee/Register $iot/v1/ZigBee/UnRegister $iot/v1/RF/Register $iot/v1/RF/UnRegister $iot/v1/ZWave/Register $iot/v1/ZWave/UnRegister
(2) Proc_Bridge
訂閱主題:
$iot/v1/Device/Report
發出數據的主題:
$iot/v1/Device/Control $iot/v1/Device/Remove $iot/v1/Auto/AddRule $iot/v1/Auto/RemoveRule
(3) Proc_Protocol
訂閱主題:
$iot/v1/Device/Control $iot/v1/Device/Remove $iot/v1/ZigBee/Report $iot/v1/RF/Report $iot/v1/ZWave/Report
發送數據主題:
$iot/v1/Device/Report $iot/v1/ZigBee/Control $iot/v1/ZigBee/Remove $iot/v1/RF/Control $iot/v1/RF/Remove $iot/v1/ZWave/Control $iot/v1/ZWave/Remove
(4) Proc_Auto
訂閱主題:
$iot/v1/Auto/AddRule $iot/v1/Auto/RemoveRule $iot/v1/Device/Report
發送數據主題:
$iot/v1/Device/Control
(5) Proc_ZigBee
訂閱主題:
$iot/v1/ZigBee/Control $iot/v1/ZigBee/Remove
發送數據主題:
$iot/v1/ZigBee/Register $iot/v1/ZigBee/UnRegister $iot/v1/ZigBee/Report
(6) Proc_RF
訂閱主題:
$iot/v1/RF/Control $iot/v1/RF/Remove
發送數據主題:
$iot/v1/RF/Register $iot/v1/RF/UnRegister $iot/v1/RF/Report
(7) Proc_ZWave
訂閱主題:
$iot/v1/ZWave/Control $iot/v1/ZWave/Remove
發送數據主題:
$iot/v1/ZWave/Register $iot/v1/ZWave/UnRegister $iot/v1/ZWave/Report
以上這些主題 topic 的設計,還是有些粗略的。如果借助通配符(#, +, $),可以設計出更靈活的層次結構。
多層通配符: “#”是用于匹配主題中任意層級的通配符,多層通配符表示它的父級和任意數量的子層級。單層通配符:“+”加號是只能用于單個主題層級匹配的通配符,在主題過濾器的任意層級都可以使用單層通配符,包括第一個和最后一個層級。通配符:“$”表示匹配一個字符,只要不是放在主題的最開頭,其它情況下都表示匹配一個字符。
我們以一個控制指令為例,來梳理一下數據是如何通過 topic 進行流動:
Proc_Bridge 進程從服務器接收到控制指令后,發送到消息總線上的 topic: $iot/v1/Device/Control。由于 Proc_Protocol 進程訂閱了這個 topic,所以立刻接收到指令。Proc_Protocol 分析指令內容,發現是一個 ZigBee 設備,于是進行協議轉換,發送一個 ZigBee 控制指令到消息總線上的 topic: $iot/v1/ZigBee/Control。由于 Proc_ZigBee 進程訂閱了這個 topic,因此它接收到這個控制指令。Proc_ZigBee 把控制指令轉換成 ZigBee 無線通信模塊要求的格式,通過硬件發送給設備燈泡。
我們再分析一下設備數據上報的場景:
先關注圖中紅色箭頭,忽略藍色箭頭:
門磁打開后,通過無線通信把信息上報給進程 Proc_CF。Proc_RF 進程接收到 RF433 通信模塊上報的數據,把“門磁打開”這個信息發送到消息總線上的 topic:$iot/v1/RF/Report。由于 Proc_Protocol 進程訂閱了這個 topic,因此接收到上報的門磁數據。Proc_Protocol 分析數據,把 RF433 協議的數據轉成統一的應用層協議的數據,發送到消息總線上的 topic:$iot/v1/Device/Report。由于 Proc_Bridge 進程訂閱了這個 topic,因此就接收到了這次上報的數據。Proc_Bridge 進程把數據上報給服務器。
再來看一下藍色箭頭流程:
在上面的第 4 步:Proc_Protocol 進程把 RF433 協議數據轉成應用層統一協議之后,把數據發送到消息總線上的 topic:$iot/v1/Device/Report 之后,Proc_Auto 進程同時進行如下操作:
由于 Proc_Auto 也訂閱了這個 topic,因此它也接收到了門磁上報的這個應用層協議的數據。Proc_Auto 查找自己的配置信息(假設用戶已經提前配置好了一條規則:當門磁打開的時候,就觸發聲光報警器),發現匹配到了“門磁->報警器”這條規則,于是發出一條控制報警器的指令,發送到消息總線上的 topic: $iot/v1/Device/Control。
后面的 7,8,9,10 這四個步驟就與上面的控制指令流程完全一樣了。
3.4 與 DBUS 總線的對比
從上面描述的 3 個數據流向的場景中,是不是感覺到使用 topic 為“數據管道”的這種通信方式,與 Linux 系統中的 DBUS 總線特別的相似?
DBUS 總線也是用于進程之間的通信,按照我個人的理解,DBUS中其實是把進程之間的兩種通信組織在一起了:
基于信號的數據傳輸;基于方法的 RPC 遠程調用;
DBUS 總線包含的概念更復雜一些,包括:路徑、對象、接口、方法等等,這些概念組織在一起共同定位到一個具體的服務提供者了。
相比較而言,我感覺 MQTT 這樣的方式更簡潔一些。
所謂的 RPC 遠程調用,就是調用位于遠程機器上的一個函數,主要解決兩個問題:
網絡連接;數據的序列化和反序列化;
后面我會專門寫一篇文章,利用 protobuf 框架來實現 RPC 調用。
四、網關與云平臺之間的通信
上面講解的設計過程,是網關內部的各功能模塊之間通信方式,這也是我們作為嵌入式開發者能充分發揮的部分。
網關與云平臺之間的通信方式一般都是客戶指定的,就那么幾種(阿里云、華為云、騰訊云、亞馬遜AWS平臺)。一般都要求網關與云平臺之間處于長連接的狀態,這樣云端的各種指令就可以隨時發送到網關。
當然了,這些云平臺都會提供相應的 SDK 開發包,一般使用的 MQTT 協議來連接云平臺更多一些。在一些文檔中,會把位于云端的 MQTT 服務器稱作 Broker,其實就是一個服務器。
進程 Proc_Bridge 的功能主要有 2 點:
與云平臺的數據傳輸通道;協議轉換:把云平臺相關的協議轉換成網關內部的協議,以及相反的轉換。
也就是說:Proc_Bridge 進程需要同時連接到云平臺的 MQTT Broker 和網關內部的 MQTT 消息總線。在下一篇文章中,我們來專門講解這部分的內容,并提供一個實現橋接功能的代碼模板。