【笔记2-9】ESP32:mqtt通信

主要参考b站宸芯IOT老师的视频,记录自己的笔记,老师讲的主要是linux环境,但配置过程实在太多问题,就直接用windows环境了,老师也有讲一些windows的操作,只要代码会写,操作都还好,开发板用ESP-WROOM-32,其实坑还挺多的,慢慢来吧。

esp32连接到wifi后可以通过mqtt协议和服务器进行通信,先看一下关于mqtt

mqtt通信协议的格式,作为应用层不详细研究,主要看一下关于发布和订阅的机制。

通过以上描述可知,如果我们想要演示订阅和发布的过程,就至少需要一个服务器和两个客户端,首先是关于服务器,使用mqtt://broker-cn.emqx.io,一个客户端是esp32,另一个客户端,我们使用MQTTX客户端软件,下载地址是https://mqttx.app/

下载完成后,按照下图进行新建连接和配置

客户端ID使用默认,一会在代码中设置的ID不能和这里重复

用户名和密码,服务器不会校验,随意

端口号是1883(默认1883为非加密端口,8883为加密端口)

这样客户端软件就配置完成了,接下来写esp32的代码

首先还是要用STA连接的程序作为base,至少需要STA初始化的代码和事件回调函数

c 复制代码
void wifi_event_handle(void* event_handler_arg,esp_event_base_t event_base,int32_t event_id,void* event_data)
{
    if(event_base == WIFI_EVENT)
    {
        switch(event_id)
        {
            case WIFI_EVENT_STA_START://说明STA工作模式启动
                esp_wifi_connect();//开始用WiFi config配置尝试连接
            break;

            case WIFI_EVENT_STA_CONNECTED://已经连接了
                ESP_LOGI(TAG,"wifi connect !! !! !!");
            break;

            case WIFI_EVENT_STA_DISCONNECTED://断开
                esp_wifi_connect();//重连
                ESP_LOGI(TAG,"wifi disconnect !! !! !!");
            break;

            default:break;
        }
    }
    if(event_base == IP_EVENT)
    {
        switch(event_id)
        {
            case IP_EVENT_STA_GOT_IP://已经获取到从路由器分配的IP,此时才认为真正连接上了路由器
                ESP_LOGI(TAG,"get ip address !! !! !!");
                xSemaphoreGive(s_wifi_connect_sem);
            break;
        }
    }
}

在上述基础上追加mqtt相关代码

首先追加一些宏定义

c 复制代码
#define MQTT_ADDRESS "mqtt://broker-cn.emqx.io"
#define MQTT_CLIENTID "mqttx_test_1203"
#define MQTT_USERNAME "mqtt_test_name"
#define MQTT_PASSWORD "mqtt_test_password"

#define MQTT_TOPIC1 "/topic/esp32_1203"         //用于发布的主题,esp32往这个主题发布消息
#define MQTT_TOPIC2 "/topic/mqttx_1203"         //用于订阅的主题,mqttx往这个主题推送消息

然后是mqtt初始化的配置和事件回调函数注册

c 复制代码
void mqtt_start()
{
    esp_mqtt_client_config_t mqtt_cfg = {0};
    mqtt_cfg.broker.address.uri = MQTT_ADDRESS;
    mqtt_cfg.broker.address.port = 1883;
    mqtt_cfg.credentials.client_id = MQTT_CLIENTID;//不能和软件上的ID重复
    mqtt_cfg.credentials.username = MQTT_USERNAME;
    mqtt_cfg.credentials.authentication.password = MQTT_PASSWORD;

    mqtt_handle = esp_mqtt_client_init(&mqtt_cfg);

    esp_mqtt_client_register_event(mqtt_handle , ESP_EVENT_ANY_ID , mqtt_event_callback , NULL);
    esp_mqtt_client_start(mqtt_handle);


}

接下来实现事件回调函数,这里主要关注以下5个事件

MQTT_EVENT_CONNECTED 成功连接服务器事件

MQTT_EVENT_DISCONNECTED 断开事件

MQTT_EVENT_PUBLISHED: 向服务器发布一条消息,并受到服务器ACK

MQTT_EVENT_SUBSCRIBED: 收到订阅的消息

MQTT_EVENT_DATA 获取事件数据

c 复制代码
static esp_mqtt_client_handle_t mqtt_handle =NULL;

