(1)、什么是MQTT;
(2)、MQTT的通信基本流程;
(3)、MQTT的编程方法;
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是IBM开发的一个即时通讯协议,它比较适合于在低带宽、不可靠的网络的进行远程传感器和控制设备通讯等,正在日益成为物联网通信协议的重要组成部分。MQTT现在主要用于即时通讯,物联网M2M,物联网采集等。目前一些开源MQTT服务中间件有:
· Mosquitto:https://mosquitto.org/
· VerneMQ:https://vernemq.com/
· EMQTT:http://emqtt.io/
MQTT协议是为大量计算能力有限,且工作在低带宽、不可靠的网络的远程传感器和控制设备通讯而设计的协议,它具有以下主要的几项特性:
1、使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合;
2、对负载内容屏蔽的消息传输;
3、使用 TCP/IP 提供网络连接;
4、有三种消息发布服务质量:
“至多一次”,消息发布完全依赖底层 TCP/IP 网络。会发生消息丢失或重复。这一级别可用于如下情况,环境传感器数据,丢失一次读记录无所谓,因为不久后还会有第二次发送。
“至少一次”,确保消息到达,但消息重复可能会发生。
“只有一次”,确保消息到达一次。这一级别可用于如下情况,在计费系统中,消息重复或丢失会导致不正确的结果。
5、小型传输,开销很小(固定长度的头部是 2 字节),协议交换最小化,以降低网络流量;
6、使用 Last Will 和 Testament 特性通知有关各方客户端异常中断的机制;
在通讯过程中,MQTT 协议中有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。
MQTT 传输的消息分为:主题(Topic)和负载(payload)两部分:
Topic,可以理解为消息的类型,订阅者订阅(Subscribe)后,就会收到该主题的
消息内容(payload);
payload,可以理解为消息的内容,是指订阅者具体要使用的内容。
·每一个终端设备都会向MQTT服务器订阅或发布消息;
·每个终端可以订阅自己发布的消息,服务器就是回发测试;
·走标准化流程,解放了私有协议制定、实现、调试、测试一整套复杂的流程
·MQTT 让逻辑变得更清晰,需要什么订阅什么
这里介绍大部分MQTT库的使用更详细的请参考:
更多API 参考esp-idf\components\mqtt\esp-mqtt\include\mqtt_client.h
MQTT API和http API以及Websocket API的封装类似使用起来也似曾相识。
主要是对esp_mqtt_client_config_t结构体和esp_mqtt_client_handle_t结构体的操作。
(1)、初始化创建MQTT句柄
/**
* @brief 根据配置创建mqtt客户端句柄
*
* @param config mqtt配置结构
*
* @return mqtt_client_handle(如果成功创建),错误时为NULL
*/
esp_mqtt_client_handle_t esp_mqtt_client_init(const esp_mqtt_client_config_t *config);
(2)、其他相关API
esp_err_t esp_mqtt_client_start(esp_mqtt_client_handle_t client);// 使用已创建的客户端句柄启动mqtt客户端
esp_err_t esp_mqtt_client_reconnect(esp_mqtt_client_handle_t client);//重新连接
esp_err_t esp_mqtt_client_stop(esp_mqtt_client_handle_t client);// Stops mqtt client tasks
esp_err_t esp_mqtt_client_register_event(esp_mqtt_client_handle_t client, esp_mqtt_event_id_t event, esp_event_handler_t event_handler, void* event_handler_arg);// 注册mqtt事件
esp_err_t esp_mqtt_set_config(esp_mqtt_client_handle_t client, const esp_mqtt_client_config_t *config);//mqtt configuration structure
(3)、客户端发送发布消息和订阅消息
/**
* @brief 客户端发送发布消息给服务代理
*
*注意:
*-客户端不必连接即可发送发布消息
*(尽管它将丢弃所有qos = 0消息,但将使qos> 1消息入队)
*-这是线程安全的,请参考`esp_mqtt_client_subscribe`
*
* @param 客户端mqtt客户端句柄
* @param topic主题字符串
* @param 数据有效负载字符串(设置为NULL,发送空的有效负载消息)
* @param len数据长度,如果设置为0,则根据有效负载字符串计算长度
* @param qos发布消息的qos
* @param 保留保留标志
*
*成功返回发布消息的@return message_id(对于QoS 0,message_id始终为零)。
* -1失败。
*/
int esp_mqtt_client_publish(esp_mqtt_client_handle_t client, const char *topic, const char *data, int len, int qos, int retain);
/**
* @brief 为客户端订阅具有定义的qos的定义的主题
*
*注意:
*-必须连接客户端才能发送订阅消息
*-此API可以从用户任务或
*来自mqtt事件回调,即内部mqtt任务
*(API受内部互斥保护,因此可能会阻止
*如果正在进行更长的数据接收操作。
*
* @param 客户端mqtt客户端句柄
* @param 主题
* @param qos
*
* @return message_id成功时的订阅消息
* -1失败
*/
int esp_mqtt_client_subscribe(esp_mqtt_client_handle_t client, const char *topic, int qos);
Mqtt_client其操作也主要是围绕esp_websocket_client_config_t结构体的操作。先来看看结构体成员描述:
/**
* MQTT client configuration structure
*/
typedef struct {
mqtt_event_callback_t event_handle; /*!< 在传统模式下将MQTT事件作为回调处理 */
esp_event_loop_handle_t event_loop_handle; /*!< MQTT事件循环库的句柄 */
const char *host; /*!< MQTT服务器域(ipv4作为字符串) */
const char *uri; /*!< 完整的MQTT经纪人URI */
uint32_t port; /*!< MQTT server port */
const char *client_id; /*!< 默认客户端ID为``ESP32_%CHIPID%'',其中%CHIPID%是MAC地址的后3个字节(十六进制格式) */
const char *username; /*!< MQTT用户名 */
const char *password; /*!< MQTT password */
} esp_websocket_client_config_t;
这个结构体跟esp_http_client_config_t结构体类似。
(1)、初始化结构体并创建句柄
esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);
(2)、注册回调函数函数原型为
esp_event_handler_register_with(client->config->event_loop_handle, MQTT_EVENTS, event, event_handler, event_handler_arg);
事件esp_event_base_t为MQTT_EVENTS,回调函数的详细讲解参见【ESP32 IDF开发 应用篇⑬ 连接Wifi回调函数esp_event_handler_register专题】
(3)、启动并创建任务,在任务中执行回调函数
esp_mqtt_client_start(client);
esp_err_t esp_mqtt_client_start(esp_mqtt_client_handle_t client)
{
if (xTaskCreate(esp_mqtt_task, "mqtt_task", client->config->task_stack, client, client->config->task_prio, &client->task_handle) != pdTRUE) {
}
(4)、在回调函数中订阅和发布消息,其消息的数据结构体为:
/**
* MQTT event configuration structure
*/
typedef struct {
esp_mqtt_event_id_t event_id; /*!< MQTT event type */
esp_mqtt_client_handle_t client; /*!< 此事件的MQTT客户端句柄 */
void *user_context; /*!< User context passed from MQTT client config */
char *data; /*!< 与该事件相关的数据 */
int data_len; /*!< 此事件的数据长度 */
int total_data_len; /*!< 数据的总长度(较长的数据随多个事件提供) */
int current_data_offset; /*!< 与此事件关联的数据的实际偏移量 */
char *topic; /*!< 与此事件相关的主题 */
int topic_len; /*!< 与该事件关联的该事件的主题长度 */
int msg_id; /*!< MQTT messaged id of message */
int session_present; /*!< 连接事件的MQTT session_present标志 */
esp_mqtt_error_codes_t *error_handle; /*!< esp-mqtt错误句柄,包括esp-tls错误和内部mqtt错误 */
} esp_mqtt_event_t;
7、实例
本实例使用的url是官网提供的测试连接mqtt://mqtt.eclipse.org
源码如下:
在wifi连接成功后开始调用测试函数
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
esp_mqtt_event_handle_t event = (esp_mqtt_event_handle_t )event_data;
esp_mqtt_client_handle_t client = event->client;
int msg_id;
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;
}
}
static void vTaskMQTT(void *pvParameters)
{
//等待连接成功,或已经连接有断开连接,此函数会一直阻塞,只有有连接
xEventGroupWaitBits(xCreatedEventGroup_WifiConnect,// 事件标志组句柄
WIFI_CONNECTED_BIT, // 等待bit0和bit1被设置
pdFALSE, //TRUE退出时bit0和bit1被清除,pdFALSE退出时bit0和bit1不清除
pdTRUE, //设置为pdTRUE表示等待bit1和bit0都被设置,pdFALSE表示等待bit1或bit0其中一个被设置
portMAX_DELAY); //等待延迟时间,一直等待
mqtt_test();
for(;;)
{
int msg_id;
//发布消息,在回调函数中订阅
msg_id = esp_mqtt_client_publish(client, "/topic/qos0", "hello_qos0", 0, 0, 0);
ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
msg_id = esp_mqtt_client_publish(client, "/topic/qos1", "hello_qos1", 0, 1, 0);
ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
static void mqtt_test(void)
{
//mqtt客户端结构体设置配置
esp_mqtt_client_config_t mqtt_cfg = {
.uri = "mqtt://mqtt.eclipse.org",
};
ESP_LOGI(TAG, "Connecting to %s", mqtt_cfg.uri);
//创建mqtt_client句柄
client = esp_mqtt_client_init(&mqtt_cfg);
//注册mqtt_client回调函数用于检测执行过程中的一些状态,跟wifi连接回调函数一样
esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, client);
//启动mqtt_client任务,开始检测回调状态
esp_mqtt_client_start(client);
}
这里使用的是一个模块发布和订阅自己的消息,大家也可以使用两个模式A模块发布,B模块订阅。
所有文章源代码:https://download.csdn.net/download/lu330274924/88518092