1、背景
在实际产品开发过程中,在线升级可以远程解决产品软件开发引入的问题,更好地满足用户需求。
2、OTA 简介
OTA(空中)更新是使用 Wi-Fi 连接而不是串行端口将固件加载到 ESP 模块的过程。
2.1、ESP32 的 OTA 升级有三种方式:
- Arduino IDE:主要用于软件开发阶段,实现不接线固件烧写
- Web Browser:通过 Web 浏览器手动提供应用程序更新模块
- HTTP Server:自动使用http服务器 - 针对产品应用
在三种升级情况下,必须通过串行端口完成第一个固件上传。
OTA 进程没有强加的安全性,需要确保开发人员只能从合法/受信任的来源获得更新。更新完成后,模块将重新启动,并执行新的代码。开发人员应确保在模块上运行的应用程序以安全的方式关闭并重新启动。
2.2、保密性 Security
模块必须以无线方式显示,以便通过新的草图进行更新。 这使得模块被强行入侵并加载了其他代码。 为了减少被黑客入侵的可能性,请考虑使用密码保护您的上传,选择某些OTA端口等。
可以提高安全性的 ArduinoOTA 库接口:
void setPort(uint16_t port);
void setHostname(const char* hostname);
void setPassword(const char* password);
void onStart(OTA_CALLBACK(fn));
void onEnd(OTA_CALLBACK(fn));
void onProgress(OTA_CALLBACK_PROGRESS(fn));
void onError(OTA_CALLBACK_ERROR (fn));
已经内置了某些保护功能,不需要开发人员进行任何其他编码。ArduinoOTA和espota.py使用Digest-MD5来验证上传。使用MD5校验和,在ESP端验证传输数据的完整性
2.2、OTA 升级策略 - 针对 http
ESP32 连接 HTTP 服务器,发送请求 Get 升级固件;每次读取1KB固件数据,写入Flash。
ESP32 SPI Flash 内有与升级相关的(至少)四个分区:OTA data、Factory App、OTA_0、OTA_1。其中 FactoryApp 内存有出厂时的默认固件。
首次进行 OTA 升级时,OTA Demo 向 OTA_0 分区烧录目标固件,并在烧录完成后,更新 OTA data 分区数据并重启。
系统重启时获取 OTA data 分区数据进行计算,决定此后加载 OTA_0 分区的固件执行(而不是默认的 Factory App 分区内的固件),从而实现升级。
同理,若某次升级后 ESP32 已经在执行 OTA_0 内的固件,此时再升级时 OTA Demo 就会向 OTA_1 分区写入目标固件。再次启动后,执行 OTA_1 分区实现升级。以此类推,升级的目标固件始终在 OTA_0、OTA_1 两个分区之间交互烧录,不会影响到出厂时的 Factory App 固件。
3、OTA 实例解析
3,1、Arduino IDE 方案固件更新
从 Arduino IDE 无线上传模块适用于以下典型场景:
在固件开发过程中,通过串行加载更快的替代方案 - 用于更新少量模块,只有模块在与 Arduino IDE 的计算机相同的网络上可用。
参考实例:
#include
#include
#include
#include
const char* ssid = "..........";
const char* password = "..........";
void setup() {
Serial.begin(115200);
Serial.println("Booting");
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.println("Connection Failed! Rebooting...");
delay(5000);
ESP.restart();
}
// Port defaults to 3232
// ArduinoOTA.setPort(3232);
// Hostname defaults to esp3232-[MAC]
// ArduinoOTA.setHostname("myesp32");
// No authentication by default
// ArduinoOTA.setPassword("admin");
// Password can be set with it's md5 value as well
// MD5(admin) = 21232f297a57a5a743894a0e4a801fc3
// ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3");
ArduinoOTA
.onStart([]() {
String type;
if (ArduinoOTA.getCommand() == U_FLASH)
type = "sketch";
else // U_SPIFFS
type = "filesystem";
// NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
Serial.println("Start updating " + type);
})
.onEnd([]() {
Serial.println("nEnd");
})
.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("Progress: %u%%r", (progress / (total / 100)));
})
.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
else if (error == OTA_END_ERROR) Serial.println("End Failed");
});
ArduinoOTA.begin();
Serial.println("Ready");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
}
void loop() {
ArduinoOTA.handle();
}
3,2、Web Browser 方案固件更新
该方案使用场景:
直接从 Arduino IDE 加载是不方便或不可能的
用户无法从外部更新服务器公开 OTA 的模块
在设置更新服务器不可行时,将部署后的更新提供给少量模块
参考实例:
#include
#include
#include
#include
#include
const char* host = "esp32";
const char* ssid = "xxx";
const char* password = "xxxx";
WebServer server(80);
/*
* Login page
*/
const char* loginIndex =
"
|