2018年10月8日 星期一

當 ESP8266 遇上中華電信 IoT 智慧聯網大平台 { 入門 - 06 } - 了解 MQTT 協議,學習如何訂閱 MQTT 主題與接收 MQTT 發佈消息

網頁最後修改時間:2018/10/08

不同 QoS 的主題訂閱與消息發佈通訊流程
對於 MQTT 協議規範的介紹,前面已經針對使用中華電信 IoT 智慧聯網大平台 ( 下面簡稱 平台 或 CHT IoT SP ) 講述了消息發佈的流程,但都只侷限 QoS=0 的情況,這是因為 CHT IoT SP 現只支援 QoS=0 的通訊。

由於 MQTT 的主題訂閱與接收是重點,因此在此篇會另外引入支援完整 QoS 0、1 和 2 的 HiveMQ MQTT Broker 來輔助此篇的說明,用它來了解一下,不同的 QoS level 對於訂閱 MQTT 主題和接收來自 MQTT Broker (伺服器) 發佈的消息有著什麼不一樣的通訊流程以及注意的事項。

在 "{ 入門 - 04 } - 了解 MQTT 協議,學習如何發佈 MQTT 消息" 說明了 PUBLISH 發佈消息的過程,但是沒有細說的部分是:不同的 QoS level 對於通訊流程有何不同和影響 ?

這部分若是擺在當時後來做說明,其實不是那麼恰當!為什麼這麼說呢 ? 因為不僅是發佈主題時可以指定 QoS level,訂閱主題時也可以!若是只說明其中一部分,那麼很容易造成觀念與使用上的混淆,所以我擺在此篇一起來做說明。

在 "{ 入門 - 04 } - 了解 MQTT 協議,學習如何發佈 MQTT 消息" 這一篇,IoT Device 只單純作為消息發佈用,因此視為客戶端 ( Client ),MQTT Broker 只單純作為接收主題訊息用,因此視為伺服端 ( Server ),雖然也分別給了另一個名稱 (裝置端與通訊端),但是在這篇,這樣的解釋就顯得有點不適用!

原因是:當 IoT Device 訂閱主題和 MQTT Broker 接受訂閱之後,兩者的角色就不單純只是發送與接收消息而已,客戶端和服務端既可以是發送者也可以是接收者;使用時要搞清楚兩者在消息傳遞的時機上,何時是扮演發送者?何時是扮演接收者?才不會弄錯了通訊的機制。

接著來談談前面都未真正深入談及的 QoS level!

要談論 MQTT 的 QoS ( Quality of Service, 服務品質 ) level,可由下面兩種訊息傳遞的面向來思考:
  • 由 IoT Device 傳遞訊息到 MQTT Broker
    發送者是 IoT Device;接收者是 MQTT Broker
  • 由 MQTT Broker 傳遞訊息到該信息主題訂閱的 IoT Device
    發送者是 MQTT Broker;接收者是 IoT Device

當某一 IoT Device 訂閱一個感興趣的主題時,回覆的 SUBACK 控制封包裡的 Return Code 代表 MQTT Broker 是否支援所要求的 QoS level,這個數值同時也影響著發佈消息給訂閱者時所能使用的最高 QoS level。

如果 MQTT Broker 能夠支援此 QoS level 的話,就會在返回 SUBACK 控制封包裡的 Return code ( 稱為 Granted QoS ) 位元組賦予相同的 QoS level;但若是不支援的話,所要求的 QoS level 就會被降級到該 MQTT Broker 所能支援的 QoS level。所以,不是訂閱主題時要求使用 QoS level 是多少就能夠使用,要看 MQTT Broker 是否支援而定。

實際通訊範例:

舉個訂閱主題以及發佈消息的例子來做說明。

我們要使用這個例子來說明,訂閱主題時所要求使用 QoS level,對於上面所提及的那兩個面向,在通訊上有什麼的影響?

IoT Device 1、IoT Device 2 以及 MQTT Broker,三者都支援最大的 QoS level = 2。IoT Device 1 只負責發佈 topic1 的消息給 MQTT Broker;Iot Device 2 則只負責接收它向 MQTT Broker 訂閱的 topic1 這個主題的任何消息。

首先,IoT Device 2 向 MQTT Broker 發送 SUBSCRIBE 控制封包,封包裡面除了訂閱的主題名稱之外,還包含了向 MQTT Broler 要求的 QoS level ( Requested QoS=1 )。

