接上一篇文章,本篇重新整理了处理逻辑。
最终实现串口输入数据后保存在nvs中,等所有数据都拿到之后在进行wifi的连接及mqtt消息的发送,主要使用了freertos中的事件通知,及信号量等知识,esp32则使用了nvs,uart,wifi等相关初始化及对应操作。
代码整体步骤如下
1.app_main函数中,创建事件组,创建二进制信号量,在需要创建两个task,一个任务用于串口数据的接收,另一个任务用于监听事件的标志位,本工程定义了两个标志位bit0与bit1,分别表示wifi数据的接收和mqtt信息的接收
2.串口接收输入字符串,根据字符串的格式,截取获得wifi的用户名密码和mqtt的地址,
3.串口获取后使用write_nvs_str操作存入nvs中,并且将事件的标志位置为1
4.当所有需要的数据都得到后开始先初始化wifi,wifi连接成功后初始化mqtt,向mqtt发送连接成功消息,中间使用信号量用来控制两个程序执行的顺序,只有等wifi连接成功才会执行mqtt相关代码,当mqtt发送完成后在将事件的标志位清除。
具体修改的代码如下
1.main函数相关代码
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "mqtt_client.h"
#include "simple_wifi_sta.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "mymqtt.h"
#include "mynvs.h"
#include "myuart.h"
SemaphoreHandle_t wifi_connected_semaphore;
EventGroupHandle_t xEventGroup;
extern char mqtt_address;
static const char *TAG = "main";
void task1(void *pvParameters) {
printf("task1 begin \r\n");
EventBits_t uxBits;
while (1) {
// 等待BIT_0和BIT_1事件都被设置
printf("xEventGroupWaitBits start! \r\n");
uxBits = xEventGroupWaitBits(xEventGroup, BIT_0_AND_1, pdFALSE, pdFALSE,portMAX_DELAY);//pdMS_TO_TICKS(1000)
printf("xEventGroupWaitBits end! \r\n");
printf("bits: 0x%lx\n", uxBits);
uxBits = xEventGroupGetBits(xEventGroup);
printf("bits get : 0x%lx\n", uxBits);
if ((uxBits & BIT_0_AND_1) == BIT_0_AND_1) {
printf("task1 start \r\n");
char read_ssid[64];
char read_pwd[64];
size_t len_s = 0;
size_t len_p = 0;
len_s =read_nvs_str(NVS_WIFI_NAMESPACE,NVS_SSID_KEY,read_ssid,64);
if(len_s){
ESP_LOGI(TAG,"Read read_ssid:%s",read_ssid);
}
else{
ESP_LOGI(TAG,"Read read_ssid fail,please perform nvs_erase_key and try again");
}
len_p =read_nvs_str(NVS_WIFI_NAMESPACE,NVS_PWD_KEY,read_pwd,64);
if(len_p){
ESP_LOGI(TAG,"Read read_pwd :%s",read_pwd);
}
else{
ESP_LOGI(TAG,"Read read_pwd fail,please perform nvs_erase_key and try again");
}
wifi_config_params_t *params = malloc(sizeof(wifi_config_params_t)); // 分配内存
trim_spaces(read_pwd);
trim_spaces(read_ssid);
params->password = read_pwd;
params->ssid = read_ssid;
wifi_sta_init(params);
char read_mqtt[64];
size_t len = 0;
len =read_nvs_str(NVS_MQTT_NAMESPACE,NVS_ADDRESS_KEY,read_mqtt,64);
if(len){
ESP_LOGI(TAG,"Read mqtt SEX:%s",read_mqtt);
}
else{
ESP_LOGI(TAG,"Read mqtt fail,please perform nvs_erase_key and try again");
}
if (xSemaphoreTake(wifi_connected_semaphore, portMAX_DELAY) == pdTRUE) {
mqtt_start(read_mqtt);
}
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void app_main(void)
{
wifi_connected_semaphore = xSemaphoreCreateBinary();
xEventGroup = xEventGroupCreate();
printf("start\r\n");
uart_init();
printf("end\r\n");
ESP_ERROR_CHECK(nvs_flash_init());
ESP_ERROR_CHECK(esp_netif_init()); //用于初始化tcpip协议栈
ESP_ERROR_CHECK(esp_event_loop_create_default()); //创建一个默认系统事件调度循环,之后可以注册回调函数来处理系统的一些事件
esp_netif_create_default_wifi_sta(); //使用默认配置创建STA对象
register_wifi_events();
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
//NVS出现错误,执行擦除
ESP_ERROR_CHECK(nvs_flash_erase());
//重新尝试初始化
ESP_ERROR_CHECK(nvs_flash_init());
}
xTaskCreatePinnedToCore(receive_task, "uart_rx_task", 4096, NULL, configMAX_PRIORITIES - 1, NULL,1);
xTaskCreatePinnedToCore(task1, "task1", 4096, NULL, configMAX_PRIORITIES - 2, NULL,1);
//if (xSemaphoreTake(wifi_connected_semaphore, portMAX_DELAY) == pdTRUE) {
//xTaskCreate(mqtt_start, "mqtt_task", 2048, NULL, 5, NULL);
//mqtt_start();
//}
return;
}
2.nvs操作相关代码
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_err.h"
static const char* TAG = "main";
/** 从nvs中读取字符值
* @param namespace NVS命名空间
* @param key 要读取的键值
* @param value 读到的值
* @param maxlen 外部存储数组的最大值
* @return 读取到的字节数
*/
size_t read_nvs_str(const char* namespace,const char* key,char* value,int maxlen)
{
nvs_handle_t nvs_handle;
esp_err_t ret_val = ESP_FAIL;
size_t required_size = 0;
ESP_ERROR_CHECK(nvs_open(namespace, NVS_READWRITE, &nvs_handle));
ret_val = nvs_get_str(nvs_handle, key, NULL, &required_size);
if(ret_val == ESP_OK && required_size <= maxlen)
{
nvs_get_str(nvs_handle,key,value,&required_size);
}
else
required_size = 0;
nvs_close(nvs_handle);
return required_size;
}
/** 写入值到NVS中(字符数据)
* @param namespace NVS命名空间
* @param key NVS键值
* @param value 需要写入的值
* @return ESP_OK or ESP_FAIL
*/
esp_err_t write_nvs_str(const char* namespace,const char* key,const char* value)
{
nvs_handle_t nvs_handle;
esp_err_t ret;
ESP_ERROR_CHECK(nvs_open(namespace, NVS_READWRITE, &nvs_handle));
ret = nvs_set_str(nvs_handle, key, value);
nvs_commit(nvs_handle);
nvs_close(nvs_handle);
return ret;
}
/** 从nvs中读取字节数据(二进制)
* @param namespace NVS命名空间
* @param key 要读取的键值
* @param value 读到的值
* @param maxlen 外部存储数组的最大值
* @return 读取到的字节数
*/
size_t read_nvs_blob(const char* namespace,const char* key,uint8_t *value,int maxlen)
{
nvs_handle_t nvs_handle;
esp_err_t ret_val = ESP_FAIL;
size_t required_size = 0;
ESP_ERROR_CHECK(nvs_open(namespace, NVS_READWRITE, &nvs_handle));
ret_val = nvs_get_blob(nvs_handle, key, NULL, &required_size);
if(ret_val == ESP_OK && required_size <= maxlen)
{
nvs_get_blob(nvs_handle,key,value,&required_size);
}
else
required_size = 0;
nvs_close(nvs_handle);
return required_size;
}
/** 擦除nvs区中某个键
* @param namespace NVS命名空间
* @param key 要读取的键值
* @return 错误值
*/
esp_err_t erase_nvs_key(const char* namespace,const char* key)
{
nvs_handle_t nvs_handle;
esp_err_t ret_val = ESP_FAIL;
ESP_ERROR_CHECK(nvs_open(namespace, NVS_READWRITE, &nvs_handle));
ret_val = nvs_erase_key(nvs_handle,key);
ret_val = nvs_commit(nvs_handle);
nvs_close(nvs_handle);
return ret_val;
}
/** 写入值到NVS中(字节数据)
* @param namespace NVS命名空间
* @param key NVS键值
* @param value 需要写入的值
* @return ESP_OK or ESP_FAIL
*/
esp_err_t write_nvs_blob(const char* namespace,const char* key,uint8_t* value,size_t len)
{
nvs_handle_t nvs_handle;
esp_err_t ret;
ESP_ERROR_CHECK(nvs_open(namespace, NVS_READWRITE, &nvs_handle));
ret = nvs_set_blob(nvs_handle, key, value,len);
nvs_commit(nvs_handle);
nvs_close(nvs_handle);
return ret;
}
3.uart代码接收的逻辑修改如下
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"
#include "driver/uart.h"
#include "string.h"
#include "driver/gpio.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "simple_wifi_sta.h"
#include <stdio.h>
#include <ctype.h>
#include "mymqtt.h"
#include "myuart.h"
#include "mynvs.h"
static const int RX_BUF_SIZE = 1024;
#define TXD_PIN (GPIO_NUM_1)
#define RXD_PIN (GPIO_NUM_3)
#define UART_NUM (UART_NUM_0)
#define BUF_SIZE (1024)
char wifi_ssid[33];
char wifi_password[65];
char mqtt_address[65];
extern EventGroupHandle_t xEventGroup;
bool str_starts_with(const char *str, const char *prefix);
void trim_spaces(char *str);
void uart_init(void)
{
printf("uart_init\r\n");
const uart_config_t uart_config = {
.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_DEFAULT,
};
// We won't use a buffer for sending data.
ESP_ERROR_CHECK(uart_driver_install(UART_NUM, RX_BUF_SIZE * 2, 0, 0, NULL, 0));
ESP_ERROR_CHECK(uart_param_config(UART_NUM, &uart_config));
ESP_ERROR_CHECK(uart_set_pin(UART_NUM, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
}
void receive_task(void *arg){
static const char *RX_TASK_TAG = "RX_TASK";
char *at_str = "AT+U";
char *m_str = "AT+M";
uint8_t *data = (uint8_t *) malloc(RX_BUF_SIZE + 1);
ESP_LOGI(RX_TASK_TAG, "Receive task start ");
while (1) {
// Read data from the UART
int len = uart_read_bytes(UART_NUM, data, (RX_BUF_SIZE - 1), 20 / portTICK_PERIOD_MS);
// Write data back to the UART
uart_write_bytes(UART_NUM, (const char *) data, len);
if (len>0) {
data[len] = '\0';
ESP_LOGI(RX_TASK_TAG, "Recv str: %s", (char *) data);
if(str_starts_with((char *) data,at_str)){
printf("this char is%s",(char *) data);
if (sscanf((char *) data, "AT+U%32[^P]P%64[^\n]", wifi_ssid, wifi_password) == 2) {
ESP_LOGI(RX_TASK_TAG, "SSID: %s, Password: %s \r\n", wifi_ssid, wifi_password);
printf("ssid is %s \r\n",wifi_ssid);
printf("Password is %s \r\n",wifi_password);
wifi_config_params_t *params = malloc(sizeof(wifi_config_params_t)); // 分配内存
if (params == NULL) {
printf("Memory allocation failed\n");
return;
}
trim_spaces(wifi_ssid);
trim_spaces(wifi_password);
write_nvs_str(NVS_WIFI_NAMESPACE,NVS_SSID_KEY,wifi_ssid);
write_nvs_str(NVS_WIFI_NAMESPACE,NVS_PWD_KEY,wifi_password);
xEventGroupSetBits(xEventGroup, BIT_0); // 设置BIT_0事件
free(params);
} else {
ESP_LOGE(RX_TASK_TAG, "Failed to extract strings.");
}
}
if(str_starts_with((char *) data,m_str)){
printf("this char mqtt is%s",(char *) data);
if (sscanf((char *) data, "AT+M%64[^\n]", mqtt_address) == 1) {
ESP_LOGI(RX_TASK_TAG, "mqtt_address: %s", mqtt_address);
printf("mqtt_address is %s \r\n",mqtt_address);
trim_spaces(mqtt_address);
//存储mqtt相关信息
write_nvs_str(NVS_MQTT_NAMESPACE,NVS_ADDRESS_KEY,mqtt_address);
xEventGroupSetBits(xEventGroup, BIT_1); // 设置BIT_1事件
} else {
ESP_LOGE(RX_TASK_TAG, "Failed to extract strings.");
}
}
}
}
free(data);
vTaskDelete(NULL);
}
/**
* 去除字符串两侧的空格
* @param str 要处理的字符串
*/
void trim_spaces(char *str) {
char *end, *start = str;
// 去除字符串左侧的空格
while (isspace((unsigned char)*start)) start++;
if (*start == 0) { // 如果字符串全是空格
*str = 0; // 将其设为空字符串
return;
}
// 去除字符串右侧的空格
end = start + strlen(start) - 1;
while (end > start && isspace((unsigned char)*end)) end--;
// 写入字符串结束符
*(end + 1) = 0;
// 将剔除空格后的字符串前移
memmove(str, start, end - start + 2);
}
bool str_starts_with(const char *str, const char *prefix) {
size_t prefix_len = strlen(prefix);
return strncmp(str, prefix, prefix_len) == 0;
}
4.mqtt代码 (主要就是增加了一行清除事件标志位的代码,还有就是mqtt的参数化)
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "mqtt_client.h"
#include "simple_wifi_sta.h"
#include "mymqtt.h"
static const char* TAG = "main";
#define MQTT_ADDRESS "mqtt://broker-cn.emqx.io" //MQTT连接地址
#define MQTT_PORT 1883 //MQTT连接端口号
#define MQTT_CLIENT "mqttx_d11213" //Client ID(设备唯一,大家最好自行改一下)
#define MQTT_USERNAME "wangxiaohuang" //MQTT用户名
#define MQTT_PASSWORD "11111111" //MQTT密码
#define MQTT_PUBLIC_TOPIC "/test/topic1" //测试用的,推送消息主题
#define MQTT_SUBSCRIBE_TOPIC "/test/topic2" //测试用的,需要订阅的主题
extern EventGroupHandle_t xEventGroup;
//MQTT客户端操作句柄
static esp_mqtt_client_handle_t s_mqtt_client = NULL;
//MQTT连接标志
static bool s_is_mqtt_connected = false;
/**
* mqtt连接事件处理函数
* @param event 事件参数
* @return 无
*/
static void aliot_mqtt_event_handler(void* event_handler_arg,
esp_event_base_t event_base,
int32_t event_id,
void* event_data)
{
esp_mqtt_event_handle_t event = event_data;
esp_mqtt_client_handle_t client = event->client;
// your_context_t *context = event->context;
switch ((esp_mqtt_event_id_t)event_id) {
case MQTT_EVENT_CONNECTED: //连接成功
ESP_LOGI(TAG, "mqtt connected");
s_is_mqtt_connected = true;
//连接成功后,订阅测试主题
esp_mqtt_client_subscribe_single(s_mqtt_client,MQTT_SUBSCRIBE_TOPIC,1);
break;
case MQTT_EVENT_DISCONNECTED: //连接断开
ESP_LOGI(TAG, "mqtt disconnected");
s_is_mqtt_connected = false;
break;
case MQTT_EVENT_SUBSCRIBED: //收到订阅消息ACK
ESP_LOGI(TAG, " mqtt subscribed ack, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_UNSUBSCRIBED: //收到解订阅消息ACK
break;
case MQTT_EVENT_PUBLISHED: //收到发布消息ACK
ESP_LOGI(TAG, "mqtt publish ack, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_DATA:
printf("TOPIC=%.*s\r\n", event->topic_len, event->topic); //收到Pub消息直接打印出来
printf("DATA=%.*s\r\n", event->data_len, event->data);
break;
case MQTT_EVENT_ERROR:
ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
break;
default:
break;
}
}
/** 启动mqtt连接
* @param 无
* @return 无
*/
void mqtt_start(char* address)
{
esp_mqtt_client_config_t mqtt_cfg = {0};
mqtt_cfg.broker.address.uri = MQTT_ADDRESS;
mqtt_cfg.broker.address.port = MQTT_PORT;
//Client ID
mqtt_cfg.credentials.client_id = MQTT_CLIENT;
//用户名
mqtt_cfg.credentials.username = MQTT_USERNAME;
//密码
mqtt_cfg.credentials.authentication.password = MQTT_PASSWORD;
ESP_LOGI(TAG,"mqtt connect->clientId:%s,username:%s,password:%s",mqtt_cfg.credentials.client_id,
mqtt_cfg.credentials.username,mqtt_cfg.credentials.authentication.password);
//设置mqtt配置,返回mqtt操作句柄
s_mqtt_client = esp_mqtt_client_init(&mqtt_cfg);
//注册mqtt事件回调函数
esp_mqtt_client_register_event(s_mqtt_client, ESP_EVENT_ANY_ID, aliot_mqtt_event_handler, s_mqtt_client);
//启动mqtt连接
esp_mqtt_client_start(s_mqtt_client);
esp_mqtt_client_publish(s_mqtt_client, "/test/topic1", "WiFi Connected", 0, 1, 0);
//清除标志位
xEventGroupClearBits(xEventGroup, BIT_0 | BIT_1);
}
void send_topic(){
static char mqtt_pub_buff[64];
int count = 0;
while(1)
{
// 生成随机数
//延时2秒发布一条消息到/test/topic1主题
if(s_is_mqtt_connected)
{
snprintf(mqtt_pub_buff,64,"{\"count\":\"%d\"}",count);
esp_mqtt_client_publish(s_mqtt_client, MQTT_PUBLIC_TOPIC,
mqtt_pub_buff, strlen(mqtt_pub_buff),1, 0);
count++;
}
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
5.wifi相关代码跟上一篇的代码一致,删除掉mqtt_start()即可
遇到的主要问题就是事件组的用法
EventBits_t uxBits = xEventGroupWaitBits(
xEventGroup, // 事件组的句柄
BIT_0, // 等待的事件位
pdTRUE, // 等待所有位 clearOnExit
pdFALSE, // 不清除事件位
portMAX_DELAY // 无限期等待
);
在本文中,因为操作事件标志位在两个不同的任务中,如果clearOnExit为pdTRUE,那么在 xEventGroupWaitBits 返回后,BIT_0 将被清除。就无法进行if ((uxBits & BIT_0_AND_1) == BIT_0_AND_1)里边的操作,所以这块需要设置为false,这样串口在接收到两部分的数据后,将bit0与bit1全部置为1之后,才会执行if块操作,最后在mqtt的相关代码中,手动清楚两个事件的标志位即可。