HTTP OTA 设置
HTTP OTA 设置
本节介绍 HTTP OTA 的配置和实施方法。HTTP OTA 是最常用的远程升级方式,适合局域网或可控网络环境中的设备。学习完成后,您将能够:
- 搭建 HTTP OTA 服务器
- 在 ESP32 中实现 HTTP OTA 下载
- 通过 MQTT 触发远程更新
- 评估 HTTP OTA 的安全性
在开始本节之前,请确保:
- 理解 OTA 双分区机制
- 了解 HTTP 协议基本概念
- 安装了 HTTP 服务器(如 Python HTTP Server 或 Nginx)
- ESP32 可与 HTTP 服务器通信
Server Setup
Section titled “Server Setup”使用 Python HTTP Server(开发测试)
Section titled “使用 Python HTTP Server(开发测试)”# 在固件文件目录启动 HTTP 服务器cd /path/to/firmware/directorypython3 -m http.server 8080
# 输出:# Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...目录结构:
firmware/├── index.html├── esp32-factory-v1.0.0.bin # 最新固件├── esp32-factory-v1.1.0.bin├── esp32-factory-v2.0.0.bin└── firmware.json # 固件版本信息使用 Nginx(生产环境)
Section titled “使用 Nginx(生产环境)”server { listen 80; server_name firmware.example.com;
root /var/www/firmware;
location / { autoindex on; autoindex_exact_size off; autoindex_localtime on; }
# 限速 10MB/s,防止单个 OTA 占满带宽 location /firmware/ { limit_rate 10m; }}ESP32 HTTP OTA Implementation
Section titled “ESP32 HTTP OTA Implementation”#include <WiFi.h>#include <HTTPClient.h>#include <Update.h>
const char* ssid = "YourWiFiSSID";const char* password = "YourWiFiPassword";const char* firmware_url = "http://192.168.1.100:8080/firmware.bin";
void performOTA() { HTTPClient http; http.begin(firmware_url);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK) { int contentLength = http.getSize();
if (contentLength > 0) { Serial.printf("固件大小: %d bytes\n", contentLength);
// 检查是否有足够的分区空间 bool canBegin = Update.begin(contentLength);
if (canBegin) { Serial.println("开始 OTA 更新...");
// 获取网络流 WiFiClient* stream = http.getStreamPtr();
// 写入固件数据到 OTA 分区 size_t written = Update.writeStream(*stream);
if (written == contentLength) { Serial.printf("写入完成: %d bytes\n", written); } else { Serial.printf("写入不完整: %d / %d\n", written, contentLength); }
if (Update.end()) { Serial.println("OTA 完成"); if (Update.isFinished()) { Serial.println("准备重启..."); ESP.restart(); } } else { Serial.printf("OTA 失败: %s\n", Update.errorString()); } } else { Serial.println("分区空间不足"); } } else { Serial.println("无效的固件大小"); } } else { Serial.printf("HTTP 请求失败, 状态码: %d\n", httpCode); }
http.end();}
void setup() { Serial.begin(115200); WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("连接 WiFi..."); }
Serial.println("WiFi 已连接"); performOTA();}
void loop() {}带 MQTT 触发的 OTA
Section titled “带 MQTT 触发的 OTA”结合 MQTT 实现远程触发升级:
#include <WiFi.h>#include <PubSubClient.h>#include <Update.h>
const char* mqtt_server = "192.168.1.100";const char* ota_topic = "esp32/ota/command";const char* status_topic = "esp32/ota/status";const char* firmware_base_url = "http://192.168.1.100:8080/";
WiFiClient espClient;PubSubClient client(espClient);
void callback(char* topic, byte* payload, unsigned int length) { String command; for (int i = 0; i < length; i++) { command += (char)payload[i]; }
if (command == "update") { client.publish(status_topic, "{\"status\": \"updating\"}");
// 获取最新的固件 URL String firmwareUrl = String(firmware_base_url) + "firmware.bin"; performOTA(firmwareUrl); } else if (command.startsWith("update:")) { // 指定固件版本: update:v2.0.0 String version = command.substring(7); String firmwareUrl = String(firmware_base_url) + "esp32-factory-" + version + ".bin"; performOTA(firmwareUrl); }}
void setup() { Serial.begin(115200); WiFi.begin(ssid, password); // ... WiFi 连接代码 ...
client.setServer(mqtt_server, 1883); client.setCallback(callback); client.subscribe(ota_topic);}
void loop() { client.loop();}Firmware Server Integration
Section titled “Firmware Server Integration”固件版本 JSON
Section titled “固件版本 JSON”{ "latest_version": "2.0.0", "minimum_version": "1.0.0", "firmware_url": "http://192.168.1.100:8080/esp32-factory-v2.0.0.bin", "firmware_size": 1245184, "checksum": "sha256:a1b2c3d4e5f6...", "release_notes": [ "修复: WiFi 重连机制优化", "新增: 支持 MQTT 心跳检测", "优化: 内存使用减少 15%" ], "release_date": "2026-05-15", "force_update": false}版本检查逻辑
Section titled “版本检查逻辑”struct FirmwareInfo { String version; String url; int size; String checksum;};
bool checkVersion() { HTTPClient http; http.begin("http://192.168.1.100:8080/firmware.json");
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK) { String payload = http.getString();
// 解析 JSON(需 ArduinoJson 库) DynamicJsonDocument doc(1024); deserializeJson(doc, payload);
String latestVersion = doc["latest_version"]; String currentVersion = "1.0.0"; // 当前固件版本
if (latestVersion != currentVersion) { Serial.printf("发现新版本: %s\n", latestVersion); return true; } }
http.end(); return false;}OTA Progress Reporting
Section titled “OTA Progress Reporting”通过 MQTT 上报进度
Section titled “通过 MQTT 上报进度”void performOTAWithProgress(const char* url) { HTTPClient http; http.begin(url);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK) { int contentLength = http.getSize();
if (Update.begin(contentLength)) { WiFiClient* stream = http.getStreamPtr(); size_t total = 0; uint8_t buffer[256];
while (stream->available() && total < contentLength) { size_t bytesRead = stream->readBytes(buffer, sizeof(buffer)); Update.write(buffer, bytesRead); total += bytesRead;
// 每 10% 上报一次进度 int progress = (total * 100) / contentLength; if (progress % 10 == 0) { char statusMsg[64]; snprintf(statusMsg, sizeof(statusMsg), "{\"progress\": %d, \"written\": %d}", progress, total); client.publish(status_topic, statusMsg); } }
if (Update.end()) { client.publish(status_topic, "{\"status\": \"success\"}"); ESP.restart(); } } }
http.end();}Security Considerations
Section titled “Security Considerations”HTTP OTA 安全风险
Section titled “HTTP OTA 安全风险”| 风险 | 风险等级 | 缓解措施 |
|---|---|---|
| 中间人攻击 | 🔴 高 | 移至 HTTPS OTA |
| 固件被篡改 | 🔴 高 | 添加固件签名 |
| 未授权更新 | 🟡 中 | 添加更新口令 |
| 重放攻击 | 🟡 中 | 添加时间戳和序列号 |
添加固件签名验证
Section titled “添加固件签名验证”#include <mbedtls/md.h>
bool verifyFirmware(const uint8_t* firmware, size_t len, const char* expected_hash) { uint8_t hash[32]; mbedtls_md_context_t ctx;
mbedtls_md_init(&ctx); mbedtls_md_setup(&ctx, MBEDTLS_MD_SHA256, 0); mbedtls_md_starts(&ctx); mbedtls_md_update(&ctx, firmware, len); mbedtls_md_finish(&ctx, hash); mbedtls_md_free(&ctx);
// 将 hash 转换为十六进制字符串比较 char hashStr[65]; for (int i = 0; i < 32; i++) { sprintf(hashStr + i * 2, "%02x", hash[i]); }
return strcmp(hashStr, expected_hash) == 0;}Pre-sales Key Points
Section titled “Pre-sales Key Points”| 场景 | HTTP OTA 适用性 | 升级建议 |
|---|---|---|
| 局域网设备升级 | ✅ 安全可用 | 配合签名校验 |
| 互联网设备升级 | ⚠️ 需升级 HTTPS | 建议使用 HTTPS OTA |
| 开发测试环境 | ✅ 推荐 | 快速部署和迭代 |
| 批量设备管理 | ✅ 可行 | 配合 MQTT 触发 + 版本管理 |
Summary
Section titled “Summary”本节介绍了 HTTP OTA 的实施方法:
- 服务器搭建:Python HTTP Server(测试)或 Nginx(生产)
- ESP32 实现:HTTP 下载固件 → Update 库写入 OTA 分区 → 重启
- MQTT 触发:远程命令触发 OTA 更新
- 版本管理:firmware.json 版本自动检查
- 安全风险:HTTP OTA 无加密,局域网适用,互联网建议升级 HTTPS