You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

咨询ESP32如何通过HTTP multipart/form-data实现OTA固件接收

ESP32 OTA 基于 HTTP multipart/form-data 实现方案

一、HTTPClient.h 是否支持 multipart/form-data?

ESP32 原生的 HTTPClient.h 没有封装 multipart/form-data 的现成解析/构建逻辑,但完全可以手动拼接请求、解析响应实现需求。如果嫌手动处理麻烦,推荐用 ESPAsyncWebServer 或带扩展的 ArduinoHttpClient,能大幅减少重复代码。

二、接收 multipart/form-data 固件的通用流程

  • 解析服务器响应头,提取 Content-Type 中的 boundary 分隔符(比如 ----WebKitFormBoundary7MA4YWxkTrZu0gW)
  • 逐字节读取响应体,用 boundary 分割各个表单块
  • 识别包含固件文件的块(通常带有 filename 参数),跳过块头的描述信息,提取核心文件内容
  • 将文件内容写入 ESP32 的 OTA 分区(用官方 Update 类)
  • 验证写入的固件完整性,成功后重启设备

三、代码示例(ESP32 作为客户端接收固件)

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

const char* ssid = "你的WiFi名称";
const char* password = "你的WiFi密码";
const char* updateServerUrl = "http://你的服务器地址/ota/update";

void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi 已连接");

  // 步骤1:检查是否有更新
  if (checkForUpdate()) {
    Serial.println("检测到可用更新,开始下载固件...");
    // 步骤3-5:请求并接收固件,完成OTA更新
    if (downloadAndUpdateFirmware()) {
      Serial.println("更新完成,即将重启设备");
      ESP.restart();
    } else {
      Serial.println("更新失败,请检查服务器或网络");
    }
  } else {
    Serial.println("当前已是最新版本");
  }
}

// 检查更新逻辑:向服务器发送当前版本,获取更新状态
bool checkForUpdate() {
  HTTPClient http;
  bool hasUpdate = false;
  
  if (http.begin(updateServerUrl)) {
    http.addHeader("Content-Type", "application/x-www-form-urlencoded");
    // 替换成你的版本号和请求参数
    int httpCode = http.POST("action=check&current_ver=1.0.0");
    
    if (httpCode == HTTP_CODE_OK) {
      String resp = http.getString();
      // 假设服务器返回"YES"表示有更新,可根据实际调整
      if (resp.trim() == "YES") {
        hasUpdate = true;
      }
    }
    http.end();
  }
  return hasUpdate;
}

// 下载并写入固件:解析multipart响应,写入OTA分区
bool downloadAndUpdateFirmware() {
  HTTPClient http;
  bool updateSuccess = false;
  
  if (http.begin(updateServerUrl)) {
    http.addHeader("Content-Type", "application/x-www-form-urlencoded");
    int httpCode = http.POST("action=download"); // 请求服务器返回固件
    
    if (httpCode == HTTP_CODE_OK) {
      // 从响应头提取multipart分隔符
      String contentType = http.header("Content-Type");
      int boundaryIdx = contentType.indexOf("boundary=");
      if (boundaryIdx == -1) {
        Serial.println("未找到multipart分隔符,无法解析");
        http.end();
        return false;
      }
      String boundary = "--" + contentType.substring(boundaryIdx + 9);
      
      WiFiClient* stream = http.getStreamPtr();
      bool isFileBlock = false;
      size_t writtenBytes = 0;
      String tempLine = "";

      // 初始化OTA更新
      if (!Update.begin(UPDATE_SIZE_UNKNOWN)) {
        Update.printError(Serial);
        http.end();
        return false;
      }

      // 逐字节解析multipart数据
      while (stream->connected() && (http.connected() || stream->available())) {
        if (stream->available()) {
          char c = stream->read();
          tempLine += c;

          // 检测到分隔符,切换块状态
          if (tempLine.endsWith(boundary)) {
            tempLine = "";
            isFileBlock = false;
          } 
          // 当前处于文件块,写入OTA分区
          else if (isFileBlock) {
            if (Update.write(&c, 1) != 1) {
              Update.printError(Serial);
              http.end();
              return false;
            }
            writtenBytes++;
          } 
          // 检测到固件文件块的标识,准备开始写入
          else if (tempLine.indexOf("filename=\"firmware.bin\"") != -1) {
            isFileBlock = true;
            // 跳过块头的空行,定位到文件内容起始位置
            while (stream->available()) {
              char tempChar = stream->read();
              if (tempChar == '\n' && stream->peek() == '\n') {
                stream->read();
                break;
              }
            }
            tempLine = "";
          }
        }
      }

      // 结束OTA更新并验证
      if (Update.end(true)) {
        Serial.printf("固件写入完成,总大小:%d bytes\n", writtenBytes);
        updateSuccess = true;
      } else {
        Update.printError(Serial);
      }
    } else {
      Serial.printf("请求固件失败,HTTP状态码:%d\n", httpCode);
    }
    http.end();
  }
  return updateSuccess;
}

void loop() {
  // 空循环,OTA完成后会重启
}

四、实用库推荐

  • ESPAsyncWebServer:不管是作为客户端解析multipart响应,还是作为服务器接收OTA请求,这个库都内置了完善的multipart处理工具类,能省去手动解析的繁琐工作。
  • ArduinoHttpClient:部分版本扩展了multipart表单的构建与解析方法,比原生HTTPClient更易用,注意选择适配ESP32的版本。

五、OTA流程优化建议

  • 版本校验:在检查更新时,务必带上当前固件的版本号(如MD5或语义化版本),避免无效的下载请求。
  • 固件完整性校验:让服务器返回固件的SHA256/MD5值,ESP32下载完成后自行校验,防止固件损坏导致设备变砖。
  • 异常重试:添加网络断开、写入失败等场景的重试逻辑,提升OTA的可靠性。

内容的提问来源于stack exchange,提问作者rnjsrkdmf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 16:15:30