SUBSCRIBE 發送之後,不管主題訂閱成不成功,MQTT Broker 都會返回 SUBACK 控制封包,並在封包裡的 Return Code 位元組填入 MQTT Broker 支援的 QoS level ( 稱為 Granted QoS,只能是 0x01, 0x02, 0x03 或 0x80 (Failure) 這四個值 ),這個數值就是 MQTT Broker 在發佈訊息給訂閱者時,所能使用的最高 QoS level。

之後,只要當 MQTT Broker 接收到 topic1 發佈的消息 (不管從哪裡來;但在這裡指的是 IoT Device 1 ),就會將消息傳遞 ( PUBLISH ) 到訂閱的裝置端 ( IoT Device 2)。
ex01-主題訂閱
下面來看看,不同的 QoS level 設定時,對於客戶端 (IoT Device 1) 與伺服端 (MQTT Broker) 發送 PUBLISH 控制封包時,通訊方式有什麼不同?

* 情況 1:

如下圖,當 IoT Device 1 發送 topic1 更新的訊息到 MQTT Broker 時, 這時通訊就分為上面講述的兩個面向通訊。

左邊:由於 MQTT Broker 支援 QoS level 可到 2,因此與 IoT Device 1 就可進行 QoS=2 的通訊溝通方式。

右邊:IoT Device 1 使用 QoS=2 發送消息給 MQTT Broler,MQTT Broker 接收到消息之後與其進行 QoS=2 的通訊。但由於 IoT Device 2 訂閱主題時要求使用的 QoS=1,所以 MQTT Broker 將訊息傳遞給訂閱者 ( IoT Device 2 ) 所能使用的 QoS level 將不能大於 1,故分送消息給訂閱者只能進行 QoS=1 的通訊;根據 MQTT 協議規範,訂閱者只能以 PUBACK 作為回應的控制封包。
HiveMQ - PUBLISH with QoS=2
 * 情況 2:

若 IoT Device 1 使用 QoS=1 進行消息發佈,那麼發佈者與訂閱者與 MQTT Broker 的通訊就如下面所示。
HiveMQ - PUBLISH with QoS=1
* 情況 3:

下面這個情況就有些許不同;當 IoT Device 1 發佈消息所使用的 QoS level 小於當初訂閱主題要求的 QoS level 時,MQTT Broker 分送消息給訂閱者只能進行該層級的通訊 ( 也就是 QoS=0 );根據 MQTT 協議規範,訂閱者不須要回應任何的控制封包。
HiveMQ - PUBLISH with QoS=0

所以從上面所舉的例子可以知道,訂閱主題裡的 QoS level 所影響的是 MQTT Broker 發送訊息時所能使用的最高 QoS level。發佈者發佈訊息到 MQTT Broker 並不限定使用 QoS level 為多少,但它會影響 MQTT Broker 發佈訊息給訂閱者所使用的 QoS level ( ≤ Granted QoS )。

/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* 演示影片:

下面影片實際的演示了上面的三種情況,所有的 MQTT 通訊的紀錄都在 Wireshark 裡,每一種情況測試之後可以暫停一下影片,看看 MQTT 的通訊是否如我上面所說的那樣。

/*-/--*-*/*/*/*/***//-*-*-**-*/*-*-/*/*/*-*-/-////--/**/**--**/--///--//**----**//--**//**----***//*-**//*
由於 CHT IoT SP 現在只支援 QoS=0,所以測試的 MQTT Broker 於影片中是使用 HiveMQ 來取代,IoT Device 1 由 MQTT.fx 擔任,IoT Device 2 由 MQTTbox 擔任。
/*-/--*-*/*/*/*/***//-*-*-**-*/*-*-/*/*/*-*-/-////--/**/**--**/--///--//**----**//--**//**----***//*-**//*


相較於 HiveMQ 支援全 QoS level,CHT IoT SP 現只支援 QoS=0,所以不管我們訂閱主題時要求的是哪一個 QoS level,都只能使用 QoS=0;訊息發佈也是一樣!

不過因為 CHT IoT SP 只支援 QoS=0 ,所以 QoS=1 和 QoS=2 的通訊流程在 Wireshark 抓不到,所以下一節關於 MQTT 控制封包的講解,是使用上面影片中 Wireshark 的 MQTT 通訊紀錄。

MQTT 控制封包格式:

在 "{ 入門 - 04 } - 了解 MQTT 協議,學習如何發佈 MQTT 消息" 已經對於幾個 MQTT 控制封包和基本結構做過介紹,接下來的部分將著重在主題訂閱相關的控制封包上。

