ESP32 IDF开发 应用篇㉑ MQTT通信

ESP32 IDF开发 应用篇㉑ MQTT通信

    • 1、博主写这篇技术文章的目的:
    • 2、概述
    • 3、MQTT特点
    • 4、MQTT通信结构
    • 5、MQTT相关API的介绍
    • 6、软件设计
    • 8、调试结果

别迷路-导航栏
快速导航找到你想要的(文章目录)

此篇文章如果对你有用,请点赞收藏,您的支持就是博主坚持的动力。

1、博主写这篇技术文章的目的:

(1)、什么是MQTT;
(2)、MQTT的通信基本流程;
(3)、MQTT的编程方法;

2、概述

MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是IBM开发的一个即时通讯协议,它比较适合于在低带宽、不可靠的网络的进行远程传感器和控制设备通讯等,正在日益成为物联网通信协议的重要组成部分。MQTT现在主要用于即时通讯,物联网M2M,物联网采集等。目前一些开源MQTT服务中间件有:
· Mosquitto:https://mosquitto.org/
· VerneMQ:https://vernemq.com/
· EMQTT:http://emqtt.io/

3、MQTT特点

MQTT协议是为大量计算能力有限,且工作在低带宽、不可靠的网络的远程传感器和控制设备通讯而设计的协议,它具有以下主要的几项特性:
1、使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合;
2、对负载内容屏蔽的消息传输;
3、使用 TCP/IP 提供网络连接;
4、有三种消息发布服务质量:
“至多一次”,消息发布完全依赖底层 TCP/IP 网络。会发生消息丢失或重复。这一级别可用于如下情况,环境传感器数据,丢失一次读记录无所谓,因为不久后还会有第二次发送。
“至少一次”,确保消息到达,但消息重复可能会发生。
“只有一次”,确保消息到达一次。这一级别可用于如下情况,在计费系统中,消息重复或丢失会导致不正确的结果。
5、小型传输,开销很小(固定长度的头部是 2 字节),协议交换最小化,以降低网络流量;
6、使用 Last Will 和 Testament 特性通知有关各方客户端异常中断的机制;

4、MQTT通信结构

在通讯过程中,MQTT 协议中有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。
MQTT 传输的消息分为:主题(Topic)和负载(payload)两部分:
 Topic,可以理解为消息的类型,订阅者订阅(Subscribe)后,就会收到该主题的
消息内容(payload);
 payload,可以理解为消息的内容,是指订阅者具体要使用的内容。
在这里插入图片描述
·每一个终端设备都会向MQTT服务器订阅或发布消息;
·每个终端可以订阅自己发布的消息,服务器就是回发测试;
·走标准化流程,解放了私有协议制定、实现、调试、测试一整套复杂的流程
·MQTT 让逻辑变得更清晰,需要什么订阅什么

5、MQTT相关API的介绍

这里介绍大部分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);

6、软件设计

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);

}

8、调试结果

ESP32 IDF开发 应用篇㉑ MQTT通信_第1张图片
这里使用的是一个模块发布和订阅自己的消息,大家也可以使用两个模式A模块发布,B模块订阅。

所有文章源代码:https://download.csdn.net/download/lu330274924/88518092

你可能感兴趣的:(ESP32,IDF小白到大师实战,嵌入式硬件,网络协议,单片机)