nrf9160做主控連線阿里雲——(mqtt_simple例程)

2022-10-29 21:04:16

簡介:基本每一個雲都支援MQTT,這種輕量級協定在資料量不大的應用上是一個很好的選擇。上一篇部落格使用SLM例程去連線了阿里雲,本次使用mqtt_simple去連線雲進行測試,關於一些已近在前面文章中演示過環境設定就不在贅述了,mqtt_simple例子只能使用MQTT的方式去連線雲,沒有像SLM那樣可以使用AT指令通過各種方式(http、https、mqtt)去連線雲。

在開始之前依然把我們需要的軟硬體列舉一些:

前期準備:

1、nrf9160的官方開發板或者9160的模組一塊;

2、物聯網路卡一張

3、官方的mqtt_simple例程和工具,這個在nordic官方都可以下載,如果你是一次接觸,那麼可以看一下我前面的部落格,或者直接去看官方的中文部落格,去下載安裝好NCS還有開發環境,中文官方部落格連線:開發你的第一個nRF Connect SDK(NCS)/Zephyr應用程式 - iini - 部落格園 (cnblogs.com)

下面依然走一遍流程(在其餘部落格中有詳細演示的就只簡單帶過,如果不知道可以在其餘部落格中去尋找答案)

注:本次主要使用了1.8版本的SDK(NCS),到本文編寫時最新的NCS-V2.1版本的也進行過測試,沒有問題。

一、給nrf9160下載韌體

1、官網下載modem韌體

去官網下載modem韌體,注意使用和NCS版本對應的modem韌體:

下載完成後你會得到一個如下圖所示的壓縮包(該壓縮包對應NCS-V1.8版本)。

2、燒寫modem

然後我們使用nordic的PC端工具programmer進行下載modem韌體,把nrf9160的開發板或者模組連線到PC端,然後開啟後如下(我使用的是DK板,所以顯示為DK,如果你使用的是模組可能不一樣),然後我們點選連線,等待識別完成。

可以點選擦除一下,然後拖拽剛剛下載好的modem韌體壓縮包到工具介面,然後進行下載。

 這裡依然提醒一下:modem韌體的存放位置路徑不要有中文,如果出現有中文,會導致無法下載成功。

等待片刻後下載成功如下,關閉即可:

二、註冊阿里雲裝置

本次實驗依然採用的是阿里雲的免費物聯網雲進行測試,接入方式依然為一機一密方式,在阿里雲檔案中的阿里雲物聯網平臺有相關的檔案介紹:

2.1、註冊開啟物聯網平臺

(這一小節阿里雲裝置建立即為nrf9160做modem——測試連線阿里雲 - 星辰_stars - 部落格園 (cnblogs.com)中的流程)

在瀏覽器中搜尋阿里雲(https://www.aliyun.com/?utm_content=se_1012440659),如果你沒有註冊過,請註冊然後登陸,登陸後在搜尋方塊中輸入物聯網平臺,然後搜尋。

 在跳轉的介面點選進入控制檯

 跳轉到如下介面,點選公共範例

 在跳轉的介面如下操作開始建立產品

 2.2、建立物聯網產品

在點選建立產品後,在產品建立介面建立自己的裝置

1)、設定名字為nrf9160_test

 2)、選擇所屬類別

你可以根據自己的需要選擇標準品類,或者自定義,我本次選擇標準品類,然後選擇任意一個型別

3)、節點型別

這裡必選選擇直連裝置

 4)、連網與資料

聯網選擇蜂窩,資料必須為ICA的JSON格式

 5)、認證方式

選擇為裝置祕鑰方式

設定以上選項,本次測試在次建立一個產品裝置,本次建立的裝置資訊如下(區別於上一篇部落格的是,為了方便後續講解在聯網方式上選擇了wifi,如給你是物聯網裝置(在板子上需要SIM卡的)請你依然選擇蜂窩方式):

 在產品介面我可以看到我們建立好的產品:

