物聯網網關開發:基於MQTT消息總線的設計過程(上)



道哥的第 021 篇原創

一、前言

在上一篇中,我們聊了在一個嵌入式系統中,如何利用MQTT消息總線在各進程之間進行通信,文章鏈接:《我最喜歡的進程之間通信方式-消息總線 》。

這樣的通信模型,我之前已經在多個項目中應用過,對於非工控產品來說,通信速度完全足夠。我以前做過測試,在x86平台和ARM平台,一條數據從本地到雲端繞一下,然后再回到本地,可以控制在毫秒級別

上篇文章只是簡單的介紹了這樣的一種設計思路,並沒有詳細的討論其中的一些細節問題。這一次,我們就來具體的聊一聊物聯網系統中的網關內部程序應該如何設計。

閱讀這篇文章,你可以有如下收獲:

  1. 物聯網系統中,設備之間是如何通信的;
  2. 網關中的進程之間消息總線通信模型;
  3. 網關內部消息總線上的數據如何與服務器進行通信;
  4. 作為消遣,了解一下物聯網系統中的一些基本知識;

二、網關的作用

物聯網這個詞語的范疇太廣,似乎所有的硬件設備,只要能夠接入網絡,就可以稱之為物聯網產品,似乎物聯網這個詞可以把一切都納入到其中。這么空洞的詞語不利於我們的講解,因此我們就用一個可以感知、想象的場景來代替,那就是智能家居系統,這是最能代表物聯網時代的典型產品了。

2.1 指令轉發

在一個智能家居系統中,假設有這么幾個設備:

這些設備的通信模塊,如果是 WiFi 或者是藍牙,那么一般都可以直接通過手機來控制(當然,需要廠家提供相應的手機 APP),手機就相當於一個中心節點,控制着所有的設備。目前市面上的一些智能設備單品都是這樣的通信方式,例如:空調、吸塵器、空氣凈化器、冰箱等等。只要在這些設備中加一個無線通信模塊即可(例如:ESP8266模塊)。

如果通信模塊是其它的通信模塊,例如:RF433、ZigBee、ZWave等,由於手機沒有這些通信模塊,因此就需要一個網關來“轉發”指令。手機和網關都連接到家中的路由器,處於同一個局域網中,手機把控制指令發送給網關,網關再把指令轉發給相應的設備。通信模型如下:

2.2 外網通信

在上面的通信模型中,手機和網關由於處於同一個局域網中,因此可以直接通信。如果手機不在局域網中呢?那么就要通過雲端的服務器來轉發了,通信模型如下:

  1. 手機把指令發到服務器;
  2. 服務器把指令轉發給網關;
  3. 網關把指令發給指定的設備;

以上描述的是控制指令的流程,如果是設備發出的報警信息呢,數據的流向就是倒過來進行的

可以看出,網關是所有設備之間通信的中心節點,也是內網與外網之間通信的中轉節點,也就是把各種智能設備連接到互聯網的中轉器。

2.3 協議轉換

上面已經提到,硬件設備上的通信模塊都是確定的(RF,ZigBee,ZWave等等),一般來說,可以把這些通信模塊稱呼為無線通信協議。在一套智能家居系統中,所有設備的無線通信協議大部分都是相同的。

那么,不同類型的無線通信協議設備是否可以共存在同一個系統中呢?

答案是:可以。只要在網關中,集成了相應的無線通信協議模塊就可以達到這個目的!如下圖所示:

從手機APP上看,所有的設備都是相同的,不會關心設備的無線通信協議是什么,因此,發出的控制指令都是協議無關的。

當網關接收到控制指令時,首先根據指令內容查找出目標設備,然后確定目標設備的無線通信協議,最后把指令發送給對應的硬件通信模塊,由該通信模塊通過無線電信號把控制指令發送到設備。

從這個指令的傳輸過程來看,網關就充當着協議轉換的角色

另外還有一種通信場景:當系統中的一個“輸入”設備與一個“輸出”設備進行綁定/關聯時,例如:

  1. 紅外感應器與聲光報警器綁定:當紅外感應器監測到人體時,發出信號,然后控制聲光報警器發出報警;
  2. 門磁與燈綁定:當開門時,門磁發出信號,自動打開燈光;

如果“輸入”設備與“輸出”設備是不同類型的無線通信協議,也需要網關來進行協議轉換

2.4 設備管理

在一個智能家居系統中,設備可多可少,對這些設備進行管理也是很重要的事情。網關作為系統的中心節點,對設備進行管理的重任理所當然就由網關來承擔。

