本文章 来自原创专栏《ESP32教学专栏 (基于ESP-IDF)》,讲解如何使用 ESP-IDF 构建 ESP32 程序,发布文章并会持续为已发布文章添加新内容! 每篇文章都经过了精打细磨!
↓↓↓通过下方对话框进入专栏目录页↓↓↓
CSDN 请求进入目录 _ O x
是否进入ESP32教学导航(基于ESP-IDF)?
确定
MQTT协议(消息队列遥测传输协议),是一种基于publish/subscribe(即发布与订阅)模式的轻量通讯协议。此协议基于TCP/IP。
MQTT是一种低开销、低带宽占用的即时通讯协议。轻量、简单、开放和易于实现的。其最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。使其在物联网、小型设备、移动应用等方面有较广泛的应用。如:机器与机器(M2M)通信和物联网(IoT)。其在,通过卫星链路通信传感器、偶尔拨号的医疗设备、智能家居、及一些小型化设备中已广泛使用。
ESP-MQTT是MQTT协议的一个实现
特性:
TCP、SSL(使用mbedtls)、Websocket、Websocket Secure的MQTT实现。目前支持的协议:
| 协议 | URI schemes |
|---|---|
| TCP | mqtt |
| SSL | mqtts |
| Websocket | ws |
| Websocket Secure | wss |
URI示例
① mqtt://mqtt.eclipseprojects.io:基于TCP协议的MQTT,端口号为默认1883
② mqtt://mqtt.eclipseprojects.io:1884:同上,端口号自定义为1884
③ mqtt://用户名:密码@mqtt.eclipseprojects.io:1884:同上,再加上用户名和密码
④ mqtts://mqtt.eclipseprojects.io:基于SSL,端口默认8883,也可以自定义端口号如上
⑤ ws://mqtt.eclipseprojects.io:80/mqtt:基于WebSocket
⑥ wss://mqtt.eclipseprojects.io:443/mqtt:基于Websocket Secure
一个简单的示例:
// 创建MQTT配置,最简单的配置只需要填写URI
const esp_mqtt_client_config_t mqtt_cfg = {
.uri = "mqtt://mqtt.eclipseprojects.io",
};
// 传递配置,创建一个MQTT客户端
esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);
// 注册事件
// 注:每个mqtt客户端都携带一个自己的事件循环,esp-mqtt并没有使用默认事件循环
esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, EventHandle函数, client);
// 启动mqtt客户端
esp_mqtt_client_start(client);
待续
MQTT允许在客户非正常断开连接时,通过遗嘱(LWT)消息通知其他客户。
配置在esp_mqtt_client_config_t结构体中lwt_开头的配置:
lwt_topic: 遗嘱topic。lwt_msg: 遗嘱内容。lwt_msg_len:遗嘱内容长度。lwt_qos: 遗嘱的消息服务质量lwt_retain: 消息保留标志。其余配置请查看官方文档中对于esp_mqtt_client_config_t的说明,或直接查看其源码中的注释。
官方文档对于esp_mqtt_client_config_t的解释:https://docs.espressif.com/projects/esp-idf/zh_CN/v4.4.2/esp32/api-reference/protocols/mqtt.html#other-configuration-parameters
除此之外,在idf.py menuconfig里还有几个配置项。在Component config -> ESP-MQTT Configuration中。详细信息:https://docs.espressif.com/projects/esp-idf/zh_CN/v4.4.2/esp32/api-reference/protocols/mqtt.html#change-settings-in-project-configuration-menu
从上文的小示例的注释中写道,每个client都携带一个事件循环event loop。因此事件操作本质上就是操作这个事件循环。
ESP-IDF为我们提供了简化的API来操作这个事件循环。
esp_mqtt_client_register_event(客户对象, 监听的事件ID, 事件Handler函数, 自定义参数)
Handler函数的原型与esp-event一致:
void mqtt_event_handler(
void *handler_args, // 这个参数是注册事件时的“自定义参数”
esp_event_base_t base, // 由于这个handler特用于MQTT,因此可以忽略
int32_t event_id, // 事件ID
void *event_data // 事件数据,请转型为esp_mqtt_event_handle_t使用
) {
// TODO:操作事件
}
事件:
MQTT_EVENT_BEFORE_CONNECT: 即将准备连接代理
MQTT_EVENT_CONNECTED: 连接成功,可以发消息了
MQTT_EVENT_DISCONNECTED: 由于无法读写数据,例如服务器不可用,客户端终止了连接。
MQTT_EVENT_SUBSCRIBED: 订阅成功。event data中包含订阅消息的ID
MQTT_EVENT_UNSUBSCRIBED: 退订成功。event data中包含退订消息的ID
MQTT_EVENT_PUBLISHED: QoS
>
>
> 0的消息发送成功(因为QoS为0的消息不支持确认)。event data中包含消息的ID
MQTT_EVENT_DATA: 收到消息。
event data包含:
① message ID
② name of the topic it was published to
③ received data and its length
④ 如果Data长度超过缓冲区,则消息会被拆成若干个碎片,并产生多个MQTT_EVENT_DATA事件。并且event中会包含current_data_offset和total_data_len请用户自行拼接碎片消息。
MQTT_EVENT_ERROR: MQTT客户端出错,请使用event的error_handle字段查看错误详情。
MQTT客户端的配置:
// 创建MQTT配置,最简单的配置只需要填写URI
const esp_mqtt_client_config_t mqtt_cfg = {
.uri = "mqtt://mqtt.eclipseprojects.io",
};
// 传递配置,创建一个MQTT客户端
esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);
// 注册事件
// 注:每个mqtt客户端都携带一个自己的事件循环,esp-mqtt并没有使用默认事件循环
esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, EventHandle函数, client);
// 启动mqtt客户端
esp_mqtt_client_start(client);
事件eventloop handler:
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%d", base, event_id);
esp_mqtt_event_handle_t event = event_data;
esp_mqtt_client_handle_t client = event->client;
int msg_id;
switch ((esp_mqtt_event_id_t)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);
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");
if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) {
log_error_if_nonzero("reported from esp-tls", event->error_handle->esp_tls_last_esp_err);
log_error_if_nonzero("reported from tls stack", event->error_handle->esp_tls_stack_err);
log_error_if_nonzero("captured as transport's socket errno", event->error_handle->esp_transport_sock_errno);
ESP_LOGI(TAG, "Last errno string (%s)", strerror(event->error_handle->esp_transport_sock_errno));
}
break;
default:
ESP_LOGI(TAG, "Other event id:%d", event->event_id);
break;
}
}