/*-/--*-*/*/*/*/***//-*-*-**-*/*-*-/*/*/*-*-/-////--/**/**--**/--///--//**----**//--**//**----***//*-**//*
軟體:
  • MQTT.fx Version 1.7.0 ( 作為 IoT Device 1 )
    MQTT 的發布與訂閱之用;請安裝最新版本。
  • MQTTbox ( 作為 IoT Device 2 )
    MQTT 的發布與訂閱之用;請安裝最新版本。
  • Wireshark ( 阿榮福利味下載可攜版 )
    網路封包分析工具
MQTT.fx 和 MQTTbox 的用法在網頁中會說明,至於 Wireshark 在網頁中用到的部分只有其中一小部分,這一部分只要參考 Wireshark-基礎教學 ( 來自於政大資訊科學系 張鴻慶老師 "計算機網路課程" 的教學資料 ) PDF 文件的介紹就足夠,所以這裡不再特別贅述! 

如果在網頁中沒有特別說明的話,軟體的連線與參數設定都是採用軟體預設值。

Version: MQTT v3.1.1

MQTT Broker:
  • IP      : broker.hivemq.com
  • Port  : 1883
  • Topic: myhome/groundfloor/livingroom/temperature
/*-/--*-*/*/*/*/***//-*-*-**-*/*-*-/*/*/*-*-/-////--/**/**--**/--///--//**----**//--**//**----***//*-**//*

IoT Device 1 和 IoT Device 2 先與 HiveMQ 完成連接 ( CONNECT ) ,IoT Device 2 先進行主題訂閱 ( SUBSCRIBE ) 的動作,若 HiveMQ  接收到主題訂閱的要求,那麼就會返回訂閱確認 ( SUBACK ) 的控制封包。

/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* MQTT SUBSCRIBE 控制封包格式:

SUBSCRIBE 控制封包的 Fixed Header 如下表所示。第一個 byte 的值是固定的 0x82;Remaining Length 的計算方式,每個控制封包都是一樣的 (  入門 4 已做過說明,請自行參照 )。

SUBSCRIBE - Fixed Header
Variable Header 的部分則只有 Packet Identifier 這個部分需要填寫,這個值可以自己定義,只要不超過 65535 就可以;測試使用的值是 58662,因此可得到

Variable Header = [ Packet Identifier ] = e5 26

SUBSCRIBE - Variable Header
Payload = [ Topic Length ] + [ Topic ] + [ Requested QoS ]
  • Topic Length:主題名稱的長度 ( 2-byte );因為主題名稱已知 ( 長度是 41 bytes ),完整字串是 0x00 0x29
  • Topic:要訂閱的主題名稱;完整字串是 0x6d 0x79 0x68 0x6f 0x6d 0x65 0x2f 0x67 0x72 0x6f 0x75 0x6e 0x64 0x66 0x6c 0x6f 0x6f 0x72 0x2f 0x6c 0x69 0x76 0x69 0x6e 0x67 0x72 0x6f 0x6f 0x6d 0x2f 0x74 0x650x6d 0x70 0x65 0x72 0x61 0x74 0x75 0x72 0x65 (整段代表 myhome/groundfloor/livingroom/temperature)
  • Requested QoS:向 MQTT 訂閱主題時要求的 QoS level;只能使用 0x00, 0x02 或 0x03 這三個值其中一個,0x80 則是 Failure,其他是保留值,若使用會導致連線斷開;這裡採用的是 QoS=1,所以要填入 0x01
SUBSCRIBE - Payload
總和上面的 Variable Header 和 Payload 所使用的位元組數目,可以得知

Remaining Length = [Packet Identifier] + [ Topic Length ] + [ Topic ] + [ Requested QoS ] = 0x2e

沒有超過 127 bytes,所以 Remaining Length 只佔據 1 byte。

整合上面所有的資料後,可得到一個完整的 SUBSCRIBE 控制封包格式

[Fixed Header] [Variable Header] )                         { [Payload] }

[Fixed Header] [Packer Identifier] ) { [Topic Length] [Topic] [Requested QoS] }

82 2e e5 26 00 29 6d 79 68 6f 6d 65 2f 67 72 6f 75 6e 64 66 6c 6f 6f 72 2f 6c 69 76 69 6e 67 72 6f 6f 6d 2f 74 65 6d 70 65 72 61 74 75 72 65 01

