Инструкции

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

12 мин чтения·Август 2026·RoboArm R1

Пока плата лежит на столе, кабель не мешает. Как только она оказывается в корпусе под потолком или внутри робота, каждое исправление превращается в разборку. OTA — Over The Air — позволяет заливать прошивку по Wi-Fi. Разбираем три способа и главное требование, без которого ни один из них не заработает.

Примеры проверены на пакете плат esp32 by Espressif Systems 3.x.

Сначала — схема разделов

Обновление по воздуху устроено так: новая прошивка пишется во второй раздел приложения, и только после успешной записи загрузчик переключается на него. Значит, разделов приложения должно быть два.

Откройте Tools → Partition Scheme и убедитесь, что выбранная схема поддерживает OTA:

СхемаПриложениеOTA
Default 4MB with spiffs2 × 1,25 МБДа
Minimal SPIFFS (1.9MB APP)2 × 1,875 МБДа
No FS 4MB (2MB APP x2)2 × 2 МБДа
No OTA (2MB APP/2MB SPIFFS)2 МБНет
Huge APP (3MB No OTA/1MB SPIFFS)3 МБНет
⚠️

Со схемой «Huge APP» или любой «No OTA» скетч скомпилируется и зальётся по кабелю как обычно, а вот попытка обновиться по воздуху упадёт уже во время работы: второго раздела нет, и Update.begin() вернёт ошибку No suitable partition found. Это частая и неочевидная причина «OTA не работает».

Отсюда же следует, что доступный размер прошивки равен размеру одного раздела, а не их сумме. Если скетч не влезает в 1,25 МБ схемы по умолчанию, берите Minimal SPIFFS — 1,875 МБ на приложение ценой урезанной до 128 КБ файловой системы. Это последний вариант, где OTA ещё есть. Подробнее про разделы — в статье Хранение данных на ESP32.

Способ 1: ArduinoOTA — заливка прямо из среды

Самый удобный вариант для разработки: плата появляется в списке портов Arduino IDE как сетевой порт, и заливка идёт привычной кнопкой Upload.

#include <WiFi.h>
#include <ESPmDNS.h>
#include <NetworkUdp.h>
#include <ArduinoOTA.h>

const char* ssid = "MyNetwork";
const char* password = "MyPassword";

void setup() {
  Serial.begin(115200);

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("Не удалось подключиться, перезагрузка");
    delay(5000);
    ESP.restart();
  }

  ArduinoOTA.setHostname("roboarm");
  ArduinoOTA.setPassword("secret");

  ArduinoOTA
    .onStart([]() {
      Serial.println("Начало обновления");
    })
    .onProgress([](unsigned int progress, unsigned int total) {
      Serial.printf("Прогресс: %u%%\n", progress / (total / 100));
    })
    .onEnd([]() {
      Serial.println("\nГотово");
    })
    .onError([](ota_error_t error) {
      Serial.printf("Ошибка [%u]\n", error);
    });

  ArduinoOTA.begin();
  Serial.println(WiFi.localIP());
}

void loop() {
  ArduinoOTA.handle();   // обязательно вызывать в каждом проходе
}

После первой заливки по кабелю плата появится в Tools → Port в разделе сетевых портов под именем, заданным в setHostname(). Дальше обновляйте как обычно.

⚠️

В ядре 3.x подключается NetworkUdp.h, а не WiFiUdp.h, как было в версии 2.x. Если переносите старый скетч и получаете ошибку о ненайденном заголовке — дело именно в этом.

Вызов ArduinoOTA.handle() должен выполняться регулярно. Если loop() надолго блокируется, плата перестанет отвечать на запросы обновления — ещё одна причина писать неблокирующий код.

Способ 2: загрузка через веб-страницу

Когда устройство уже у пользователя, среда разработки недоступна. Тогда прошивку загружают через браузер: страница с полем выбора файла и кнопкой.

Писать это руками не нужно — в ядре есть готовая библиотека, которая навешивает обработчик на существующий веб-сервер:

#include <WiFi.h>
#include <WebServer.h>
#include <HTTPUpdateServer.h>

WebServer server(80);
HTTPUpdateServer updateServer;

void setup() {
  Serial.begin(115200);

  WiFi.mode(WIFI_STA);
  WiFi.begin("MyNetwork", "MyPassword");
  while (WiFi.status() != WL_CONNECTED) delay(500);

  server.on("/", []() {
    server.send(200, "text/html", "<a href='/update'>Обновить прошивку</a>");
  });

  updateServer.setup(&server, "/update", "admin", "secret");
  server.begin();

  Serial.print("Откройте http://");
  Serial.print(WiFi.localIP());
  Serial.println("/update");
}

void loop() {
  server.handleClient();
}

Готовый файл прошивки берётся из Arduino IDE: Sketch → Export Compiled Binary, затем ищите .bin в папке скетча.

Логин и пароль в setup() — не формальность: без них любой в той же сети сможет залить на устройство что угодно.