2.3、在產品中建立物聯網裝置

點選管理裝置:

 

 然後新增裝置:

 這次隨意新增一個裝置T123:

然後我點選產品介面,test產品的檢視,釋出一下我們的產品(不理解這一步的可以看一下前面的文章)

三、根據三元組計算連結引數——MQTT的CONNECT(連線伺服器端)報文

3.1、獲取三元組

重要:在建立好裝置後就可以獲取裝置的三元組,然後根據三元組和阿里雲的要求計算獲得連線引數寫到mqtt_simple程式中,即可進行程式連線了,有些雲不一定需要,不同的雲可能有不同的要求,可以諮詢提供雲服務的官方或者參考相關雲的官方檔案。

點選裝置,找到剛剛建立的裝置,然後點選Devicesecret可以獲取到我的三元組資訊

 然後一鍵複製貼上到剪下板

3.2、MQTT協定的CONNECT命令解析——並計算連線資訊

講到這想要記錄一下我學習的MQTT協定連線命令——CONNECT連線伺服器端命令,可以便於我們在出現連線錯誤有不知道為什麼時,可以抓包進行分析,這一步我覺得是很有必要的。下面就讓我們來一起學習一下。

MQTT報文一共有14條,下面附圖,在本節主要講解CONNECT報文:

表3.1:

 本節參考了MQTT 協定 3.1.1 中文版,在現有的NCS中使用的也是mqtt-V3.1.1版本。

由上圖可知CONNECT報文一共由三個部分組成分別是固定報頭、可變報頭、負載

在這之前我們先來了解一下MQTT的訊息質量是三個等級(句號後的黃色部分是作者自己理解的,如有錯誤歡迎指正):

  • QoS 0:訊息最多傳遞一次。如果當時使用者端不可用,則會丟失該訊息。只要傳送了一條訊息就不再關心它有沒有傳送到對方,也不設定任何重發機制
  •  
  • QoS 1:訊息傳遞至少 1 次。包含了從傳機制,如果伺服器來不及應答,就會導致使用者端端超時,再次傳送一次訊息,伺服器端每次對沒一個訊息都要回復,在有多條訊息時,不會像QoS2一樣去和使用者端確認說,你連續發了這麼多,是都要執行嘛,還是隻執行一次,多發的是誤發


  • QoS 2:訊息恰好傳送送一次。相同的命令值希望執行一次,不會由於如QoS1一樣導致說,本來只想執行一次的資訊,多執行了幾次,由於有了這個保證,要完成這個機制(多一個應答機制),導致系統開銷會大,但是保證了訊息的精準性,QoS2質量的訊息只有在比較重要的領域應用,如軍事 ,航天等

QoS2因此是最高質量的訊息,就如我們本次使用的阿里雲佔時還在不支援這個等級的訊息。 

3.2.1、固定報頭

1)第一位元組:

 

 

 如截圖,第一位元組,一共8bit,分為兩部分,4~7bit定義了每一包報文是一個什麼型別,所有報文如表3.1中所示,下圖是MQTT協定中對CONNECT報文的定義:

 

由此我們可以確定整個報文的第一位元組為0x10(為了書寫方便後續將不在寫0x這個十六進位制的標號,將直接使用10標識)。

2)第二位元組(也有可能是第2到第3、或者2到4,或者2到5)

為什麼會有不同的選擇,這是由於在第一位元組確定了本包報文是什麼型別後,會在後續告訴對方後面的可變報頭和負載一共有多少位元組,當後面只有120個位元組時可以用一個位元組就表示好,但是當有500,或者1000時就不是一個位元組可以表示的長度了(二進位制表示方式,不理解的這裡可以自己百度)。為什麼會有500,或者1000的那麼大的差別呢,這由於有些可選設定,如有需要可設定進去(然後再後面的可變報頭給對應的bit位寫1,表示我要使用改設定,那麼伺服器端檢測到該標誌就可以知道說原來你本次有這個可選設定,我會在檢測負載資料時進行檢測讀取的),然後就是如果你設定裝置名字或者密碼等時給了一個很長的名字,那資料不就是增加了,所以才在這把長度搞成這樣的可選,然後還經過特殊的設定讓接收裝置可以很好的知道本次報文這部分到低是用幾個位元組表示後面資料的長度,接下來我們會詳細講解一下,先把截圖放在下面,這就是為什麼剩餘長度bute2...有三個點的原因:

 分別表示(每個位元組的低 7 位用於編碼資料, 最高位是標誌位) :
