此篇博客主要介紹 ESP-IDF 里的 MQTT 示例相關知識,分為以下幾個小節:
- ESP-MQTT 介紹
- MQTT 基本知識簡介
- ESP-IDF MQTT 示例入門
- ESP-MQTT 常見問題
1 ESP-MQTT 介紹
在 ESP-IDF 中,MQTT 部分主要使用到了 ESP-MQTT 庫,ESP-MQTT 是 MQTT 協議客戶端的實作(MQTT 是輕量級的發布/訂閱訊息協議),它具備以下特征:
- 支持 MQTT over TCP、SSL with mbedtls、MQTT over Websocket、MQTT over Websocket Secure,
- 可輕松配置 URI
- 多個實體(一個應用程式中有多個客戶端)
- 支持訂閱、發布、身份驗證、last will 訊息、keep alive ping 和所有 3 個 QoS 等級,基本組成了一個功能齊全的客戶端,
- MQTT 分為 4 個版本:MQTT 5,MQTT 3.1.1,MQTT 3.1,MQTT - SN v1.2. ESP-IDF 支持的版本為 MQTT 3.1.1 和 MQTT 3.1
2 MQTT 基本知識簡介
請參考 MQTT 基本知識簡介.
3 ESP-IDF MQTT 示例入門
ESP-IDF 里主要有以下 MQTT 示例:
- protocols/mqtt/tcp:基于 tcp 的MQTT,默認埠 1883
- protocols/mqtt/ssl:基于 tcp 的 MQTT,默認埠 8883
- protocols/mqtt/ssl_psk:MQTT over tcp 使用預共享密鑰進行身份驗證,默認埠 8883
- protocols/mqtt/ws:Websocket 上的 MQTT,默認埠 80
- protocols/mqtt/wss:MQTT over Websocket Secure,默認埠 443
以下為 MQTT 示例里的主要配置,
3.1 URI
目前支持 mqtt, mqtts, ws, wss 這幾種 URI 型別,以下是 mqtt_tcp 對應的代碼段:
const esp_mqtt_client_config_t mqtt_cfg = {
.uri = "mqtt://mqtt.eclipseprojects.io",
// .user_context = (void *)your_context
};
esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);
esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, client);
esp_mqtt_client_start(client);
配置上述代碼段里的 broker URI 即可,
如果需要 mqtt_ssl 等加密套件,對應代碼段如下:
const esp_mqtt_client_config_t mqtt_cfg = {
.uri = "mqtts://mqtt.eclipseprojects.io:8883",
.event_handle = mqtt_event_handler,
.cert_pem = (const char *)mqtt_eclipse_org_pem_start,
};
此時要從服務器獲得證書,以服務器 mqtt.eclipseprojects.io 為例,可以使用以下指令來生成:
openssl s_client -showcerts -connect mqtt.eclipseprojects.io:8883 </dev/null 2>/dev/null|openssl x509 -outform PEM >mqtt_eclipse_org.pem
如果證書不是以空字符結尾,則 cert_len還應設定,其他與 SSL 相關的配置引數如下:
-
use_global_ca_store:使用全域證書存盤來驗證服務器證書,更多資訊可以查看 esp-tls.h -
client_cert_pem:指向用于 SSL 相互認證的 PEM 或 DER 格式的證書資料的指標,默認為 NULL,如果不需要相互認證則不需要 -
client_cert_len:client_cert_pem指向的緩沖區的長度,對于以 NULL 結尾的 pem,可能為 0 -
client_key_pem:指向用于 SSL 相互認證的 PEM 或 DER 格式的私鑰資料的指標,默認為 NULL,如果不需要相互認證則不需要 -
client_key_len:client_key_pem指向的緩沖區的長度,對于以 NULL 結尾的 pem,可能為 0 -
psk_hint_key:指向 esp-tls.h 中定義的 PSK 結構的指標,以啟用 PSK 身份驗證(作為證書驗證的替代方法),如果不是 NULL 且服務器/客戶端證書為 NULL,則啟用 PSK -
alpn_protos:用于 ALPN 的以 NULL 結尾的協議串列
3.2 最后遺囑
MQTT 允許最后遺囑 (LWT) 訊息在客戶端例外斷開連接時通知其他客戶端,由 esp_mqtt_client_config_t 結構體中的以下欄位配置,
-
lwt_topic:指向 LWT 訊息主題的指標 -
lwt_msg:指向 LWT 訊息的指標 -
lwt_msg_len:LWT 訊息的長度,如果 lwt_msg 不是以空字符結尾則是必需的 -
lwt_qos:LWT 訊息的服務質量 -
lwt_retain:指定 LWT 訊息的保留標志
3.3 其他配置引數
-
disable_clean_session:確定連接訊息的干凈會話標志,默認為干凈會話 -
keepalive:確定客戶端在斷開連接前等待 ping 回應的秒數,默認為 120 秒, -
disable_auto_reconnect:啟用以阻止客戶端在錯誤或斷開連接后重新連接到服務器 -
user_context:將傳遞給事件處理程式的自定義背景關系 -
task_prio:MQTT 任務優先級,默認為 5 -
task_stack:MQTT 任務堆疊大小,默認為 6144 位元組,設定此項將覆寫 menuconfig 中的設定 -
buffer_size:MQTT 發送/接識訓沖區的大小,默認為 1024 位元組 -
username:指向用于連接到代理的用戶名的指標 -
password:指向用于連接到代理的密碼的指標 -
client_id:指向客戶端 ID 的指標,默認為ESP32_%CHIPID%其中 %CHIPID% 是十六進制格式的 MAC 地址的最后 3 個位元組 -
host:MQTT 代理域(ipv4 作為字串),設定 uri 將覆寫此 -
port:MQTT 代理埠,在 uri 中指定埠將覆寫此 -
transport:設定傳輸協議,設定 uri 后會覆寫此設定 -
refresh_connection_after_ms:在這個值之后重繪連接(以毫秒為單位) -
event_handle:處理 MQTT 事件作為遺留模式下的回呼 -
event_loop_handle:MQTT 事件回圈庫的句柄
3.4 更改專案配置選單中的設定
可以通過 idf.py menuconfig ,在 Component config -> ESP-MQTT Configuration 下使用 找到 MQTT 的配置,
以下設定可用:
CONFIG_MQTT_PROTOCOL_311 : 啟用 3.1.1 版本的 MQTT 協議
CONFIG_MQTT_TRANSPORT_SSL , CONFIG_MQTT_TRANSPORT_WEBSOCKET:啟用特定的 MQTT 傳輸層,例如 SSL、WEBSOCKET、WEBSOCKET_SECURE
CONFIG_MQTT_CUSTOM_OUTBOX:禁用 mqtt_outbox 的默認實作,因此可以提供特定的實作
3.5 事件
MQTT 客戶端可能會發布以下事件:
MQTT_EVENT_BEFORE_CONNECT:客戶端已初始化并即將開始連接到代理,
MQTT_EVENT_CONNECTED:客戶端已成功建立與代理的連接,客戶端現在已準備好發送和接收資料,
MQTT_EVENT_DISCONNECTED:由于無法讀取或寫入資料,例如因為服務器不可用,客戶端已中止連接,
MQTT_EVENT_SUBSCRIBED:代理已確認客戶端的訂閱請求,事件資料將包含訂閱訊息的訊息 ID,
MQTT_EVENT_UNSUBSCRIBED:代理已確認客戶端的退訂請求,事件資料將包含取消訂閱訊息的訊息 ID,
MQTT_EVENT_PUBLISHED:代理已確認客戶端的發布訊息,這只會針對服務質量級別 1 和 2 發布,因為級別 0 不使用確認,事件資料將包含發布訊息的訊息 ID,
MQTT_EVENT_DATA:客戶端已收到發布訊息,事件資料包含:訊息 ID、發布到的主題名稱、接收到的資料及其長度,對于超出內部緩沖區的資料,將發布多個 MQTT_EVENT_DATA 并更新來自事件資料的 current_data_offset 和 total_data_len 以跟蹤碎片化訊息,
MQTT_EVENT_ERROR:客戶端遇到錯誤,esp_mqtt_error_type_t 來自事件資料中的 error_handle 可以用來進一步判斷錯誤的型別,錯誤的型別將決定 error_handle 結構的哪些部分被填充,
3.6 Publish & Subscribe API 使用
參考 mqtt_tcp 例程,如下:
static esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event)
{
esp_mqtt_client_handle_t client = event->client;
int msg_id;
// your_context_t *context = event->context;
switch (event->event_id) {
case MQTT_EVENT_CONNECTED:
ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
msg_id = esp_mqtt_client_publish(client, "/topic/qos1", "data_3", 0, 1, 0);
ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
msg_id = esp_mqtt_client_subscribe(client, "/topic/qos0", 0);
ESP_LOGI(

TAG, "sent subscribe successful, msg_id=%d", msg_id);
msg_id = esp_mqtt_client_subscribe(client, "/topic/qos1", 1);
ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);
msg_id = esp_mqtt_client_unsubscribe(client, "/topic/qos1");
ESP_LOGI(TAG, "sent unsubscribe successful, msg_id=%d", msg_id);
break;
case MQTT_EVENT_DISCONNECTED:
ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
break;
case MQTT_EVENT_SUBSCRIBED:
ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
msg_id = esp_mqtt_client_publish(client, "/topic/qos0", "data", 0, 0, 0);
ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
break;
case MQTT_EVENT_UNSUBSCRIBED:
ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_PUBLISHED:
ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_DATA:
ESP_LOGI(TAG, "MQTT_EVENT_DATA");
printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
printf("DATA=%.*s\r\n", event->data_len, event->data);
break;
case MQTT_EVENT_ERROR:
ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
break;
default:
ESP_LOGI(TAG, "Other event id:%d", event->event_id);
break;
}
return ESP_OK;
}
可以看到對應的 API :
-
Publish :
int esp_mqtt_client_publish(esp_mqtt_client_handle_t client, const char *topic, const char *data, int len, int qos, int retain);clientmqtt client handletopictopic stringdatapayload string (set to NULL, sending empty payload message)lendata length, if set to 0, length is calculated from payload stringqosqos of publish messageretainretain flag
-
Subscribe:
int esp_mqtt_client_subscribe(esp_mqtt_client_handle_t client, const char *topic, int qos);clientmqtt client handletopictopic stringqosqos of publish message
4 要點說明
4.1 Retain 標志位
當使用 MQTT 客戶端發布訊息(PUBLISH)時,如果將 RETAIN 標志位設定為 true,那么 MQTT 服務器會將最近收到的一條 RETAIN 標志位為 true 的訊息保存在服務器端(記憶體或檔案),
特別注意:MQTT 服務器只會為每一個 Topic 保存最近收到的一條 RETAIN 標志位為 true 的訊息!也就是說,如果 MQTT 服務器上已經為某個 Topic 保存了一條 Retained 訊息,當客戶端再次發布一條新的 Retained 訊息,那么服務器上原來的那條訊息會被覆寫
每當 MQTT 客戶端連接到 MQTT 服務器并訂閱了某個 Topic,如果該 Topic 下有 Retained 訊息,那么 MQTT 服務器會立即向客戶端推送該條 Retained 訊息
- 發布 RETAIN 訊息:如果想讓 MQTT 服務器為某個 Topic 保留訊息,只需要在發布訊息的時候指定 RETAIN 標志位為 true 即可
- 洗掉 RETAIN 訊息:如果客戶端想讓 MQTT 服務器洗掉某個 Topic 下保存的 Retained 訊息,唯一的方法是向 MQTT 服務器發布一條 RETAIN 標志位為 true 的空訊息
4.2 LWT 標志位
LWT 全稱為 Last Will and Testament,也就是我們在連接到 Broker 時提到的遺愿,包括遺愿主題、遺愿 QoS、遺愿訊息等,
當 Broker 檢測到 Client 非正常地斷開連接的時候,就會向遺愿主題里面發布一條訊息,遺愿相關的設定是在建立連接的時候,在 CONNECT 資料包里面指定的,
- Will Flag:是否使用 LWT
- Will Topic:遺愿主題名,不可使用通配符
- Will Qos:發布遺愿訊息時使用的 QoS
- Will Retain:遺愿訊息的 Retain 標識
- Will Message:遺愿訊息內容
Broker 在以下情況下認為 Client 是非正常斷開連接的:
- Broker 檢測到底層的 I/O 例外;
- Client 未能在 Keep Alive 的間隔內和 Broker 之間有訊息互動;
- Client 在關閉底層 TCP 連接前沒有發送 DISCONNECT 資料包;
- Broker 因為協議錯誤關閉和 Client 的連接,比如 Client 發送了一個格式錯誤的 MQTT 資料包,
如果 Client 通過發布 DISCONNECT 資料包斷開連接,這個屬于正常斷開連接,不會觸發 LWT 的機制,同時,Broker 還會丟棄掉這個 Client 在連接時指定的 LWT 引數,通常,如果我們關心一個設備,比如傳感器的連接狀態,可以使用 LWT,
5 ESP-MQTT 常見問題
Q : disable_clean_session 這個引數配置的作用是什么?
A :cleanSession 標志是 MQTT 協議中對一個客戶端建立 TCP 連接后是否關心之前狀態的定義,具體語意如下:
cleanSession = true:客戶端再次上線時,將不再關心之前所有的訂閱關系以及離線訊息,
cleanSession = false:客戶端再次上線時,還需要處理之前的離線訊息,而之前的訂閱關系也會持續生效,
Q : 為什么單次只能 publish 最多 1 K 的資料?
A :這是因為 buffer_size 默認為 1 K, 可根據應用需求自行配置,
6 參考文章
- MQTT 的 RETAIN 標志位的作用
- lesson8. :Retained 訊息和 LWT
- MQTT what is the purpose or usage of Last Will Testament?
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/325808.html
標籤:其他
上一篇:如何評估它?
