这个实验的功能演示 ESP32MQTT 的使用方法。 这个实验的代码为工程“4_10_wifi_MQTT”目录。
4.10.1. 实验内容
学习 ESP32 注册移动 onenet MQTT 服务器
学习 ESP32 通过 MQTT 上传数据到移动 onenet
学习通过移动 onenet 下发数据控制开发板
4.10.2. MQTT 介绍
MQTT(消息队列遥测传输)是 ISO 标准(ISO/IEC PRF 20922)下基于发布/订阅范式的消息协议。它工作在 TCP/IP 协议族上,是为硬件性能低下的远程设备以及网络状况糟糕的情况下而设计的发布/订阅型消息协 议,为此,它需要一个消息中间件 (服务器)。
MQTT 协议是为大量计算能力有限,且工作在低带宽、不可靠的网络的远程传感器和控制设备通讯而 设计的协议,它具有以下主要的几项特性:
(1) 使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合;
(2) 对负载内容屏蔽的消息传输; (3) 使用 TCP/IP 提供网络连接; (4) 有三种消息发布服务质量:
“至多一次”,消息发布完全依赖底层 TCP/IP 网络。会发生消息丢失或重复。这一级别可用于 如下情况,环境传感器数据,丢失一次读记录无所谓,因为不久后还会有第二次发送。
“至少一次”,确保消息到达,但消息重复可能会发生。
“只有一次”,确保消息到达一次。这一级别可用于如下情况,在计费系统中,消息重复或丢失 会导致不正确的结果。
(5) 小型传输,开销很小(固定长度的头部是 2 字节),协议交换最小化,以降低网络流量; 实现 MQTT 协议需要客户端和服务器端通讯完成,在通讯过程中,MQTT 协议中有三种身份:发布
者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。 在我们的这个实验里,消息的发布者是 ESP32 开发板开发板,服务器使用的是移动的 onenet MQTT 服
务器。
4.10.3. 函数介绍
在这个实验里,我们使用到了 NVS、smartconfig、STAwifi 连接、DTH11 数据读取、ADC 光敏数据读取、
LED 彩灯、显示屏 LCD、MQTT 和任务创建和通信。在介绍函数的时候,只介绍 MQTT 新知识部分。
(1) MQTT 配置信息 esp_mqtt_client_config_t;
通过这个结构体,可以配置 MQTT 的服务器 IP,PORT,用户名,密码,client ID,结构定义:
typedef struct {
mqtt_event_callback_t event_handle; /*回调*/
const char *host; /*!< MQTT 服务器域名(ipv4 as string)*/ const char *uri; /*!< MQTT 服务器域名 */
uint32_t port; /*!< MQTT 服务器端口*/
const char *client_id; /*MQTT Client 的名字默认是 ESP32_加上 MAC 后 3hex*/ const char *username; /*MQTT 用户名*/
const char *password; /*MQTT 密码*/
const char *lwt_topic; /*!< LWT 主题,默认为空*/ const char *lwt_msg; /*!< LWT 信息,默认为空*/ int lwt_qos; /*!< LWT 消息质量*/
int lwt_retain; /*!< LWT 保留消息标志*/ int lwt_msg_len; /*!< LWT 消息长度*/
int disable_clean_session; /*!< mqtt clean session,默认为真*/ int keepalive; /*MQTT 心跳,默认 120 秒 */
bool disable_auto_reconnect; /*错误,断开后重连,true 不连*/ void *user_context; /*用户信息 */
int task_prio; /*!< MQTT 任务优先级,默认为 5,可以在 make menuconfig 中修改*/
int task_stack; /*!< MQTT 任务堆栈大小,默认 6144 bytes,可以在 make menuconfig 中修改*/ int buffer_size; /*!< MQTT 收发缓存,默认 1024 */
const char *cert_pem; /*指向用于服务器验证(使用 SSL)的 PEM 格式的证书数据的指针,默认值为空,不需 要验证服务器*/
const char *client_cert_pem; /*指向用于 SSL 相互身份验证的 PEM 格式的证书数据的指针,默认值为空, 如果不需要相互身份验证,则不需要。如果不为空,还必须提供“客户机密钥”。*/
const char *client_key_pem; /*指向用于 SSL 相互身份验证的 PEM 格式的私钥数据的指针,默认值为空, 如果不需要相互身份验证,则不需要。如果不为空,还必须提供“client-cert-pem”。*/
(2) MQTT Client 初始化函数
函数定义:
esp_mqtt_client_handle_t esp_mqtt_client_init(const esp_mqtt_client_config_t *config);
参数说明:
esp_mqtt_client_config_t *config:MQTT 配置参数 返回值:
esp_mqtt_client_handle_t:MQTT 操作句柄,订阅、发布消息就通过这个句柄实现。
(3) MQTT Client 启动函数
函数定义:
esp_err_t esp_mqtt_client_start(esp_mqtt_client_handle_t client);
参数说明:
esp_mqtt_client_handle_t client:MQTT 操作句柄,初始化的返回值 返回值:
ESP_OK:成功。
(4) MQTT Client 停止函数
函数定义:
esp_err_t esp_mqtt_client_stop(esp_mqtt_client_handle_t client);
参数说明:
esp_mqtt_client_handle_t client:MQTT 操作句柄,初始化的返回值 返回值:
ESP_OK:成功。
(5) MQTT Client 订阅主题函数
函数定义:
esp_err_t esp_mqtt_client_subscribe(esp_mqtt_client_handle_t client, const char *topic, int qos);
参数说明:
esp_mqtt_client_handle_t client:MQTT 操作句柄,初始化的返回值 char *topic:订阅的主题
int qos:服务质量 返回值:
ESP_OK:成功。
(6) MQTT Client 取消订阅主题函数
函数定义:
esp_err_t esp_mqtt_client_unsubscribe(esp_mqtt_client_handle_t client, const char *topic);
参数说明:
esp_mqtt_client_handle_t client:MQTT 操作句柄,初始化的返回值 char *topic:订阅的主题
返回值:
ESP_OK:成功。
(7) MQTT Client 发布主题函数
函数定义:
int esp_mqtt_client_publish(esp_mqtt_client_handle_t client, const char *topic, const char *data, int len, int qos, int retain);
参数说明:
esp_mqtt_client_handle_t client:MQTT 操作句柄,初始化的返回值 char *topic:订阅的主题
char *data:发布的数据 Int len:数据长度
int qos:服务质量 返回值:
消息 ID。
4.10.4. 代码讲解
我们的这个实验,主要代码有 main 目录下,文件夹 components 下是之前讲过的 DHT11、LCD 和 LED
驱动文件。
下面按照程序启动的流程讲解。
(1) 程序启动
程序启动进入 app_main 后,读取 nvs 数据、初始化 LED、创建显示屏任务和创建检测传感器任务,根 据读取的 NVS 数据决定是启动 smartconfig 还是直接 wifi 连接。
//用户函数入口,相当于 main 函数 void app_main(void)
{
ESP_ERROR_CHECK(nvs_flash_init()); tcpip_adapter_init(); read_Smartconfig();//读 smartconfig 配置 initLed();//LED IO 口初始化
//创建任务
//事件组
display_event_group = xEventGroupCreate(); xTaskCreate(display_task, "display_task", 4096, NULL, 2, NULL);
xTaskCreate(sensor_check_task, "sensor_check_task", 4096, NULL, 10, NULL);
//根据 NVS 数据决定是否要进入 Smartconfig if(1!=wifi_isConfig){
led_red(LED_ON);//打开红灯,表示正在配置中 startSmartconfig();//配置 wifi 进入 smartconfig
//通过事件刷新显示
xEventGroupSetBits(display_event_group, SMARTCONFIG_DISPLAY_BIT);
}else{
//启动 STA,连接 AP wifi_init_sta();
//通过事件刷新显示
xEventGroupSetBits(display_event_group, STA_START_DISPLAY_BIT);
}
}
函数 read_Smartconfig()是 4.7.4 有讲解,是用于读取 NVS 数据,读取的数据里有是否成功配对、wifi 的 SSID 和密码。
显示任务专们用于显示屏控制,根据收到的事件去刷新不同的 LCD 区域,代码如下:
//显示任务
void display_task(void *parm)
{
char lcd_buff[100]={0}; EventBits_t uxBits;
//显示屏初始化以及显示相关的提示 Lcd_Init();
Gui_DrawFont_GBK16(16,0,VIOLET,WHITE,(u8 *)"深圳亿研电子"); Gui_DrawFont_GBK16(32,20,BLUE,WHITE,(u8 *)"欢迎您");
while(1) {
//等待对应的事件发生
uxBits = xEventGroupWaitBits(display_event_group, SMARTCONFIG_DISPLAY_BIT|STA_START_DIS PLAY_BIT|WIFI_CONNECTED_DISPLAY_BIT|DHT11_DISPLAY_BIT|ADC_DISPLAY_BIT, true, false, portMAX_DELAY);
//开机进入 smartconfig
if (uxBits & SMARTCONFIG_DISPLAY_BIT)
.....
//开机进入 STA 连接
if (uxBits & STA_START_DISPLAY_BIT)
.....
//wifi 已连接
if (uxBits & WIFI_CONNECTED_DISPLAY_BIT)
.....
//DHT11 显示
if (uxBits & DHT11_DISPLAY_BIT)
.....
//ADC 显示
if (uxBits & ADC_DISPLAY_BIT)
.....
}
}
传感器检测任务,用于 3 秒读一次 DHT11 数据,1 秒读一次 ADC 光照值。温度数据保存在变量 dht11_t,
湿度数据保存在变量 dht11_h,光照的 ADC 值保存在 light_adc,光照的电压值保存在 light_mv 上,代码如下:
//传感器数据采集任务
void sensor_check_task(void *parm)
{
int second_count=0; adc_Init();//adc 初始化
while(1) {
//DHT11 3 秒取一次数据 if((second_count%3)==0)
{
DHT11();//读取温湿度数据
//如果读取数据不正确返回 250 if(wendu!=250)
{
dht11_t=wendu;//温度 dht11_h=shidu;//湿度
//通过事件刷新显示
xEventGroupSetBits(display_event_group, DHT11_DISPLAY_BIT);
}
}
//光照采集 light_adc=adc1_get_voltage(ADC1_TEST_CHANNEL);//采集光照 ADC light_mv=(light_adc*1100)/4096;//转成电压值
//通过事件刷新显示
xEventGroupSetBits(display_event_group, ADC_DISPLAY_BIT);
//延时 3 秒 vTaskDelay(100);
if(second_count++>1000) second_count=0;
(2) 启动 MQTT 任务
不管是否启用了 smartconfig,当网络连接上之后,都会进入文件 app_smartConfig.c 里的 wifi 回调函数, 当 wifi 取得 IP 后,就启动 MQTT 任务,关键代码如下:
//wifi 连接事件回调函数
static esp_err_t smartconfig_event_handler(void *ctx, system_event_t *event)
{
......
case SYSTEM_EVENT_STA_GOT_IP://获取 IP ESP_LOGI(TAG1, "SYSTEM_EVENT_STA_GOT_IP");
......
//启动 MQTT 服务
extern void mqtt_app_start(void); mqtt_app_start();
break;
......
}
void mqtt_app_start(void)
{
mqtt_event_group = xEventGroupCreate(); xTaskCreate(mqtt_task, "mqtt_task", 4096, NULL, 3, NULL);
}
(3) MQTT 任务
MQTT 任务通过三步完成 MQTT 的连接,通过 MQTT 的事件回调函数,去检测 MQTT 是否连接上,是 否断开连接,是否收到订阅消息。
//mqtt 初始化
void mqtt_task(void *parm)
{
//第一步:配置 MQTT 参数 esp_mqtt_client_config_t mqtt_cfg = {
.host = "183.230.40.39", //MQTT 服务器 IP
.port=6002, //端口
.event_handle = mqtt_event_handler, //MQTT 事件
.username = "269223", //用户名
.password = "mfyIRyFEGojbzzwExjHrEAHLMXg=", //密码
.client_id= "541022266",
};
......
vTaskDelay(300);
//第二步:MQTT 初始化
mqtt_client = esp_mqtt_client_init(&mqtt_cfg);
//第三步:MQTT 启动 esp_mqtt_client_start(mqtt_client);
//等 mqtt 连上
xEventGroupWaitBits(mqtt_event_group, CONNECTED_BIT, false, true, portMAX_DELAY);
vTaskDelete(NULL);
}
我们的这个实验是使用移动 onenet MQTT 平台做实验的,IP 和端口号是不用修改的,用户名、密码 和 client_id 有一个文档专们说明,文档位于目录:
移动 onenet MQTT 平台的三个参数,对应关系如下图:
(4) MQTT 的事件回调函数
在回调函数里,我们关心两种事件,第一种 MQTT 连接成功事件(MQTT_EVENT_CONNECTED):收到 这个事件表求成功连接 MQTT 服务器, 我们就可以周期发送数据了; 第二种 MQTT 收到数据事件
(MQTT_EVENT_DATA):通过对收到的主题和数据分析,可以对开发板做相应的操作,如我们代码里的点 灯;
//MQTT 事件回调函数
static esp_err_t mqtt_event_handler(esp_mqtt_event_handle_t event)
{
esp_mqtt_client_handle_t client = event->client; int msg_id;
switch (event->event_id) {
case MQTT_EVENT_CONNECTED://MQTT 连上事件 ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
mqtt_connected=true;//连接上标志 xEventGroupSetBits(mqtt_event_group, CONNECTED_BIT);
//创建任务用于周期发数据
xTaskCreate(mqtt_SendData, "mqtt_SendData", 4096, NULL, 3, NULL); break;
case MQTT_EVENT_DISCONNECTED://MQTT 断开连接事件 ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
mqtt_connected=false; xEventGroupClearBits(mqtt_event_group, CONNECTED_BIT); break;
case MQTT_EVENT_SUBSCRIBED://MQTT 发送订阅事件
ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id); msg_id = esp_mqtt_client_publish(client, "/topic/qos0", "订阅成功
", 0, 0, 0);
ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
case MQTT_EVENT_UNSUBSCRIBED://MQTT 取消订阅事件
ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id); break;
case MQTT_EVENT_PUBLISHED://MQTT 发布事件
ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id); break;
case MQTT_EVENT_DATA://MQTT 接受数据事件 ESP_LOGI(TAG, "MQTT_EVENT_DATA");
printf("TOPIC=%.*s\r\n", event->topic_len, event->topic); //主题 printf("DATA=%.*s\r\n", event->data_len, event->data); //内容
//控制 LED 灯
if(event->data_len==2)
{
if(strncmp(event->data, "R1",2)==0)//开红灯
{
led_red(LED_ON);
}
(5) 周期发送 MQTT 数据任务
通过这个任务,3 秒上传一次开发板的温度、湿度和光照 ADC 值,使用函数 mqtt_publishToOnenet()上 传数据。
void mqtt_SendData(void *parm)
{
while(1) {
//只有连接上才发数据 if(mqtt_connected){
mqtt_publishToOnenet(dht11_t,dht11_h,light_adc);
}
//延时三秒 vTaskDelay(300);
}
}
/打包 JSON 数据
unsigned char OneNet_FillBuf(char *buf,int t, int h, int l)
{
char text[50];
memset(text, 0, sizeof(text));
strcpy(buf, "{");
//温度
memset(text, 0, sizeof(text)); sprintf(text, "\"temp1\":%d", t); strcat(buf, text);
//湿度
memset(text, 0, sizeof(text)); strcat(buf, ",");
sprintf(text, "\"hum2\":%d", h); strcat(buf, text);
//光照
memset(text, 0, sizeof(text)); strcat(buf, ",");
sprintf(text, "\"light\":%d", l); strcat(buf, text);
strcat(buf, "}");
memset(text, 0, sizeof(text)); return strlen(buf);
}
//数据上传 onenet
void mqtt_publishToOnenet(int temp, int hum, int light)
{
int msg_id;
char buf[128]={0}; short body_len = 0;
body_len = OneNet_FillBuf(&buf[3], temp, hum, light); //获取当前需要发送的数据流的总长度 buf[0] = 3;
buf[1] = body_len >> 8; buf[2] = body_len & 0xFF;
//数据上传服务器
msg_id = esp_mqtt_client_publish(mqtt_client, "$dp", buf, body_len+3, 1, 0); ESP_LOGI(TAG, "mqtt_publishToOnenet successful, msg_id=%d", msg_id);
}
4.10.5. 实验过程
做这个实验的前提条件,必须接以下文档的方法先申请 onenet 帐号和创建设备:
当修改了代码里的这三个参数后,就可以编绎代码,并下载到开发板上了。配置下载串口、波特率、 编绎和程序下载的详细过程请往回看 3.1.4,在这个实验里都是一笔带过。
(1) 把开发板通过 USB 线接到电脑上,通过设备管理器查看生成的串口。开发板在我们演示电脑上生 成的是 COM3。
(2) 在 menuconfig 菜单里配置下载程序串口。提供的例程配置的串口是 COM3,波特率为 921600。
(3) 通过 make all 编绎工程。
(4) 当编绎通过之后,使用命令 make flash 把程序下载到开发板上。或者参考 2.3.2 节,使用工具 下载。
(5) 使用串口工具打开开发板生成的串口,默认的波特率是 115200。 串口工具在目录:.\开发软件\串口工具-sscom32.rar。
(6) 打开按下开发板的复位键,让程序运行起来。第一次启动开发板上应该是红灯亮起,此时需要按 照 4.3.5 里的手机一键配置 smartconfig,配置 ESP32 的 wifi 名字和密码。如果开发板的蓝灯亮 起表示 wifi 已经正确连接,显示屏显示如下:
注意:连接的 wifi 一定要能上网的,否则无法连接外网的 onenet 服务器 注意:连接的 wifi 一定要能上网的,否则无法连接外网的 onenet 服务器 注意:连接的 wifi 一定要能上网的,否则无法连接外网的 onenet 服务器
(7) Smartconfig 配置后,打开 onenet 查看设备,正确情况下处于在线状态。
(8) 点击设备右边的“数据流”,可以看到开发板上传的数据。
(9) 下发命令控制开发板。 分别下发命令“A1”、“A0”、“R1”和“R0”观察彩灯的变化。
(10)实验过程中,观察串口工具的输出。
(11)在 MQTT 接入成功后,可以增加消息订阅、通过按键发布消息,和其他的开发板,如我们店里的 ESP8266 开发板、NB 开发板通信,实在不行可以使用提供的 PC 模拟器和 ESP32 通信。