物聯網閘道器開發:基於MQTT訊息匯流排的設計過程(上)

sewain發表於2021-02-24

道哥的第 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指令碼語言,讓你徹底明白除錯原理

相關文章