主要参考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软件可以确认双方通信成功


参考文献