void   mqtt_event_callback(void* event_handler_arg,esp_event_base_t event_base,int32_t event_id,void* event_data)
{
    esp_mqtt_event_handle_t data = (esp_mqtt_event_handle_t)event_data;
    esp_mqtt_client_handle_t client = data->client;
    switch(event_id)
    {
        case MQTT_EVENT_CONNECTED://已经成功连上服务器
            ESP_LOGI(TAG, "mqtt_connect !! !! !!");
            esp_mqtt_client_subscribe_single(mqtt_handle , MQTT_TOPIC2 , 1);//参数三是qos1
        break;

        case MQTT_EVENT_DISCONNECTED:
             ESP_LOGI(TAG, "mqtt_disconnect !! !! !!");
        break;

        case MQTT_EVENT_PUBLISHED://向服务器发布一条消息,并受到服务器ACK
            ESP_LOGI(TAG, "mqtt_published !! !! !!");
        break;

        case MQTT_EVENT_SUBSCRIBED://收到订阅的消息
            ESP_LOGI(TAG, "mqtt_subscribed !! !! !!");
        break;

        case MQTT_EVENT_DATA:
            // ESP_LOGI(TAG, "topic->%s" , data->topic);//输出对应的主题
            // ESP_LOGI(TAG, "payload->%s" , data->data);//输出数据

            printf("TOPIC=%.*s\r\n", data->topic_len, data->topic);       //收到Pub消息直接打印出来
            printf("DATA=%.*s\r\n", data->data_len, data->data);
        break;

        default:break;
    }
}

这里有一个问题,如果用ESP_LOGI输出事件数据就会产生乱码,用printf就没问题,暂时不知道原因。

最后我们再用一个信号量来通知wifi连接后才开启mqtt

完整代码如下

c 复制代码
#include <stdio.h>
#include <string.h>
#include "nvs_flash.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_err.h"//错误检查头文件
#include "esp_smartconfig.h"
#include "esp_netif.h"
#include "mqtt_client.h"
#include "freertos/semphr.h"

#define TAG "mqtt-test"

#define MQTT_ADDRESS "mqtt://broker-cn.emqx.io"
#define MQTT_CLIENTID "mqttx_test_1203"
#define MQTT_USERNAME "mqtt_test_name"
#define MQTT_PASSWORD "mqtt_test_password"

#define MQTT_TOPIC1 "/topic/esp32_1203"         //用于发布的主题,esp32往这个主题发布消息
#define MQTT_TOPIC2 "/topic/mqttx_1203"         //用于订阅的主题,mqttx往这个主题推送消息


 #define TEST_SSID "vivo S19"
 #define TEST_KEY "33335554"

static esp_mqtt_client_handle_t mqtt_handle =NULL;

static SemaphoreHandle_t s_wifi_connect_sem = NULL;//用信号量通知wifi连接

void   mqtt_event_callback(void* event_handler_arg,esp_event_base_t event_base,int32_t event_id,void* event_data)
{
    esp_mqtt_event_handle_t data = (esp_mqtt_event_handle_t)event_data;
    esp_mqtt_client_handle_t client = data->client;
    switch(event_id)
    {
        case MQTT_EVENT_CONNECTED://已经成功连上服务器
            ESP_LOGI(TAG, "mqtt_connect !! !! !!");
            esp_mqtt_client_subscribe_single(mqtt_handle , MQTT_TOPIC2 , 1);//参数三是qos1
        break;

        case MQTT_EVENT_DISCONNECTED:
             ESP_LOGI(TAG, "mqtt_disconnect !! !! !!");
        break;

        case MQTT_EVENT_PUBLISHED://向服务器发布一条消息,并受到服务器ACK
            ESP_LOGI(TAG, "mqtt_published !! !! !!");
        break;

        case MQTT_EVENT_SUBSCRIBED://收到订阅的消息
            ESP_LOGI(TAG, "mqtt_subscribed !! !! !!");
        break;

        case MQTT_EVENT_DATA:
            // ESP_LOGI(TAG, "topic->%s" , data->topic);//输出对应的主题
            // ESP_LOGI(TAG, "payload->%s" , data->data);//输出数据

            printf("TOPIC=%.*s\r\n", data->topic_len, data->topic);       //收到Pub消息直接打印出来
            printf("DATA=%.*s\r\n", data->data_len, data->data);
        break;

        default:break;
    }
}


void wifi_event_handle(void* event_handler_arg,esp_event_base_t event_base,int32_t event_id,void* event_data)
{
    if(event_base == WIFI_EVENT)
    {
        switch(event_id)
        {
            case WIFI_EVENT_STA_START://说明STA工作模式启动
                esp_wifi_connect();//开始用WiFi config配置尝试连接
            break;

            case WIFI_EVENT_STA_CONNECTED://已经连接了
                ESP_LOGI(TAG,"wifi connect !! !! !!");
            break;

            case WIFI_EVENT_STA_DISCONNECTED://断开
                esp_wifi_connect();//重连
                ESP_LOGI(TAG,"wifi disconnect !! !! !!");
            break;

            default:break;
        }
    }
    if(event_base == IP_EVENT)
    {
        switch(event_id)
        {
            case IP_EVENT_STA_GOT_IP://已经获取到从路由器分配的IP,此时才认为真正连接上了路由器
                ESP_LOGI(TAG,"get ip address !! !! !!");
                xSemaphoreGive(s_wifi_connect_sem);
            break;
        }
    }
}


