李寧運(yùn)動(dòng)服網(wǎng)站建設(shè)規(guī)劃書b2b網(wǎng)站有哪些平臺(tái)
文章目錄
- 目的
- 基礎(chǔ)說明
- 示例代碼
- 總結(jié)
目的
MQTT是比較常用在物聯(lián)網(wǎng)設(shè)備中的通訊協(xié)議,這篇文章將使用 Arudino ESP32
作為MQTT客戶端進(jìn)行通訊使用演示。目前Arduino的MQTT客戶端庫中最常使用的是 PubSubClient
,所以本文也將以此進(jìn)行說明。
主頁:https://pubsubclient.knolleary.net/
項(xiàng)目地址:https://github.com/knolleary/pubsubclient
目前 PubSubClient
庫版本為 v2.8
,主要基于 MQTT 3.1.1
,不支持 MQTT 5.0
的新增特性,訂閱主題只支持 Qos 0 和 1
。
This library provides a client for doing simple publish/subscribe messaging with a server that supports MQTT.
基礎(chǔ)說明
MQTT的一些基礎(chǔ)內(nèi)容可以參考下面文章:
《MQTT基礎(chǔ)入門與資料收集》
因?yàn)闇y試需要有 MQTT Broker(服務(wù)器)
,可以參考上面文章進(jìn)行啟動(dòng),或者也可以申請(qǐng)一個(gè)免費(fèi)的在線的云服務(wù)使用。
PubSubClient
庫使用很簡單,主要就是分為下面幾步:
- 聲明
PubSubClient
對(duì)象; - 因?yàn)镸QTT需要在TCP之上工作,所以需要給
PubSubClient
對(duì)象一個(gè)TCP對(duì)象; - 使用
setServer
方法設(shè)置MQTT服務(wù)器的地址和端口號(hào); - 使用
setCallback
方法設(shè)置通訊消息回調(diào)函數(shù)void callback(char *topic, byte *payload, unsigned int length)
(如果不需要訂閱消息則無需此步驟); - 使用
connect
方法啟動(dòng)連接; - 使用
subscribe
方法訂閱主題或使用publish
方法向某個(gè)主題發(fā)布消息;
上面步驟中 2、3、4 步順序并無要求,并且可以在第一步聲明對(duì)象的構(gòu)造函數(shù)中直接傳入。
在使用 connect
方法進(jìn)行連接時(shí)可以選擇填入 Will Qos
Will Retain
Will Message
cleanSession
信息(默認(rèn)為 0 0 0 1
)。發(fā)送消息時(shí)可以選擇填入 retained
(默認(rèn)為 false
)。
默認(rèn)情況下發(fā)送和接收數(shù)據(jù)都會(huì)依賴buffer,當(dāng)發(fā)送或者接收的消息比buffer可容納的空間(默認(rèn)256字節(jié))大的時(shí)候?qū)?huì)忽略這條消息??梢允褂?setBufferSize
方法來設(shè)置buffer大小。
上面的 publish
方法發(fā)送消息時(shí)會(huì)先將消息拷貝到緩存,這在大數(shù)據(jù)發(fā)送時(shí)效率并不好,可以先使用 beginPublish
方法啟動(dòng)傳輸,然后單次或多次使用 write
方法寫數(shù)據(jù)(也可以使用 print
等方法),最后使用 endPublish
完成本次消息發(fā)送,減少一次拷貝,效率上會(huì)高很多。
可以使用 setKeepAlive
方法來設(shè)置 Keep Alive
時(shí)間(默認(rèn)為15s)。
使用 disconnect
方法可以關(guān)閉連接,使用 connected
方法可以檢查是否連接。使用 unsubscribe
方法可以取消訂閱消息。
使用 state
方法可以獲得 PubSubClient
對(duì)象當(dāng)前的狀態(tài),狀態(tài)定義如下:
// Possible values for client.state()
#define MQTT_CONNECTION_TIMEOUT -4
#define MQTT_CONNECTION_LOST -3
#define MQTT_CONNECT_FAILED -2
#define MQTT_DISCONNECTED -1
#define MQTT_CONNECTED 0
#define MQTT_CONNECT_BAD_PROTOCOL 1
#define MQTT_CONNECT_BAD_CLIENT_ID 2
#define MQTT_CONNECT_UNAVAILABLE 3
#define MQTT_CONNECT_BAD_CREDENTIALS 4
#define MQTT_CONNECT_UNAUTHORIZED 5
示例代碼
#include <WiFi.h>
#include <PubSubClient.h>// WiFi相關(guān)配置信息
const char *wifi_ssid = "********";
const char *wifi_password = "********";// MQTT相關(guān)配置信息
const char *mqtt_broker_addr = "********"; // 服務(wù)器地址
const uint16_t mqtt_broker_port = 1883; // 服務(wù)端口號(hào)
const char *mqtt_username = "********"; // 賬號(hào)(非必須)
const char *mqtt_password = "********"; // 密碼(非必須)
const uint16_t mqtt_client_buff_size = 4096; // 客戶端緩存大小(非必須)
String mqtt_client_id = "esp32_client"; // 客戶端ID
const char *mqtt_topic_pub = "esp32/test"; // 需要發(fā)布到的主題
const char *mqtt_topic_sub = "esp32/test"; // 需要訂閱的主題WiFiClient tcpClient;
PubSubClient mqttClient;// MQTT消息回調(diào)函數(shù),該函數(shù)會(huì)在PubSubClient對(duì)象的loop方法中被調(diào)用
void mqtt_callback(char *topic, byte *payload, unsigned int length)
{Serial.printf("Message arrived in topic %s, length %d\n", topic, length);Serial.print("Message:");for (int i = 0; i < length; i++){Serial.print((char)payload[i]);}Serial.println("\n----------------END----------------");
}void setup()
{Serial.begin(115200);Serial.println();// 連接網(wǎng)絡(luò)Serial.printf("\nConnecting to %s", wifi_ssid);WiFi.begin(wifi_ssid, wifi_password);while (WiFi.status() != WL_CONNECTED){delay(500);Serial.print(".");}Serial.println("ok.");Serial.print("IP address: ");Serial.println(WiFi.localIP());// 設(shè)置MQTT客戶端mqttClient.setClient(tcpClient);mqttClient.setServer(mqtt_broker_addr, mqtt_broker_port);mqttClient.setBufferSize(mqtt_client_buff_size);mqttClient.setCallback(mqtt_callback);
}unsigned long previousConnectMillis = 0; // 毫秒時(shí)間記錄
const long intervalConnectMillis = 5000; // 時(shí)間間隔
unsigned long previousPublishMillis = 0; // 毫秒時(shí)間記錄
const long intervalPublishMillis = 5000; // 時(shí)間間隔void loop()
{unsigned long currentMillis = millis(); // 讀取當(dāng)前時(shí)間// 連接MQTT服務(wù)器if (!mqttClient.connected()) // 如果未連接{if (currentMillis - previousConnectMillis > intervalConnectMillis){previousConnectMillis = currentMillis;mqtt_client_id += String(WiFi.macAddress()); // 每個(gè)客戶端需要有唯一的ID,不然上線時(shí)會(huì)把其他相同ID的客戶端踢下線if (mqttClient.connect(mqtt_client_id.c_str())) // 嘗試連接服務(wù)器// if (mqttClient.connect(mqtt_client_id.c_str(), mqtt_username, mqtt_password)){mqttClient.publish(mqtt_topic_pub, "hello mqtt!"); // 連接成功后可以發(fā)送消息mqttClient.subscribe(mqtt_topic_sub); // 連接成功后可以訂閱主題}}}// 定期發(fā)送消息if (mqttClient.connected()){if (currentMillis - previousPublishMillis >= intervalPublishMillis) // 如果和前次時(shí)間大于等于時(shí)間間隔{previousPublishMillis = currentMillis;mqttClient.publish(mqtt_topic_pub, "naisu 233~~~");}}// 處理MQTT事務(wù)mqttClient.loop();
}
上面代碼示例演示的是非加密的mqtt,實(shí)際業(yè)務(wù)中更多的可能會(huì)使用加密的mqtts,這個(gè)時(shí)候TCP客戶端就需要使用 #include <WiFiClientSecure.h>
庫中的 WiFiClientSecure
對(duì)象了。TCP客戶端需要使用 setCACert
或者 getFingerprintSHA256
等方法設(shè)置證書或者指紋,另外可能需要從NTP服務(wù)器獲取時(shí)間。( WiFiClientSecure對(duì)象可以使用 setInsecure
方法,可以不用管證書這些,測試使用沒問題,實(shí)際使用中可能會(huì)有安全風(fēng)險(xiǎn))
總結(jié)
MQTT作為客戶端使用本身比較簡單,PubSubClient用起來也非常簡單,基本上一般的使用有上面內(nèi)容就夠了。