Способ 3: обновление с сервера

Для парка устройств удобнее, когда плата сама проверяет наличие новой версии и скачивает её:

#include <WiFi.h>
#include <HTTPUpdate.h>

void checkForUpdate() {
  NetworkClient client;

  httpUpdate.onProgress([](int current, int total) {
    Serial.printf("Загружено %d из %d\n", current, total);
  });

  t_httpUpdate_return result =
      httpUpdate.update(client, "http://example.com/firmware/robot.bin");

  switch (result) {
    case HTTP_UPDATE_OK:
      Serial.println("Обновлено, перезагрузка");
      break;
    case HTTP_UPDATE_NO_UPDATES:
      Serial.println("Обновлений нет");
      break;
    case HTTP_UPDATE_FAILED:
      Serial.printf("Ошибка (%d): %s\n",
                    httpUpdate.getLastError(),
                    httpUpdate.getLastErrorString().c_str());
      break;
  }
}

Обратите внимание на имена: класс называется HTTPUpdate, готовый экземпляр — httpUpdate со строчной буквы. В экосистеме ESP8266 он назывался иначе (ESPhttpUpdate), и примеры оттуда не подойдут.

По умолчанию после успешного обновления плата перезагружается сама. Отключается это вызовом httpUpdate.rebootOnUpdate(false).

Откат на рабочую версию

Что если новая прошивка окажется нерабочей и устройство перестанет выходить в сеть? На этот случай есть механизм отката: свежезалитый образ считается непроверенным, и если он не подтвердит свою работоспособность до перезагрузки, загрузчик вернётся на предыдущий.

В Arduino этот механизм уже включён, и ядро подтверждает образ автоматически ещё до вызова setup(). Специально ничего делать не нужно — базовая защита работает из коробки.

Но автоматическое подтверждение происходит слишком рано: прошивка объявляется рабочей до того, как выяснится, подключается ли она к сети. Чтобы отложить решение, ядро предлагает переопределить две слабые функции:

#include <esp_ota_ops.h>

// откладываем автоматическое подтверждение
bool verifyRollbackLater() {
  return true;
}

void setup() {
  Serial.begin(115200);
  WiFi.begin("MyNetwork", "MyPassword");

  if (WiFi.waitForConnectResult() == WL_CONNECTED) {
    esp_ota_mark_app_valid_cancel_rollback();       // самопроверка пройдена
    Serial.println("Прошивка подтверждена");
  } else {
    esp_ota_mark_app_invalid_rollback_and_reboot(); // откат, не возвращается
  }
}

Теперь прошивка, которая не смогла подключиться к сети, автоматически откатится на предыдущую рабочую версию. Для устройства в труднодоступном месте это разница между поездкой с ноутбуком и отсутствием проблемы.

Безопасность

OTA — это удалённая замена кода на вашем устройстве. Примеры выше рассчитаны на доверенную домашнюю сеть; для устройства, доступного из интернета, их недостаточно.

MD5 — это не защита. Методы setMD5sum() у httpUpdate и setMD5() у Update ловят повреждение файла при передаче, и только. Против злоумышленника они бесполезны: раз прошивка идёт по обычному HTTP, подменить вместе с ней и контрольную сумму — задача того же уровня сложности. То же с HTTP Basic Auth: пароль передаётся открытым текстом и перехватывается вместе со всем остальным.

Что действительно защищает:

  • HTTPS с проверкой сертификата вместо HTTP — тогда подменить содержимое по пути нельзя. Используйте NetworkClientSecure с закреплённым корневым сертификатом.
  • Подпись образа. ESP-IDF поддерживает проверку цифровой подписи прошивки перед установкой, а Secure Boot — ещё и на уровне загрузчика. Это единственный механизм, который защищает, даже если канал скомпрометирован.
  • Пароль в любом случае, и у ArduinoOTA, и у веб-интерфейса — он отсекает случайный доступ соседа по сети.
  • Не оставляйте OTA включённым «на всякий случай» в устройстве, которое не планируется обновлять.
⚠️

Простое правило: если устройство доступно только из домашней сети и обновляете его вы — примеров выше достаточно. Если оно стоит у заказчика или смотрит в интернет, поднимайте HTTPS и подпись образа, иначе OTA превращается из удобства в способ залить на устройство чужой код.

Частые проблемы

Плата не появляется в списке сетевых портов. Проверьте, что компьютер и плата в одной сети, что вызывается ArduinoOTA.handle() в loop(), и что mDNS работает — в некоторых сетях он блокируется, тогда вместо имени указывайте IP-адрес вручную.

Обновление начинается и обрывается. Чаще всего это loop(), который блокируется надолго. Или нехватка места: прошивка должна поместиться в один раздел.

No suitable partition found. Выбрана схема разделов без OTA, см. начало статьи.

После обновления плата перезагружается по кругу. Новая прошивка нерабочая — именно для этого случая нужен механизм отката выше.

Что дальше

Рекомендуемые продукты

Связанные статьи