1 個位元組時(整個報文包的第2位元組), 從 0(0x00)127(0x7f)
2 個位元組時(整個報文包的第2位元組和第3位元組), 從 128(0x80,0x01)16383(0Xff,0x7f)
3 個位元組時(整個報文包的第2、3、4位元組), 從 16384(0x80,0x80,0x01)2097151(0xFF,0xFF,0x7F)
4 個位元組時(整個報文包的第2、3、4、5位元組), 從 2097152(0x80,0x80,0x80,0x01)268435455(0xFF,0xFF,0xFF,0x7F))

長度計算方式:

  每個位元組只取前面7位表示資料,第8位元表示有沒有進位,如果第8位元為1就表示有進位,長度還應該檢查第3位元組的前前7位來乘128,因為2的7次方為128(這裡不明白為什麼是2的7次方可以百度),同理第3位元組的第八為如果也是1,那麼就應該檢查第4位元組來加入計算,注意這裡是乘於128*128,一直到第5位元組:

  eg1:假設我們現在有的可變報頭和負載一共有100(十進位制)位元組資料

    100(十進位制)的十六進位製為0x64——所以我們該部分只有0x64即可

  eg2:假設可變報頭和負載一共有500個資料(十進位制)位元組資料

    500/128=3剩餘116,那麼116轉化為0x74,但是由於有進位所以第8位元應該為1,所以原本的0x74(01110100)第8位元變1(11110100)0xF4,所以第二位元組為0xF4,那麼由於有進位就有第三位元組,所以第三位元組為0x03。

3)阿里雲連結報文CONNECT的固定報頭確定

有上面的講解,我們可以確定本次連結報文的固定報頭為(十六進位制):

10 ?(問號的意思是現在還不知道我們本次可變報頭個負載資料長度,我們最後新增)

3.2.2、可變報頭

在MQTT協定棧中規定可變報頭包含4個欄位,分別為協定名(Protocol name)、協定級別(Protocol  Level),連線標誌(Protocol  flags)、保持連線(Keep alive),下面我, 來分別看一下。

1)、協定名

這一共6個位元組,是協定直接規定的,我們直接帶入就行,每一位元組資料如下:

  說明 7 6 5 4 3 2 1 0
byte1 長度MSB(0) 0 0 0 0 0 0 0 0
byte2 長度LSB(4) 0 0 0 0 0 1 0 0
byte3 「M」 0 1 0 0 1 1 0 1
byte4 「Q」 0 1 0 1 0 0 0 1
byte5 「T」 0 1 0 1 0 1 0 0
byte6 「T」 0 1 0 1 0 1 0 0

那根據協定規定我們可以得到如下的資料:

10 ?00 04 40 51 54 54

2)協定級別

用一個位元組表示協定級別,前面有說過我們使用和參考的協定為MQTT-V3.1.1,那麼他的標識就是0x04,

3)連線標誌

