Зачем OTA
Устройство висит под потолком или закопано в теплице — разбирать для прошивки не хочется. OTA (Over-The-Air) позволяет заливать новую прошивку по Wi-Fi: из Arduino IDE напрямую, через веб-страницу или самим устройством с вашего сервера.
ESP32 имеет две области прошивки (OTA-partition): при обновлении новая прошивка льётся в неактивную, проверяется и только потом переключается — при сбое устройство откатывается на рабочую версию. Безопасный механизм из коробки.
ArduinoOTA: прошивка из IDE
Библиотека ArduinoOTA идёт в комплекте ядра ESP32:
#include <WiFi.h>
#include <ArduinoOTA.h>
void setup() {
WiFi.begin("SSID", "pass");
while (WiFi.status() != WL_CONNECTED) delay(500);
ArduinoOTA.setHostname("esp32-device");
// опционально: пароль на OTA
// ArduinoOTA.setPassword("admin");
ArduinoOTA.onStart([]() { Serial.println("OTA старт"); });
ArduinoOTA.onEnd([]() { Serial.println("OTA конец"); });
ArduinoOTA.onProgress([](unsigned p, unsigned t) {
Serial.printf("прогресс: %u%%\n", p * 100 / t);
});
ArduinoOTA.onError([](ota_error_t e) {
Serial.printf("ошибка OTA: %u\n", e);
});
ArduinoOTA.begin();
}
void loop() {
ArduinoOTA.handle(); // обязательно — обрабатывает входящие OTA
// остальной код
}
После заливки через USB один раз — устройство появляется в списке портов Arduino IDE по сети (IP-адрес). Выбираете его и жмёте «Загрузить» — прошивка идёт по воздуху.
ElegantOTA: веб-интерфейс для бинарника
Для ручной загрузки .bin через браузер удобнее ElegantOTA — поднимает на устройстве страницу /update с формой:
#include <WiFi.h>
#include <WebServer.h>
#include <ElegantOTA.h>
WebServer server(80);
void setup() {
WiFi.begin("SSID", "pass");
while (WiFi.status() != WL_CONNECTED) delay(500);
server.on("/", []() { server.send(200, "text/plain", "OK"); });
ElegantOTA.begin(&server); // /update — страница загрузки
server.begin();
}
void loop() {
server.handleClient();
ElegantOTA.loop();
}
Прошиваете новый .bin через http://esp32.local/update — выбираете файл, жмёте Update. Бинарник собирается в IDE через «Export compiled binary» (Скетч → Экспорт скомпилированного).
HTTP-OTA: самообновление с сервера
Устройство само проверяет наличие новой прошивки на вашем сервере:
#include <HTTPClient.h>
#include <Update.h>
void checkUpdate() {
HTTPClient http;
http.begin("https://yourserver.ru/firmware.bin");
if (http.GET() == 200) {
int len = http.getSize();
WiFiClient* stream = http.getStreamPtr();
if (Update.begin(len)) {
Update.writeStream(*stream);
if (Update.end()) {
Serial.println("обновлено — перезагрузка");
ESP.restart();
}
}
}
http.end();
}
Для «проверь версию и обновись если новее» добавьте endpoint, отдающий номер актуальной версии — устройство сравнивает со своей и качает прошивку при необходимости.
Разметка памяти для OTA
OTA требует двух OTA-partition — место для старой и новой прошивки одновременно. При выборе схемы памяти в IDE выбирайте с OTA («Minimal SPIFFS (1.9MB APP with OTA)» и т.п.) — иначе места не хватит.
Проверка:
const esp_partition_t* running = esp_ota_get_running_partition();
Serial.println(running->label); // ota_0 или ota_1
Откат при неудачной прошивке
Если новая прошивка зависает на старте — механизм rollback сам вернёт рабочую:
// в начале setup новой прошивки, до полной готовности:
if (esp_ota_check_rollback_possible()) {
// если мы сюда дошли — прошивка живая, помечаем как валидную
esp_ota_mark_app_valid_cancel_rollback();
}
Без mark_app_valid при сбое ESP вернётся на предыдущую — защита от «кирпича» при OTA.
Безопасность
- Пароль на OTA:
ArduinoOTA.setPassword()— иначе любой в сети перепрошьёт устройство. - HTTPS для сервера: самоподписанный или Let's Encrypt, проверка сертификата.
- Подпись прошивки: для продукта — secure boot + подписанные образы, чтобы чужой .bin не приняли.
- OTA только из доверенной сети: не выставляйте порт наружу.
Типичные проблемы
- Не видно устройство в IDE:
ArduinoOTA.handle()забыт, firewall режет порт 3232, устройство в другой подсети. - Не хватает места: схема памяти без OTA — выберите partition scheme с двумя app-партициями.
- Обновился и завис: нет
mark_app_valid— откатился автоматом; добавьте проверку. - ElegantOTA не грузит .bin: формат не тот (нужен compiled binary, не .elf), память кончилась.
- Прерывание на середине: Wi-Fi нестабилен — повторная попытка автоматическая, старая прошивка цела.
Итог
OTA — это свобода от проводов: ArduinoOTA для IDE, ElegantOTA для ручной загрузки через браузер, HTTP-OTA для самообновления по расписанию. Помните: partition scheme с OTA, handle() в loop, пароль на обновление и mark_app_valid против кирпичей. Прошивка по воздуху — признак зрелого устройства.
Полный пример: HTTP-OTA с проверкой версии
#include <HTTPClient.h>
#include <Update.h>
const char* FW_VERSION = "1.2.0";
void checkUpdate() {
HTTPClient http;
http.begin("https://yourserver.ru/esp32/firmware.bin");
http.addHeader("X-FW-Version", FW_VERSION);
int code = http.GET();
if (code != 200) { http.end(); return; }
int len = http.getSize();
WiFiClient* stream = http.getStreamPtr();
if (Update.begin(len)) {
size_t written = Update.writeStream(*stream);
if (written == len && Update.end()) {
Serial.println("обновлено — перезагрузка");
ESP.restart();
}
}
http.end();
}
Сервер отдаёт .bin только если X-FW-Version старее актуальной — устройство не качает зря.
ArduinoOTA: полный скетч
#include <WiFi.h>
#include <ArduinoOTA.h>
void setup() {
WiFi.begin("SSID", "pass");
while (WiFi.status() != WL_CONNECTED) delay(500);
ArduinoOTA
.setHostname("esp32-sensor")
.setPassword("admin");
ArduinoOTA.onStart([] {
String type = ArduinoOTA.getCommand() == U_FLASH ? "скетч" : "файловая система";
Serial.printf("OTA старт: %s\n", type.c_str());
});
ArduinoOTA.onEnd([] { Serial.println("OTA конец"); });
ArduinoOTA.onProgress([](unsigned p, unsigned t) {
Serial.printf("OTA: %u%%\r", p * 100 / t);
});
ArduinoOTA.onError([](ota_error_t e) {
Serial.printf("OTA ошибка: %u\n", e);
});
ArduinoOTA.begin();
}
void loop() {
ArduinoOTA.handle();
}
Откат при битой прошивке
// в начале setup новой прошивки — до полной готовности
if (esp_ota_check_rollback_possible()) {
// запустились после OTA — помечаем валидной
esp_ota_mark_app_valid_cancel_rollback();
}
Если новая прошивка зависла до mark_app_valid — при следующей перезагрузке ESP вернётся на предыдущую.
Безопасность
- Пароль на OTA:
ArduinoOTA.setPassword(). - HTTPS для сервера с прошивкой.
- Не выставляйте OTA-порт наружу — только локальная сеть.
- Для продукта — secure boot + подписанные образы.
Схемы памяти для OTA
OTA требует двух партиций приложения — место под старую и новую прошивку. В Arduino IDE выберите Partition Scheme с OTA («Minimal SPIFFS (1.9MB APP with OTA)» или подобное) — иначе места не хватит.
Частые ошибки
ArduinoOTA.handle()забыт — устройство не видно в IDE.- Схема без OTA — обновление не влезает.
- Без
mark_app_valid— откат после первой же ошибки. - Прерывание Wi-Fi — повторная попытка автоматическая, старая прошивка цела.
- Забыли пароль — любой в сети может перепрошить.