void mqtt_start()
{
    esp_mqtt_client_config_t mqtt_cfg = {0};
    mqtt_cfg.broker.address.uri = MQTT_ADDRESS;
    mqtt_cfg.broker.address.port = 1883;
    mqtt_cfg.credentials.client_id = MQTT_CLIENTID;//不能和软件上的ID重复
    mqtt_cfg.credentials.username = MQTT_USERNAME;
    mqtt_cfg.credentials.authentication.password = MQTT_PASSWORD;

    mqtt_handle = esp_mqtt_client_init(&mqtt_cfg);

    esp_mqtt_client_register_event(mqtt_handle , ESP_EVENT_ANY_ID , mqtt_event_callback , NULL);
    esp_mqtt_client_start(mqtt_handle);


}



void app_main(void)
{
    ESP_ERROR_CHECK(nvs_flash_init());//用ESP_ERROR_CHECK检查初始化
    ESP_ERROR_CHECK(esp_netif_init());//初始化TCPIP协议栈,esp32用的是lwip
    ESP_ERROR_CHECK(esp_event_loop_create_default());//创建事件系统循环,wifi连接过程中产生各种事件,通过回调函数通知我们
    esp_netif_create_default_wifi_sta();//创建sta
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();//wifi配置初始化结构体,这里使用默认配置
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));


    s_wifi_connect_sem = xSemaphoreCreateBinary();//创建信号量

    //注册wifi事件和网络事件回调函数
    esp_event_handler_register(WIFI_EVENT,ESP_EVENT_ANY_ID,&wifi_event_handle,NULL);//注册事件回调函数,参数分别是事件类型,事件ID(any全部事件),事件处理函数
    esp_event_handler_register(IP_EVENT,IP_EVENT_STA_GOT_IP,&wifi_event_handle,NULL);//连接到路由器获取IP地址后,触发这个事件

    wifi_config_t wifi_config ={
        .sta.threshold.authmode = WIFI_AUTH_WPA2_PSK,//配置加密模式
        .sta.pmf_cfg.capable =true,//是否启用保护管理帧,启用可以增强安全性
        .sta.pmf_cfg.required = false,//是否只和启用保护管理帧的 设备通信
    };

    memset(&wifi_config.sta.ssid,0,sizeof(wifi_config.sta.ssid));
    memcpy(wifi_config.sta.ssid,TEST_SSID,strlen(TEST_SSID));

    memset(&wifi_config.sta.password,0,sizeof(wifi_config.sta.password));
    memcpy(wifi_config.sta.password,TEST_KEY,strlen(TEST_KEY));

    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));//设置工作模式
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA,&wifi_config));//WIFI_IF_STA是STA的网卡
    ESP_ERROR_CHECK(esp_wifi_start());

    xSemaphoreTake(s_wifi_connect_sem , portMAX_DELAY);//信号量和等待时间
    mqtt_start();
}

将代码烧写到esp32中,连接wifi,再点击MQTTX的连接,设置MQTTX的主题为刚才代码中订阅的主题,点击发送按钮,可以在log窗口观察到,收到了订阅主题的信息

这样就实现了mqtt订阅的功能

接下来是发布功能,首先在MQTTX上追加一个订阅的主题


接下来在main函数中追加发布相关代码,每隔2秒发布一次

c 复制代码
mqtt_start();

int count =0;
while(1)
{
    char count_str[32];
    snprintf(count_str , sizeof(count_str) , "{\"count = \":%d}" , count);//json格式发送
    esp_mqtt_client_publish(mqtt_handle , MQTT_TOPIC1 , count_str , strlen(count_str) , 1 , 0);
    count++;
    vTaskDelay(pdMS_TO_TICKS(2000));
}

通过log和MQTT软件可以确认双方通信成功


参考文献

工程可以下载

相关推荐
后季暖12 小时前
langgraph笔记(2)&& fastapi笔记
笔记
小小筱筱13 小时前
LabVIEW笔记:解决周立功CAN卡驱动缺失导致的启动报错问题
笔记·labview
Wang's Blog13 小时前
PostgreSQL笔记4: 市场地位、生态全景与行业应用实践
数据库·笔记·postgresql
RainCity13 小时前
Java Swing 自定义组件库分享(十六)
java·笔记·后端
蒸蒸yyyyzwd15 小时前
cpp选手秋招准备 学习笔记day7 webserver
笔记·学习
Chen—LSN17 小时前
C语言——数据在内存中的存储
c语言·开发语言·经验分享·笔记
bitbrowser1 天前
giffgaff突然停用?先分清闲置停用和长期境外断开
笔记
九硕智慧建筑一体化厂家1 天前
数字化管控落地,直流照明构筑安全照明体系
运维·人工智能·笔记·安全·智慧城市
AI云海1 天前
天幕红尘笔记
笔记
青山是哪个青山1 天前
LangChain 学习笔记(三):LangSmith 调试与监控
笔记·学习·langchain