用一個位元組表示連結標誌,其中每一位都有不同的意思,連線標誌如圖示所示

  • clean session:0——不清除裝置的連線資訊,全新的裝置A第一次連線記錄了一下資訊Aq,那麼第二次連線還是要有這些資訊或者基於進行連線或者保留,1——每一次斷開連線都清除連線記錄
  • Will Flag:0——沒有遺囑訊息,1——有遺囑訊息(如當裝置1,2,3都訂閱了遺囑訊息,當1裝置突然斷電,導致還沒有發DISCONNECT報文就斷開了(還有其餘情況下的錯誤),那麼2和3這兩個連結到伺服器的裝置就會收到一條1的遺囑訊息)
  • Will QoS:用於指定釋出遺囑訊息時使用的服務質量等級,0——(will flag=0時必須為零),當will flag=1時可以設定為0,1,2表示訊息質量(如前面訊息質量)
  • Will retain:遺囑保留,0——不保留資訊,掉線後從連無法獲取資訊,1——保留資訊,掉線後重連可以獲取資訊(這一點我理解的是這樣,不知道對不對,如有更好的解釋可以在評論區討論)
  • password flag:密碼,0——在負載中不包含密碼,1——在負載中包含密碼
  • user name flag:使用者名稱,0——在負載中不包含使用者名稱,1——在負載中包含使用者名稱

由於我們連線的是阿里雲,阿里雲要求必須是有使用者名稱和密碼的,不使用遺囑訊息,且有不保留資訊,也就是要清除所以這一位元組為(11000010)0xC2

4)保持連線

這一部分為兩個位元組,在實際連線中,要不間斷的在規定時間內給伺服器傳送PING保活包,那這個規定時間內時間是多長時間,就在這個設定好,當伺服器和你連線完成後,你沒有其他任何控制報文時,需要在一定時間內傳送PING包,如果你在這個時間內沒有傳送到PING包,那麼伺服器就認為你斷開連線了。單位是秒。這裡每個伺服器在MQTT協定規定的最大時間內還可以規定自己的最大時間,本次測試就設定為100s(64s)內必須有PING包出現,不然就認為是斷開連線,對於嵌入式裝置來說這個時間越長越低功耗。

總結:

  • 使用者端和伺服器沒有任何報文交流時,規定時間內要有保活包(PING報文)
  • 無控制報文交流的期間,使用者端在任何時候都可以傳送PING報文
  • 使用者端傳送PINGREQ(PING的一種)後,在合理時間(博主佔時沒找到說明)沒有收到伺服器端來的PINGRESP(PING的一種),使用者端要斷開連線。
  • 保持連線值非零,設為T,那麼在1.5T倍時間類伺服器端沒有收到任何報文,伺服器端需要斷開連線
  • 保持連線為零,伺服器端不需要因為使用者端不活躍而斷開連線

因此我們的封包就變成了如下這樣:

10 ?00 04 40 51 54 54 04 c2 00 64

3.2.3、負載(也就資料)

負載資料就是前面標誌位中設定要包含的資料,全部有使用者端識別符號、遺囑主題、遺囑訊息、使用者名稱、密碼,五部分組成,在這個值講解三部分。這裡先看一下阿里雲對連線密碼使用者名稱和使用者端識別符號的要求

三元組:

1)、使用者端識別符號

阿里雲參考規範:mqttClientId : clientId+"|securemode=3,signmethod=hmacsha1,timestamp=132323232|"

根據對引數的解釋,clientId就為三元組中的T123,securemode由於選擇一機一密所以已經固定,不要改預設就行,signmethod預設加密為hmacmd5沒有改,後續的timestamp我們不需要設定省略掉

最終mqttClientId就為「T123|securemode=3,signmethod=hmacmd5|」

我們把這一串轉換為十六進位製為54 31 32 33 7C 73 65 63 75 72 65 6D 6F 64 65 3D 33 2C 73 69 67 6E 6D 65 74 68 6F 64 3D 68 6D 61 63 6D 64 35 7C 在再前面加上使用者端ID的長度(兩位元組)最後變為:

00 25 54 31 32 33 7C 73 65 63 75 72 65 6D 6F 64 65 3D 33 2C 73 69 67 6E 6D 65 74 68 6F 64 3D 68 6D 61 63 6D 64 35 7C

2)、使用者名稱

阿里雲參考規範:mqttUsername:deviceName+"&"+productKey

用三元元件替換mqttUsername:T123&a1tETt7fUG1

變成十六進位制:54 31 32 33 26 61 31 74 45 54 74 37 66 55 47 31 

加上長後為:00 10 54 31 32 33 26 61 31 74 45 54 74 37 66 55 47 31 

3)、密碼獲取

阿里雲參考規範:mqttPassword::sign_hmac(deviceSecret,content)

這裡需要用到加密演演算法hmacmd5使用三元組中的DeviceSecret做為祕鑰對clientId*deviceName*productKey#加密後作為密碼——其中*號為裝置名稱,#ProductKey

即clientIdT123deviceNameT123productKeya1tETt7fUG1,然後再網頁上開啟一個網頁版加密工具:線上加密解密 - chahuo.com

 由此獲得我們的密碼:86a087f11cad5c325127ae5f79305109,經過轉化後並加上兩位元組長度資訊後:

00 20 38 36 61 30 38 37 66 31 31 63 61 64 35 63 33 32 35 31 32 37 61 65 35 66 37 39 33 30 35 31 30 39

由此我們來組合一下我們的CONNECT報文包

10 ? 00 04 4D 51 54 54 04 C2 00 64 00 25 54 31 32 33 7C 73 65 63 75 72 65 6D 6F 64 65 3D 33 2C 73 69 67 6E 
6D 65 74 68 6F 64 3D 68 6D 61 63 6D 64 35 7C 00 10 54 31 32 33 26 61 31 74 45 54 74 37 66 55 47 31 00 20 38
36 61 30 38 37 66 31 31 63 61 64 35 63 33 32 35 31 32 37 61 65 35 66 37 39 33 30 35 31 30 39

那麼由此我就可以知道問號後面一共多少個位元組了就是後面的綠色和黑色部分位元組長度一共101(65)

因此整個報文資訊就為:

10 65 00 04 4D 51 54 54 04 C2 00 64 00 25 54 31 32 33 7C 73 65 63 75 72 65 6D 6F 64 65 3D 33 2C 73 69 67 6E 
6D 65 74 68 6F 64 3D 68 6D 61 63 6D 64 35 7C 00 10 54 31 32 33 26 61 31 74 45 54 74 37 66 55 47 31 00 20 38 
36 61 30 38 37 66 31 31 63 61 64 35 63 33 32 35 31 32 37 61 65 35 66 37 39 33 30 35 31 30 39 

3.3、PC端工具連線測試

我們用PC端網路工具進行一下測試:

在測試前還需要知道雲的地址,在阿里雲這進行檢視:

Url:a1tETt7fUG1.iot-as-mqtt.cn-shanghai.aliyuncs.com

port:1883

 可見伺服器回覆20 02 00 00,接受我們連線了(其中20,表示報文型別,20是回覆包,02是剩餘長度——後面還有兩個位元組,00相當於站位位元組,最後以一個00,表示已經接受)

MQTT協定定義的響應命令如下:

阿里雲上也顯示我們的裝置線上:

如果連線包存在錯誤會是怎麼樣的結果,我們來看一下: 

 04提示我們無效的密碼或者使用者名稱,因為我們把原來的39改為了00,密碼錯誤。

以上就是對CONNECT報文的講解,那麼我在上一篇部落格使用了一個阿里雲的計算器,其實就是完成上面我們複製的計算,只要複製我們的裝置資訊,就可以一鍵生成我們的密碼名稱等。工具連線如下:阿里雲引數小工具 (lovemcu.cn)這裡值得注意的是每一個雲的情況不一樣,要去根據雲的檔案確定,但是報文格式是一樣的。

上面是為了使用PC端工具進行連線,以便於我們更好的理解CONNNECT報文包,那下我們來使用nrf9160連線阿里雲,本節獲取的雲連線資訊如下:

3.3.1、連線資訊(有三元組計算得到)

綜上所述連線資訊如下:

