本教程将引导你在局域网内完成 ESP32 的 HTTP OTA(Over‑The‑Air)远程升级,实现“设备主动请求、服务器响应固件 URL、设备下载并自我更新”的完整流程。测试成功后,你可以轻松将方案扩展到公网,实现真正的远程升级。

1. 整体架构

  • ESP32 设备:定期向 Flask 服务器发送 HTTP GET 请求,携带设备 ID(MAC 地址)和当前固件版本。

  • Flask 服务器:根据设备 ID 查询“目标版本”,若目标版本高于当前版本,则返回固件下载 URL。

  • HTTP 文件服务器:提供固件文件(.bin)的下载服务。

  • 流程:设备轮询 → 服务器返回更新指令 → 设备下载固件 → 设备烧录并重启 → 设备运行新固件。

2. 准备工作

硬件

  • ESP32 开发板(如 ESP32‑DevKitC) ×1

  • USB 数据线(用于首次烧录和调试)

  • 电脑(Windows / macOS / Linux)与 ESP32 连接在同一路由器/局域网

软件

  • Arduino IDE(已安装 ESP32 开发板支持包)

  • Python 3.x(用于运行 Flask 服务和 HTTP 文件服务器)

  • 库安装(在 Arduino IDE 中安装 ArduinoJson 库,用于解析 JSON)

3. 设备端代码(ESP32)

创建新 Arduino 项目,复制以下代码。注意:首次烧录需通过 USB 线,后续升级可通过 OTA。


#include <WiFi.h>
#include <HTTPClient.h>
#include <Update.h>
#include <ArduinoJson.h>

// ========== 配置区 ==========
const char* ssid = "你的WiFi名称";
const char* password = "你的WiFi密码";

// 设备唯一标识(使用MAC地址)
String device_id = WiFi.macAddress();

// 当前固件版本(语义化版本,如 "1.0.0")
String current_version = "1.0.0";

// 检查更新的API地址(填写你的电脑IP和端口)
const char* check_url = "http://192.168.x.x:8080/check_update";

// 检查间隔(毫秒),测试时可设为30秒,正式使用建议1小时以上
const unsigned long check_interval = 30000;   // 30秒,仅测试
// ==========================

void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  Serial.print("Connecting to WiFi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  Serial.print("Device ID: ");
  Serial.println(device_id);
}

void loop() {
  static unsigned long last_check = 0;
  if (millis() - last_check > check_interval) {
    last_check = millis();
    checkForUpdate();
  }
  // 你的其他业务代码(不要用长时间delay阻塞OTA)
}

void checkForUpdate() {
  HTTPClient http;
  String url = String(check_url) + "?device_id=" + device_id + "&version=" + current_version;
  Serial.print("Checking update: ");
  Serial.println(url);
  
  http.begin(url);
  int httpCode = http.GET();
  if (httpCode == 200) {
    String payload = http.getString();
    Serial.println("Response: " + payload);
    
    // 解析JSON
    DynamicJsonDocument doc(1024);
    DeserializationError error = deserializeJson(doc, payload);
    if (!error) {
      bool update = doc["update"];
      if (update) {
        String firmware_url = doc["url"].as<String>();
        Serial.println("Firmware URL: " + firmware_url);
        performOTA(firmware_url);
      } else {
        Serial.println("No update available.");
      }
    } else {
      Serial.println("JSON parse failed");
    }
  } else {
    Serial.printf("HTTP GET failed, error: %s\n", http.errorToString(httpCode).c_str());
  }
  http.end();
}

void performOTA(String url) {
  HTTPClient http;
  http.setTimeout(10000); // 10秒超时
  http.begin(url);
  int httpCode = http.GET();
  if (httpCode != 200) {
    Serial.printf("Download failed, HTTP code: %d\n", httpCode);
    http.end();
    return;
  }
  int contentLength = http.getSize();
  if (contentLength <= 0) {
    Serial.println("Invalid content length");
    http.end();
    return;
  }
  Serial.printf("Starting OTA, size: %d bytes\n", contentLength);
  if (!Update.begin(contentLength)) {
    Serial.println("Not enough space to begin OTA");
    http.end();
    return;
  }
  size_t written = Update.writeStream(http.getStream());
  if (written == contentLength) {
    Serial.println("Written : " + String(written) + " bytes successfully");
    if (Update.end()) {
      Serial.println("OTA done, restarting...");
      ESP.restart();
    } else {
      Serial.println("Error in Update.end()");
    }
  } else {
    Serial.println("Written only : " + String(written) + "/" + String(contentLength) + ". Retry?");
  }
  http.end();
}

关键点说明

  • 使用 WiFi.macAddress() 作为设备唯一标识,服务器据此区分不同设备。

  • 当前版本号 current_version 与服务器目标版本比较,决定是否更新。

  • 使用 ArduinoJson 库解析服务器返回的 JSON,避免字符串匹配的脆弱性。

  • performOTA() 函数负责下载固件并写入 Flash,内置超时和错误处理。

4. 服务端代码(Flask)

在电脑上创建文件 ota_server.py,内容如下:


from flask import Flask, request, jsonify

app = Flask(__name__)

# 模拟数据库:设备ID -> 目标版本号
# 从串口监视器获取ESP32的MAC地址,替换为实际值
target_versions = {
    "1C:69:20:2B:A7:AC": "2.0.0",   # 示例:将该设备目标版本设为2.0.0
    # 其他设备可继续添加
}

# 固件下载的基础URL(使用电脑IP和8000端口)
FIRMWARE_BASE_URL = "http://192.168.x.x:8000/"   # 替换为你的电脑IP

@app.route('/check_update')
def check_update():
    device_id = request.args.get('device_id')
    current_ver = request.args.get('version')
    print(f"Device: {device_id}, current: {current_ver}")
    
    target = target_versions.get(device_id)
    if target and target != current_ver:
        firmware_url = FIRMWARE_BASE_URL + "firmware_" + target + ".bin"
        return jsonify({"update": True, "url": firmware_url})
    else:
        return jsonify({"update": False})

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080, debug=True)

说明

  • target_versions 字典存储每个设备的“目标版本”。当设备上报的版本与目标版本不一致时,即触发更新。

  • 固件 URL 按版本号拼接,例如 firmware_2.0.0.bin,请确保实际文件名与此一致。

  • Flask 监听所有网络接口(0.0.0.0),端口 8080

5. 固件准备与文件服务

生成固件文件(.bin)

  1. 在 Arduino IDE 中打开你的项目(注意:固件中必须包含上述 OTA 代码)。

  2. 选择 项目 → 导出已编译的二进制文件

  3. 项目文件夹下会生成 .bin 文件,如 ESP32_V3_02.ino.bin

  4. 将文件重命名为 firmware_2.0.0.bin(版本号与服务器目标版本一致),并放入一个目录,例如 C:\Users\YourName\Desktop\OTA\firmware\

启动 HTTP 文件服务器

打开命令提示符(或终端),进入存放 .bin 文件的目录,执行:

python -m http.server 8000

此命令会在 http://你的电脑IP:8000 提供文件下载服务。
注意:电脑的防火墙需允许 8000 端口入站,否则 ESP32 无法下载固件。

6. 运行测试

  1. 启动 Flask 服务
    在命令行中进入 ota_server.py 所在目录,执行:

    python ota_server.py

    看到类似 Running on http://0.0.0.0:8080 的输出即表示服务启动。

  2. 启动 HTTP 文件服务器(如果还未启动)
    在另一个命令行窗口,进入固件目录,执行:

    python -m http.server 8000
  3. 确认电脑 IP 地址
    在命令行输入 ipconfig(Windows)或 ifconfig(Linux/macOS),找到当前网络适配器的 IPv4 地址,例如 192.168.1.100
    确保 ESP32 的 WiFi 连接与电脑在同一局域网

  4. 修改 ESP32 代码中的 IP
    将 check_url 和 FIRMWARE_BASE_URL 中的 IP 改为你的电脑实际 IP,例如:

    const char* check_url = "http://192.168.1.100:8080/check_update";

    然后通过 USB 线将代码烧录到 ESP32。

  5. 观察串口监视器
    打开 Arduino IDE 的串口监视器(波特率 115200),ESP32 启动后会连接 WiFi,并每隔 30 秒检查一次更新。
    若 target_versions 中设备的版本与当前版本不同,则会触发 OTA 下载并重启。

    预期输出类似:

    WiFi connected
    IP address: 192.168.1.50
    Device ID: 1C:69:20:2B:A7:AC
    Checking update: http://192.168.1.100:8080/check_update?device_id=1C:69:20:2B:A7:AC&version=1.0.0
    Response: { "update": true, "url": "http://192.168.1.100:8000/firmware_2.0.0.bin" }
    Firmware URL: http://192.168.1.100:8000/firmware_2.0.0.bin
    Starting OTA, size: 989232 bytes
    Written : 989232 bytes successfully
    OTA done, restarting...

  6. 升级后验证
    设备重启后,串口监视器应显示 current_version 变为 2.0.0,且再次检查时服务器返回 {"update":false}

7. 常见问题排查

现象 可能原因 解决方法
设备无法连接 WiFi SSID/密码错误,或路由器信号弱 检查代码中的 WiFi 凭据,确保设备靠近路由器
检查更新时无响应或超时 电脑 IP 错误,或防火墙阻挡 8080 端口 确认电脑 IP 不变(可设为静态),暂时关闭防火墙测试
收到 JSON 但显示“No update available.” JSON 解析失败(字符串匹配问题) 确保使用了 ArduinoJson 库,并正确解析 doc["update"]
下载固件时输出 Written only : 0/xxx ESP32 与电脑不在同一网段,或 8000 端口被防火墙拦截 检查 ESP32 的 IP 与电脑 IP 是否同网段;检查防火墙规则
下载后设备反复重启 新固件有 bug 或缺少 OTA 代码 回退到旧固件(USB 烧录),并检查新固件是否包含 ArduinoOTA.handle() 或 OTA 检查代码

8. 下一步:扩展到公网

本地测试成功后,你可以:

  • 将 Flask 服务部署到具有公网 IP 的云服务器;

  • 将固件上传至对象存储(如阿里云 OSS)获取公网 URL;

  • 修改 ESP32 代码中的 check_url 为公网地址;

  • 为服务添加基本安全措施(如 HTTPS、API Token)。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