在 Wireshark 最上方畫面點選 Subscribe Request,中間部份選擇並打開 MQ Telemetry Transport Protocol, Subscribe Request ,在最下面的畫面就會出現與上面相同的結果
HiveMQ - MQTT SUBSCRIBE with QoS=1 控制包格式抓取結果
/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* MQTT SUBACK 控制封包格式:

一但 MQTT Broker 接收到主題訂閱的要求後,就會返回 SUBACK 控制封包。由於整個字串長度是固定的,對於 Fixed Header 來說就可得到完整字串為 0x90 0x03

SUBACK - Fixed Header
Variable Header 的部分只有 2 bytes 的 Packet Identifier,這個返回值必須與訂閱主題時所用的 Packet Identifier 相同;所以完整字串就是 0xe5 0x26

SUBACK - Variable Header
Payload 的部分是返回給訂閱者的 QoS level,也可稱為 Granted QoS;若 MQTT Broker 能支援訂閱者所要求的 QoS level,則會返回並填入相同的 QoS level 在此位元組,否則就填入 MQTT Broker 所能支援的 QoS level。

對於 HiveMQ 來說,最大支援 QoS=2;由於訂閱者要求的是 QoS=1,所以 Return Code 值就是 0x01

若是以 CHT IoT SP 來說,不管訂閱者要求的 QoS 為何,Return Code 的值都只會是 0x00
SUBACK - Payload
所以整合上面的資料,可以得到 SUBACK 控制封包

[Fixed Header] [Variable Header] )      { [Payload] }

[Fixed Header] [Packer Identifier] ) { [Return Code] }

90 03 e5 26 01

在 Wireshark 最上方畫面點選 Subscribe Ack,中間部份選擇並打開 MQ Telemetry Transport Protocol, Subscribe Ack ,在最下面的畫面就會出現與上面相同的結果
HiveMQ - MQTT SUBACK 控制包格式抓取結果
SUBACK 控制封包裡的 Return Code,代表的就是 MQTT Broker 發佈消息給訂閱者時,所能使用的最大 QoS level,但這取決於發佈者發佈時所使用的 QoS level (影片就是使用不同 QoS 發佈的通訊例子) 而定,下面是早前已經說過的 QoS=0 和 QoS=1 的情況
  • QoS=0
    不需要回傳任何 MQTT 控制封包
  • QoS=1
    回傳 PUBACK 控制封包
QoS=2 的情況就不只是單純回應一個訊息而已!

對照情況1的示意圖,IoT Device 1 發佈訊息 (QoS=2) 給 MQTT Broker,MQTT Broker 再將訊息發佈 (QoS=1) 給訂閱者 IoT Device 2
HiveMQ - QoS=2 發佈訊息,QoS=1 發佈訊息至訂閱者
如下圖所示,利用 Wireshark 可以抓取到三者之間的 MQTT 通訊資料。藍色框由上而下對應表示上圖左邊通訊,紅色框由上而下對應表示上圖右邊通訊。
情況 1,Wireshark MQTT 控制封包抓取結果
紅色框的部分就不再重新贅述;藍色框則是使用 QoS=2 發佈訊息時的通訊步驟,確保訊息發佈確實傳送一次 ( Exactly once )。

IoT Device 1 (發佈者) 使用 QoS=2 發佈訊息 ( PUBLISH ) 給 MQTT Broker,MQTT Broker 回覆已收到發佈訊息 ( PUBREC,PUBlish RECeived ) 的控制封包,並且會暫存 Packet Identifier,以防止重複處理相同的訊息。當 IoT Device 1 接收到 PUBREC 控制封包後,會回覆釋放發佈訊息 ( PUBREL,PUBlish RELease ) 的控制封包,通知 MQTT Broker 可以將訊息發佈給 IoT Device 2 (訂閱者),然後 MQTT Broker 回覆發佈完成 ( PUBCOMP,PUBlish COMPlete ) 給 IoT Device 1,並刪除前面暫存的訊息。

雖然 QoS=2 訊息發佈的通訊多了幾個步驟,但格式都很簡短 (4 bytes ),很容易了解!

PUBRECPUBREL PUBCOMP Fixed Header 的格式都是相同的,只差在第一個位元組的值不同,但都是占據兩個位元組的長度;而 Variable Header 的部分,填入的是 Packet Identifier,也是兩個位元組的長度,而且三個控制封包的值都是相同的;Payload 的部分,三個控制封包都沒有。

MQTT Broker 發佈訊息給 IoT Device 2 時,使用的 Packet Identifier = 0x01PUBLISH 控制封包如下 Wireshark 畫面所示
MQTT PUBLISH with QoS=2 控制封包抓取結果
/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* MQTT PUBREC 控制封包格式:

PUBREC - Fixed Header
整合上面的資料後,可以得到完整的 PUBREC 控制封包格式

[Fixed Header] [Variable Header] )

[Fixed Header] [Packer Identifier] )

50 02 00 01

在 Wireshark 最上方畫面點選 Publish Received,中間部份選擇並打開 MQ Telemetry Transport Protocol, Publish Received ,在最下面的畫面就會出現與上面相同的結果
MQTT PUBREC 控制封包抓取結果
/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* MQTT PUBREL 控制封包格式:

PUBREL - Fixed Header
整合上面的資料後,可以得到完整的 PUBREL 控制封包格式

[Fixed Header] [Variable Header] )

[Fixed Header] [Packer Identifier] )

62 02 00 01

在 Wireshark 最上方畫面點選 Publish Release,中間部份選擇並打開 MQ Telemetry Transport Protocol, Publish Release ,在最下面的畫面就會出現與上面相同的結果
MQTT PUBREL 控制封包抓取結果
當 IoT Device 1 回覆 PUBREL 給 MQTT Broker 後,MQTT Broker 就會將此訊息和相同的 Packet Identifier (0x01) 一同傳遞給 IoT Device 2;由於 IoT Device 1 使用高於當初 IoT Device 2 訂閱的 QoS level,所以只能使用較低的 QoS=1 來發佈消息給訂閱者。

完整的 PUBLISH 控制封包格式如下圖所示
MQTT Broker 傳遞訊息到 IoT Device 2 (訂閱者) 的控制封包抓取結果
/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* MQTT PUBCOMP 控制封包格式:

PUBCOMP - Fixed Header
整合上面的資料後,可以得到完整的 PUBCOMP 控制封包格式

[Fixed Header] [Variable Header] )

[Fixed Header] [Packer Identifier] )

70 02 00 01

在 Wireshark 最上方畫面點選 Publish Complete,中間部份選擇並打開 MQ Telemetry Transport Protocol, Publish Complete ,在最下面的畫面就會出現與上面相同的結果
MQTT PUBCOMP 控制封包抓取結果
PUBCOMP 發送之後就完成 QoS=2 的訊息發佈的動作。

CHT IoT SP 的主題訂閱與訊息接收:

為了要講解 QoS > 0 的訊息發佈情況,所以不得已使用 HiveMQ 作為 MQTT Broker。接下來返回到 CHT IoT SP 上來手動輸入控制封包做主題訂閱以及訊息接收,將這些資料做為下一篇程式撰寫之用。

/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* 測試流程說明:

依照下圖所提供的資料,輸入 AT 指令設置 ESP8266 建立與 MQTT Broker 的 TCP 連線並進入到透傳模式。
MQTT Broker 連線相關資訊
根據 {入門 4} 網頁中專案設定的資料,輸入 MQTT CONNECT 控制封包連線至 MQTT Broker,並且訂閱 SayHelloSHT31_Temperature SHT31_Humidity 三個感測器的資料變動通知。

CONNECT ➔ SUBSCRIBE SayHello 
                 ➔ SUBSCRIBE SHT31_Temperature 
                 ➔ SUBSCRIBE SHT31_Humidity

為了避免手動輸入會一不小心超出 Keep Alive 的時間,所以不定時要插入 PINGREQ (心跳請求) 的控制封包。

一但主題訂閱成功之後,不管使用何種方式改變了該主題的內容,都會收到來自 MQTT Broker 發佈的訊息;除了單一主題內容的變更通知之外,更可以一次同時改變三個主題內容,也能同時接收到三個主題的更新通知。

完成測試之後,輸入 DISCONNECT 控制封包斷開與 MQTT Broker 的連接,然後斷開 ESP8266 與網路的連接。

關於與 CHT IoT SP 進行 MQTT 通訊都已在 {入門 4} 網頁中做過詳細說明,在這裡只補充 SUBSCRIBE 控制封包的完整格式字串的說明。

