一、MQTT简介
1.1 实现方式
实现MQTT协议需要客户端和服务器端通讯完成,在通讯过程中,MQTT协议中有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。
MQTT传输的消息分为:主题(Topic)和负载(payload)两部分:
Topic,可以理解为消息的类型,订阅者订阅(Subscribe)后,就会收到该主题的消息内容(payload);
payload,可以理解为消息的内容,是指订阅者具体要使用的内容。
- 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 级别(它应该是一个功能齐全的客户端)。
二、API说明
以下 MQTT 客户端接口位于 components/mqtt/esp-mqtt/include/mqtt_client.h 。
2.1 esp_mqtt_client_init
2.2 esp_mqtt_client_register_event
2.3 esp_mqtt_client_start
2.4 esp_mqtt_client_publish
2.5 esp_mqtt_client_subscribe
2.6 esp_mqtt_client_unsubscribe
三、MQTT客户端
3.1 主要流程
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官网下载:https://www.emqx.com/zh/downloads?product=broker
- 下载EMQ X开源版
- 解压后进入
emqx-windows-4.3.8\emqx\bin
目录 - Shift+右键在此处打开 Powershell 窗口,输入命令
emqx start
- 打开浏览器,输入
http://127.0.0.1:18083/
,账号admin
,密码public
,进入管理界面 - 工具 - Websocket,选择连接
- 订阅主题和发布消息
六、运行测试
配置连接方式:
选择WIFI连接方式,并修改要连接路由器的SSID和密码
调试打印:
服务器查看: