跳转到内容

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 服务器通信

使用 Python HTTP Server(开发测试)

Section titled “使用 Python HTTP Server(开发测试)”
Terminal window
# 在固件文件目录启动 HTTP 服务器
cd /path/to/firmware/directory
python3 -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 # 固件版本信息
/etc/nginx/sites-available/firmware
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;
}
}
#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 实现远程触发升级:

#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();
}
{
"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
}
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;
}
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();
}
风险风险等级缓解措施
中间人攻击🔴 高移至 HTTPS OTA
固件被篡改🔴 高添加固件签名
未授权更新🟡 中添加更新口令
重放攻击🟡 中添加时间戳和序列号
#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;
}
场景HTTP OTA 适用性升级建议
局域网设备升级✅ 安全可用配合签名校验
互联网设备升级⚠️ 需升级 HTTPS建议使用 HTTPS OTA
开发测试环境✅ 推荐快速部署和迭代
批量设备管理✅ 可行配合 MQTT 触发 + 版本管理

本节介绍了 HTTP OTA 的实施方法:

  1. 服务器搭建:Python HTTP Server(测试)或 Nginx(生产)
  2. ESP32 实现:HTTP 下载固件 → Update 库写入 OTA 分区 → 重启
  3. MQTT 触发:远程命令触发 OTA 更新
  4. 版本管理:firmware.json 版本自动检查
  5. 安全风险:HTTP OTA 无加密,局域网适用,互联网建议升级 HTTPS