当前位置: 首页 > news >正文

ESP32开发入门(七):HTTP开发实践

一、HTTP协议基础

1.1 什么是HTTP?

HTTP(HyperText Transfer Protocol,超文本传输协议)是互联网上应用最为广泛的一种网络协议,用于从服务器传输超文本到本地浏览器。它是一种无状态的请求/响应协议,工作在客户端-服务器计算模型中。

1.2 HTTP的工作原理

HTTP协议基于请求-响应模型,主要包含以下组件:

  • 客户端(Client):发送HTTP请求(如浏览器、ESP32等设备)

  • 服务器(Server):接收请求并返回响应

  • 请求方法:GET、POST、PUT、DELETE等

  • 状态码:200(成功)、404(未找到)、500(服务器错误)等

ESP32设备(客户端) --HTTP请求--> Web服务器 <--HTTP响应-- 浏览器或其他客户端

1.3 HTTP的核心特性

  1. 简单快速:基于文本的简单协议

  2. 无连接:每次连接只处理一个请求

  3. 无状态:协议不保留之前的请求信息

  4. 灵活:可以传输任意类型的数据

  5. 支持多种请求方法:满足不同场景需求

1.4 HTTP在物联网中的应用

  1. 设备数据上报:向服务器发送传感器数据

  2. 远程配置:从服务器获取设备配置

  3. 固件升级:通过HTTP下载固件包

  4. Web控制界面:提供设备管理页面

  5. API交互:与其他系统集成

二、ESP32-S3 HTTP通信程序 (FreeRTOS + Arduino框架)

下面是一个基于ESP32-S3的HTTP通信程序,使用FreeRTOS和Arduino框架实现。这个程序包含HTTP客户端功能,可以向服务器发送GET和POST请求。

