当前位置: 首页 > news >正文

esp32课设记录(三)mqtt通信记录 附mqtt介绍

目录

安装mqttx(云端部署)

安装mosquitto(本地部署)

编程,连接wifi

编程,连接mqtt,实现数据接收

实际效果展示:

附录:mqtt介绍

工作流程简述:

工作流程具体介绍:

1. 建立连接(CONNECT/CONNACK)

2. 订阅主题(SUBSCRIBE/SUBACK)

3. 发布消息(PUBLISH)

4. 心跳保持(PINGREQ/PINGRESP)

5. 断开连接(DISCONNECT)

主要特点

QoS(服务质量)等级

MQTT协议组成


看一眼课设要求,只需要连接MQTT并且下发显示英文字符串就行。下面开干。

首先老师给了一个mqtt的broker ip地址,这个很简单只要安装mqttx就直接能用。

当然由于我一开始不知道有mqttx,于是使用了Mosquitto。这个可以在本地部署mqtt的broker服务器,我也成功了。这两个我都会描述一下。

安装mqttx(云端部署)

前往官网MQTTX 下载

下载后,点击new connection,然后填写自己的配置。这里是学校老师给的。

连接成功。

安装mosquitto(本地部署)

先安装mosquitto,前往官网下载安装:Download | Eclipse Mosquitto,安装后进入文件夹,可以看到mosquitto.conf

添加监听窗口和基础设置。

listener 1885allow_anonymous false
password_file ./passwdpersistence true
persistence_location ./mosquitto_data/

打开命令提示符(CMD),并切换到 Mosquitto 的安装目录。例如,如果 Mosquitto 安装在 C:\Program Files\mosquitto,则运行:

cd C:\Program Files\mosquitto

创建密码文件。然后输入密码。

mosquitto_passwd -c ./passwd iot040

然后,使用以下命令启动 Mosquitto Broker:

mosquitto -v -c mosquitto.conf

这将根据 mosquitto.conf 文件中的配置启动 Mosquitto。

现在已经成功在本地搭建服务器了,套接字用的1885。

测试如下:

订阅主题(终端1)

mosquitto_sub -h localhost -t "test/topic" -v

  • -h localhost:连接本地服务器
  • -t "test/topic":订阅主题
  • -v:显示详细消息

发布消息(终端2)

mosquitto_pub -h localhost -t "test/topic" -m "Hello MQTT"

  • -m "Hello MQTT":消息内容

预期结果

终端1会立即收到消息:

test/topic Hello MQTT

在成功后,可以使用图形化客户端,比如:

  • MQTT.fx(Windows/macOS/Linux)
  • MQTT Explorer(跨平台)

连接配置:

  • Broker Address: localhost
  • Port: 1883
  • 用户名/密码:按照配置的来

编程,连接wifi

最后我的整个工程会开源到github。git还是很方便的。本课设采用ESP-IDF库编程,这是基于freertos的。

首先是wifi的初始化,

初始化需要执行以下操作:

1.创建FreeRTOS事件组用于同步

2.初始化ESP32的网络接口

3.配置并启动WiFi连接

4.阻塞等待连接成功或失败

先定义wifi的基本配置。
 

// 添加WiFi配置
#define WIFI_SSID ""    //wifi名称
#define WIFI_PASSWORD ""    //wifi密码
#define WIFI_MAXIMUM_RETRY 5   //最大重试次数
#define WIFI_CONNECTED_BIT BIT0    //WiFi连接成功标志
#define WIFI_FAIL_BIT BIT1    //WiFi连接失败标志// FreeRTOS事件组,用于WiFi连接
EventGroupHandle_t s_wifi_event_group;  //FreeRTOS事件组句柄,同步WiFi连接状态,允许程序等待WiFi状态变化,而不是持续轮询
int s_retry_num = 0;//跟踪WiFi连接尝试次数

按顺序编写wifi的初始化。这里面绝大多数函数都是库里面自带的。主要是#include "esp_wifi.h"。

