news 2026/5/28 18:59:57

ESP32纯HTTP OTA升级库:支持固件与SPIFFS双模更新

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ESP32纯HTTP OTA升级库:支持固件与SPIFFS双模更新

1. 项目概述

esp32HttpJsonOTA是一个面向 ESP32 平台的轻量级、纯 HTTP 协议驱动的固件(Firmware)与文件系统(SPIFFS)空中升级(OTA)库。其核心设计目标是在不依赖服务端脚本(如 PHP/Python 后端)的前提下,通过标准 HTTP 协议完成版本校验与二进制镜像下载更新。该库完全基于 ESP-IDF 或 Arduino-ESP32 框架构建,兼容官方 WiFi 和 OTA API,适用于生产环境中的远程设备维护与功能迭代。

与传统 OTA 方案(如 ESP-IDF 的esp_https_ota或 Arduino 的ArduinoOTA)不同,esp32HttpJsonOTA采用“客户端主动拉取 + JSON 元数据驱动”的模式:设备启动后,首先向预设 URL 发起 HTTP GET 请求获取 JSON 配置文件;解析其中的版本号、目标主机、端口及二进制路径;比对本地当前版本;若存在更高版本,则发起第二次 HTTP GET 下载.bin文件并执行烧录。整个流程无需 TLS 加密(仅支持 HTTP),极大降低了服务端部署门槛——可直接使用静态 Web 服务器(Apache/Nginx)、Google Cloud Storage(GCS)、AWS S3、GitHub Pages 或任何支持 HTTP 文件直链的存储服务。

该项目由 Franck RONDOT 于 2020 年 3 月发布,是在 Chris Joyce 开发的esp32FOTA基础上深度重构的分支。主要增强点包括:

  • 双模 OTA 支持:同时支持 Firmware(应用程序主镜像)与 SPIFFS(SPI Flash 文件系统)独立升级;
  • JSON 版本控制机制:通过结构化元数据实现语义化版本管理;
  • 设备标识集成能力:支持基于设备唯一 ID(如 MAC 地址哈希)的差异化更新策略;
  • 零服务端逻辑依赖:所有决策逻辑均在 ESP32 端完成,服务端仅需提供静态 JSON 与 BIN 文件。

该方案特别适用于资源受限、无 HTTPS 证书管理能力、或需快速搭建 OTA 基础设施的嵌入式场景,例如工业传感器节点、农业物联网终端、教育开发套件等。

2. 核心架构与工作原理

2.1 整体流程图解

OTA 流程分为两个关键阶段:HTTP 版本检查(execHTTPcheck固件/文件系统烧录(execOTA。二者严格解耦,允许开发者按需组合调用:

[设备启动] ↓ [WiFi 连接建立] ↓ [调用 execHTTPcheck()] ├─→ 构造 HTTP GET 请求 → JSON URL(如 http://update.example.com/fw.json) ├─→ 解析响应体为 JSON 对象 ├─→ 提取 'version' 字段并与本地版本(构造时传入或运行时设置)比对 └─→ 返回布尔值:true 表示存在新版本,false 表示已是最新 ↓(仅当返回 true 时执行) [调用 execOTA()] ├─→ 构造 HTTP GET 请求 → bin URL(如 http://storage.googleapis.com/bucket/fw.bin) ├─→ 流式接收 HTTP 响应体(Chunked Transfer Encoding 兼容) ├─→ 将数据块写入 OTA 分区(Firmware)或 SPIFFS 分区(SPIFFS) └─→ 校验 CRC32 / 写入完成标志 → 触发重启(Firmware)或重挂载(SPIFFS)

此流程规避了 HTTPS 握手开销与证书管理复杂度,但要求开发者自行保障传输通道安全性(如通过私有网络、IP 白名单、URL 签名等方式)。

2.2 JSON 配置文件规范

JSON 文件是 OTA 的“指挥中枢”,必须严格遵循以下 Schema。服务端只需确保该文件可通过 HTTP GET 访问,内容为 UTF-8 编码纯文本。

字段名类型必填说明
namestring设备应用名称,用于日志与调试标识,不参与版本逻辑判断
typestring更新类型,仅接受'FIRMWARE''SPIFFS',区分烧录目标分区
versionnumber当前远端版本号,整数类型。设备端将此值与本地版本比较决定是否更新
hoststring目标 bin 文件所在 HTTP 服务器域名/IP,如storage.googleapis.com
portnumberHTTP 端口,通常为80(HTTP)或443(HTTPS,但本库不支持)
binstringbin 文件在服务器上的相对路径,如/bucket/fw-esp32.bin

Firmware JSON 示例:

{ "name": "ESP32APPFR", "type": "FIRMWARE", "version": 1, "host": "storage.googleapis.com", "port": 80, "bin": "/update/esp32ehjo/fw-esp32ehjo.bin" }

SPIFFS JSON 示例:

{ "name": "ESP32APPFR", "type": "SPIFFS", "version": 1, "host": "storage.googleapis.com", "port": 80, "bin": "/update/esp32ehjo/fs-esp32ehjo.bin" }

⚠️关键注意事项

  • Google Cloud Storage 等对象存储服务默认 MIME 类型为application/json(JSON)和text/plain(BIN),但 ESP32 HTTP 客户端对非application/octet-stream类型的二进制响应可能解析异常。务必在 GCS 控制台中将.bin文件的 Content-Type 设置为application/octet-stream
  • 若使用带 Web 应用防火墙(WAF)的 CDN 或子域名(如 Cloudflare),部分 WAF 会拦截包含ESP32Arduino等 UA 字符串的请求。建议在测试阶段使用curl --head <JSON_URL>验证可访问性,必要时临时禁用 WAF 或自定义 User-Agent。

2.3 版本比对与状态管理

版本比对是 OTA 的决策核心。库提供两种版本管理方式:

  1. 编译期固定版本(推荐用于 Firmware)
    在创建esp32HttpJsonOTA实例时传入OTA_VER宏定义值:

    #define OTA_VER 1 esp32HttpJsonOTA majFW(OTA_NAME, "FIRMWARE", OTA_VER, FWOTA_JSONURL);

    此方式下,execHTTPcheck()直接将 JSON 中的version与构造时传入的OTA_VER比较。适用于主程序版本与硬件绑定紧密的场景。

  2. 运行时动态版本(必需用于 SPIFFS)
    SPIFFS 内容(如网页、配置文件)常需独立于固件更新。此时需在运行时读取 SPIFFS 中的版本文件(如/ver):

    int verFS() { File myFile = SPIFFS.open("/ver", "r"); if (myFile) { String verStr = myFile.readString(); myFile.close(); return verStr.toInt(); // 返回当前 SPIFFS 版本号 } return 1; // 默认版本 } // 在检查前设置 majFS.setVer(verFS()); bool needUpdate = majFS.execHTTPcheck();

    setVer()方法将运行时获取的版本号注入实例,供后续比对使用。

3. API 接口详解与源码逻辑

3.1 主要类与构造函数

esp32HttpJsonOTA是一个 C++ 类,封装了全部 OTA 功能。其构造函数签名如下:

esp32HttpJsonOTA(const char* name, const char* type, uint32_t currentVersion, const char* jsonUrl);
参数类型说明
nameconst char*设备/应用名称,仅用于日志输出,无业务逻辑作用
typeconst char*更新类型字符串,必须为"FIRMWARE""SPIFFS"
currentVersionuint32_t当前本地版本号,用于与 JSON 中version比较
jsonUrlconst char*JSON 配置文件的完整 HTTP URL(含协议、域名、路径)

构造过程关键操作:

  • 复制nametype到内部缓冲区(长度限制为 32 字节);
  • 存储currentVersion至成员变量m_currentVersion
  • 解析jsonUrl,提取协议(强制http://)、主机名、端口(默认 80)、路径;
  • 初始化内部状态机(m_state = STATE_IDLE)。

3.2 核心成员函数解析

bool execHTTPcheck()

功能:执行 HTTP 版本检查,返回是否需要更新。

源码逻辑(简化):

bool esp32HttpJsonOTA::execHTTPcheck() { // 1. 创建 HTTP 客户端,连接 jsonUrl 主机 http_client_config_t config = {.url = m_jsonUrl}; esp_http_client_handle_t client = esp_http_client_init(&config); esp_http_client_open(client, 0); // GET 请求 // 2. 读取响应头,验证状态码 200 int status_code = esp_http_client_get_status_code(client); if (status_code != 200) { /* 错误处理 */ } // 3. 流式读取响应体至缓冲区(最大 1024 字节) char buffer[1024]; int len = esp_http_client_read(client, buffer, sizeof(buffer)-1); buffer[len] = '\0'; // 4. 使用 cJSON 解析 JSON cJSON *root = cJSON_Parse(buffer); if (!root) { /* JSON 解析失败 */ } // 5. 提取 version 字段 cJSON *verObj = cJSON_GetObjectItemCaseSensitive(root, "version"); uint32_t remoteVersion = (verObj && cJSON_IsNumber(verObj)) ? verObj->valueint : 0; // 6. 比较版本:remoteVersion > m_currentVersion bool needUpdate = (remoteVersion > m_currentVersion); cJSON_Delete(root); esp_http_client_cleanup(client); return needUpdate; }

关键点:

  • 使用 ESP-IDF 官方esp_http_client组件,稳定可靠;
  • JSON 解析依赖cJSON库(需在sdkconfig中启用CONFIG_CJSON_ENABLE);
  • 未做 JSON Schema 校验,若字段缺失则使用默认值(如version=0),导致比对失败。
void execOTA()

功能:执行实际的二进制下载与烧录。

源码逻辑(分 Firmware/SPIFFS):

Firmware 分支:

// 1. 获取 OTA 分区信息 const esp_partition_t* update_partition = esp_ota_get_next_update_partition(NULL); // 2. 初始化 OTA 句柄 esp_ota_handle_t ota_handle; esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &ota_handle); // 3. 构造 bin URL 并发起 HTTP GET char binUrl[256]; snprintf(binUrl, sizeof(binUrl), "http://%s:%d%s", m_host, m_port, m_binPath); // 4. 流式读取 HTTP 响应,写入 OTA 分区 while ((len = esp_http_client_read(client, buffer, sizeof(buffer))) > 0) { esp_ota_write(ota_handle, (const void*)buffer, len); } // 5. 结束 OTA,校验,重启 esp_ota_end(ota_handle); esp_ota_set_boot_partition(update_partition); esp_restart();

SPIFFS 分支:

// 1. 打开 SPIFFS 文件(覆盖模式) File file = SPIFFS.open(m_binPath, "w"); // 注意:此处 m_binPath 是目标文件名,如 "/fs.bin" // 2. 流式写入 HTTP 响应体 while ((len = esp_http_client_read(client, buffer, sizeof(buffer))) > 0) { file.write((uint8_t*)buffer, len); } file.close(); // 3. 重挂载 SPIFFS(可选,确保新文件可见) SPIFFS.end(); SPIFFS.begin(true); // 格式化并重新挂载

关键点:

  • Firmware 更新后强制重启,由 Bootloader 加载新分区;
  • SPIFFS 更新后不自动重启,但需调用SPIFFS.end()/begin()以使新文件生效;
  • 无内置 CRC 校验,依赖 HTTP 层的 TCP 校验和,生产环境建议在 JSON 中增加checksum字段并自行验证。
void setVer(uint32_t version)void forceUpdate(const char* ip, uint16_t port, const char* binPath, const char* type)
  • setVer():覆盖构造时传入的m_currentVersion,用于运行时动态版本管理(如 SPIFFS)。
  • forceUpdate():绕过 JSON 检查,直接指定 IP、端口、bin 路径进行强制更新。适用于内网调试或紧急修复,例如:
    void forceUpd() { majFW.forceUpdate("192.168.0.100", 80, "/firmware.bin", "FIRMWARE"); }
void useDeviceID = true

启用设备唯一 ID 模式。库会自动获取 ESP32 的 MAC 地址(esp_wifi_get_mac(WIFI_IF_STA, mac)),并将其哈希(如 MD5 前 8 字节)作为设备标识。此时,JSON URL 可动态拼接为http://server.com/update/<device_id>.json,实现千人千面的差异化更新策略。

4. 工程实践与代码示例

4.1 完整 Arduino 示例解析

以下为 README 中示例的逐行工程化解读:

#include <Arduino.h> #include <WiFi.h> #include <esp_log.h> #include <SPIFFS.h> #include "esp32HttpJsonOTA.h" #define SSID "WIFISSID" #define PASSWORD "WIFIPASSWORD" #define OTA_NAME "ESP32APPFR" #define OTA_VER 1 #define FWOTA_JSONURL "http://update.website.com/update/esp32ehjo/fw-esp32ehjo.json" #define FSOTA_JSONURL "http://update.website.com/update/esp32ehjo/fs-esp32ehjo.json" // 创建两个 OTA 实例:一个管固件,一个管文件系统 esp32HttpJsonOTA majFW(OTA_NAME, "FIRMWARE", OTA_VER, FWOTA_JSONURL); esp32HttpJsonOTA majFS(OTA_NAME, "SPIFFS", OTA_VER, FSOTA_JSONURL); static const char* TAG = "EHJO"; // 【SPIFFS 版本读取函数】 int verFS() { // 1. 初始化 SPIFFS if (!SPIFFS.begin(true)) { // true=格式化(首次运行) ESP_LOGE(TAG, "Mount error of SPIFFS..."); return 0; } // 2. 打开版本文件 /ver(纯文本,内容为数字) File myFile = SPIFFS.open("/ver", "r"); if (myFile) { String ver = myFile.readString(); // 读取全部内容 myFile.close(); ESP_LOGD(TAG, "SPIFFS version : %d", ver.toInt()); return ver.toInt(); } else { ESP_LOGW(TAG, "No /ver file found, assume version 1"); return 1; } } // 【固件更新检查】 bool newVerFW() { bool maj = majFW.execHTTPcheck(); // 发起 JSON 请求并比对 ESP_LOGD(TAG, "Sketch update available : %s", maj ? "Yes" : "No"); return maj; } // 【执行固件更新】 void updateFW() { majFW.execOTA(); // 下载并烧录,完成后自动重启 } // 【SPIFFS 更新检查】 bool newVerFS() { majFS.setVer(verFS()); // 设置当前 SPIFFS 版本 bool maj = majFS.execHTTPcheck(); ESP_LOGD(TAG, "SPIFFS update available : %s", maj ? "Yes" : "No"); return maj; } // 【执行 SPIFFS 更新】 void updateFS() { majFS.execOTA(); // 下载 bin 到 SPIFFS,需手动重挂载 } // 【WiFi 连接函数】 void setup_wifi() { WiFi.begin(SSID, PASSWORD); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(500); } ESP_LOGI(TAG, "IP address : %s", WiFi.localIP().toString().c_str()); } // 【主函数】 void setup() { esp_log_level_set("*", ESP_LOG_VERBOSE); // 全局日志级别 Serial.begin(115200); setup_wifi(); // 启动时检查更新(顺序:先固件,再文件系统) if (newVerFW()) { ESP_LOGI(TAG, "Sketch update available !"); updateFW(); // 此处会重启,后续代码不执行 } if (newVerFS()) { ESP_LOGI(TAG, "SPIFFS update available !"); updateFS(); // SPIFFS 更新后需重挂载才能生效 SPIFFS.end(); SPIFFS.begin(true); } } void loop() { delay(100); }

工程要点总结:

  • SPIFFS.begin(true)在首次运行时格式化分区,生产环境应移除此参数,避免意外擦除用户数据;
  • 固件更新 (updateFW()) 后设备立即重启,因此newVerFS()检查必须放在updateFW()之后,否则重启会中断流程;
  • SPIFFS 更新后必须调用SPIFFS.end()/begin(),否则新文件不可见;
  • 日志级别设为ESP_LOG_VERBOSE便于调试,量产时建议降为ESP_LOG_INFO以节省 Flash 空间。

4.2 生产环境增强建议

1. 添加 OTA 状态持久化

避免每次启动都检查更新,可将上次成功更新的版本号写入 NVS(Non-Volatile Storage):

#include "nvs_flash.h" #include "nvs.h" void saveCurrentVersion(const char* key, uint32_t version) { nvs_handle_t my_handle; nvs_open("ota", NVS_READWRITE, &my_handle); nvs_set_u32(my_handle, key, version); nvs_commit(my_handle); nvs_close(my_handle); } uint32_t loadCurrentVersion(const char* key, uint32_t defaultVer) { nvs_handle_t my_handle; uint32_t ver; nvs_open("ota", NVS_READONLY, &my_handle); esp_err_t err = nvs_get_u32(my_handle, key, &ver); nvs_close(my_handle); return (err == ESP_OK) ? ver : defaultVer; }
2. 实现断点续传(HTTP Range)

对于大固件(>1MB),网络中断可能导致更新失败。可扩展execOTA()支持Range请求头,记录已下载字节数。

3. 集成 FreeRTOS 任务

将 OTA 封装为独立任务,避免阻塞loop()

void otaTask(void* pvParameters) { while(1) { if (newVerFW()) updateFW(); vTaskDelay(60000 / portTICK_PERIOD_MS); // 每分钟检查一次 } } xTaskCreate(otaTask, "ota_task", 8192, NULL, 5, NULL);

5. 常见问题与故障排查

5.1 HTTP 连接失败(execHTTPcheck()返回 false)

现象可能原因排查步骤
HTTP request failed: ESP_ERR_HTTP_CONNECT_FAILUREDNS 解析失败ping update.website.com检查域名可达性;确认WiFi.status() == WL_CONNECTED
HTTP request failed: ESP_ERR_HTTP_INVALID_SERVER_CERT误用 HTTPS URL确认 JSON URL 以http://开头,非https://
HTTP request failed: ESP_ERR_HTTP_NO_CONTENT服务器返回空响应curl -v <JSON_URL>查看响应头与体;检查 GCS 的Content-Type是否为application/json

5.2 版本比对始终为 false

  • 检查 JSON 文件中version字段是否为纯数字(无引号),如"version": 1✅,"version": "1"❌;
  • 确认OTA_VER宏定义值与 JSON 中version严格一致(整数比较);
  • 若使用setVer(),在execHTTPcheck()前添加ESP_LOGI(TAG, "Current ver: %d, Remote ver: %d", majFS.m_currentVersion, remoteVersion)日志。

5.3 SPIFFS 更新后文件不可见

  • 确认updateFS()执行后调用了SPIFFS.end()/begin()
  • 检查m_binPathexecOTA()中是否被正确解析为 SPIFFS 内部路径(如/fs.bin),而非服务器路径;
  • 使用SPIFFS.open("/", "r")列出根目录,确认新文件存在。

5.4 Google Cloud Storage 配置指南

  1. 创建存储桶(Bucket),设置为“公共读取”;
  2. 上传fw.jsonfw.bin
  3. 在 GCS 控制台中,选中fw.bin→ “编辑” → “Content-Type” → 修改为application/octet-stream
  4. 获取公开 URL:https://storage.googleapis.com/<bucket-name>/fw.json
  5. 注意:GCS 的https://URL 在本库中不可用,需替换为http://并指定端口80,即http://storage.googleapis.com:80/<bucket-name>/fw.json

6. 安全性与生产部署考量

esp32HttpJsonOTA的 HTTP-only 设计在简化部署的同时,引入了明确的安全边界:

  • 无传输加密:所有通信明文传输,禁止在公网暴露敏感设备。生产环境必须部署于受控网络(如企业内网、VPC、IoT 专用 APN);
  • 无身份认证:JSON 与 BIN 文件为公开资源,任何获知 URL 的设备均可下载。可通过以下方式加固:
    • URL 签名:服务端生成带时效性签名的 URL(如?expires=1735689600&signature=abc123),设备端在构造 URL 时拼接;
    • IP 白名单:Web 服务器(Nginx/Apache)配置allow 192.168.1.0/24; deny all;
    • 设备 ID 绑定:启用useDeviceID,服务端根据设备 ID 提供专属 JSON,避免全局更新风暴;
  • 无固件签名验证:库不校验 BIN 文件完整性。强烈建议在 JSON 中增加sha256字段,并在execOTA()下载完成后调用mbedtls_sha256()计算并比对。

一个健壮的生产级 OTA 流程应为:
WiFi 连接 → 设备 ID 上报 → 获取签名 JSON → 校验 JSON 签名 → 解析 bin URL → 下载 bin → 校验 bin SHA256 → 烧录 → 重启
esp32HttpJsonOTA提供了其中 3 个环节(JSON 获取、BIN 下载、烧录)的坚实基础,其余环节需开发者根据安全等级需求补充。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/23 2:00:42

Mac安装IDEA 2025.1.1.1 安装 保姆级图文教程(附安装包)

文章目录获取方式1.安装好后&#xff0c;先关闭2.执行命令获取方式 链接: https://pan.baidu.com/s/1-RN96QC3uQFfZCAljS-6RQ?pwd6m2b 提取码: 6m2b 1.安装好后&#xff0c;先关闭 先安装软件打开后关闭&#xff0c;在执行操作 2.执行命令 点击下载的工具包&#xff0c;右…

作者头像 李华
网站建设 2026/5/23 2:00:33

Ubuntu 20.04 升级到 24.04 实战详细教程/记录

Ubuntu 20.04 升级到 24.04 实战记录&#xff1a;20.04 → 22.04 → 24.04 一、前言 最近我把一台 Ubuntu 20.04.6 LTS 虚拟机成功升级到了 Ubuntu 24.04.4 LTS。 整个过程里踩了几个典型坑&#xff0c;比如&#xff1a; VMware 虚拟机网络不通软件源异常升级过程中出现配置文件…

作者头像 李华
网站建设 2026/5/23 2:00:38

告别翻墙!用DeepSeek API在国内零成本玩转Claude Code(保姆级配置教程)

国内开发者零门槛体验Claude Code的完整指南 第一次听说Claude Code时&#xff0c;我正在为一个JavaScript项目焦头烂额。作为独立开发者&#xff0c;我常常需要快速原型开发&#xff0c;但又受限于时间和资源。Claude Code的出现像是一剂强心针——这个能直接在终端运行的AI编…

作者头像 李华
网站建设 2026/5/23 2:00:41

bilibili-downloader:免费获取B站4K视频的技术实现与场景应用

bilibili-downloader&#xff1a;免费获取B站4K视频的技术实现与场景应用 【免费下载链接】bilibili-downloader B站视频下载&#xff0c;支持下载大会员清晰度4K&#xff0c;持续更新中 项目地址: https://gitcode.com/gh_mirrors/bil/bilibili-downloader 在数字内容消…

作者头像 李华
网站建设 2026/5/22 22:47:14

Featurize深度学习训练全流程解析:从数据上传到模型输出

1. 数据上传&#xff1a;从本地到云端的高效迁移 第一次使用Featurize上传数据集时&#xff0c;我习惯性地点开了网页端的上传按钮&#xff0c;结果发现系统自动启用了分片上传机制。这个细节让我印象深刻——当我的10GB图像数据集在上传过程中网络波动时&#xff0c;竟然不需要…

作者头像 李华