#include <WiFi.h>
#include <HTTPClient.h>
#include <Arduino.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
​
// WiFi配置
const char* ssid = "你的WiFi名称";
const char* password = "你的WiFi密码";
​
// 服务器配置
const char* serverUrl = "http://你的服务器地址:端口/api/data"; // 示例:"http://192.168.1.100:3000/api/data"
​
// FreeRTOS任务句柄
TaskHandle_t httpTaskHandle = NULL;
TaskHandle_t wifiTaskHandle = NULL;
​
// 连接WiFi函数
void connectToWiFi() {Serial.println();Serial.print("正在连接WiFi: ");Serial.println(ssid);
​WiFi.begin(ssid, password);
​while (WiFi.status() != WL_CONNECTED) {vTaskDelay(500 / portTICK_PERIOD_MS);Serial.print(".");}
​Serial.println("");Serial.println("WiFi已连接");Serial.print("IP地址: ");Serial.println(WiFi.localIP());
}
​
// 发送HTTP GET请求
void sendHttpGetRequest() {if (WiFi.status() == WL_CONNECTED) {HTTPClient http;Serial.print("发送GET请求到: ");Serial.println(serverUrl);http.begin(serverUrl);int httpCode = http.GET();if (httpCode > 0) {Serial.printf("HTTP响应码: %d\n", httpCode);if (httpCode == HTTP_CODE_OK) {String payload = http.getString();Serial.println("服务器响应:");Serial.println(payload);}} else {Serial.printf("GET请求失败, 错误: %s\n", http.errorToString(httpCode).c_str());}http.end();} else {Serial.println("WiFi未连接,无法发送请求");}
}
​
// 发送HTTP POST请求
void sendHttpPostRequest() {if (WiFi.status() == WL_CONNECTED) {HTTPClient http;Serial.print("发送POST请求到: ");Serial.println(serverUrl);http.begin(serverUrl);http.addHeader("Content-Type", "application/json");// 创建JSON格式的POST数据String httpRequestData = "{\"deviceId\":\"ESP32-S3\",\"temperature\":25.5,\"humidity\":60}";int httpCode = http.POST(httpRequestData);if (httpCode > 0) {Serial.printf("HTTP响应码: %d\n", httpCode);if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_CREATED) {String payload = http.getString();Serial.println("服务器响应:");Serial.println(payload);}} else {Serial.printf("POST请求失败, 错误: %s\n", http.errorToString(httpCode).c_str());}http.end();} else {Serial.println("WiFi未连接,无法发送请求");}
}
​
// HTTP任务函数
void httpTask(void *pvParameters) {while (1) {// 每隔10秒发送一次请求static uint32_t lastRequestTime = 0;uint32_t now = millis();if (now - lastRequestTime > 10000) {lastRequestTime = now;// 交替发送GET和POST请求static bool sendGet = true;if (sendGet) {sendHttpGetRequest();} else {sendHttpPostRequest();}sendGet = !sendGet;}vTaskDelay(100 / portTICK_PERIOD_MS);}
}
​
// WiFi监控任务函数
void wifiMonitorTask(void *pvParameters) {while (1) {if (WiFi.status() != WL_CONNECTED) {Serial.println("WiFi连接丢失,尝试重新连接...");connectToWiFi();}vTaskDelay(10000 / portTICK_PERIOD_MS); // 每10秒检查一次}
}
​
void setup() {Serial.begin(115200);// 初始化WiFi连接connectToWiFi();// 创建HTTP任务xTaskCreatePinnedToCore(httpTask,           // 任务函数"HTTP Task",        // 任务名称8192,               // 堆栈大小NULL,               // 参数1,                  // 优先级&httpTaskHandle,    // 任务句柄1                   // 运行在核心1上);// 创建WiFi监控任务xTaskCreatePinnedToCore(wifiMonitorTask,    // 任务函数"WiFi Task",        // 任务名称4096,               // 堆栈大小NULL,               // 参数1,                  // 优先级&wifiTaskHandle,    // 任务句柄0                   // 运行在核心0上);
}
​
void loop() {// 主循环为空,所有功能由FreeRTOS任务处理vTaskDelay(1000 / portTICK_PERIOD_MS);
}

2.1 代码说明

  1. WiFi连接

    • 使用WiFi.begin()连接到指定的WiFi网络

    • 单独的WiFi监控任务持续检查连接状态并在断开时重新连接

  2. HTTP功能

    • 使用HTTPClient库实现HTTP协议

    • 支持GET和POST请求

    • POST请求发送JSON格式数据

    • 自动处理HTTP响应

  3. FreeRTOS集成

    • 创建了两个任务:一个用于HTTP通信,一个用于WiFi监控

    • 任务运行在不同的核心上以提高效率

    • 使用vTaskDelay()代替delay()以确保不阻塞其他任务

  4. 多任务处理

    • HTTP任务负责定期发送HTTP请求

    • WiFi任务持续监控网络连接状态

2.2 使用说明

  1. 修改ssidpassword为你自己的WiFi配置

  2. 修改serverUrl为你的服务器地址和API端点

  3. 根据需要调整POST请求的内容和格式

  4. 请求频率可以在httpTask函数中调整

2.3 所需库

  • WiFi.h (Arduino ESP32核心自带)

  • HTTPClient (Arduino ESP32核心自带)

三、HTTP验证步骤 - 搭建Node.js服务器

为了验证ESP32的HTTP功能,我们可以使用Node.js搭建一个简单的服务器,接收ESP32的请求并返回响应。

下面我将详细介绍如何搭建一个完整的Node.js服务器,并将HTML页面数据整合到server.js文件中,以便于ESP32通过HTTP协议与服务器进行通信。

注意:若你电脑没安装node,请自行百度安装,网上教程较多,这里就不赘述了。

3.1 创建Node.js服务器

  1. 新建一个文件夹作为项目目录

  2. 在该目录下创建server.js文件,内容如下:

const express = require('express');
const bodyParser = require('body-parser');
​
const app = express();
const port = 3000;
​
// 中间件
app.use(bodyParser.json());
​
// 存储接收到的数据
let receivedData = [];
​
// HTML页面内容
const htmlPage = `
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>ESP32 数据监控</title><style>body {font-family: Arial, sans-serif;margin: 20px;}.data-container {margin-top: 20px;padding: 15px;border: 1px solid #ddd;border-radius: 5px;background-color: #f9f9f9;}button {padding: 10px 15px;background-color: #4CAF50;color: white;border: none;border-radius: 4px;cursor: pointer;}button:hover {background-color: #45a049;}.data-item {margin-bottom: 10px;padding: 10px;border-bottom: 1px solid #eee;}.timestamp {color: #666;font-size: 0.9em;}</style>
</head>
<body><h1>ESP32 数据监控</h1><button id="refreshBtn">刷新数据</button><div class="data-container"><h2>最新上报数据 (共<span id="dataCount">0</span>条)</h2><div id="dataDisplay"><p>暂无数据...</p></div></div>
​<script>const refreshBtn = document.getElementById('refreshBtn');const dataDisplay = document.getElementById('dataDisplay');const dataCount = document.getElementById('dataCount');// 格式化数据显示function formatData(data) {if (data.receivedData && data.receivedData.length > 0) {return data.receivedData.map(item => \`<div class="data-item"><div><strong>设备ID:</strong> \${item.data.deviceId || '未知'}</div><div><strong>温度:</strong> \${item.data.temperature || 'N/A'}°C</div><div><strong>湿度:</strong> \${item.data.humidity || 'N/A'}%</div><div class="timestamp">\${new Date(item.timestamp).toLocaleString()}</div></div>\`).join('');}return '<p>暂无数据...</p>';}// 获取数据函数async function fetchData() {try {const response = await fetch('/api/data');const data = await response.json();dataCount.textContent = data.receivedData ? data.receivedData.length : 0;dataDisplay.innerHTML = formatData(data);} catch (error) {dataDisplay.innerHTML = \`<p style="color:red;">获取数据失败: \${error.message}</p>\`;}}// 初始加载数据document.addEventListener('DOMContentLoaded', fetchData);// 按钮点击事件refreshBtn.addEventListener('click', fetchData);// 每5秒自动刷新setInterval(fetchData, 5000);</script>
</body>
</html>
`;
​
// 首页路由 - 返回HTML页面
app.get('/', (req, res) => {res.send(htmlPage);
});
​
// GET请求处理 - 获取所有数据
app.get('/api/data', (req, res) => {console.log('收到GET请求');res.status(200).json({message: '数据获取成功',receivedData: receivedData,timestamp: new Date().toISOString()});
});
​
// POST请求处理 - 接收ESP32数据
app.post('/api/data', (req, res) => {console.log('收到POST请求:', req.body);// 验证数据if (!req.body.deviceId) {return res.status(400).json({error: '缺少必要字段: deviceId'});}// 存储数据receivedData.push({data: req.body,timestamp: new Date().toISOString()});// 限制存储的数据量if (receivedData.length > 50) {receivedData = receivedData.slice(-50);}res.status(201).json({message: '数据接收成功',yourData: req.body});
});
​
// 清空数据接口
app.delete('/api/data', (req, res) => {receivedData = [];res.status(200).json({message: '所有数据已清空'});
});
​
// 启动服务器
app.listen(port, () => {console.log(`服务器运行在 http://localhost:${port}`);console.log(`API端点:`);console.log(`GET /            - 查看数据监控页面`);console.log(`GET /api/data    - 获取所有接收到的数据`);console.log(`POST /api/data   - 接收ESP32发送的数据`);console.log(`DELETE /api/data - 清空所有数据`);
});
3.1.1 代码详细说明
3.1.1.1 初始化设置
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const port = 3000;
  • 引入Express框架和body-parser中间件

  • 创建Express应用实例

  • 设置服务器端口为3000

3.1.1.2 数据存储
let receivedData = [];
  • 使用一个数组来存储ESP32发来的所有数据

  • 每个数据项包含原始数据和接收时间戳

3.1.1.3 HTML页面整合
const htmlPage = `...`;
  • 将完整的HTML页面内容作为模板字符串存储在变量中

  • 包含CSS样式和内联JavaScript

  • 使用ES6模板字符串语法方便插入变量

3.1.1.4 路由处理
  1. 首页路由

    app.get('/', (req, res) => {res.send(htmlPage);
    });
    • 处理根路径请求

    • 直接返回HTML页面内容

  2. GET API接口

    app.get('/api/data', (req, res) => {res.json({message: '数据获取成功',receivedData: receivedData,timestamp: new Date().toISOString()});
    });
    • 返回所有存储的数据

    • 包含状态信息和时间戳

  3. POST API接口

    app.post('/api/data', (req, res) => {// 数据验证和存储res.status(201).json({message: '数据接收成功',yourData: req.body});
    });
    • 接收ESP32发来的JSON数据

    • 验证必要字段

    • 存储数据并返回确认

  4. DELETE API接口

    app.delete('/api/data', (req, res) => {receivedData = [];res.json({ message: '所有数据已清空' });
    });
    • 清空存储的数据

    • 用于测试和调试

3.1.1.5 前端JavaScript功能
// 格式化数据显示
function formatData(data) {// 将JSON数据转换为HTML显示
}
​
// 获取数据函数
async function fetchData() {// 从/api/data获取数据并更新页面
}
​
// 事件监听和自动刷新
document.addEventListener('DOMContentLoaded', fetchData);
refreshBtn.addEventListener('click', fetchData);
setInterval(fetchData, 5000);
  • 使用Fetch API获取数据

  • 动态更新页面内容

  • 自动刷新和手动刷新功能

  • 数据格式化显示

3.2 安装依赖

在项目目录下运行以下命令安装必要的依赖:

npm init -y
npm install express body-parser

3.3 启动服务器

node server.js

服务器启动后,你将在控制台看到:

服务器运行在 http://localhost:3000

现在你可以通过浏览器访问http://localhost:3000来查看ESP32上报的数据。

3.4 验证步骤

  1. 确保你的PC和ESP32在同一个局域网

  2. 修改ESP32代码中的serverUrl为你的PC的IP地址和端口(如http://192.168.1.100:3000/api/data

  3. 上传ESP32代码并打开串口监视器

  4. 在浏览器中访问http://localhost:3000

  5. 观察串口输出和网页显示的数据

3.6 预期结果

  • 串口输出

    发送GET请求到: http://192.168.1.100:3000/api/data
    HTTP响应码: 200
    服务器响应:
    {"message":"Hello from Node.js server!","receivedData":[...],"timestamp":"..."}
    ​
    发送POST请求到: http://192.168.1.100:3000/api/data
    HTTP响应码: 201
    服务器响应:
    {"message":"Data received successfully","yourData":{"deviceId":"ESP32-S3","temperature":25.5,"humidity":60}}
  • 网页显示

    最新上报数据
    {"message": "Hello from Node.js server!","receivedData": [{"data": {"deviceId": "ESP32-S3","temperature": 25.5,"humidity": 60},"timestamp": "..."}],"timestamp": "..."
    }

四、实际项目应用示例

4.1 环境监测系统

功能设计

  • 定期上报温湿度数据

  • 从服务器获取配置参数

  • 实现固件升级检查

void checkForUpdates() {HTTPClient http;http.begin("http://yourserver.com/api/update");int httpCode = http.GET();if (httpCode == HTTP_CODE_OK) {String payload = http.getString();DynamicJsonDocument doc(1024);deserializeJson(doc, payload);if (doc["available"] == true) {String newVersion = doc["version"];String firmwareUrl = doc["url"];if (newVersion != currentFirmwareVersion) {startFirmwareUpdate(firmwareUrl);}}}http.end();
}

4.2 远程控制面板

功能设计

  • 提供Web控制界面

  • 实现设备状态实时显示

  • 支持多设备管理

void handleRoot() {String html = "<html><body>";html += "<h1>ESP32 Control Panel</h1>";html += "<p>Temperature: " + String(readTemperature()) + "°C</p>";html += "<p>Humidity: " + String(readHumidity()) + "%</p>";html += "<form method='post' action='/control'>";html += "<button name='led' value='on'>Turn LED On</button>";html += "<button name='led' value='off'>Turn LED Off</button>";html += "</form>";html += "</body></html>";server.send(200, "text/html", html);
}

五、HTTP最佳实践与优化

  1. 安全考虑

    • 使用HTTPS替代HTTP

    • 实现API密钥验证

    • 限制请求频率

  2. 性能优化

    • 复用HTTPClient对象

    • 减少不必要的头信息

    • 使用连接池

  3. 错误处理

    • 实现自动重试机制

    • 添加超时设置

    • 记录错误日志

  4. 数据格式

    • 使用JSON进行数据交换

    • 压缩大数据量

    • 分页获取大量数据

六、常见HTTP服务器选择

  1. 本地测试

    • Node.js + Express

    • Python Flask

    • PHP内置服务器

  2. 生产环境

    • Nginx

    • Apache

    • IIS

  3. 云服务

    • AWS API Gateway

    • 阿里云API网关

    • 腾讯云API网关

七、总结与扩展

HTTP作为互联网的基础协议,与ESP32的结合为物联网设备提供了简单可靠的数据通信方案,相对于上一篇MQTT协议,HTTP协议的开发和验证更为简单,若你对MQTT开发感兴趣,可查看ESP32开发入门(六):MQTT开发实践。掌握HTTP开发后,您可以进一步:

  1. 研究HTTPS安全连接

  2. 学习WebSocket实现实时通信

  3. 探索RESTful API设计

  4. 了解gRPC等高效协议

通过本篇教程,您应该已经掌握了ESP32上HTTP开发的核心知识。实际项目中,建议从简单的原型开始,逐步增加功能复杂度,并始终考虑安全性和性能问题。

相关文章:

  • Spring框架(1)
  • Idea Code Templates配置
  • CCDO|企业数字化转型:机制革新与人才培育的双重引擎
  • 【库(Library)、包(Package)和模块(Module)解析】
  • 关系模式-无损连接和保持函数依赖的判断
  • WPF 3D图形编程核心技术解析
  • 互联网大厂Java求职面试:基于AI的实时异常检测系统设计与实现
  • JAVA将一个同步方法改为异步执行
  • Altera系列FPGA纯verilog视频图像去雾,基于暗通道先验算法实现,提供4套Quartus工程源码和技术支持
  • 基 LabVIEW 的多轴电机控制系统
  • 如何训练deepseek语言大模型
  • Vue3 中 ref 与 reactive 的区别及底层原理详解
  • sqli-labs靶场18-22关(http头)
  • 人工智能顶会ICLR 2025论文分享│PointOBB-v2:更简单、更快、更强的单点监督有向目标检测
  • NestJS 框架深度解析
  • fakebook
  • Midscene.js Chrome 插件实战:AI 驱动的 UI 自动化测试「喂饭教程」
  • 数据库索引
  • 使用 docker 安装 nacos3.x
  • MKS RGA 校准调试MKS eVision和Vision 1000p RGA步骤(图文并茂)
  • 美乌基金协议:美国搞了一套可在资源富集地区复刻的商业模式
  • 乡村快递取件“跑腿费”屡禁不止?云南元江县公布举报电话
  • 售卖自制外挂交易额超百万元,一男子因提供入侵计算机系统程序被抓
  • 上海市委常委会扩大会议传达学习习近平总书记考察上海重要讲话和在部分省区市“十五五”时期经济社会发展座谈会上的重要讲话精神
  • “穿越看洪武”,明太祖及其皇后像台北故宫博物院南园展出
  • 特朗普考虑任命副幕僚长米勒任国安顾问,曾策划驱逐移民行动