設備管理功能包括:

設備的添加和刪除;
設備狀態的管理(電量、設備斷網、失聯等等);
設備樹的管理;

2.5 邊沿計算(自動化控制)

在正常的情況下,網關是可以通過路由器,與服務器保持着長連接的。如果服務器的處理能力比較強大,智能家居系統中所有需要處理的事情都可以丟給服務器來計算、處理,服務器在計算之后把處理結果再發送給網關。看起來想法很完美!

但是,考慮下面這 2 種情況:

  1. 路由器出現問題了,網關無法連接到服務器,因此就無法把本地數據及時上報;
  2. 系統中出現了異常情況,需要緊急處理,如果把信息上報到服務器,由服務器計算之后再回傳給網關,耗費的時間可能超過了可容忍時間,該如何處理?(可以用車聯網系統來腦補一下這個場景:自動駕駛中的汽車遇到緊急情況,如果把所有信息上傳給服務器,然后等待服務器的下一步指令?)

對於上面的這些場景,把一些計算、處理操作放在網關這一端來處理也許更合適!這也是近幾年比較流行的邊沿計算

1. 邊緣計算,是指在靠近物或數據源頭的一側,采用網絡、計算、存儲、應用核心能力為一體的開放平台,就近提供最近端服務。

2. 其應用程序在邊緣側發起,產生更快的網絡服務響應,滿足行業在實時業務、應用智能、安全與隱私保護等方面的基本需求。

3. 邊緣計算處於物理實體和工業連接之間,或處於物理實體的頂端。而雲端計算,仍然可以訪問邊緣計算的歷史數據

三、網關內部進程之間的通信

在設計一個應用程序的架構時,可以通過多線程來實現,也可以通過</font color=orange>多進程來實現,每個人的習慣都不一樣,各有各的好處。我們這里不去討論孰優孰劣,因為我對多進程這樣的設計思想比較偏愛,所以就直接按照多進程的程序架構來討論。

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消息總線

