携手创作,一起成长!这是我参与「日新方案 8 月更文应战」的第15天,点击检查活动概况

一、MQTT简介

1.1 完成方法

完成MQTT协议需求客户端和服务器端通讯完成,在通讯过程中,MQTT协议中有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,音讯的发布者和订阅者都是客户端,音讯代理是服务器,音讯发布者能够一起是订阅者。

MQTT传输的音讯分为:主题(Topic)和负载(payload)两部分:

Topic,能够理解为音讯的类型,订阅者订阅(Subscribe)后,就会收到该主题的音讯内容(payload);

payload,能够理解为音讯的内容,是指订阅者具体要运用的内容。

ESP32学习笔记(46)——MQTT客户端

  • MQTT服务器的首要作业是数据分发,没有数据保存功用。
  • 能够订阅自己发布的主题,服务器就是回发测验。
  • MQTT让逻辑变得更清晰,需求什么订阅什么。
  • 走标准化流程,解放了私有协议拟定、完成、调试、测验一整套复杂的流程。

1.2 ESP-MQTT

ESP-MQTT 是 MQTT 协议客户端的完成(MQTT 是轻量级的发布/订阅音讯协议)。

  • 支撑 MQTT over TCP、SSL with mbedtls、MQTT over Websocket、MQTT over Websocket Secure。
  • 运用 URI 轻松设置
  • 多个实例(一个使用程序中有多个客户端)
  • 支撑订阅、发布、身份验证、最后遗言音讯、保持活动 ping 和所有 3 个 QoS 等级(它应该是一个功用齐全的客户端)。

ESP-IDF 编程指南——ESP-MQTT

二、API阐明

以下 MQTT 客户端接口坐落 components/mqtt/esp-mqtt/include/mqtt_client.h

2.1 esp_mqtt_client_init

ESP32学习笔记(46)——MQTT客户端

2.2 esp_mqtt_client_register_event

ESP32学习笔记(46)——MQTT客户端

2.3 esp_mqtt_client_start

ESP32学习笔记(46)——MQTT客户端

2.4 esp_mqtt_client_publish

ESP32学习笔记(46)——MQTT客户端

2.5 esp_mqtt_client_subscribe

ESP32学习笔记(46)——MQTT客户端

2.6 esp_mqtt_client_unsubscribe

ESP32学习笔记(46)——MQTT客户端

三、MQTT客户端

3.1 首要流程

ESP32学习笔记(46)——MQTT客户端

3.2 装备MQTT参数

首要,要界说一个 MQTT 客户端装备结构体,最小装备即填入 MQTT 服务器的 URL 即可。

esp_mqtt_client_config_t mqtt_cfg = {
    .uri = CONFIG_BROKER_URL,
};

esp_mqtt_client_config_t 结构体如下:

typedef struct {
    mqtt_event_callback_t event_handle; /*回调*/
    const char *host; /*!< MQTT 服务器域名(ipv4 as string)*/
    const char *uri; /*!< MQTT 服务器域名 */
    uint32_t port; /*!< MQTT服务器端口*/
    const char *client_id; /*MQTT Client的名字默许是ESP32_加上MAC后3hex*/
    const char *username; /*MQTT用户名*/
    const char *password; /*MQTT暗码*/
    const char *lwt_topic; /*!< LWT主题,默许为空*/
    const char *lwt_msg; /*!< LWT信息,默许为空*/
    int lwt_qos; /*!< LWT音讯质量*/
    int lwt_retain; /*!< LWT保存音讯标志*/
    int lwt_msg_len; /*!< LWT音讯长度*/
    int disable_clean_session; /*!< mqtt clean session,默许为真*/
    int keepalive; /*MQTT心跳,默许120秒 */
    bool disable_auto_reconnect; /*过错,断开后重连,true不连*/
    void *user_context; /*用户信息 */
    int task_prio; /*!< MQTT使命优先级,默许为5,能够在make menuconfig中修正*/
    int task_stack; /*!< MQTT 使命仓库巨细,默许6144 bytes,能够在make menuconfig中修正*/
    int buffer_size; /*!< MQTT收发缓存,默许1024 */
    const char *cert_pem; /*指向用于服务器验证(运用SSL)的PEM格局的证书数据的指针,默许值为空,不需求验证服务器 */
    const char *client_cert_pem; /*指向用于SSL彼此身份验证的PEM格局的证书数据的指针,默许值为空,假如不需求彼此身份验证,则不需求。假如不为空,还必须提供“客户机密钥”。*/
    const char *client_key_pem; /*指向用于SSL彼此身份验证的PEM格局的私钥数据的指针,默许值为空,假如不需求彼此身份验证,则不需求。假如不为空,还必须提供“client-cert-pem”。*/
    esp_mqtt_transport_t transport; /*覆盖URI传输*/
} esp_mqtt_client_config_t;

3.3 初始化MQTT客户端

然后经过 esp_mqtt_client_init() 获取一个 MQTT 客户端结构体指针,参数是 MQTT 客户端装备结构体。

esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);

3.4 注册MQTT事情

默许情况下,MQTT 客户端运用事情循环库来发布相关的 MQTT 事情(已衔接,已订阅,已发布等)。

所以我们要注册一个 MQTT 事情,填入 MQTT 事情处理函数 mqtt_event_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);
    mqtt_event_handler_cb(event_data);
}
  • 第一个参数为MQTT客户端结构体,
  • 第二个是事情ID对应的事情类型,
  • 第三个参数即事情处理函数,
  • 第四个参数为事情处理函数的参数。

3.5 敞开MQTT客户端

esp_mqtt_client_start(client);

3.6 MQTT事情处理

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

四、示例代码

依据 examples\protocols\mqtt\tcp 中的例程修正 依据服务器地址修正.host = "192.168.61.67",

