【ESP32S3】esp32获取串口数据并通过http上传到前端
通过前面的学习(前面没发过,因为其实就是跑它的demo)了解到串口配置以及开启线程实现功能的工作流程,与此同时还有esp32作为STA节点,将数据通过http发送到服务器。
将这两者联合
其实是可以得到一个:esp32获取串口数据并通过http上传到前端,这样的功能的。假设收到的数据是温湿度数据。
文章食用提醒:
本文用到的ESP框架是ESP-IDF,服务器端处理代码格式是js,数据库采用mongoDB。
http part
#define MAX_HTTP_RECV_BUFFER 512
#define MAX_HTTP_OUTPUT_BUFFER 2048
static const char *TAG = "HTTP_CLIENT 0313";
static char response_data[1024]; // 自定义缓存空间储存一次响应数据
static int recived_len = 0; // 自定义变量储存一次响应中接收到分片数据的累计偏移
// http客户端的事件处理回调函数
static esp_err_t http_client_event_handler(esp_http_client_event_t *evt)
{
switch (evt->event_id)
{
case HTTP_EVENT_ON_CONNECTED:
ESP_LOGI(TAG, "connected to web-server");
recived_len = 0;
break;
case HTTP_EVENT_ON_DATA:
if (evt->user_data)
{
memcpy(evt->user_data + recived_len, evt->data, evt->data_len); // 将分片的每一片数据都复制到user_data
recived_len += evt->data_len;//累计偏移更新
}
break;
case HTTP_EVENT_ON_FINISH:
ESP_LOGI(TAG, "finished a request and response!");
recived_len = 0;
break;
case HTTP_EVENT_DISCONNECTED:
ESP_LOGI(TAG, "disconnected to web-server");
recived_len = 0;
break;
case HTTP_EVENT_ERROR:
ESP_LOGE(TAG, "error");
recived_len = 0;
break;
default:
break;
}
return ESP_OK;
}
char* create_json_from_data(float temperature, float humidity) {
// 创建根对象
cJSON *root = cJSON_CreateObject();
// 向JSON对象中添加键值对
cJSON_AddNumberToObject(root, "temperature", temperature);
cJSON_AddNumberToObject(root, "humidity", humidity);
// 将cJSON对象转换为字符串
char *json_data = cJSON_Print(root);
// 释放cJSON对象占用的内存
cJSON_Delete(root);
return json_data;
}
uart part
#define CONFIG_UART_TXD 4
#define CONFIG_UART_RXD 5
#define UART_PIN_RTS (-1)
#define UART_PIN_CTS (-1)
#define CONFIG_UART_PORT_NUM 2
#define CONFIG_UART_BAUD_RATE 115200
#define CONFIG_TASK_STACK_SIZE 3072
#define BUF_SIZE (1024)
#define QUEUE_LENGTH 10
static QueueHandle_t xQueue = NULL;
// 假设收到的数据是温湿度数据
typedef struct data_dht11
{
float temperature;
float humidity;
}data_t;
static void uart_rx_task(void *arg)
{
// 配置串口
uart_config_t uart_config = {
.baud_rate = CONFIG_UART_BAUD_RATE,
.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,
};
int intr_alloc_flags = 0;
#if CONFIG_UART_ISR_IN_IRAM
intr_alloc_flags = ESP_INTR_FLAG_IRAM;
#endif
ESP_ERROR_CHECK(uart_driver_install(CONFIG_UART_PORT_NUM, BUF_SIZE * 2, 0, 0, NULL, 0));
ESP_ERROR_CHECK(uart_param_config(CONFIG_UART_PORT_NUM, &uart_config));
ESP_ERROR_CHECK(uart_set_pin(CONFIG_UART_PORT_NUM, CONFIG_UART_TXD, CONFIG_UART_RXD, UART_PIN_RTS, UART_PIN_CTS));
uint8_t *data = (uint8_t *)malloc(BUF_SIZE);
// 接收数据,把数据存放在队列里
while (1) {
int len = uart_read_bytes(CONFIG_UART_PORT_NUM, data, (BUF_SIZE - 1), 20 / portTICK_PERIOD_MS);
if(len > 0) {
data[len] = '\0';
data_t item;
sscanf((char*)data, "%f %f", &item.temperature, &item.humidity); // 假设数据格式为"temperature humidity"
xQueueSend(xQueue, &item, portMAX_DELAY);
}
vTaskDelay(pdMS_TO_TICKS(10)); // 让出CPU时间片,分给其他任务
}
free(data);
}
main part
void app_main(void)
{
esp_err_t ret;
nvs_flash_init();
esp_netif_init();
esp_event_loop_create_default();
example_connect();
xQueue = xQueueCreate(QUEUE_LENGTH, sizeof(data_t));
if (xQueue == NULL) {
ESP_LOGE(TAG, "Failed to create queue");
return;
}
xTaskCreate(uart_rx_task, "uart_rx_task", 2048, NULL, configMAX_PRIORITIES-1, NULL);
// http配置
const esp_http_client_config_t cfg = {
.url = "http://124.223.186.76:3000",
.event_handler = http_client_event_handler,
.user_data = response_data,
.disable_auto_redirect = true, // 根据需求选择是否禁用自动重定向
.transport_type = HTTP_TRANSPORT_OVER_TCP, // 强制使用TCP传输
.timeout_ms = 10000, // 设置超时时间为10秒
};
//使用http服务器配置参数对http客户端初始化
esp_http_client_handle_t httpclient = esp_http_client_init(&cfg);
// 进入循环接收串口数据并发给服务器上
while (true)
{
data_t item;
if(xQueueReceive(xQueue, &item, portMAX_DELAY) == pdPASS)
{
// 调用函数创建JSON格式的数据
char *json_data = create_json_from_data(item.temperature, item.humidity);
// 设置HTTP请求的各种参数
esp_http_client_set_method(httpclient, HTTP_METHOD_POST);
esp_http_client_set_url(httpclient, "/add");
// 添加或更新"Connection"头为"close"
esp_http_client_set_header(httpclient, "Connection", "close");
// 设置请求头
esp_http_client_set_header(httpclient, "Content-Type", "application/json");
// 设置请求体为刚刚创建的JSON数据
esp_http_client_set_post_field(httpclient, json_data, strlen(json_data));
// 初始化重试计数器
int max_retries = 3; // 最大重试次数
esp_err_t ret;
for(int retry = 0; retry <= max_retries; ++retry) {
ret = esp_http_client_perform(httpclient);
if(ret == ESP_OK) {
// 请求成功,打印响应数据
printf("POST:%s\n", response_data);
ESP_LOGD(TAG,"HTTP POST Status = %d, content_length = %lld",
esp_http_client_get_status_code(httpclient),
esp_http_client_get_content_length(httpclient));
break; // 成功后退出循环
} else {
if(retry < max_retries) {
// 如果还有剩余重试次数,则等待一段时间后重试
ESP_LOGW(TAG, "Attempt %d failed, retrying in 1 second...", retry + 1);
vTaskDelay(pdMS_TO_TICKS(1000)); // 等待1秒后再重试
} else {
// 达到最大重试次数,记录错误信息
ESP_LOGE(TAG, "Error occurred during HTTP request after %d retries, failed: %s", max_retries, esp_err_to_name(ret));
}
}
}
free(json_data); // 释放动态分配的内存
}
else
{
// 处理超时或其他错误情况
ESP_LOGW(TAG, "Failed to receive data from queue.");
}
}
esp_http_client_cleanup(httpclient);//清空http客户端描述符
vQueueDelete(xQueue); // 删除队列
}
merge code
const express = require('express')
const bodyParser = require('body-parser')
const { MongoClient } = require('mongodb');
const request = require('request')
const fs = require('fs')
const app = express()
app.use(bodyParser.json())
// MongoDB URI 和客户端初始化
const uri = "mongodb://admin:123456@localhost:27017/myDatabase?authSource=admin";
const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true });
let db; // 定义一个变量用于存储数据库引用
async function startServer() {
try {
await client.connect(); // 连接到MongoDB
console.log("Connected to database");
db = client.db('myDatabase'); // 获取数据库引用
// 监听端口
app.listen(3000, '0.0.0.0', () => {
console.log('mwt server running at http://124.223.186.76:3000');
});
} catch (err) {
console.error("Failed to connect to the database:", err);
process.exit(1); // 如果无法连接数据库,则退出程序
}
}
startServer();
app.get('/add', (req, res) => {
const x = req.query.x;
const y = req.query.y;
if (!x || !y) {
return res.status(400).json({ result: false, message: "Missing parameters" });
}
res.json({
result: true,
method: "GET",
message: Number(x) + Number(y)
});
});
// 处理外设数据上传的POST请求
app.post('/add', async (req, res) => {
const data = req.body; // 直接获取到JSON对象
console.log("Received data:", data); // 新增日志记录
if (!data.temperature || !data.humidity) {
return res.status(400).json({ result: false, message: "Missing parameters" });
}
try {
if (!db) {
return res.status(500).json({ result: false, message: "Database connection not established" });
}
// 插入文档到集合'readings'
const collection = db.collection('readings'); // 使用之前定义的db变量
await collection.insertOne(data);
console.log(`A document was inserted with the _id: ${data._id}`);
// 写到当前路径下的log里
fs.appendFile('sensor_data.log', JSON.stringify(data) + '\n', (err) => {
if (err) throw err;
});
res.json({
result: true,
method: "POST",
message: "Data received and saved successfully"
});
} catch (err) {
console.error("Failed to save data:", err.stack);
res.status(500).json({ result: false, message: "Failed to save data" });
}
});
// 获取所有读数并显示在前端
app.get('/readings', async (req, res) => {
try {
const collection = db.collection('readings');
// 查找所有文档
const readings = await collection.find({}).toArray();
// 返回HTML页面或JSON数据
res.send(`
<html>
<head><title>Sensor Readings</title></head>
<body>
<h1>Sensor Readings</h1>
<ul>
${readings.map(r => `<li>Temperature: ${r.temperature}, Humidity: ${r.humidity}</li>`).join('')}
</ul>
</body>
</html>
`);
} catch (err) {
console.error("Failed to fetch data:", err);
res.status(500).send("Error fetching data");
}
});
接着在Linux下写服务器的处理内容
index.js code
const express = require('express')
const bodyParser = require('body-parser')
const { MongoClient } = require('mongodb');
const request = require('request')
const fs = require('fs')
const app = express()
app.use(bodyParser.json())
// MongoDB URI 和客户端初始化,这里的账号密码记得填你自己的
const uri = "mongodb://yourusername:yourpasswd@localhost:27017/myDatabase?authSource=admin";
const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true });
let db; // 定义一个变量用于存储数据库引用
async function startServer() {
try {
await client.connect(); // 连接到MongoDB
console.log("Connected to database");
db = client.db('myDatabase'); // 获取数据库引用,这里的myDatabase是数据库名
// 监听端口
app.listen(3000, '0.0.0.0', () => {
console.log('mwt server running at http://124.223.186.76:3000');
});
} catch (err) {
console.error("Failed to connect to the database:", err);
process.exit(1); // 如果无法连接数据库,则退出程序
}
}
startServer();
// 处理外设数据上传的POST请求
app.post('/add', async (req, res) => {
const data = req.body; // 直接获取到JSON对象
console.log("Received data:", data); // 新增日志记录
if (!data.temperature || !data.humidity) {
return res.status(400).json({ result: false, message: "Missing parameters" });
}
try {
if (!db) {
return res.status(500).json({ result: false, message: "Database connection not established" });
}
// 插入文档到集合'readings'
const collection = db.collection('readings'); // 使用之前定义的db变量,这里的readings是表名
await collection.insertOne(data);
console.log(`A document was inserted with the _id: ${data._id}`);
// 写到当前路径下的log里
fs.appendFile('sensor_data.log', JSON.stringify(data) + '\n', (err) => {
if (err) throw err;
});
res.json({
result: true,
method: "POST",
message: "Data received and saved successfully"
});
} catch (err) {
console.error("Failed to save data:", err.stack);
res.status(500).json({ result: false, message: "Failed to save data" });
}
});
// 获取所有读数并简单地显示在前端,也可以另创一个html文件或新建一个web工程按实际情况导进去
app.get('/readings', async (req, res) => {
try {
const collection = db.collection('readings'); //表名是readings
// 查找所有文档
const readings = await collection.find({}).toArray();
// 返回HTML页面或JSON数据
res.send(`
<html>
<head><title>Sensor Readings</title></head>
<body>
<h1>Sensor Readings</h1>
<ul>
${readings.map(r => `<li>Temperature: ${r.temperature}, Humidity: ${r.humidity}</li>`).join('')}
</ul>
</body>
</html>
`);
} catch (err) {
console.error("Failed to fetch data:", err);
res.status(500).send("Error fetching data");
}
});
在服务器部署nodejs,并安装配置mongoDB环境,这样前端就可以从mongoDB拿到数据并显示出来了。
假设已经安装好nodejs,也装好了mongoDB。
新建一个目录,我这里叫my_node_app0314,然后把上面的js处理脚本放进这个文件夹里,这里我把它命名为index.js
然后启动应用
node index.js
如果看到active running就说明启动好了
ubuntu@VM-12-13-ubuntu:~$ sudo systemctl status my_node_app0314.service # 看状态
● my_node_app0314.service - My Node.js Application
Loaded: loaded (/etc/systemd/system/my_node_app0314.service; enabled; vendor preset: enabled)
Active: active (running) since Fri 2025-03-21 10:43:59 CST; 24s ago
Main PID: 316472 (node)
Tasks: 11 (limit: 8816)
Memory: 24.6M
CGroup: /system.slice/my_node_app0314.service
└─316472 /usr/bin/node /home/ubuntu/my_node_app0314/index.js
Mar 21 10:43:59 VM-12-13-ubuntu systemd[1]: Started My Node.js Application.
Mar 21 10:44:00 VM-12-13-ubuntu node[316472]: (node:316472) [MONGODB DRIVER] Warning: useNewUrlParser i>
Mar 21 10:44:00 VM-12-13-ubuntu node[316472]: (Use `node --trace-warnings ...` to show where the warnin>
Mar 21 10:44:00 VM-12-13-ubuntu node[316472]: (node:316472) [MONGODB DRIVER] Warning: useUnifiedTopolog>
Mar 21 10:44:00 VM-12-13-ubuntu node[316472]: Connected to database
Mar 21 10:44:00 VM-12-13-ubuntu node[316472]: mwt server running at http://124.223.186.76:3000
lines 1-15/15 (END)
最后
编译运行esp32s3,并打开浏览器
很好的是esp32s3支持很多的i2c扩展,我这里用串口只是为了方便,dht11是单总线的,以上数据来自串口助手。
【全文完】
参考链接
:ESP32+idf开发之WIFI通信入门(5)HTTP通信
这个博主的文章代码亲测可用,在这里也很谢谢他。