進入到 中華電信 IoT 智慧聯網大平台 \ API 文件 \ MQTT 網頁,點擊 "訂閱感測資料變動通知" 開啟更詳細的說明。
CHT IoT SP MQTT API 文件 - 訂閱感測資料變動通知
其中,Topic 用在 MQTT SUBSCRIBE Payload 裡的 byte 1 ... N。${device_id} 是裝置編號,可以在專案中看到,所以要用 7581817123 代入;${sensor_id} 是感測器識別編號,可在上面圖上找到,或是使用自己定義的,但一次只能訂閱一個。

所以代入相關資料之後,可得到三個感測器所對應的 Topic
  • SayHello
    /v1/device/7581817213/sensor/SayHello/rawdata
  • SHT31_Temperature
    /v1/device/7581817213/sensor/SHT31_Temperature/rawdata
  • SHT31_Humidity
    /v1/device/7581817213/sensor/SHT31_Humidity/rawdata
Response 說明 解釋了當接收來自 MQTT Broker 傳遞訊息的 PUBLISH 控制封包時,在它的 Payload 部分所包含的 JSON 訊息裡面包含的欄位和格式,訂閱者接收資料的主要工作,就是在解析這個 JSON 字串,從中取出感興趣的欄位值。

有了上述這些資訊之後,就可以來整理出完整的 SUBSCRIBE 控制封包格式字串。

/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* 訂閱 CHT IoT SP 的 MQTT SUBSCRIBE 控制封包格式字串:

還記得 SUBSCRIBE 控制封包格式吧 ?

[Fixed Header] [Variable Header] )                         { [Payload] }

[Fixed Header] [Packer Identifier] ) { [Topic Length] [Topic] [Requested QoS] }

** SayHello:

Variable Header 的部分只需要 Packet Identifier。它占據兩個位元組長度,可指定為任意值,這裡指定為 1 ( 0x00 0x01 )

Topic/v1/device/7581817213/sensor/SayHello/rawdata ) 佔據 45 bytes,以 16 進位表示則為

0x2f 0x76 0x31 0x2f 0x64 0x65 0x76 0x69 0x63 0x65 0x2f 0x37 0x35 0x38 0x31 0x38 0x31 0x37 0x32 0x31 0x33 0x2f 0x73 0x65 0x6e 0x73 0x6f 0x72 0x2f 0x53 0x61 0x79 0x48 0x65 0x6c 0x6c 0x6f 0x2f 0x72 0x61 0x77 0x64 0x61 0x74 0x61

所以可得到 Topic Length = 0x00 0x2d

Requested QoS = 0x00

Fixed Header Remaining Length 等於 Variable Header 加上 Payload 兩個部分所使用的位元組數目 = 2 + 45 +2 + 1 = 50 = 0x32

Fixed Header 第一個 byte 的值是固定的,所以可得此部分完整字串為 0x82 0x32

所以對於要訂閱 SayHello 感測器資料變動通知來說,完整的 SUBSCRIBE 控制封包字串如下所示:

[Fixed Header] [Packer Identifier] ) { [Topic Length] [Topic] [Requested QoS] }

82 32 00 01 00 2d 2f 76 31 2f 64 65 76 69 63 65 2f 37 35 38 31 38 31 37 32 31 33 2f 73 65 6e 73 6f 72 2f 53 61 79 48 65 6c 6c 6f 2f 72 61 77 64 61 74 61 00

** SHT31_Temperature:

這裡指定 SHT31_TemperaturePacket Identifier = 2,故 Variable Header = 0x00 0x02

Topic ( /v1/device/7581817213/sensor/SHT31_Temperature/rawdata ) 佔據 54 bytes,以 16 進位表示則為

0x2f 0x76 0x31 0x2f 0x64 0x65 0x76 0x69 0x63 0x65 0x2f 0x37 0x35 0x38 0x31 0x38 0x31 0x37 0x32 0x31 0x33 0x2f 0x73 0x65 0x6e 0x73 0x6f 0x72 0x2f 0x53 0x48 0x54 0x33 0x31 0x5f 0x54 0x65 0x6d 0x70 0x65 0x72 0x61 0x74 0x75 0x72 0x65 0x2f 0x72 0x61 0x77 0x64 0x61 0x74 0x61

所以可得到 Topic Length = 0x00 0x36

Requested QoS = 0x00

Fixed Header 的 Remaining Length 等於 Variable Header 加上 Payload 兩個部分所使用的位元組數目 = 2 + 54 +2 + 1 = 59 = 0x3b

Fixed Header 第一個 byte 的值是固定的,所以可得此部分完整字串為 0x82 0x3b

所以對於要訂閱 SHT31_Temperature 感測器資料變動通知來說,完整的 SUBSCRIBE 控制封包字串如下所示:

[Fixed Header] [Packer Identifier] ) { [Topic Length] [Topic] [Requested QoS] }

82 3b 00 02 00 36 2f 76 31 2f 64 65 76 69 63 65 2f 37 35 38 31 38 31 37 32 31 33 2f 73 65 6e 73 6f 72 2f 53 48 54 33 31 5f 54 65 6d 70 65 72 61 74 75 72 65 2f 72 61 77 64 61 74 61 00

** SHT31_Humidity:

這裡指定 SHT31_Humidity 的 Packet Identifier = 3,故 Variable Header = 0x00 0x03

Topic ( /v1/device/7581817213/sensor/SHT31_Humidity/rawdata ) 佔據 51 bytes,以 16 進位表示則為

0x2f 0x76 0x31 0x2f 0x64 0x65 0x76 0x69 0x63 0x65 0x2f 0x37 0x35 0x38 0x31 0x38 0x31 0x37 0x32 0x31 0x33 0x2f 0x73 0x65 0x6e 0x73 0x6f 0x72 0x2f 0x53 0x48 0x54 0x33 0x31 0x5f 0x48 0x75 0x6d 0x69 0x64 0x69 0x74 0x79 0x2f 0x72 0x61 0x77 0x64 0x61 0x74 0x61

所以可得到 Topic Length = 0x00 0x33

Requested QoS = 0x00

Fixed Header 的 Remaining Length 等於 Variable Header 加上 Payload 兩個部分所使用的位元組數目 = 2 + 51 +2 + 1 = 50 = 0x38

Fixed Header 第一個 byte 的值是固定的,所以可得此部分完整字串為 0x82 0x38

所以對於要訂閱 SHT31_Humidity 感測器資料變動通知來說,完整的 SUBSCRIBE 控制封包字串如下所示:

[Fixed Header] [Packer Identifier] ) { [Topic Length] [Topic] [Requested QoS] }

82 38 00 03 00 33 2f 76 31 2f 64 65 76 69 63 65 2f 37 35 38 31 38 31 37 32 31 33 2f 73 65 6e 73 6f 72 2f 53 48 54 33 31 5f 48 75 6d 69 64 69 74 79 2f 72 61 77 64 61 74 61 00

得到了這三個主題訂閱的控制封包格式字串之後,就可以直接來測試看看,並了解一下實際與 CHT IoT SP 通訊時的情況,做為下一篇程式撰寫的依據。

/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* 實際測試影片:

有了上面所得到的 MQTT 控制封包格式字串之後,藉由實際動手操作這些過程,是最容易了解整個 MQTT 主題訂閱過程的最好方法,請看下面影片的演示



結論:

到現在為止,關於 MQTT 通訊的所有控制封包格式的講解與演示都已完成 (其中關於 UNSUBSCRIBE UNSUBACK 這兩個控制封包格式,因為程式撰寫不會用到,所以就沒特別拿出來說明,但這兩個的格式與 SUBSCRIBESUBACK 格式都是相同的,讀者可自行研究),雖然網頁主題是以 CHT IoT SP 為主,但是在這篇網頁一開始講解 MQTT 主題訂閱與 QoS level 關係時,用的卻是 HiveMQ MQTT Broker。所以千萬不要因為主題是以 CHT IoT SP 為主就認為所談論的東西只與它有關!重點是 MQTT 的觀念和控制封包的格式,這些才是需要深入了解的東西。

另外,在講解 SUBSCRIBE 控制封包格式時,我漏掉了一些東西沒講完整,現在補充在這裡。

在影片中,有三個感測器的資料變動通知要做訂閱,所以 ESP8266 發送了三次 SUBSCRIBE 控制封包給 MQTT Broker,但根據 MQTT v3.1.1 規範協議是可以一次發送的!只不過在 MQTT.fx 和 MQTTbox (或其他軟體) 只支援一次一個主題訂閱的功能,所以沒辦法測試!若此部分要用微控制器 (MCU,MicroController Unit) 來實現,肯定會需要較多的記憶體空間;再者,訂閱多個主題就表示有可能同時接收到這些主題訊息同時的發佈,Serial 接收的緩衝區不夠或是處理的機制不好,就很容易出現接收的 PUBLISH 控制封包字串漏字的問題,這部分留待下一篇有需要再做討論!

