本文詳細講解了基於合宙Air780EP模組LuatOS開發的多個MQTT應用示例。
本文同樣適用於合宙的以下型號:
Air780EPA/Air780EPT/Air780EPS
Air780E/Air780EX/Air201…
一、相關準備工作
1.1 硬體準備
合宙EVB_Air780EP開發板一套,包括天線、SIM卡;
USB線
PC電腦
1.2 軟體準備
登入合宙官方IoT後臺:
https://iot.openluat.com/
LuatOS韌體下載:
https://gitee.com/openLuat/LuatOS/releases
DEMO原始碼下載:
https://gitee.com/openLuat/LuatOS/tree/master/demo/mqtt
注:進入LuatOS韌體頁面按下Ctrl+F,搜尋Air780EP即可找到對應的韌體。
推薦選用:相關型號韌體名稱後面數字版本號最高的最新relase版本進行除錯。
二、名詞解析及API說明
2.1 名詞解析
user_name使用者名稱
password密碼
client_id使用者標識
MQTT可以透過前兩個引數保證連線的安全,透過client_id確保裝置唯一性。
QOS訊息質量
分為0/1/2三個等級,分別表示只發一次,至少收到一次和只收到一次。透過QOS可以根據資料的重要性靈活,選擇傳送方式以節省頻寬和保證資料可靠。
關於publish時QOS值的說明:
QOS為0時,壓入底層TCP傳送堆疊,視為成功;
QOS為1時,收到伺服器回應PUBACK,視為成功;
QOS為2時,收到伺服器響應PUBREC,立即上行PUBCOMP壓入TCP傳送佇列,視為成功。
mqtt_host主機伺服器地址
mqtt_port埠
MQTT需要連線的伺服器引數。
mqtt_host可以說是ip或者域名。
topic主題
根據主題區別訊息型別和來源,主要用來分類資料。同時mqtt是釋出訂閱模型,topic是釋出和訂閱者通訊的重要通道。
payload訊息內容
釋出和訂閱的具體資料。
retain保留訊息
保留訊息是一條將保留標誌(retained flag)置為true的普通MQTT訊息。broker會針對主題依照QoS級別保留最後一條保留訊息,當訂閱者訂閱主題時會立即收到保留訊息。broker僅為每個主題保留一條保留訊息。
2.2 API說明
三、MQTT實現流程
3.1 建立一個TASK協程
介面:
sys.taskInit(func, arg1, arg2, argN)
引數:
返回值:
3.2 等待網路就緒
3.3 建立一個mqtt例項
介面:
mqtt.create(adapter,host,port,ssl,isipv6)
引數:
返回值:
3.4 連線伺服器
介面:
mqttc:connect()
引數:
無
返回值:
注意:本函式僅代表發起成功, 後續仍需根據ready函式判斷mqtt是否連線正常。
3.5 訂閱主題
介面:
mqttc:subscribe(topic, qos)
引數:
返回值:
3.6 釋出訊息
介面:
mqttc:publish(topic, data, qos, retain)
引數:
返回值:
3.7 接收訊息
3.8 斷開伺服器的連線
介面:mqttc:disconnect()
引數:無
返回值:
四、MQTT示例說明
本文以demo_lua\LuatOS\demo\mqtt這個demo為例作為演示,以MQTTX工具來測試MQTT執行狀況。
**4.1 在MQTTX軟體上建立連線 **
4.2 在MQTTX軟體上新增訂閱
注意:
訂閱主題格式要求預設為 /luatos/pub/ 加模組的IMEI號。
例如:/luatos/pub/868488076506128
4.3 設定MQTTX軟體上釋出訊息的主題
注意:
格式要求預設為 /luatos/sub/ 加模組的IMEI號。
例如:/luatos/sub/868488076506128
4.4 燒錄指令碼
上電開機後模組會自動向伺服器傳送訊息,這是程式碼預設設定的,若不需要可以刪掉。
4.5 模組向伺服器傳送訊息
透過Luatools軟體可以看到:
Luatools軟體下載:
https://wiki.luatos.com/pages/tools.html
五、MQTT單連結示例
5.1 修改指定引數
在程式碼開頭,可根據自己的伺服器修改指定的引數。
注意:
user_name、password在有些伺服器上是可以不傳入的,或者是對傳入的值沒有要求限制;要根據實際伺服器要求來填寫。
--根據自己的伺服器修改以下引數
local mqtt_host = "lbsmqtt.airm2m.com"
local mqtt_port = 1884
local mqtt_isssl = false
local client_id = "abc"
local user_name = "user"
local password = "password"
local pub_topic = "/luatos/pub/" .. (mcu.unique_id():toHex())
-- 該變數在下方程式碼有重新賦值,根據實際應用場景,可自行修改指令碼邏輯
local sub_topic = "/luatos/sub/" .. (mcu.unique_id():toHex())
-- 該變數在下方程式碼有重新賦值,根據實際應用場景,可自行修改指令碼邏輯
5.2 連線/訂閱/釋出流程
此task實現的是MQTT的連線、訂閱訊息、釋出訊息的流程。
注意:
要先等待網路就緒之後,才可進行MQTT後續操作。
待網路就緒之後,根據程式碼編寫情況此時client_id、pub_topic和sub_topic會發生變化,會覆蓋掉程式碼開頭部分時的配置,這點需要注意;device_id為模組的IMEI號。
示例原始碼下載:
https://gitee.com/openLuat/LuatOS/tree/master/demo/mqtt
sys.taskInit(function()
-- 等待聯網
local ret, device_id = sys.waitUntil("net_ready")
-- 下面的是mqtt的引數均可自行修改
-- client_id = device_id
pub_topic = "/luatos/pub/" .. device_id
sub_topic = "/luatos/sub/" .. device_id
-- 列印一下上報(pub)和下發(sub)的topic名稱
-- 上報: 裝置 ---> 伺服器
-- 下發: 裝置 <--- 伺服器
-- 可使用mqtt.x等客戶端進行除錯
log.info("mqtt", "pub", pub_topic)
log.info("mqtt", "sub", sub_topic)
-- 列印一下支援的加密套件, 通常來說, 韌體已包含常見的99%的加密套件
-- if crypto.cipher_suites then
-- log.info("cipher", "suites", json.encode(crypto.cipher_suites()))
-- end
if mqtt == nil then
while 1 do
sys.wait(1000)
log.info("bsp", "本bsp未適配mqtt庫, 請查證")
end
end
-------------------------------------
-------- MQTT 演示程式碼 --------------
-------------------------------------
mqttc = mqtt.create(nil, mqtt_host, mqtt_port, mqtt_isssl, ca_file)
mqttc:auth(client_id,user_name,password) -- client_id必填,其餘選填
-- mqttc:keepalive(240) -- 預設值240s
mqttc:autoreconn(true, 3000) -- 自動重連機制
mqttc:on(function(mqtt_client, event, data, payload)
-- 使用者自定義程式碼
log.info("mqtt", "event", event, mqtt_client, data, payload)
if event == "conack" then
-- 聯上了
sys.publish("mqtt_conack")
mqtt_client:subscribe(sub_topic)--單主題訂閱
-- mqtt_client:subscribe({[topic1]=1,[topic2]=1,[topic3]=1})--多主題訂閱
elseif event == "recv" then
log.info("mqtt", "downlink", "topic", data, "payload", payload)
sys.publish("mqtt_payload", data, payload)
elseif event == "sent" then
-- log.info("mqtt", "sent", "pkgid", data)
-- elseif event == "disconnect" then
-- 非自動重連時,按需重啟mqttc
-- mqtt_client:connect()
end
end)
-- mqttc自動處理重連, 除非自行關閉
mqttc:connect()
sys.waitUntil("mqtt_conack")
while true do
-- 演示等待其他task傳送過來的上報資訊
local ret, topic, data, qos = sys.waitUntil("mqtt_pub", 300000)
if ret then
-- 提供關閉本while迴圈的途徑, 不需要可以註釋掉
if topic == "close" then break end
mqttc:publish(topic, data, qos)
end
-- 如果沒有其他task上報, 可以寫個空等待
--sys.wait(60000000)
end
mqttc:close()
mqttc = nil
end)
5.3 傳送資料
此task的功能為模組每3秒向伺服器傳送一次資料。
-- 這裡演示在另一個task裡上報資料, 會定時上報資料,不需要就註釋掉
sys.taskInit(function()
sys.wait(3000)
local data = "123,"
local qos = 1 -- QOS0不帶puback, QOS1是帶puback的
while true do
sys.wait(3000)
if mqttc and mqttc:ready() then
local pkgid = mqttc:publish(pub_topic, data .. os.date(), qos)
-- local pkgid = mqttc:publish(topic2, data, qos)
-- local pkgid = mqttc:publish(topic3, data, qos)
end
end
end)
5.4 mqtt-uart透傳
此程式碼可實現mqtt-uart透傳,利用串列埠工具給伺服器發訊息或者接收來著伺服器的訊息。
注意:
要使用串列埠1,且波特率為9600。
-- 以下是演示與uart結合, 簡單的mqtt-uart透傳實現,不需要就註釋掉
local uart_id = 1
uart.setup(uart_id, 9600)
uart.on(uart_id, "receive", function(id, len)
local data = ""
while 1 do
local tmp = uart.read(uart_id)
if not tmp or #tmp == 0 then
break
end
data = data .. tmp
end
log.info("uart", "uart收到資料長度", #data)
sys.publish("mqtt_pub", pub_topic, data)
end)
sys.subscribe("mqtt_payload", function(topic, payload)
log.info("uart", "uart傳送資料長度", #payload)
uart.write(1, payload)
end)
六、MQTT多連結示例
多連結的核心,就是要建立兩個mqtt客戶端的物件,透過不同的變數控制,程式碼部分如下。
示例原始碼下載:
https://gitee.com/openLuat/LuatOS/tree/master/demo/mqtt
--------------------第一個mqtt客戶端--------------------
mqttc1 = mqtt.create(nil, mqtt_host, mqtt_port, mqtt_isssl, ca_file) -- 建立的第一個mqtt物件
mqttc1:auth(client1_id,user_name,password) -- client_id必填,其餘選填
-- mqttc1:keepalive(240) -- 預設值240s
mqttc1:autoreconn(true, 3000) -- 自動重連機制
mqttc1:on(function(mqtt_client, event, data, payload)
-- 使用者自定義程式碼
log.info("mqtt", "event", event, mqtt_client, data, payload)
if event == "conack" then
-- 聯上了
sys.publish("mqtt_conack")
mqtt_client:subscribe(sub_topic_client)--單主題訂閱
-- mqtt_client:subscribe({[topic1]=1,[topic2]=1,[topic3]=1})--多主題訂閱
elseif event == "recv" then
-- 客戶端1 接收資料
log.info("mqtt", "downlink", "topic", data, "payload", payload)
sys.publish("mqtt_payload", data, payload)
elseif event == "sent" then
-- log.info("mqtt", "sent", "pkgid", data)
-- elseif event == "disconnect" then
-- 非自動重連時,按需重啟mqttc
-- mqtt_client:connect()
end~
end)
--------------------第二個mqtt客戶端--------------------
mqttc2 = mqtt.create(nil, mqtt_host, mqtt_port, mqtt_isssl, ca_file) -- 建立的第二個mqtt物件
mqttc2:auth(client2_id,user_name,password) -- client_id必填,其餘選填
-- mqttc2:keepalive(240) -- 預設值240s
mqttc2:autoreconn(true, 3000) -- 自動重連機制
mqttc2:on(function(mqtt_client, event, data, payload)
-- 使用者自定義程式碼
log.info("mqtt", "event", event, mqtt_client, data, payload)
if event == "conack" then
-- 聯上了
sys.publish("mqtt_conack")
mqtt_client:subscribe(sub_topic_client) -- 主題訂閱 -> 訂閱主題可以額外自定義
-- mqtt_client:subscribe({[topic1]=1,[topic2]=1,[topic3]=1})--多主題訂閱
elseif event == "recv" then
-- 客戶端2 接收資料
log.info("mqtt", "downlink", "topic", data, "payload", payload)
sys.publish("mqtt_payload", data, payload)
elseif event == "sent" then
-- log.info("mqtt", "sent", "pkgid", data)
-- elseif event == "disconnect" then
-- 非自動重連時,按需重啟mqttc
-- mqtt_client:connect()
end
end)
-- 客戶端1 傳送資料
mqttc1:publish(topic, data, qos) -- 釋出topic主題可以自定義,可以不相同
-- 客戶端2 傳送資料
mqttc2:publish(topic, data, qos) -- 釋出topic主題可以自定義,可以不相同
七、MQTT SSL不帶證書連線示例
示例原始碼下載:
https://gitee.com/openLuat/LuatOS/tree/master/demo/mqtt
local mqtt_host = "broker.emqx.io"
local mqtt_port = 8883
local mqtt_isssl = true -- 是否使用ssl> false 不加密 | true 無證書加密 | table 有證書加密
local client_id = "abc"
local user_name = "user"
local password = "password"
local pub_topic = "/luatos/pub/"
local sub_topic = "/luatos/sub/"
-- 統一聯網函式
sys.taskInit(function()
local device_id = mcu.unique_id():toHex()
-----------------------------
-- 統一聯網函式, 可自行刪減
----------------------------
if wlan and wlan.connect then
-- wifi 聯網, ESP32系列均支援
local ssid = "luatos1234"
local password = "12341234"
log.info("wifi", ssid, password)
-- TODO 改成自動配網
-- LED = gpio.setup(12, 0, gpio.PULLUP)
wlan.init()
wlan.setMode(wlan.STATION) -- 預設也是這個模式,不呼叫也可以
device_id = wlan.getMac()
wlan.connect(ssid, password, 1)
elseif mobile then
-- Air780E/Air600E系列
--mobile.simid(2) -- 自動切換SIM卡
-- LED = gpio.setup(27, 0, gpio.PULLUP)
device_id = mobile.imei()
elseif w5500 then
-- w5500 乙太網, 當前僅Air105支援
w5500.init(spi.HSPI_0, 24000000, pin.PC14, pin.PC01, pin.PC00)
w5500.config() --預設是DHCP模式
w5500.bind(socket.ETH0)
-- LED = gpio.setup(62, 0, gpio.PULLUP)
elseif socket or mqtt then
-- 適配的socket庫也OK
-- 沒有其他操作, 單純給個註釋說明
else
-- 其他不認識的bsp, 迴圈提示一下吧
while 1 do
sys.wait(1000)
log.info("bsp", "本bsp可能未適配網路層, 請查證")
end
end
-- 預設都等到聯網成功
sys.waitUntil("IP_READY")
sys.publish("net_ready", device_id)
end)
sys.taskInit(function()
-- 等待聯網
local ret, device_id = sys.waitUntil("net_ready")
-- 下面的是mqtt的引數均可自行修改
client_id = device_id
pub_topic = "/luatos/pub/" .. device_id
sub_topic = "/luatos/sub/" .. device_id
-- 列印一下上報(pub)和下發(sub)的topic名稱
-- 上報: 裝置 ---> 伺服器
-- 下發: 裝置 <--- 伺服器
-- 可使用mqtt.x等客戶端進行除錯
log.info("mqtt", "pub", pub_topic)
log.info("mqtt", "sub", sub_topic)
if mqtt == nil then
while 1 do
sys.wait(1000)
log.info("bsp", "本bsp未適配mqtt庫, 請查證")
end
end
-------------------------------------
-------- MQTT 演示程式碼 --------------
-------------------------------------
mqttc = mqtt.create(nil, mqtt_host, mqtt_port, mqtt_isssl, ca_file)
mqttc:auth(client_id,user_name,password) -- client_id必填,其餘選填
-- mqttc:keepalive(240) -- 預設值240s
mqttc:autoreconn(true, 3000) -- 自動重連機制
mqttc:on(function(mqtt_client, event, data, payload)
-- 使用者自定義程式碼
log.info("mqtt", "event", event, mqtt_client, data, payload)
if event == "conack" then
-- 聯上了
sys.publish("mqtt_conack")
mqtt_client:subscribe(sub_topic)--單主題訂閱
-- mqtt_client:subscribe({[topic1]=1,[topic2]=1,[topic3]=1})--多主題訂閱
elseif event == "recv" then
log.info("mqtt", "downlink", "topic", data, "payload", payload)
sys.publish("mqtt_payload", data, payload)
elseif event == "sent" then
-- log.info("mqtt", "sent", "pkgid", data)
-- elseif event == "disconnect" then
-- 非自動重連時,按需重啟mqttc
-- mqtt_client:connect()
end
end)
-- mqttc自動處理重連, 除非自行關閉
mqttc:connect()
sys.waitUntil("mqtt_conack")
while true do
-- 演示等待其他task傳送過來的上報資訊
local ret, topic, data, qos = sys.waitUntil("mqtt_pub", 300000)
if ret then
-- 提供關閉本while迴圈的途徑, 不需要可以註釋掉
if topic == "close" then break end
mqttc:publish(topic, data, qos)
end
-- 如果沒有其他task上報, 可以寫個空等待
--sys.wait(60000000)
end
mqttc:close()
mqttc = nil
end)
八、MQTT SSL帶證書連線示例
示例原始碼下載:
https://gitee.com/openLuat/LuatOS/tree/master/demo/mqtt
local mqtt_host = "broker.emqx.io"
local mqtt_port = 8883
local mqtt_isssl = { -- 是否使用ssl> false 不加密 | true 無證書加密 | table 有證書加密
server_cert = io.readFile("/luadb/broker.emqx.io-ca.crt"), -- 把證書檔案作為指令碼檔案一起燒錄到模組內,就可以用/luadb/路徑直接讀取
client_cert=nil,
client_key=nil,
client_password=nil,
}
local client_id = "abc"
local user_name = "user"
local password = "password"
local pub_topic = "/luatos/pub/"
local sub_topic = "/luatos/sub/"
-- 統一聯網函式
sys.taskInit(function()
local device_id = mcu.unique_id():toHex()
-----------------------------
-- 統一聯網函式, 可自行刪減
----------------------------
if wlan and wlan.connect then
-- wifi 聯網, ESP32系列均支援
local ssid = "luatos1234"
local password = "12341234"
log.info("wifi", ssid, password)
-- TODO 改成自動配網
-- LED = gpio.setup(12, 0, gpio.PULLUP)
wlan.init()
wlan.setMode(wlan.STATION) -- 預設也是這個模式,不呼叫也可以
device_id = wlan.getMac()
wlan.connect(ssid, password, 1)
elseif mobile then
-- Air780E/Air600E系列
--mobile.simid(2) -- 自動切換SIM卡
-- LED = gpio.setup(27, 0, gpio.PULLUP)
device_id = mobile.imei()
elseif w5500 then
-- w5500 乙太網, 當前僅Air105支援
w5500.init(spi.HSPI_0, 24000000, pin.PC14, pin.PC01, pin.PC00)
w5500.config() --預設是DHCP模式
w5500.bind(socket.ETH0)
-- LED = gpio.setup(62, 0, gpio.PULLUP)
elseif socket or mqtt then
-- 適配的socket庫也OK
-- 沒有其他操作, 單純給個註釋說明
else
-- 其他不認識的bsp, 迴圈提示一下吧
while 1 do
sys.wait(1000)
log.info("bsp", "本bsp可能未適配網路層, 請查證")
end
end
-- 預設都等到聯網成功
sys.waitUntil("IP_READY")
sys.publish("net_ready", device_id)
end)
sys.taskInit(function()
-- 等待聯網
local ret, device_id = sys.waitUntil("net_ready")
-- 下面的是mqtt的引數均可自行修改
client_id = device_id
pub_topic = "/luatos/pub/" .. device_id
sub_topic = "/luatos/sub/" .. device_id
-- 列印一下上報(pub)和下發(sub)的topic名稱
-- 上報: 裝置 ---> 伺服器
-- 下發: 裝置 <--- 伺服器
-- 可使用mqtt.x等客戶端進行除錯
log.info("mqtt", "pub", pub_topic)
log.info("mqtt", "sub", sub_topic)
if mqtt == nil then
while 1 do
sys.wait(1000)
log.info("bsp", "本bsp未適配mqtt庫, 請查證")
end
end
-------------------------------------
-------- MQTT 演示程式碼 --------------
-------------------------------------
mqttc = mqtt.create(nil, mqtt_host, mqtt_port, mqtt_isssl, ca_file)
mqttc:auth(client_id,user_name,password) -- client_id必填,其餘選填
-- mqttc:keepalive(240) -- 預設值240s
mqttc:autoreconn(true, 3000) -- 自動重連機制
mqttc:on(function(mqtt_client, event, data, payload)
-- 使用者自定義程式碼
log.info("mqtt", "event", event, mqtt_client, data, payload)
if event == "conack" then
-- 聯上了
sys.publish("mqtt_conack")
mqtt_client:subscribe(sub_topic)--單主題訂閱
-- mqtt_client:subscribe({[topic1]=1,[topic2]=1,[topic3]=1})--多主題訂閱
elseif event == "recv" then
log.info("mqtt", "downlink", "topic", data, "payload", payload)
sys.publish("mqtt_payload", data, payload)
elseif event == "sent" then
-- log.info("mqtt", "sent", "pkgid", data)
-- elseif event == "disconnect" then
-- 非自動重連時,按需重啟mqttc
-- mqtt_client:connect()
end
end)
-- mqttc自動處理重連, 除非自行關閉
mqttc:connect()
sys.waitUntil("mqtt_conack")
while true do
-- 演示等待其他task傳送過來的上報資訊
local ret, topic, data, qos = sys.waitUntil("mqtt_pub", 300000)
if ret then
-- 提供關閉本while迴圈的途徑, 不需要可以註釋掉
if topic == "close" then break end
mqttc:publish(topic, data, qos)
end
-- 如果沒有其他task上報, 可以寫個空等待
--sys.wait(60000000)
end
mqttc:close()
mqttc = nil
end)
九、常見問題Q&A
以下針對客戶朋友們實際應用中的反饋,整理了MQTT應用中的常見問題:
-
模組支援MQTT最新的版本是多少?
支援MQTT_V3.1、MQTT_V3.1.1版本。 -
模組最多支援幾路連結?
MQTT/TCP/UDP的連結公用8路通道。 -
client id是否允許重複?
client id不允許重複,要保證唯一性,如果在已經有一個該id的連結情況下,另外一個裝置也用了相同的clientid接入,會把另一個在連連結擠掉。 -
Qos0、Qos1、Qos2如何選擇?
應用允許的情況下,建議使用Qos0;Qos1和Qos2會加重網路負擔,4G網路還好,特別是2G網路,在網路擁堵和較差的情況下,資料傳輸的次數越多,掉線的機率就會越高。 -
為什麼mqtt ssl訪問失敗?
檢查伺服器是否支援模組支援的加密套件,用模組程式中的三元組和證書,在mqttx或者mqttfx客戶端工具上嘗試是否可以成功連結。 -
排查是否裝置單體問題:
如果同一地點,某些裝置正常,某些裝置異常,按照如下幾種情況分析。
01)分析正常裝置和異常裝置的使用環境是否相同:
如果不同,例如異常裝置固定在鋼製牆壁上,正常裝置放置在桌子上,鋼製牆壁可能對天線射頻有干擾,將異常裝置和正常裝置放置在同樣的使用環境中,再對比測試。
如果相同,參考下方第2)步。
2)分析正常和異常的裝置,駐留的小區是否相同:
如果相同,重點排查異常裝置的天線射頻部分。
如同不同,多測試幾次,確認下,是不是在異常小區內很容易出問題,如果異常小區很容易出問題,可能就是小區擁堵造成的。