網頁最後修改時間:2018/09/30
此篇網頁以了解基本 MQTT 協議的控制封包格式為主,配合 Wireshark 抓取特定TCP 連線的封包為輔,直接由電腦發送不同的 MQTT 控制封包 (Control Packets):CONNECT (連接伺服器)、PUBLISH (發佈消息)、PINGREQ (心跳請求)、SUBSCRIBE (訂閱主題) 和 DISCONNECT (斷開伺服器) ... 等,得到完整的發送與接收格式 。利用這樣的方式可得到完整的客戶端 (Client) 與服務端 (Server) 之間一來一回的 Request 和 Response 控制封包格式,不但可用來直接與 MQTT 規格文件做對照來加速了解用法,而且寫程式的時候也可以直接套用。
在本篇的最後,用影片演示了 ESP8266 在 AT 指令的透傳模式下,如何與 CHT IoT SP 進行 MQTT 通訊並發佈消息,以此作為接下來撰寫程式的依據。
/*-/--*-*/*/*/*/***//-*-*-**-*/*-*-/*/*/*-*-/-////--/**/**--**/--///--//**----**//--**//**----***//*-**//*
觀看此篇網頁前,建議先看過下面網頁!
/*-/--*-*/*/*/*/***//-*-*-**-*/*-*-/*/*/*-*-/-////--/**/**--**/--///--//**----**//--**//**----***//*-**//*
軟體:
下面是網頁接下來會用到的工具軟體,對於 MQTT 開發上有很大的助益,更是學習的一大助手!
- MQTT.fx Version 1.7.0
MQTT 的發布與訂閱之用,用來取代前面幾篇網頁所使用的 MQTTlens;請安裝最新版本。 - Wireshark ( 阿榮福利味下載可攜版 )
網路封包分析工具
新建專案:
為了測試上的方便,可先在 CHT IoT SP 上建立一個新的專案。
新專案設置的 "權限資料" 頁面中,預設已先建立一個 admin 的權限,一般的情況下不需要用到如此高的權限,最多只需要讀和寫的權限即可,因此請手動設定一個並先記下來,下面測試時會用到;假設新增的 read_write 權限的內容為 PK4LQMHCHE1ZTQFQRW (此權限在網頁發佈後就會移除,使用時請自行更換為自己的權限內容)
專案存取權限設置 |
專案設置完成畫面 |
為了避免混淆,網頁中有一些名詞如下定義:
- Client,稱為客戶端或裝置端,負責發送數據到遠端儲存的裝置或設備
- Server,稱為伺服端或通訊端,負責接收數據(或儲存)的裝置或設備,這裡指的是 MQTT Broker
平台 MQTT 協定格式:
專案設置小節裡,圖上的 ${PROJECT_KEY}、${device_id} 和 ${sensor_id} 欄位值很重要,在跟 MQTT Broker 通訊時會用到,別忘了可到這裡找。
CHT IoT SP MQTT API 文件
一般使用 MQTT 軟體 ( 例如 MQTTLens 或 MQTT.fx 等 ) 的情況下,大概只需要 MQTT Broker IP 地址和 Port 號碼、使用者帳號和密碼、MQTT Topic ... 等資訊,就能執行連線 ( CONNECT )、斷線 ( DISCONNECT )、發佈 ( PUBLISH ) 和訂閱主題 ( SUBSCRIBE ) 的動作,而且也不用自己處理來自 MQTT Broker ( CONNACK、PUBACK、SUBACK ... 等 ) 的回應或是裝置端與 MQTT Broker 的心跳請求 ( PINGREQ ) 和心跳響應 ( PINGRESP )。雖然對於初學者的門檻較低,但是也容易導致使用者知其然而不知所以然的情況出現,而且對於要撰寫 MQTT 通訊程式的使用者來說,遠遠不夠!只能做為測試通訊之用,需要再深入了解其控制封包格式才行。
另外,不是所有的 MQTT Broker 都使用相同版本的協議規範 (當然有些支援多版本),開始之前,要先知道 CHT IoT SP 支援什麼 ?
經過測試,CHT IoT SP 至少接受 MQTT v3.1 和 MQTT v3.1.1 兩種版本的協議規範。由於 MQTTLens ( 平台/服務說明/如何開始 ) 採用 MQTT v3.1 版本協議規範,因此在這裡不使用它;原因是因為連線設定時,只要在 "Add a new Connection" 使用到某些欄位,與 CHT IoT SP 就別想連線 ( 其他的 MQTT Broker 沒試過 );但改用 MQTT.fx 就沒有這個問題。
MQTT.fx 支援 MQTT v3.1 和 v3.1.1 兩種協議規範,預設值是 MQTT v3.1.1 ( single-page HTML, PDF ) ,使用上與 MQTTLens 沒有太大不同,基本上都是以協議規範為主;但,越了解協議規範,越得心應手!
後續本文所談論的協議規範,都是以 MQTT v3.1.1 為依據。
之前在入門網頁 [1] 和 [2],已分別說明了使用 RESTful HTTP GET 和 POST 取回與儲存數據的方式,現在換成了 MQTT 的方式,兩者有什麼不同 ? 也可以用同樣的手動方式來達到相同數據取回與儲存的目的嗎 ?
答案是:即使兩者通訊協議格式不同,但都可以使用入門網頁 [1] 和 [2] 所談及的方法,在 ESP8266 AT 指令下的透傳模式進行 MQTT 發佈、訂閱主題 ... 等的動作,只不過比較麻煩的是,需要先了解 MQTT 各種的控制封包格式 (如 CONNECT、PUBLISH、SUBSCRIBE ... 等 ) 才行,雖然這過程有點煩,但是不困難,有點耐心就行!
/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* ESP8266 AT 指令下的透傳模式與 RESTful 和 MQTT 通訊步驟:
首先,必須先了解下圖中的設定參數。這些部分在之前的網頁已經說過,不同的是在右下方的參數部分;MQTT Broker 的 IP 地址沒變,但 Port 號碼要改成 1883。
ESP8266 AT 指令下的透傳模式需要的參數 |
同樣的 AT 指令順序,只差在 Port 號碼的不同,用來區隔接下來要使用的 MQTT 或是 RESTful 的通訊協議。雖然 MQTT 通訊協議相比下比較複雜,但兩者都可想像是一來一回的通訊方式,只不過 MQTT 定義較多的發送與接收的通訊協議方式而已。
ESP8266 AT 指令下的透傳模式與 RESTful 和 MQTT 通訊步驟 |
但對於 MQTT 來說,即便連上 MQTT Broker 進入透傳模式,是不能直接對其取回與儲存數據的。要達到這樣的目的,裝置端 ( Client ) 必須依照 MQTT 協議規範的控制封包格式 ( Control Packet Format ) 和順序才能與通訊端 ( Broker ) 進行溝通;依照不同的通訊目的,MQTT有其對應的控制封包,熟悉這些通訊控制封包格式 ( Control Packet Format ) 就是了解 MQTT 協議規範的不二法門,但這也是最困難的一部分!還好,我們有工具軟體 Wireshark 可以用,下面就會用到。
在繼續深入控制封包格式說明之前,我們先著眼在 MQTT 控制封包上,舉個 PUBLISH 的例子,簡單的來說明一下整個通訊流程。
下圖中,IoT Device ( 指的是 ESP8266,也就是裝置端 ) 進入到透傳模式後,要與 MQTT Broker ( 指的是 iot.cht.com.tw/1883,也就是通訊端 ) 開始通訊,IoT Device 必須先行傳送 CONNECT 控制封包 (封包就是一長串的無符號字元 (unsigned char) 陣列,MQTT Broker 收到後會回傳 CONNACK 控制封包;只有成功完成 CONNECT 發送與接收到 CONNACK 後,才能開始傳送其他的 MQTT 控制封包。
PUBLISH 控制封包包含了 MQTT Topic 和儲存數據的資料 (只考慮 QoS = 0 的情況 ),成功發送之後會收到 MQTT Broker 回應 PUBACK 的控制封包。
CONNECT 控制封包中會包含一個 2-byte 長度叫做 Keep Alive 的數值 ( 單位:秒 ),這個數值表示 IoT Device 最後一筆控制封包發送給 MQTT Broker 後,超過 Keep Alive 時間後必須發一次 PINGREQ 控制封包到 MQTT Broker 去的時間 ( 表示 IoT Device 還活著 ),然後接收 MQTT Broker 回應的 PINGRESP 控制封包才算 OK。
MQTT Broker 最晚能接受接收 PINGEREQ 的時間,一般為 1.5 倍的 Keep Alive 設定值,不過這不一定,有的 Broker 可更長。
DISCONNECT 控制封包,顧名思義就是斷開與 MQTT Broker 的連線 (但此時的 TCP 連線是還存在的 ),MQTT Broker 不回傳任何東西;要重新連線只要再重新發送 CONNECT 控制封包即可。
MQTT 裝置端 ( Client ) 到通訊端 ( Broker ) 通訊協議 |
* MQTT 控制封包格式說明:
這一部分應該在網路上可找到很多的參考資料,不管是協議規範或是二次說明。對於完全沒看過 MQTT 協議的初學者來說,要看懂 MQTT v3.1 或是 MQTT v3.1.1 協議規範就是個問題。這裡建議,兩個版本不管找到的是繁體、簡體、中文和英文都下載,盡量找有索引的看,版本間可以互相對照著,對於理解控制封包格式很有幫助。
MQTT 控制封包格式 ( Control Packet Format ) 基本結構,如下表格所示,分說如下:
不管是哪一種的類型的控制封包,其格式基本可劃分為三個部分:Fixed Header ( 固定標頭 ) + Variable Header ( 可變標頭 ) + Payload ( 訊息本體 ),除 Fixed Header 一定會有之外,後面兩個部分不一定有,整體長度 ( 2 ... 5 + Remaining Length ) 未定,要依不同功能位元 (bit) 指定後才知道 (下面會解釋)。
** Fixed Header ( 固定標頭 ):
Fixed Header 可再細分為兩個部分:MQTT Control Packet Type and Flags ( MQTT 控制封包類型與旗標設定 ),和 Remaining Length ( 剩餘長度 ) 兩個部分;前者占 1 個 byte ( 位元組),後者占 1 至 4 個 byte。
Control Packer Type 佔此位元組的高四位元 ( Bit 7 ... 4 ) ,而 Flags 佔此位元組的低四位元 ( Bit 3 ... 0 )。此位元組的設定照著下表依所使用的 Control Packet Type 設定即可,但像是 PUBLISH 的 Flags 每個位元設定都有其意義,使用前就要先了解。
MQTT 控制封包格式 - Fixed Header |
Remain Length 並不是直接將 n bytes 轉換為 16 進制得到,而是以循環除 128 的方式得到。所以對於 n < 128 bytes 範圍 0x00 ~ 0x7F 的 Remaining Length 只會佔據 1 個 byte,不需要額外再做計算。
不同長度的 Remaining Length 所佔的 bytes 大小與範圍如下所示:
- 2-byte:127 < n ≤ 16,383
- 3-byte:16,383 < n ≤ 2,097,15
- 4-byte:2,097,151 < n ≤ 268,435,455
- 固定標頭 (Fixed Header) 的 MQTT 控制封包類型與旗標設定 (MQTT Control Packet Type and Flags):1 bytes
- 可變標頭 (Variable Header):30 bytes
- 訊息本體 (Payload):16,488 bytes
- 固定標頭 (Fixed Header) 的剩餘長度 (Remaining Length):???
再舉個例子:若 Remaining Length 為 188,則此數值會需要 2 個 byte
188 = 60 + 1 * 128 = 0x3C + 0x01 * 128
Remaining Length = (0x80 OR 0x3C) (0x01) = 0xbc 0x01
Fixed Header = 0x30 0xbc 0x01 (for PUBLISH, DUP=QoS level=RETAIN=0)
16,518 = 6 + 1 * 128 + 1 * 128 * 128 = 0x06 + 0x01 * 128 + 0x01 * 128 * 128
Remaining Length = (0x80 OR 0x06) (0x80 OR 0x01) (0x01)= 0x86 0x81 0x01
Fixed Header = 0x30 0x86 0x81 0x01 (for PUBLISH, DUP=QoS1=QoS0=RETAIN=0)
因為 Remain Length 位於封包前面的位置,所以除非已能事先限定整個接下來 Variable Header 和 Payload 的長度,否則只能在最後才能知道剩餘的長度值。另外,若是不要一次傳送多個 MQTT Topic 或是很大的資料量 (例如,圖片和音視訊),其實是可以藉由限定發送的長度來限制 Remaining Length 所佔據的字元組數目;這對於記憶體比較小的 MCU 是很有用的。
** Variable Header & Payload (可變標頭和訊息本體):
可變標頭和訊息本體的內容根據控制封包類型的不同而不同,但並非每一個控制封包類型都有,而且其內部還會包含封包類型需要的次級項目,這些次級項目依照封包類型的規定有其一定的順序 (必須依照此順序),但不一定全部都有 (不需要的就直接忽略掉),結合完整三個部分的內容就能建立出特定完整的控制封包格式。
這部分所牽扯到的東西比較多,非三言兩語就能解釋清楚,一開始不容易理解這是必然,但是只要借助 Wireshark 抓取 MQTT.fx 與 CHT IoT SP 間的通訊封包後,對照文件中的說明很容易就能夠理解箇中用意!
/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* 抓取 MQTT 通訊封包:
要抓取 MQTT.fx 與 CHT IoT SP 通訊的 MQTT 控制封包,可以使用 Wireshark。但為了能夠確實抓取到需要的部分,要先設定通訊封包過濾的規則,只留下真正與 CHT IoT SP 通訊的部分,其他的則一律捨棄掉!
使用管理者權限開啟 Wireshark,...use this filter: 欄位中輸入
host iot.cht.com.tw && port 1883
完成後按下 ENTER,就會進入到 Wireshark 的封包抓取的畫面
Wireshark 過濾器設定 |
Wireshark 設定 Filter 後的畫面 |
屬於視窗上方的部分:Profile Name 是這個連線設定檔的名稱,可以自己設定;Client ID 除了可自己設定,也可以使用旁邊的 "Generate" 按鈕產生,這是一個唯一的 ID,測試方便的緣故,所以也可直接設定跟下圖一樣;其他的部分照著填就可以,因為這些不能自己定義。
視窗下方分為兩個部分,如下圖為 "General" 頁面。Keep Alive Interval 這裡的數值可以自己設定,最長可以為 65,535 秒 = 18 小時 12 分鐘 15 秒,不清楚就使用預設值就行;MQTT Version 是使用的協議規範版本,這裡使用預設的 v3.1.1 版本即可;其他的部分可以不需要再做修改,除非自己知道這些欄位的定義為何。
MQTT.fx 連線設定 - 01 |
完成設定之後按下 "OK" 回到主畫面下
MQTT.fx 連線設定 - 02 |
以發佈一個字串 "hELLo" 到 SayHello 感測器為例。
要發佈資料到 MQTT Broker,先查閱一下 CHT Iot SP API 文件中的說明
平台 MQTT 發佈感測資料格式 |
Message: 修改為下面樣式 (資料只顯示,不儲存在 CHT IoT SP )
[{"id":"SayHello","save":false,"value":["hELLo"]}]
${device_id} 是專案設備編號,要使用自己建立的;在這裡用的是 7581817213
接著於 MQTT.fx 主畫面按下 "Connect" 與 MQTT Broker 連線 ( 這時 Wireshark 畫面會開始出現抓取的封包,先不用管它 ),輸入上面的資料 (如下圖畫面) 再按下 "Publish"
PUBLISH 感測資料到 SayHello 感測器 |
MQTT PUBLISH SayHello 感測器值的結果 |
打開 Wireshark 並按下左上方紅色方形按鈕,停止封包抓取!為了讓畫面的資料更加的簡潔只出現 MQTT 相關的通訊,所以上方 Apply a display filter ... <Ctrl-/> 欄位輸入 mqtt ,就會出現跟下圖類似的畫面顯示
MQTT 通訊封包抓取結果 |
或許到這裡有人會問:「用函式庫就好!為什麼要深入去了解控制封包格式裡面的意思?」
原因是,下篇網頁開始,我們不會採用 MQTT 函式庫,而是直接像在撰寫 RESTfule API 程式的那三篇入門網頁一樣,都是進入到 ESP8266 AT 指令下的透傳模式,直接來處理發送 MQTT 控制封包格式和接收,所有的 MQTT 控制封包格式都要自己建立和接收處理,所以必須要懂!
下面開始解析 Wireshark 抓取的 MQTT 控制封包格式來做說明,有不詳細的部分,請用戶自行參閱 MQTT 協議規範文件。
** MQTT CONNECT 控制封包格式:
照前面的格式說明,CONNECT 的 Fixed Header = 0x01 ????;???? 表示還未知,需要先得知 Variable Header 和 Payload 字元組數量才能計算。
Variable Header = [ Protocol Name (6-byte) ] + [ Protocol Level (1-byte) ] + [ Connect Flags (1-byte) ] + [ Keep Alive (2-byte) ]
- Protocol Name: 協議名稱;包含兩個字元的協議名稱長度位元組,和四個協議名稱的位元組,完整字串為 0x00 0x04 0x4d 0x51 0x54 0x54(黑體代表 MQTT)
- Protocol Level: MQTT 版本;對於 MQTT v3.1.1 協議規範來說,此位元組值為 0x04
- Keep Alive: 發送 PINGREQ 的間隔時間;此時間佔用兩個位元組,依所設定的時間值 (60),完整字串為 0x00 0x3c
MQTT CONNECT - Control Flags byte |
到此,CONNECT 封包格式 = 10 ???? 00 04 4d 51 54 54 04 c2 ...
Payload = [ Client ID ] + [ Will Topic ] + [ Will Message ] + [ User Name ] + [ Password ]
根據 Control Flags 的設定,Payload 可省略為
Payload = [ Client ID ] + [ User Name ] + [ Password ]
- Client ID: 可自行定義或是產生,前面 2-byte 表示後面的 Client ID 佔多少位元組;這裡採自行定義的值 ( MQTT_FX_Client),完整字串為 0x00 0x0e 0x4d 0x51 0x54 0x54 0x5f 0x46 0x58 0x5f 0x43 0x6c 0x69 0x65 0x6e 0x74
(黑體代表 MQTT_FX_Client) - User Name 或 Password: 使用者帳號和密碼,前面 2-byte 表示後面的 User Name 或 Password 佔多少位元組;兩者都是使用 ${PROJECT_KEY} = PK4LQMHCHE1ZTQFQRW,完整字串都是 0x00 0x12 0x50 0x4b 0x34 0x4c 0x51 0x4d 0x48 0x43 0x48 0x45 0x31 0x5a 0x54 0x51 0x46 0x51 0x52 0x57 (黑體代表 PK4LQMHCHE1ZTQFQRW)
Variable Header 使用 10 bytes (6+1+1+2),Payload 使用 56 bytes ( (2+14)+(2+18)+(2+18) ),兩個部分共使用 66 bytes,因為沒超過 127 bytes,所以 Fixed Header 的 Remaining Length 要填入 0x42
所以整合上面的資料後,可得到一個完整的 CONNECT 控制封包格式
[Fixed Header] [Varible Header] {[Client ID][User Name][Password]}
10 42 00 04 4d 51 54 54 04 c2 00 3c 00 0e 4d 51 54 54 5f 46 58 5f 43 6c 69 65 6e 74 00 12 50 4b 34 4c 51 4d 48 43 48 45 31 5a 54 51 46 51 52 57 00 12 50 4b 34 4c 51 4d 48 43 48 45 31 5a 54 51 46 51 52 57
在 Wireshark 最上方畫面點選 Connect Command,中間部份選擇並打開 MQ Telemetry Transport Protocol, Connect Command ,在最下面的畫面就會出現與上面相同的結果
MQTT CONNECT 控制封包格式抓取結果 |
** MQTT CONNACK 控制封包格式:
照前面的格式說明,CONNACK 的 Fixed Header = 0x20 0x02
Variable Header = [ Connect Acknowledge Flags (1 byte) ] + [ Connect Return code (1 byte) ]
Connect Acknowledge Flags (連線確認旗標) 位元組定義如下 (只需要考慮 Bit 0):
MQTT CONNACK - Connect Acknowledge Flags |
MQTT CONNACK - Connect Return code |
- Clean Session = 0
若 MQTT Broker (伺服端) 已保存了 Client ID 對應的裝置端會話狀態,接收的 CONNACK 控制封包中的旗標 SP (Session Present,當前會話) 會被設置為 1;否則,接收的 CONNACK 控制封包中的旗標 SP 和 Connect Return code (返回碼) 都會是 0。 - Clean Session = 1
接收的 CONNACK 控制封包中的旗標 SP 和 Connect Return code (返回碼) 都會是 0。
所以 Variable Header = 0x00 0x00
由於 CONNACK 不需要 Payload 部分,所以對於 Clean Session = 1 且與 MQTT Broker 連線成功;依照上面的描述,則完整的 CONNACK 控制封包格式為
[Fixed Header] [Varible Header]
20 02 00 00
在 Wireshark 最上方畫面點選 Connect Ack,中間部份選擇並打開 MQ Telemetry Transport Protocol, Connect Ack ,在最下面的畫面就會出現與上面相同的結果
MQTT CONNACK 控制封包格式抓取結果 |
PUBLISH 同時可由裝置端或是通訊端發送;當裝置端向通訊端訂閱一個主題時,通訊端的主題內容有了變更,便會向裝置端發送 PUBLISH 控制封包傳遞相關變更數據,然後接收端再回覆一個 PUBACK 的控制封包回去 (這裡用的是 QoS = 0)。
PUBLISH 的 Fixed Header 如下表所示
MQTT PUBLISH - Fixed Header |
- DUP flag: 是否為副本;若為 0,表示這是裝置端或是通訊端第一次發送這個 PUBLISH 控制封包;若此位元被裝置端或是通訊端設置為 1,表示這可能是一個之前控制封包請求的重發。當 QoS level 被設定為 0 時,DUP flag 也必須設置為 0。
- QoS level: 傳輸品質;表示裝置端與通訊端之間的的傳輸品質,但若接收到這兩個位元組都為 1 的情況時,不管是裝置端或是通訊端都必須關閉網路連線。
MQTT PUBLISH - QoS definitions |
- RETAIN: 是否保留;若裝置端發送給通訊端的 PUBLISH 控制封包中 RETAIN 位元設定為 0,則通訊端將不能儲存這個訊息,也不能移除或是取代任何現有的保留訊息;反之,則必須儲存這個訊息和 QoS level,以便可以在未來分發與主題名相吻合的訂閱者。
- Topic Name: 主題名稱;包含兩個字元的主題名稱長度位元組,和 n 個主題名稱的位元組,完整字串為
00 1d 2f 76 31 2f 64 65 76 69 63 65 2f 37 35 38 31 38 31 37 32 31 33 2f 72 61 77 64 61 74 61
( 黑體代表 /v1/device/7581817213/rawdata )
Packet Identifier: 封包識別號;QoS = 0,沒有 Packet Identifier
Payload = [ Application Message ]
- Application Message: 採用 JSON 格式發佈的 Topic 訊息的部分,完整的字串為
5b 7b 22 69 64 22 3a 22 53 61 79 48 65 6c 6c 6f 22 2c 22 73 61 76 65 22 3a 66 61 6c 73 65 2c 22 76 61 6c 75 65 22 3a 5b 22 68 45 4c 4c 6f 22 5d 7d 5d
( 黑體代表 [{"id":"SayHello","save":false,"value":["hELLo"]}] )
所以由上面的 Variable Header 和 Payload,可以來算算總共用了多少 bytes ?
Variable Header 使用 31 bytes (2+29),Payload 使用 52 bytes,兩個部分共使用 81 bytes,因為沒超過 127 bytes,所以 Fixed Header 的 Remaining Length 要填入 0x51
所以整合上面的資料後,可得到一個完整的 CONNECT 控制封包格式
[Fixed Header] [Varible Header] { Payload }
[Fixed Header] [ Topic Name ] { Application Message }
30 51 00 1d 2f 76 31 2f 64 65 76 69 63 65 2f 37 35 38 31 38 31 37 32 31 33 2f 72 61 77 64 61 74 61 5b 7b 22 69 64 22 3a 22 53 61 79 48 65 6c 6c 6f 22 2c 22 73 61 76 65 22 3a 66 61 6c 73 65 2c 22 76 61 6c 75 65 22 3a 5b 22 68 45 4c 4c 6f 22 5d 7d 5d
MQTT PUBLISH 控制封包格式抓取結果 |
通訊端會根據 PUBLISH 控制封包的 QoS level 回覆相對應的 PUBACK 控制封包,並表示在 Variable Header 的 Packet Identifier 欄位
PUBACK - Expected Publish Packet response |
若是選擇要處理,則
Fixed Header 是固定格式,依據前面表格可得完整字串為 0x40 0x02
Variable Header 只有 Packet Identifier,所以完整字串為 0x00 0x00
由於 PUBACK 不需要 Payload 部分,依照上面的描述,完整的 PUBACK 控制封包格式為
[Fixed Header] [Varible Header]
40 02 00 00
在 Wireshark 最上方畫面點選 Publish Ack,中間部份選擇並打開 MQ Telemetry Transport Protocol, Publish Ack ,在最下面的畫面就會出現與上面相同的結果
MQTT PUBACK 控制封包格式抓取結果 |
如果對此有疑問的話,可以實際動手自己試試!詳細的說明,會在其他接續的網頁中再度提及。
** MQTT PINGREQ 和 PINGRESP 控制封包格式:
PINGREQ 對於 MQTT 協議來說並不一定要有,取決於裝置端發送的間隔時間與 Keep Alive 時間差距;PINGRESP 則是通訊端的回覆。
隨便取一個上方 Wireshark 抓取結果圖就可得知,Keep Alive 的時間設定為 60 秒,只要裝置端在 60 秒鐘沒有發送任何控制封包,那麼裝置端就要發送一次 PINGREQ 控制封包到通訊端,否則最晚延遲 1.5 倍的時間後就會被通訊端關閉網路連線。而且不管在任何時候,裝置端都可以發送 PINGREQ 控制封包給通訊端,並且用 PINGRESP 判斷網路與和通訊端的狀態。
這兩個控制封包都沒有 Variable Header 和 Payload 的部分,而且 Fixed Header 位元組也沒有任何位元需要再額外設定,所以控制封包內容都是固定值
完整的 PINGREQ 發送控制封包格式為
[Fixed Header]
c0 00
完整的 PINGRESP 控制封包格式為
[Fixed Header]
d0 00
在 Wireshark 最上方畫面點選 Ping Request 或 Ping Response,中間部份選擇並打開 MQ Telemetry Transport Protocol, Ping Request 和 Ping Response,在最下面的畫面就會出現與上面相同的結果
MQTT PINGREQ 控制封包格式抓取結果 |
MQTT PINGRESP 控制封包格式抓取結果 |
DISCONNECT 是從裝置端發送給通訊端的最後一個控制封包,表示裝置端正常斷開連接。
這個控制封包只有 Fixed Header 的部分,而且通訊端也不需要回覆任何訊息。
裝置端發送 DISCONNECT 控制封包後,必須關閉網路連線 ( TCP Connection ),而且不能再通過那個網路連線發送任何控制封包。
完整的 DISCONNECT 發送控制封包格式為
[Fixed Header]
e0 00
在 Wireshark 最上方畫面點選 Disconnect Req,中間部份選擇並打開 MQ Telemetry Transport Protocol, Disconnect Req ,在最下面的畫面就會出現與上面相同的結果
MQTT DISCONNECT 控制封包格式抓取結果 |
以上就是關於與 CHT IoT SP 之間採用 MQTT 協議規範 Publish Message (訊息發佈) 的過程與 MQTT 控制封包的格式說明。
有了這些資訊,實際用 ESP8266 來跑跑看,看在 AT 指令下如何發送 MQTT 控制封包。
ESP8266 AT 指令下的 MQTT 控制封包發送與接收:
*********************************************************************************
這一小節所用的材料可自行準備,或選用新版本的升級套件
- V2.1 版 { 萬物皆聯網-ESP8266 IoT(Internet of Things)入門學習套件 }
- Arduino UNO + ESP8266 (Wi-Fi) 二合一開發板 ( 可選擇獨立與偕同開發 )
*********************************************************************************
我將一整個操作的過程做成影片。
影片一開始會先使用 Postman 清除 CHT IoT SP 上 SayHello 的感測資料,然後將 ESP8266 與電腦做 UART 通訊;藉由一連串的 AT 指令發送,ESP8266 就會與 CHT IoT SP 的 MQTT Broker 處於透傳模式下。在此模式下,所鍵即所傳,所見即所收!
MQTT 的控制封包格式裡面包含一般 ASCII 碼或是非字元碼,因此 (不管是使用何種軟體) 都要先將發送與接收的字元格式切換為 16 進位格式,否則發送會出現錯誤,而且也看不到通訊端的回覆。
為了要發佈資料到 SayHello,首先要做的就是先發送 CONNECT 控制封包 ( 會收到 20 02 00 00 的 CONNACK 回覆 ),發送之後就能 PUBLISH 資料出去 (這時 SayHello 感測資料顯示為 hELLo,並收到 40 02 00 00 的 PUBACK 回覆 )。
一般情況之下若是發佈的時間小於 Keep Alive 所設定的時間值,是不用額外發送 PINGREQ,不過在這裡我們同樣演示一下當發送 PINGREQ 控制封包出去後,通訊端就會回傳 D0 00 的 PINGRESP 控制封包回來,用來表示雙方的通訊還是存在的。
最後就是斷開與 MQTT 的連結;此時要發送 DISCONNECT 控制封包給通訊端。不過這並不會得到任何來自通訊端的回覆,裝置端與通訊端的 TCP 連線這時還是存在的,裝置端必須自行關閉與 MQTT Broker 的 TCP 連線。這就如影片中所下的 AT 指令 AT+CIPCLOSE,這樣才是真正斷開。
只有真正的將這些操作過程實際走過一遍,才能深入了解 MQTT 協議。
結論:
對於 MQTT,本篇網頁只是蜻蜓點水、引領入門而已!
若是使用封裝好的函式庫或是軟體,其實大可不必了解到控制封包的格式組成到如此詳細的地步,只需要了解控制封包中參數對於 MQTT 通訊的影響為何即可。
本篇網頁並未對 MQTT SUBSCRIBE (訂閱主題) 相關控制封包或 QoS > 0 的部分著墨,大部分的原因是那一部分的程式還未撰寫,所以就先不在這裡說明了,待日後弄好之後,會在這裡或是其他篇網頁再做補充。
接下來的網頁,就讓我們開始來撰寫 MQTT PUBLISH 的程式吧!
<< 部落格相關文章 >>
- 當 ESP8266 遇上中華電信 IoT 智慧聯網大平台 { 入門 - 07 } - 結合 Arduino + ESP8266 實現 MQTT 主題訂閱與接收
- 當 ESP8266 遇上中華電信 IoT 智慧聯網大平台 { 入門 - 06 } - 了解 MQTT 協議,學習如何訂閱 MQTT 主題與接收 MQTT 發佈消息
- 當 ESP8266 遇上中華電信 IoT 智慧聯網大平台 { 入門 - 05 } - 結合 ESP8266 發佈 MQTT 消息
- 當 ESP8266 遇上中華電信 IoT 智慧聯網大平台 { 入門 - 03 } - 使用 Arduino + ESP8266 上傳 SHT31 溫溼度數據
- 當 ESP8266 遇上中華電信 IoT 智慧聯網大平台 { 入門 - 02 } - 設備感測數據讀取與 (JSON) 解析
- 當 ESP8266 遇上中華電信 IoT 智慧聯網大平台 { 入門 - 01 } - 如何使用 ESP8266 利用 AT 指令取回中華電信 IoT 智慧聯網大平台上的設備感測器數據
- 如何使用 MCU 建立與其他 ESP8266 的 UDP 透傳通訊
- ESP8266 AT 指令下的透傳模式
- 如何使用 AT 指令讓同在 AP+STA 模式下的 ESP8266 互相通訊 ?
- 操控 ESP8266 無線模組 - 經由 AP、STA 和 AP+STA 三種模式,學習 ESP8266 AT 指令
請問大大 是否任何nb-iot的 modem卡在完成連線設定之後‘在沒有相關的mqtt函式庫之下(例如 PubSubClient.h)
回覆刪除都要撰寫如此繁複的通訊程式 才能控制通訊晶片完成 publish & subscribe的收發動作
對!
刪除PubSubClient.h 就是把一些底層的動作包裝起來用,實際去看它的程式碼,也會是類似的東西。