// 初始化WiFi连接
void wifi_init_sta(void)
{// 创建事件组,用于等待WiFi连接s_wifi_event_group = xEventGroupCreate();// 初始化网络接口ESP_ERROR_CHECK(esp_netif_init());//初始化ESP32的TCP/IP协议栈ESP_ERROR_CHECK(esp_event_loop_create_default());//创建一个默认的事件循环esp_netif_create_default_wifi_sta();//创建WiFi站点(STA)模式的网络接口// 初始化WiFiwifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();ESP_ERROR_CHECK(esp_wifi_init(&cfg));// 注册WiFi事件处理函数esp_event_handler_instance_t instance_any_id;esp_event_handler_instance_t instance_got_ip;ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,ESP_EVENT_ANY_ID,&wifi_event_handler,NULL,&instance_any_id));ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,IP_EVENT_STA_GOT_IP,&wifi_event_handler,NULL,&instance_got_ip));// 配置WiFiwifi_config_t wifi_config = {.sta = {.ssid = WIFI_SSID,.password = WIFI_PASSWORD,.threshold.authmode = WIFI_AUTH_WPA2_PSK,.pmf_cfg = {.capable = true,.required = false},},};ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));ESP_ERROR_CHECK(esp_wifi_start());/* 等待WiFi连接或达到最大尝试次数 */EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,pdFALSE,pdFALSE,portMAX_DELAY);// 标记变量为有意未使用,防止报警告(void)bits;
}

然后编写WIFI事件处理函数,逻辑如下:

这个函数不是直接被调用的,而是通过ESP-IDF的事件系统自动触发,当执行esp_wifi_start()后:

WiFi驱动启动并触发WIFI_EVENT_STA_START事件

事件系统调用wifi_event_handler,传入此事件

处理函数检测到是启动事件,执行esp_wifi_connect()开始连接

如果连接成功,TCP/IP栈获取IP地址并触发IP_EVENT_STA_GOT_IP事件

事件系统再次调用wifi_event_handler,这次传入IP事件

处理函数设置WIFI_CONNECTED_BIT标志,通知主线程连接成功

// WIFI事件处理函数
void wifi_event_handler(void *arg, esp_event_base_t event_base,int32_t event_id, void *event_data)
{// 1. WiFi驱动启动完成事件if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START){// WiFi已初始化完成,开始尝试连接到配置的APesp_wifi_connect();}// 2. WiFi断开连接事件(初次连接失败或运行中断开)else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED){// 检查是否超过最大重试次数if (s_retry_num < WIFI_MAXIMUM_RETRY){// 未超过重试上限:增加计数并尝试重新连接esp_wifi_connect();s_retry_num++;// ESP_LOGI(WIFI_TAG, "重试连接WiFi... (%d/%d)", s_retry_num, WIFI_MAXIMUM_RETRY);}else{// 超过重试上限:设置失败标志位,允许主程序继续执行xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);// ESP_LOGE(WIFI_TAG, "WiFi连接失败,已达最大重试次数");}}// 3. 成功获取IP地址事件(连接成功的最终标志)else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP){// 转换事件数据(包含IP地址信息)ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;// 标记变量为有意未使用(避免编译警告)(void)event;// 重置重试计数器,为后续可能的断线重连做准备s_retry_num = 0;// 设置连接成功标志位,允许主程序继续执行xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);// ESP_LOGI(WIFI_TAG, "成功连接WiFi,IP地址: "IPSTR, IP2STR(&event->ip_info.ip));}
}

然后在main.c调用wifi_init_sta();即可接上wifi。

// WiFi连接wifi_init_sta();

编程,连接mqtt,实现数据接收

先定义需要的变量。

其中esp_mqtt_client_handle_t 是ESP-IDF框架中定义的类型,实际上是一个指向MQTT客户端内部结构的指针,每个客户端一个,初始化为NULL说明此时没有连接任何客户端。

esp_mqtt_client_handle_t的使用流程:

创建: 通过esp_mqtt_client_init()函数创建:

mqtt_client = esp_mqtt_client_init(&mqtt_cfg);

操作: 用于所有MQTT API调用:

// 订阅主题

esp_mqtt_client_subscribe(mqtt_client, MQTT_TOPIC, 0);

// 发布消息

esp_mqtt_client_publish(mqtt_client, MQTT_PUBLISH_TOPIC, "消息内容", 0, 1, 0);

状态检查: 在发送消息前检查连接:if (mqtt_client != NULL && mqtt_connected) {

// 执行MQTT操作

}

// MQTT配置 - 使用提供的服务器信息
#define MQTT_BROKER_URL ""    //broker的url,比如mqtt://211.81.51.133:1885
#define MQTT_USERNAME ""      //用户名
#define MQTT_PASSWORD ""      //密码
#define MQTT_CLIENT_ID ""     //CLIENT_ID
#define MQTT_TOPIC "top040"   //订阅的主题
#define MQTT_PUBLISH_TOPIC "top039"// 发送的主题// MQTT状态和消息缓冲区
esp_mqtt_client_handle_t mqtt_client = NULL; // ESP-IDF框架中定义的类型,MQTT客户端句柄(指针),指向MQTT客户端内部结构
bool mqtt_connected = false;                 // 跟踪MQTT客户端的连接状态
char mqtt_message[256] = {0};//存储从MQTT服务器接收到的最新消息内容
bool new_message_received = false; // 标记是否接收到新的MQTT消息(需要处理)