clientid:T123|securemode=3,signmethod=hmacmd5|
username:T123&a1tETt7fUG1
password: 86a087f11cad5c325127ae5f79305109

三、mqtt_simple程式修改(NCSV1.8)

使用vs code建立一個mqtt_simple工程,不會的請參看我起那麼的不可或者頂部的官方中文部落格連線,這就不細講了,然後我們對程式進行修改。

1、prj.conf檔案修改

1)、開啟工程中的prj.conf組態檔,修改聯網方式,中國只有NB網路,從LTE修改為NB

CONFIG_LTE_NETWORK_MODE_NBIOT_GPS=y

2)、加入PDN定義,為了把PDN的函數編譯進來

CONFIG_PDN=y
CONFIG_PDN_LEGACY_PCO=y
CONFIG_PDN_SYS_INIT=y

編譯後,對於1.8的NCS需要確定pdn.c檔案中的AT%%XEPCO=0處為2個%分號,如果不是,請修改為

修改後如下:

 

然後再編譯

3)、加入連線引數和推播訂閱的主題

CONFIG_MQTT_PUB_TOPIC="/a1tETt7fUG1/T123/user/get"
CONFIG_MQTT_SUB_TOPIC="/a1tETt7fUG1/T123/user/update"
CONFIG_MQTT_CLIENT_ID="T123|securemode=3,signmethod=hmacmd5|"
CONFIG_MQTT_BROKER_HOSTNAME="a1tETt7fUG1.iot-as-mqtt.cn-shanghai.aliyuncs.com"
CONFIG_MQTT_BROKER_PORT=1883

#對於埠1883和前面的等號不要有空格,這一點要注意

釋出和訂閱的主題需要在阿里雲中,即我們前面建立的裝置端下topic中去找一個有訂閱和釋出許可權的型別即可(不知道在哪的可以去看上一篇文章)。

 如圖中間的${deviceName}換為我們的裝置名T123。

4)、使用者名稱和密碼緩衝區設定加入

如果不加入這,當你使用者名稱和密碼過長時會包-12的錯誤,我們給他大一點的空間

CONFIG_MQTT_MESSAGE_BUFFER_SIZE=512
CONFIG_MQTT_PAYLOAD_BUFFER_SIZE=512

2、程式修改

開啟main.c找到client_init()函數

新增如下程式碼:

#define CONFIG_MQTT_BROKER_USERNAME_test  "T123&a1tETt7fUG1"
#define CONFIG_MQTT_BROKER_PASSWORD_test  "86a087f11cad5c325127ae5f79305109"
#這加成宏定義
#以下放置在client_init中
    struct mqtt_utf8 password_test={
        .utf8=CONFIG_MQTT_BROKER_PASSWORD_test,
        .size=strlen(CONFIG_MQTT_BROKER_PASSWORD_test)
    };
    struct mqtt_utf8 user_name_test={
        .utf8=CONFIG_MQTT_BROKER_USERNAME_test,
        .size= strlen(CONFIG_MQTT_BROKER_USERNAME_test)
    };

#以下直接修改
    client->password =&password_test;
    client->user_name =&user_name_test;

修改後:

如果你發現你這樣定義後,程式依然報-12的錯誤,那麼請改變一下你使用者名稱和密碼的定義方式,不是使用宏定義,而是使用陣列的方式:

uint8_t password_test_21[50]="86a087f11cad5c325127ae5f79305109";
uint8_t user_test_21[50]="T123&a1tETt7fUG1";

    struct mqtt_utf8 password_test={
        .utf8=password_test_21,
        .size=strlen(password_test_21)
    };
    struct mqtt_utf8 user_name_test={
        .utf8=user_test_21,
        .size= strlen(user_test_21)
    };

    client->password =&password_test;
    client->user_name =&user_name_test;

3、mqtt_simple連結阿里雲

 然後我們就編譯下載:

 連線成功過,然後再阿里雲端可以看到裝置線上:

 

——未完待續,後續會繼續完善這篇部落格