當要一次同時訂閱多個主題時,SUBSCRIBE 控制封包格式可以進行下面變化:

[Fixed Header] [Variable Header] )                         { [Payload] }

當只有一個主題需要訂閱時,使用下面的格式

[Fixed Header] [Packer Identifier] ) { [Topic Length] [Topic] [Requested QoS] }

當需要一次訂閱多個主題時,主要是擴展 Payload 的部分,將其複製再複製,所以單就 Payload 的部分可擴展如下

[Payload] } = { [Topic1 Length] [Topic1] [1 Requested QoS]
                           [Topic2 Length] [Topic2] [2 Requested QoS]
                           . . .
                           [TopicN Length] [TopicN] [N Requested QoS] }

因為接下來要訂閱的主題只有 3 個,所以上面的 N=3;更因為是一次訂閱 3 個主題,所以 Packet Identifier 只需要指定一個值,這個值就代表三個;這裡指定為 0x00 0x04

SayHelloPayload 部分從上面的地方作複製,可得到:

00 2d 2f 76 31 2f 64 65 76 69 63 65 2f 37 35 38 31 38 31 37 32 31 33 2f 73 65 6e 73 6f 72 2f 53 61 79 48 65 6c 6c 6f 2f 72 61 77 64 61 74 61 00

長度為 2 + 45 + 1 = 48 bytes

相同的,SHT31_TemperaturePayload 部分:

00 36 2f 76 31 2f 64 65 76 69 63 65 2f 37 35 38 31 38 31 37 32 31 33 2f 73 65 6e 73 6f 72 2f 53 48 54 33 31 5f 54 65 6d 70 65 72 61 74 75 72 65 2f 72 61 77 64 61 74 61 00

長度為 2 + 54 + 1 = 57 bytes

SHT31_HumidityPayload 部分:

00 33 2f 76 31 2f 64 65 76 69 63 65 2f 37 35 38 31 38 31 37 32 31 33 2f 73 65 6e 73 6f 72 2f 53 48 54 33 31 5f 48 75 6d 69 64 69 74 79 2f 72 61 77 64 61 74 61 00

長度為 2 + 51 + 1 =  54 bytes

Remaining Length = 2 (Packet Identifier) + 48 + 57 + 54 = 161 bytes = 33 + 128 ➾ 0xA1 0x01

整理之後,三個主題訂閱的 SUBSCRIBE 控制封包格式字串如下所示:

[Fixed Header] [Packer Identifier] ) { [Payload 1] [Payload 2] [Payload 3] }

82 A1 01 00 04 00 2d 2f 76 31 2f 64 65 76 69 63 65 2f 37 35 38 31 38 31 37 32 31 33 2f 73 65 6e 73 6f 72 2f 53 61 79 48 65 6c 6c 6f 2f 72 61 77 64 61 74 61 00 00 36 2f 76 31 2f 64 65 76 69 63 65 2f 37 35 38 31 38 31 37 32 31 33 2f 73 65 6e 73 6f 72 2f 53 48 54 33 31 5f 54 65 6d 70 65 72 61 74 75 72 65 2f 72 61 77 64 61 74 61 00 00 33 2f 76 31 2f 64 65 76 69 63 65 2f 37 35 38 31 38 31 37 32 31 33 2f 73 65 6e 73 6f 72 2f 53 48 54 33 31 5f 48 75 6d 69 64 69 74 79 2f 72 61 77 64 61 74 61 00

這控制封包一旦成功訂閱三個主題之後,只要其中一個感測器的資料發生變化,ESP8266 就會接收到資料。

若仔細解析接收到的 PUBLISH 控制封包就會發現,每一次接收到的 PUBLISH 控制封包裡的 Packet Identifier 都採用相同的值;如果一次同時發佈三個主題訊息時,就會同時接收到三個使用同一個 Packet IdentifierPUBLISH 控制封包。上述部分可以自己動手測試,看看結果是否與我說的相同!

了解了 MQTT 主題訂閱的流程與控制封包的格式之後,下一篇將實際撰寫這部分的程式,完成這一整個入門系列的最後篇幅。


<< 部落格相關文章 >>

沒有留言:

張貼留言

留言屬名為"Unknown"或"不明"的用戶,大多這樣的留言都會直接被刪除掉,不會得到任何回覆!

發問問題,請描述清楚你(妳)的問題,別人回答前不會想去 "猜" 問題是什麼?

不知道怎麼發問,請看 [公告] 部落格提問須知 - 如何問問題 !