初始化:各个功能已经写注释里了。

// 初始化MQTT客户端
void mqtt_app_start(void)
{// 配置结构体创建esp_mqtt_client_config_t mqtt_cfg = {.broker = {.address = {.uri = MQTT_BROKER_URL, // 服务器地址},.verification = {.skip_cert_common_name_check = true, // 非TLS连接}},.credentials = {.username = MQTT_USERNAME, // MQTT服务器认证用户名.authentication = {.password = MQTT_PASSWORD, // MQTT服务器认证密码},.client_id = MQTT_CLIENT_ID, // 客户端唯一标识符}};// 客户端初始化mqtt_client = esp_mqtt_client_init(&mqtt_cfg);// 注册事件处理函数esp_mqtt_client_register_event(mqtt_client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL); // ESP_EVENT_ANY_ID表示注册所有MQTT事件(连接、断开、收发消息等),当MQTT事件发生时,系统会自动调用mqtt_event_handleresp_mqtt_client_start(mqtt_client);                                                      // 客户端启动
}

来吧,写回调函数,就是每个事件干什么事情。同上,也是自动调用。至此结束了。

// 回调函数指针
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;switch (event->event_id){case MQTT_EVENT_CONNECTED:ESP_LOGI(MQTT_TAG, "MQTT已连接");mqtt_connected = true;// 订阅主题esp_mqtt_client_subscribe(client, MQTT_TOPIC, 0);ESP_LOGI(MQTT_TAG, "已订阅主题: %s", MQTT_TOPIC);// 发布连接成功消息esp_mqtt_client_publish(client, MQTT_PUBLISH_TOPIC, "ESP32已上线", 0, 1, 0);break;case MQTT_EVENT_DISCONNECTED:ESP_LOGI(MQTT_TAG, "MQTT已断开连接");mqtt_connected = false;break;case MQTT_EVENT_SUBSCRIBED:ESP_LOGI(MQTT_TAG, "MQTT订阅成功,msg_id=%d", event->msg_id);break;case MQTT_EVENT_UNSUBSCRIBED:ESP_LOGI(MQTT_TAG, "MQTT取消订阅,msg_id=%d", event->msg_id);break;case MQTT_EVENT_PUBLISHED:ESP_LOGI(MQTT_TAG, "MQTT消息已发布,msg_id=%d", event->msg_id);break;case MQTT_EVENT_DATA:ESP_LOGI(MQTT_TAG, "MQTT收到数据:");ESP_LOGI(MQTT_TAG, "主题: %.*s", event->topic_len, event->topic);ESP_LOGI(MQTT_TAG, "数据: %.*s", event->data_len, event->data);// 将收到的消息复制到缓冲区if (event->data_len < sizeof(mqtt_message) - 1){memcpy(mqtt_message, event->data, event->data_len);mqtt_message[event->data_len] = '\0'; // 确保字符串结束符new_message_received = true;          // 设置新消息标志}break;case MQTT_EVENT_ERROR:ESP_LOGI(MQTT_TAG, "MQTT错误");break;default:ESP_LOGI(MQTT_TAG, "其他MQTT事件 id:%d", event->event_id);break;}
}

搞定!下面在主函数里面调用初始化就好了。

//初始化
mqtt_app_start();

发送消息:

// 发送消息
if (mqtt_connected) {// 准备消息内容char message[100];// 方式1:简单文本消息strcpy(message, "Hello from ESP32");// 方式2:格式化消息sprintf(message, "Sensor reading: %.2f", sensor_value);// 方式3:JSON格式消息(推荐用于复杂数据)sprintf(message, "{\"device\":\"ESP32\",\"value\":%.2f,\"status\":\"%s\"}", sensor_value, status ? "ON" : "OFF");// 发送消息esp_mqtt_client_publish(mqtt_client, MQTT_PUBLISH_TOPIC, message, 0, 1, 0);
/*参数说明:mqtt_client: MQTT客户端句柄
MQTT_PUBLISH_TOPIC: 发布主题
message: 消息内容
0: 消息长度(0表示自动计算)
1: QoS级别(0=最多一次,1=至少一次,2=只有一次)
0: 消息保留标志(0=不保留,1=保留)*/ESP_LOGI(TAG, "已发送MQTT消息: %s", message);
}

接收消息有2种方式,一种是在while循环里,通过轮询。

// 在主循环中
while (1) {// 检查是否收到新消息if (new_message_received) {// 重置消息标志new_message_received = false;// 处理收到的消息ESP_LOGI(TAG, "收到MQTT消息: %s", mqtt_message);// 解析和处理消息...if (strcmp(mqtt_message, "LED_ON") == 0) {led_set_state(true);} else if (strcmp(mqtt_message, "LED_OFF") == 0) {led_set_state(false);}// 可选:显示消息到LCDclear_text_area(lcd_buffer, LCD_H_RES, LCD_V_RES);draw_string(lcd_buffer, 0, 0, "MQTT消息", 0xFFFF, LCD_H_RES);draw_string(lcd_buffer, 0, FONT_HEIGHT + 2, mqtt_message, 0xFFFF, LCD_H_RES);esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, LCD_H_RES, LCD_V_RES, lcd_buffer);}// 其他任务处理...vTaskDelay(10 / portTICK_PERIOD_MS);
}

第二种是直接在MQTT事件处理函数中处理(适用于需要立即响应的场景)

在mqtt_event_handler函数的MQTT_EVENT_DATA部分添加处理逻辑:

case MQTT_EVENT_DATA:ESP_LOGI(MQTT_TAG, "MQTT收到数据: %.*s", event->data_len, event->data);// 将收到的消息复制到缓冲区if (event->data_len < sizeof(mqtt_message) - 1) {memcpy(mqtt_message, event->data, event->data_len);mqtt_message[event->data_len] = '\0'; // 确保字符串结束符// 立即处理特定命令if (strcmp(mqtt_message, "EMERGENCY_STOP") == 0) {// 紧急处理,不等待主循环system_emergency_stop();}// 设置标志供主循环处理new_message_received = true;}break;

实际效果展示:

初始化

接收消息

发送消息:

首先需要创建一个订阅,确保主题和宏定义的MQTT_PUBLISH_TOPIC一样。

然后在程序里面发送,就可以收到了!

附录:mqtt介绍

MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是一种轻量级的发布/订阅模式消息传输协议,专为低带宽、高延迟或不稳定的网络环境设计。是应用层协议,类似HTTP。QTT已成为物联网领域最流行的通信协议之一,被广泛应用于各种智能设备和系统中。

工作流程简述:

如图所示:

发布者(Publisher):左侧的冰箱代表一个物联网设备,它通过MQTT协议向中间的MQTT Broker(代理服务器)发布(Publish)消息。通常消息包含传感器数据、状态信息等。

代理服务器MQTT Broker):MQTT Broker负责接收和分发消息。它起到“中间人”的作用,不需要设备之间直接通信。

订阅者(Subscriber):右侧的手机、电脑和服务器代表多个订阅者。它们向MQTT Broker订阅(Subscribe)自己感兴趣的主题(Topic)。Topic是消息的分类标识,采用层级结构(如"sensor/temperature")。

一旦有消息发布到Broker,Broker会把消息分发给所有已经订阅该主题的设备。

MQTT通过“发布-订阅”模式,发布者只负责发送消息到Broker,订阅者只需从Broker接收感兴趣的数据。这样可以实现高效、灵活的物联网通信架构。

工作流程具体介绍:

1. 建立连接(CONNECT/CONNACK)

步骤说明

  1. 客户端发送CONNECT
    • 携带参数:
      • ClientID:客户端唯一标识(如device123
      • Clean Session:是否清除历史会话(true=重新开始,false=恢复之前订阅)
      • 可选的Username/Password(若Broker启用认证)
  1. 服务端回复CONNACK
    • 返回状态码:
      • 0:连接成功
      • 4:无效用户名/密码
      • 5:未授权(如客户端无权限连接)
2. 订阅主题(SUBSCRIBE/SUBACK)

步骤说明

  1. 客户端发送SUBSCRIBE
    • 指定订阅的 主题过滤器(如sensor/#匹配所有传感器主题)
    • 设置 QoS级别(0/1/2,决定消息传递质量)
  1. 服务端回复SUBACK
    • 确认订阅成功,或返回失败原因(如主题格式无效)
3. 发布消息(PUBLISH)

步骤说明

  1. 发布者发送PUBLISH
    • Topic:消息分类(如home/temperature
    • Message:实际数据(如{"value":25.5}
    • QoS:发布时的服务质量
    • Retain:是否保留消息(新订阅者立即收到最后一条)
  1. Broker转发消息
    • 根据订阅者的QoS级别,可能需确认(QoS≥1时)

QoS处理差异

  • QoS 0:Broker直接转发,无确认。
  • QoS 1:Broker存储消息直到收到订阅者的PUBACK
  • QoS 2:四次握手确保消息不重复(PUBREC→PUBREL→PUBCOMP)。

4. 心跳保持(PINGREQ/PINGRESP)

作用

  • 在长连接无数据交互时(如Keep Alive时间内),通过心跳包保持连接活跃。
  • 若Broker未及时响应PINGRESP,客户端会判定连接断开并重连。

5. 断开连接(DISCONNECT)

行为说明

  • 客户端主动断开时发送DISCONNECT,Broker释放该客户端的资源。
  • 若未发送DISCONNECT直接断开,Broker会根据遗嘱消息(Last Will)通知其他客户端。


 

主要特点

  1. 轻量级:协议头小,消息精简,适合带宽受限环境
  2. 发布/订阅模式:解耦消息生产者和消费者
  3. 低功耗:适合电池供电的物联网设备
  4. 支持QoS:提供三种消息服务质量等级
  5. 支持TCP/IP:运行在TCP协议之上

QoS(服务质量)等级

MQTT定义了三种QoS级别:

  1. QoS 0(最多一次):发后即忘(无确认)。消息发送一次,不保证送达。
  2. QoS 1(至少一次):至少一次(需PUBACK)。确保消息送达,但可能有重复。
  3. QoS 2(恰好一次):恰好一次(4次握手)。确保消息只送达一次。

MQTT协议组成

MQTT协议由以下核心组件构成:

组件

说明

固定头部(Fixed Header)

每个MQTT报文都以一个固定头部开始,包含了报文类型和标志位等重要信息。

可变报头(Variable Header)

可选字段,如报文标识符(Packet ID)、主题名长度等

有效载荷(Payload)

实际传输的数据(如消息内容),在PUBLISH报文中最常见


核心控制报文类型:

报文类型

用途

CONNECT

1

客户端连接服务器(发送用户名/密码、Clean Session标志等)

CONNACK

2

服务器响应连接(返回状态码,如0=成功,5=认证失败)

PUBLISH

3

发布消息到指定主题(含QoS、Retain标志)

SUBSCRIBE

8

客户端订阅主题(可指定多个主题及QoS)

UNSUBSCRIBE

10

取消订阅

PINGREQ

12

客户端心跳请求(保持长连接)

PINGRESP

13

服务器心跳响应

DISCONNECT

14

客户端主动断开连接


 

相关文章:

  • 软件工程-项目管理
  • 【android bluetooth 协议分析 01】【HCI 层介绍 8】【ReadLocalVersionInformation命令介绍】
  • CVE-2015-3934 Fiyo CMS SQL注入
  • 词嵌入基础
  • el-tree结合el-tree-transfer实现穿梭框里展示树形数据
  • 【android bluetooth 协议分析 01】【HCI 层介绍 7】【ReadLocalName命令介绍】
  • Feature Toggle 不再乱:如何设计一个干净、安全、可控的特性开关系统?
  • LeetCode 39. 组合总和 LeetCode 40.组合总和II LeetCode 131.分割回文串
  • 模板(template)初始
  • Spring Cloud Seata 深度解析:原理与架构设计
  • 微店平台关键字搜索商品接口技术实现
  • 题海拾贝:P2910 [USACO08OPEN] Clear And Present Danger S
  • kotlin Android AccessibilityService 无障碍入门
  • UE RPG游戏开发练手 第二十八课 重攻技能1
  • k8s节点维护的细节
  • 带你搞懂@Valid和@Validated的区别
  • 线代第三章向量第一节:n维向量及其运算
  • Electron + Vite + Vue 项目中的 IPC 通信三层封装实践
  • 解决RAGFlow部署中镜像源拉取的问题
  • vi实时查看日志
  • “宝马女司机驾车拖行虐猫”引关注,海口警方介入调查
  • 金融监管总局将研究出台专门的城市更新项目贷款管理办法:引导大力支持城中村的改造等
  • 国家发改委:系统谋划7方面53项配套举措,推动民营经济促进法落地见效
  • 中国戏剧梅花奖终评结果公示,蓝天、朱洁静等15名演员入选
  • 武汉警方通报一起故意伤害案件:1人死亡,嫌疑人已被抓获
  • 特朗普:将于19日分别与普京和泽连斯基通话