通過前面的學習(前面沒發過,因為其實就是跑它的demo)了解到串口配置以及開啟線程實現功能的工作流程,與此同時還有esp32作為STA節點,將數據通過http發送到服務器。
將這兩者聯合
其實是可以得到一個:esp32獲取串口數據并通過http上傳到前端,這樣的功能的。假設收到的數據是溫濕度數據。
文章食用提醒:
本文用到的ESP框架是ESP-IDF,服務器端處理代碼格式是js,數據庫采用mongoDB。
http part
#define MAX_HTTP_RECV_BUFFER 512
#define MAX_HTTP_OUTPUT_BUFFER 2048static 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_datarecived_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_IRAMintr_alloc_flags = ESP_INTR_FLAG_IRAM;#endifESP_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(); // 連接到MongoDBconsole.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(); // 連接到MongoDBconsole.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 ApplicationLoaded: 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 agoMain PID: 316472 (node)Tasks: 11 (limit: 8816)Memory: 24.6MCGroup: /system.slice/my_node_app0314.service└─316472 /usr/bin/node /home/ubuntu/my_node_app0314/index.jsMar 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通信
這個博主的文章代碼親測可用,在這里也很謝謝他。