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

簡介:基本每一個雲都支持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)中的流程)

在瀏覽器中搜索阿里雲(//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包,那麼服務器就認為你斷開連接了。單位是秒。這裡每個服務器在MQTT協議規定的最大時間內還可以規定自己的最大時間,本次測試就設定為100s(64s)內必須有PING包出現,不然就認為是斷開連接,對於嵌入式設備來說這個時間越長越低功耗。

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

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;

修改後:

3、mqtt_simple鏈接阿里雲

 然後我們就編譯下載:

 連接成功過,然後再阿里雲端可以看到設備在線:

 

——未完待續,後續會繼續完善這篇博客

 

Tags: