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

#新星杯·14天创作挑战营·第11期#

目录

安装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 1885


allow_anonymous false
password_file ./passwd


persistence 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)模式的网络接口

    // 初始化WiFi
    wifi_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));

    // 配置WiFi
    wifi_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已初始化完成,开始尝试连接到配置的AP
        esp_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_handler
    esp_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);
        }
        
        // 可选:显示消息到LCD
        clear_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

客户端主动断开连接


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值