【笔记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软件可以确认双方通信成功


参考文献

工程可以下载

相关推荐
北岛寒沫31 分钟前
北京大学国家发展研究院 经济学辅修 经济学原理课程笔记(第六课 生产可能性曲线、机会成本与交易)
经验分享·笔记
北岛寒沫31 分钟前
北京大学国家发展研究院 经济学辅修 经济学原理课程笔记(第五课 福利经济学)
经验分享·笔记
云霄星乖乖的果冻33 分钟前
02预备知识——李沐《动手学深度学习》个人笔记
人工智能·笔记·深度学习
落叶的悲哀37 分钟前
软件架构师笔记
笔记
宵时待雨1 小时前
C语言笔记归纳17:数据的存储
c语言·开发语言·笔记
崇山峻岭之间1 小时前
C++ Prime Plus 学习笔记037
c++·笔记·学习
CS Beginner1 小时前
【单片机】orange prime pi开发板与单片机的区别
笔记·嵌入式硬件·学习
zore_c1 小时前
【C语言】数据结构——顺序表超详解!!!(包含顺序表的实现)
c语言·开发语言·数据结构·c++·经验分享·笔记·线性回归
im_AMBER9 小时前
Leetcode 74 K 和数对的最大数目
数据结构·笔记·学习·算法·leetcode