/* MQTT (over TCP) Example
   This example code is in the Public Domain (or CC0 licensed, at your option.)
   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "esp_wifi.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "protocol_examples_common.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "lwip/sockets.h"
#include "lwip/dns.h"
#include "lwip/netdb.h"
#include "esp_log.h"
#include "mqtt_client.h"
static const char *TAG = "MQTT_EXAMPLE";
static esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event)
{
    // 获取MQTT客户端结构体指针
    esp_mqtt_client_handle_t client = event->client;
    int msg_id;
    // your_context_t *context = event->context;
    // 经过事情ID来别离处理对应的事情
    switch (event->event_id) {
        case MQTT_EVENT_CONNECTED:    // MQTT连上事情
            ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
            // MQTT Client发布主题函数,主题是/topic/qos1,服务质量qos1,发布的数据是data-3
            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);
            // MQTT Client订阅主题函数,主题是/topic/qos0,服务质量qos0
            msg_id = esp_mqtt_client_subscribe(client, "/topic/qos0", 0);
            ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);
            // MQTT Client订阅主题函数,主题是/topic/qos1,服务质量qos1
            msg_id = esp_mqtt_client_subscribe(client, "/topic/qos1", 1);
            ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);
            // MQTT Client取消订阅主题函数
            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:    // MQTT断开衔接事情
            ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
            break;
        case MQTT_EVENT_SUBSCRIBED:    // MQTT发送订阅成功事情
            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:    // MQTT取消订阅事情
            ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
            break;
        case MQTT_EVENT_PUBLISHED:    // MQTT发布成功事情
            ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
            break;
        case MQTT_EVENT_DATA:    // MQTT接收数据事情
            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;
}
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);
    mqtt_event_handler_cb(event_data);
}
static void mqtt_app_start(void)
{
     // 1、界说一个MQTT客户端装备结构体,输入MQTT的url
    esp_mqtt_client_config_t mqtt_cfg = {
        .host = "192.168.61.67",    // MQTT服务器地址
        .port = 1883,               // MQTT服务器端口
    };
#if CONFIG_BROKER_URL_FROM_STDIN
    char line[128];
    if (strcmp(mqtt_cfg.uri, "FROM_STDIN") == 0) {
        int count = 0;
        printf("Please enter url of mqtt broker\n");
        while (count < 128) {
            int c = fgetc(stdin);
            if (c == '\n') {
                line[count] = '\0';
                break;
            } else if (c > 0 && c < 127) {
                line[count] = c;
                ++count;
            }
            vTaskDelay(10 / portTICK_PERIOD_MS);
        }
        mqtt_cfg.uri = line;
        printf("Broker url: %s\n", line);
    } else {
        ESP_LOGE(TAG, "Configuration mismatch: wrong broker url");
        abort();
    }
#endif /* CONFIG_BROKER_URL_FROM_STDIN */
    // 2、经过esp_mqtt_client_init获取一个MQTT客户端结构体指针,参数是MQTT客户端装备结构体
    esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);
    // 3、注册MQTT事情
    esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, client);
    // 4、敞开MQTT功用
    esp_mqtt_client_start(client);
}
void app_main(void)
{
    ESP_LOGI(TAG, "[APP] Startup..");
    ESP_LOGI(TAG, "[APP] Free memory: %d bytes", esp_get_free_heap_size());
    ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version());
    esp_log_level_set("*", ESP_LOG_INFO);
    esp_log_level_set("MQTT_CLIENT", ESP_LOG_VERBOSE);
    esp_log_level_set("MQTT_EXAMPLE", ESP_LOG_VERBOSE);
    esp_log_level_set("TRANSPORT_TCP", ESP_LOG_VERBOSE);
    esp_log_level_set("TRANSPORT_SSL", ESP_LOG_VERBOSE);
    esp_log_level_set("TRANSPORT", ESP_LOG_VERBOSE);
    esp_log_level_set("OUTBOX", ESP_LOG_VERBOSE);
    ESP_ERROR_CHECK(nvs_flash_init());
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
     * Read "Establishing Wi-Fi or Ethernet Connection" section in
     * examples/protocols/README.md for more information about this function.
     */
    ESP_ERROR_CHECK(example_connect());
    mqtt_app_start();
}

五、搭建本地MQTT服务器

EMQ官网下载:www.emqx.com/zh/download…

  • 下载EMQ X开源版
    ESP32学习笔记(46)——MQTT客户端
  • 解压后进入 emqx-windows-4.3.8\emqx\bin 目录
    ESP32学习笔记(46)——MQTT客户端
  • Shift+右键在此处翻开 Powershell 窗口,输入指令 emqx start
    ESP32学习笔记(46)——MQTT客户端
  • 翻开浏览器,输入 http://127.0.0.1:18083/,账号 admin,暗码 public,进入管理界面
    ESP32学习笔记(46)——MQTT客户端
  • 东西 – Websocket,挑选衔接
    ESP32学习笔记(46)——MQTT客户端
  • 订阅主题和发布音讯
    ESP32学习笔记(46)——MQTT客户端

六、运行测验

装备衔接方法:

ESP32学习笔记(46)——MQTT客户端

挑选WIFI衔接方法,并修正要衔接路由器的SSID和暗码

ESP32学习笔记(46)——MQTT客户端

调试打印:

ESP32学习笔记(46)——MQTT客户端

服务器检查:

ESP32学习笔记(46)——MQTT客户端
ESP32学习笔记(46)——MQTT客户端


• 由 Leung 写于 2021 年 9 月 8 日

• 参阅:第二十一章 ESP32开发MQTT Client ESP-IDF

    ESP32学习笔记(6)MQTT使用

    ESP32开发之路(9)—ESP32衔接到MQTT服务器