以上這些進程之間需要相互通信,不是簡單的點對點通信,而是一個網狀的通信模型。比如:

  1. 設備管理進程 Proc_DevMgr:當任何一種設備被添加到系統中時,都需要處進行處理,因此它需要與 Proc_ZigBee, Proc_RF, Proc_ZWave 這些進程進行通信;
  2. 當某個設備上報數據時(例如: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:

  1. $aws/things/MyThing/jobs/get/accepted
  2. $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 的設計,還是有些粗略的。如果借助通配符(#, +, $),可以設計出更靈活的層次結構。

  1. 多層通配符: “#”是用於匹配主題中任意層級的通配符,多層通配符表示它的父級和任意數量的子層級。
  2. 單層通配符:“+”加號是只能用於單個主題層級匹配的通配符,在主題過濾器的任意層級都可以使用單層通配符,包括第一個和最后一個層級。
  3. 通配符:“$”表示匹配一個字符,只要不是放在主題的最開頭,其它情況下都表示匹配一個字符。

我們以一個控制指令為例,來梳理一下數據是如何通過 topic 進行流動:

  1. Proc_Bridge 進程從服務器接收到控制指令后,發送到消息總線上的 topic: $iot/v1/Device/Control。
  2. 由於 Proc_Protocol 進程訂閱了這個 topic,所以立刻接收到指令。
  3. Proc_Protocol 分析指令內容,發現是一個 ZigBee 設備,於是進行協議轉換,發送一個 ZigBee 控制指令到消息總線上的 topic: $iot/v1/ZigBee/Control。
  4. 由於 Proc_ZigBee 進程訂閱了這個 topic,因此它接收到這個控制指令。
  5. Proc_ZigBee 把控制指令轉換成 ZigBee 無線通信模塊要求的格式,通過硬件發送給設備燈泡。

我們再分析一下設備數據上報的場景:

先關注圖中紅色箭頭,忽略藍色箭頭:

  1. 門磁打開后,通過無線通信把信息上報給進程 Proc_CF。
  2. Proc_RF 進程接收到 RF433 通信模塊上報的數據,把“門磁打開”這個信息發送到消息總線上的 topic:$iot/v1/RF/Report。
  3. 由於 Proc_Protocol 進程訂閱了這個 topic,因此接收到上報的門磁數據。
  4. Proc_Protocol 分析數據,把 RF433 協議的數據轉成統一的應用層協議的數據,發送到消息總線上的 topic:$iot/v1/Device/Report。
  5. 由於 Proc_Bridge 進程訂閱了這個 topic,因此就接收到了這次上報的數據。
  6. Proc_Bridge 進程把數據上報給服務器。

再來看一下藍色箭頭流程:

在上面的第 4 步:Proc_Protocol 進程把 RF433 協議數據轉成應用層統一協議之后,把數據發送到消息總線上的 topic:$iot/v1/Device/Report 之后,Proc_Auto 進程同時進行如下操作:

  1. 由於 Proc_Auto 也訂閱了這個 topic,因此它也接收到了門磁上報的這個應用層協議的數據。
  2. Proc_Auto 查找自己的配置信息(假設用戶已經提前配置好了一條規則:當門磁打開的時候,就觸發聲光報警器),發現匹配到了“門磁->報警器”這條規則,於是發出一條控制報警器的指令,發送到消息總線上的 topic: $iot/v1/Device/Control。

后面的 7,8,9,10 這四個步驟就與上面的控制指令流程完全一樣了

3.4 與 DBUS 總線的對比

從上面描述的 3 個數據流向的場景中,是不是感覺到使用 topic 為“數據管道”的這種通信方式,與 Linux 系統中的 DBUS 總線特別的相似?

DBUS 總線也是用於進程之間的通信,按照我個人的理解,DBUS中其實是把進程之間的兩種通信組織在一起了:

  1. 基於信號的數據傳輸;
  2. 基於方法的 RPC 遠程調用;

DBUS 總線包含的概念更復雜一些,包括:路徑、對象、接口、方法等等,這些概念組織在一起共同定位到一個具體的服務提供者了。

相比較而言,我感覺 MQTT 這樣的方式更簡潔一些。

所謂的 RPC 遠程調用,就是調用位於遠程機器上的一個函數,主要解決兩個問題:

  1. 網絡連接;
  2. 數據的序列化和反序列化;

后面我會專門寫一篇文章,利用 protobuf 框架來實現 RPC 調用。

四、網關與雲平台之間的通信

上面講解的設計過程,是網關內部的各功能模塊之間通信方式,這也是我們作為嵌入式開發者能充分發揮的部分

網關與雲平台之間的通信方式一般都是客戶指定的,就那么幾種(阿里雲、華為雲、騰訊雲、亞馬遜AWS平台)。一般都要求網關與雲平台之間處於長連接的狀態,這樣雲端的各種指令就可以隨時發送到網關。

當然了,這些雲平台都會提供相應的 SDK 開發包,一般使用 MQTT 協議來連接雲平台的更多一些。在一些文檔中,會把位於雲端的 MQTT 服務器稱作 Broker,其實就是一個服務器。

進程 Proc_Bridge 的功能主要有 2 點:

  1. 與雲平台的數據傳輸通道;
  2. 協議轉換:把雲平台相關的協議轉換成網關內部的協議,以及相反的轉換。

也就是說:Proc_Bridge 進程需要同時連接到雲平台的 MQTT Broker 和網關內部的 MQTT 消息總線。在下一篇文章中,我們來專門講解這部分的內容,並提供一個實現橋接功能的代碼模板。

五、總結

作為一名嵌入式軟件開發人員,僅僅根據別人設計好的框架來填充代碼,時間久了就會有些倦怠,不知道技術提升的方向在哪里。仔細想想,其實方向挺多的:Linux 內核、文件系統、算法、應用程序設計等等。

這篇文章討論的內容還談不上架構設計,僅僅是一個簡單的物聯網網關內部各功能模塊的通信模型。如果你有機會設計類似的產品,不妨嘗試一下這樣的通信模型,當然你一定會設計的更好!


【原創聲明】

轉載:歡迎轉載,但未經作者同意,必須保留此段聲明,必須在文章中給出原文連接。


不吹噓,不炒作,不浮誇,認真寫好每一篇文章!

歡迎 轉發、分享給身邊的技術朋友,道哥在此表示衷心的感謝! 轉發的 推薦語已經幫您想好了:

道哥總結的這篇總結文章,寫得很用心,對我的技術提升很有幫助。好東西,要分享!



推薦閱讀

我最喜歡的進程之間通信方式-消息總線
C語言指針-從底層原理到花式技巧,用圖文和代碼幫你講解透徹
一步步分析-如何用C實現面向對象編程
提高代碼逼格的利器:宏定義-從入門到放棄
原來gdb的底層調試原理這么簡單
利用C語言中的setjmp和longjmp,來實現異常捕獲和協程
關於加密、證書的那些事
深入LUA腳本語言,讓你徹底明白調試原理


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM