Добавить в избранное

OTA-обновление прошивки ESP32 по Wi-Fi: без проводов и разборки

Чтение ≈ 5 мин

Обновление прошивки ESP32 «по воздуху»: ArduinoOTA для загрузки из IDE, AsyncElegantOTA/web-интерфейс для ручной загрузки бинарника и HTTP-OTA с сервера — устройство обновляется само без USB.

Зачем 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 — повторная попытка автоматическая, старая прошивка цела.
  • Забыли пароль — любой в сети может перепрошить.
Поделиться
Понравился материал?

Комментарии

Добавить комментарий