На практике часто возникает необходимость обновить прошивку для устройства Интернета вещей (IoT) на базе ESP32. Допустим, прибор установлен под потолком и скрыт подвесной панелью. Если подобных изделий много и требуется частое обновление, то для таких случаев существует технология обновления прошивки «по воздуху»: Over-The-Air (OTA). В рамках данной статьи мы подробно рассмотрим инструкции как прошить ESP32 через Arduino IDE и через веб-интерфейс с применением данной технологии.
Содержание
Общее описание
OTA – это технология обновления встроенного программного обеспечения (ПО) микроконтроллерного устройства через беспроводную сеть (Wi-Fi, Bluetooth, мобильная связь и т.д.), при которой новый бинарный файл записывается во flash-память контроллера без физического проводного подключения.
У ESP32 во flash-памяти есть два раздела: с текущим работающим ПО и резервный. При обновлении «по воздуху» новый бинарный файл загружается в резервный раздел, после чего система меняет загрузочный указатель и перезагружается.
Программа с OTA занимает много flash-памяти. Для NodeMCU-32S (ESP-WROOM-32, 4 МБ памяти) минимальная программа уже потребляет более 70%. В Arduino IDE в меню «Инструменты» можно найти (но не для всех моделей микроконтроллеров) пункт «Partition Scheme» (в старой версии IDE назывался «Схема разделов»). Там выбирают размер flash-памяти под прошивку: стандартный вариант Default_4MB_with_spiffs, а для OTA лучше выбрать Minimal_SPIFFS, поскольку он освобождает максимум места под код, убирая файловую систему. Вариант No_OTA не подойдёт, так как при нём обновление «по воздуху» работать не будет.
Чтобы микроконтроллер мог принимать OTA-обновления, в самом встроенном ПО должен быть код, который запускает специальный сервер. Библиотека ArduinoOTA поднимает TCP-сервер на порту 3232 и слушает команды от Arduino IDE. Когда среда отправляет бинарный файл, этот сервер принимает его и сохраняет в резервный раздел. Первая прошивка всегда заливается по USB, а все последующие – уже «по воздуху».
Прошивка через ArduinoOTA
Чтобы прошить ESP32 «по воздуху», нужно выполнить следующие шаги:
1) Убедитесь, что среда программирования Arduino IDE настроена на работу с Вашей платой
Откройте настройки среды, выбрав Файл –> Параметры –> Дополнительные ссылки для менеджера плат. Там должна быть указана ссылка:
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
В менеджере плат выберите конкретно Вашу модель. Классическая NodeMCU-32S на базе модуля ESP-WROOM-32 (на 30 или 38 ножек):
Или же ESP32-C3 Super Mini:
2) Установить библиотеку ArduinoOTA
Установить библиотеку ArduinoOTA можно двумя способами:
– скачать архив с репозитория на GitHub. Распаковать его, папку переименовать (удалить в конце «master»). После папку поместить в директорию с библиотеками среды программирования (по умолчанию имеет адрес C:\Users\имя_пользователя\Documents\Arduino\libraries). После этих манипуляций перезагрузить среду, если до этого она была открыта;
– установить при помощи менеджера библиотек:
Перезагрузив среду программирования, открываем базовый пример из библиотеки ArduinoOTA – BasicOTA:
В ранних версиях библиотеки, кроме этого примера, был файл OTAWebUpdater, который поднимал локальный веб-сервер, позволявший загружать в контроллер файл с новым ПО через веб-интерфейс в браузере на компьютере. Данный метод рассматривается ниже в разделе «OTA-обновление через веб-интерфейс».
3) Сохраните пример BasicOTA отдельно под новым именем
Скетч содержит код, который запускает специальный сервер, который потом позволит ESP32 принимать ПО по беспроводному каналу.
Укажите данные для Вашей Wi-Fi сети, к которой будет подключаться компьютер и отладочная плата:
const char *ssid = "имя_сети";
const char *password = "пароль_к_сети";
4) Скомпилируйте скетч и прошейте плату контроллера через USB-кабель
Завершив загрузку, откройте монитор порта, чтобы убедиться, что контроллер подключился к указанной Вами сети Wi-Fi.
Если Вы работаете с ESP32-C3, то проверьте, что в меню «Инструменты» среды программирования раздел «USB CDC On Boot» имеет значение «Enabled». Это активирует виртуальный COM-порт для встроенного в контроллер USB-порт, что позволит читать сообщения через монитор порта:
Если плата не будет подключаться к беспроводной сети, то в мониторе порта постоянно будет отображаться соответствующее сообщение:
При успешном соединении будет указан IP-адрес Вашей платы:
5) Пояснения к базовой части кода
Вы сохранили пример BasicOTA как отдельный файл, с которым можно дальше работать. Программа содержит необходимые директивы для загрузчика, и они должны быть теперь всегда во всех версиях скетчей, которые будут в будущем загружаться на плату. То есть объявленный в коде OTA-сервер является обязательным компонентом для каждой версии ПО, которая предназначена для беспроводного обновления.
После перезагрузки контроллер выполняет активную прошивку. И если в ней отсутствует код обработки запросов, то устройство теряет способность принимать последующие обновления по сети, что вынуждает вновь подключаться через USB-кабель к плате для восстановления.
В коде программы обязательно должны присутствовать следующие строки:
– подключение библиотек
#include <WiFi.h> // подключение к беспроводной сети Wi-Fi
#include <ESPmDNS.h> // обнаружение ESP32 в сети Wi-Fi для Arduino IDE
#include <NetworkUp.h> // внутренняя работа ArduinoOTA
#include <ArduinoOTA.h>
– функция инициализации OTA-сервера
ArduinoOTA.begin();
– вызов обработчика в главном бесконечном цикле loop()
ArduinoOTA.handle();
В примере BasicOTA есть вот такой фрагмент:
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");
}
});
Представленные методы (onStart, onEnd, onProgress, onError) – обработчики событий (колбэк-функции) OTA-процесса, которые вызываются автоматически в разные моменты обновления. Они нужны для мониторинга и отладки. Благодаря им в мониторе порта видны сообщения, указывающие на прохождение всех этапов процесса перепрошивки. Без них обновление всё равно будет работать, поэтому они необязательны.
В базовом примере также есть вот такой фрагмент кода:
// 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");
Это настройки OTA-сервера. Они закомментированы, потому что эти настройки уже работают по умолчанию. При необходимости, их можно изменить по своему усмотрению:
– setPort(3232): порт, через который среда программирования подключается к микроконтроллеру для заливки кода;
– setHostName(«myesp32»): имя устройства в сети. По умолчанию имеет название «esp3232» с припиской MAC-адреса;
– setPassword(«admin»): это пароль для защиты от случайного или чужого ПО. По умолчанию пароля нет;
– setPasswordHash(«21232f…»): это тот же пароль, но в зашифрованном виде (используется MD5-хеш).
Для экспериментов можно ничего не менять.
6) Добавьте Вашу логику в базовый код
В отдельно сохранённый Вами файл добавим новую логику, которая явно будет показывать, что контроллер действительно был «прошит по воздуху». Подключим к отладочной плате OLED-дисплей и добавим в код команду, которая выводит на экран какую-нибудь надпись.
В базовом примере в функции setup() прописано, что при инициализации микроконтроллера осуществляется подключение к беспроводной сети. Но если нужная точка доступа не будет найдена, то система будет постоянно пытаться переподключаться:
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.println("Connection Failed! Rebooting...");
delay(5000);
ESP.restart();
}
Поэтому предлагается выполнять проверку, которая не будет блокировать работу программы. Причём не только в рамках инициализации setup(), но и в главном бесконечном цикле loop(). При инициализации система пробует подключиться, и если не получается, выдаёт сообщение на дисплей и продолжает работу далее, переходя в главный бесконечный цикл. В нём каждые 60 секунд осуществляется попытка переподключиться.
В примере BasicOTA есть фрагмент, в котором описаны обработчики событий (onStart, onEnd, onProgress, onError). Сообщения от них выводятся через монитор порта только тогда, когда микроконтроллер подключен по USB-кабелю и выбран физический COM-порт. Если используется OTA-загрузка (выбран IP-адрес из списка портов), эти сообщения не отображаются, поскольку монитор порта не работает с сетевыми подключениями (поэтому данную часть кода можно удалить):
Учитывая все указанные нюансы, уберём обработчики событий и добавим механизм неблокирующего автоматического переподключения к Wi-Fi. Ниже представлен предлагаемый вариант кода, который можно уже использовать как основу для будущих учебных проектов:
#include <WiFi.h> // Для работы с Wi-Fi
#include <ESPmDNS.h> // Для обнаружения устройства в сети по имени (mDNS)
#include <NetworkUdp.h> // Для UDP-соединений (используется OTA)
#include <ArduinoOTA.h> // Библиотека для обновления прошивки по воздуху
#include <Wire.h> // Для I2C-шины (подключение OLED)
#include <Adafruit_GFX.h> // Графическая библиотека для дисплеев
#include <Adafruit_SSD1306.h> // Драйвер для OLED-дисплея SSD1306
// Настройки Wi-Fi сети
const char *ssid = "имя_сети";
const char *password = "пароль_сети";
// Настройки OLED-дисплея (размеры 128x64, I2C-адрес 0x3C)
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Глобальные переменные для управления Wi-Fi
bool wifiConnected = false; // Флаг: подключены ли к Wi-Fi
unsigned long lastWiFiCheck = 0; // Время последней проверки Wi-Fi
const unsigned long CHECK_INTERVAL = 60000; // Интервал проверки: 60 секунд
// Функция первичной инициализации контроллера
void setup() {
// Инициализация OLED-дисплея
// Если дисплей не отвечает по адресу 0x3C - зависаем в бесконечном цикле
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
while (true) {
delay(100);
}
}
// Настройка встроенного светодиода как выхода
pinMode(LED_BUILTIN, OUTPUT);
// Вывод на OLED: "Initialization"
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("Initialization");
display.display();
// Попытка подключения к Wi-Fi (неблокирующий режим)
WiFi.mode(WIFI_STA); // Режим клиента
WiFi.begin(ssid, password); // Запуск подключения
// Ждём до 5 секунд (20 попыток по 250 мс)
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(250);
attempts++;
display.print("."); // Визуальный индикатор ожидания на OLED
display.display();
}
// Проверка результата подключения
if (WiFi.status() == WL_CONNECTED) {
wifiConnected = true;
// Вывод информации об успешном подключении на OLED
display.setCursor(0, 20);
display.println("Wi-Fi connected!");
display.setCursor(0, 40);
display.println("IP address: ");
display.setCursor(0, 60);
display.println(WiFi.localIP());
display.display();
// Запуск OTA-сервера
ArduinoOTA.begin();
} else {
wifiConnected = false;
// Вывод сообщения об ошибке подключения
display.setCursor(0, 20);
display.println("WiFi not connected.");
display.setCursor(0, 40);
display.println("Will retry later.");
display.display();
}
// Основной экран (выводится всегда, независимо от Wi-Fi)
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("SPRYTRON.RU");
display.setCursor(0, 20);
display.println("Ready to work!");
if (!wifiConnected) {
display.setCursor(0, 40);
display.println("No Wi-Fi");
} else {
display.setCursor(0, 40);
display.println("Wi-Fi OK");
}
display.display();
// Запоминаем время последней проверки Wi-Fi
lastWiFiCheck = millis();
}
// Главный бесконечный цикл
void loop() {
// 1. Обработка OTA-событий (только если есть Wi-Fi)
if (wifiConnected) {
ArduinoOTA.handle(); // Слушает входящие запросы на обновление
}
// 2. Периодическая проверка Wi-Fi (каждые 60 секунд)
unsigned long currentMillis = millis();
if (currentMillis - lastWiFiCheck >= CHECK_INTERVAL) {
lastWiFiCheck = currentMillis;
// 2.1 Если соединение потеряно - пытаемся переподключиться
if (WiFi.status() != WL_CONNECTED) {
// Вывод на OLED: "Wi-Fi lost! Reconnecting..."
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("Wi-Fi lost!");
display.setCursor(0, 20);
display.println("Reconnecting...");
wifiConnected = false;
display.setCursor(0, 40);
display.println("No Wi-Fi");
display.display();
WiFi.reconnect(); // Переподключение
}
// 2.2 Если раньше не было сети, но теперь она появилась
else if (!wifiConnected) {
if (WiFi.status() == WL_CONNECTED) {
wifiConnected = true;
// Вывод на OLED: "Wi-Fi reconnected!"
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("Wi-Fi reconnected!");
display.setCursor(0, 40);
display.println("Wi-Fi OK");
display.display();
// Запуск OTA (если ещё не запущен)
ArduinoOTA.begin();
}
}
}
// 3. Основной код приложения.
// Работает всегда, даже без Wi-Fi
digitalWrite(LED_BUILTIN, HIGH); // Включаем встроенный светодиод
delay(500);
digitalWrite(LED_BUILTIN, LOW); // Выключаем встроенный светодиод
delay(500);
}
7) Подайте питание от внешнего источника и подключитесь по Wi-Fi
Используйте тот же USB-кабель, подключив к зарядному устройству для смартфона на 5 В.
Я использовал смартфон как точку доступа. Соответственно, чётко будет видно, какие устройства подключены.
Подаём питание на плату. Ждём, пока загрузится и подключится к Wi-Fi (фиксируем это на смартфоне):


Далее подключаем ноутбук (b-bb101) к специально созданной точке доступа:

В меню «Tools/Инструменты» в разделе «Port/Порт» отобразятся сетевое имя платы и её IP-адрес в локальной сети. Если в списке портов ничего не отобразится, то перезагрузите среду программирования после того, как сама плата загрузится, будучи запитанной от отдельного блока питания.
8) Загрузите Ваш новый скетч в плату «по воздуху»
При попытке загрузить ПО на плату появится диалоговое окно, в котором потребуется ввести пароль аутентификации ArduinoOTA.
По умолчанию пароля нет, и в предложенном скетче ничего не объявлялось:
// При желании можно добавить пароль аутентификации доступа к OTA-серверу
// No authentication by default
ArduinoOTA.setPassword("admin");
Если нет пароля, то достаточно просто ввести хотя бы 1 символ (поле нельзя оставить пустым), чтобы активировать кнопку «UPLOAD»:


В случае успешной загрузки контроллер начнёт исполнять новый скетч. При инициализации на OLED-дисплее отобразится соответствующий процесс, включающий в себя подключение к точке доступа:

Если в течение 5 секунд не удастся подключиться, то программа не останавливается и выполняется дальше, указав, что не удалось установить связь:

Если же соединение произведено успешно, то отобразится соответствующее сообщение:

В программе предусмотрена проверка через каждые 60 секунд: есть ли соединение. При разрыве соединения выполняется попытка переподключения:

В случае успешного восстановления будет отображено соответствующее сообщение:

Данный алгоритм позволит изделию выполнять основные функции вне зависимости от состояния подключения к беспроводной сети.
OTA-обновление через веб-интерфейс
Рассмотрим альтернативный вариант, который подразумевает беспроводную заливку скетча, используя веб-интерфейс:
1) Установите библиотеку ElegantOTA от Ayush Sharma:
Если не устанавливается через встроенный менеджер, скачайте архив с репозитория на GitHub.
2) Добавить в Ваш скетч следующие строки:
– объявление библиотек
#include <WiFi.h>
#include <WebServer.h> // Синхронный веб-сервер, предустановлен в Arduino IDE
#include <ElegantOTA.h> // Сама библиотека OTA
– параметры сети
const char* ssid = "имя_сети";
const char* password = "пароль_сети";
– создание веб-сервера
WebServer server(80); // Создаем веб-сервер на 80-м порту
– подключение к точке доступа
// Подключение к Wi-Fi
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("Connected!");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
– запуск сервера
ElegantOTA.begin(&server);
server.begin();
– обработка запросов в бесконечном цикле loop()
void loop() {
// Обработка клиентов веб-сервера (синхронный режим)
server.handleClient();
ElegantOTA.loop(); // обработки OTA-запросов
}
Каждая новая прошивка, которую вы загружаете через веб-интерфейс, сама должна содержать код для OTA. Если вы загрузите ПО без библиотеки ElegantOTA и вызова ElegantOTA.loop(), то устройство потеряет способность к обновлению “по воздуху”, и вам снова придется подключать его по USB для восстановления.
В примере используется синхронный веб-сервер (webServer), и для учебных целей данный вариант проще и полностью работоспособен. Но в реальной практике рекомендуется использовать асинхронную версию (AsyncWebServer).
3) Минимальный базовый скетч для заливки по USB-кабелю представлен ниже:
#include <WiFi.h> // Для работы с Wi-Fi
#include <WebServer.h> // Синхронный веб-сервер (нужен для ElegantOTA)
#include <ElegantOTA.h> // Библиотека для OTA через веб-интерфейс
#include <Wire.h> // Для I2C-шины (подключение OLED)
#include <Adafruit_GFX.h> // Графическая библиотека для дисплеев
#include <Adafruit_SSD1306.h> // Драйвер для OLED-дисплея SSD1306
// Настройки Wi-Fi сети
const char* ssid = "OTA_update_test"; // Имя твоей Wi-Fi сети
const char* password = "evangelion_2015"; // Пароль от неё
// Создаём веб-сервер на порту 80
WebServer server(80);
// Функция setup() - инициализация контроллера при включении
void setup() {
// Запускаем отладку через USB (можно удалить после настройки)
Serial.begin(115200);
Serial.println("Loading...");
// Подключаемся к Wi-Fi
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print("Connecting to Wi-Fi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWi-Fi connected!");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
// Запускаем ElegantOTA
ElegantOTA.begin(&server);
// Если требуется поставить пароль на вход в веб-интерфейс:
// ElegantOTA.setAuth("admin", "password");
// Запускаем веб-сервер
server.begin();
Serial.println("HTTP server started!");
Serial.println("Open http://" + WiFi.localIP().toString() + "/update");
}
// Функция loop() - главный бесконечный цикл
void loop() {
// Обработка клиентов веб-сервера (синхронный режим)
server.handleClient();
// Обработка OTA-запросов
ElegantOTA.loop();
// Здесь может быть Ваш основной код
// (он будет работать даже во время OTA-обновления)
}
Загрузите его через USB-кабель на ESP32. В мониторе порта отобразится IP-адрес отладочной платы:
В компьютере, который должен быть подключен к той же точке доступа, что и отладочная плата, откройте в браузере веб-страничку по адресу, указанному в мониторе порта:

4) Сохраните файл под новым именем. В него тоже добавим OLED-дисплей и мигание встроенным светодиодом:
#include <WiFi.h> // Для работы с Wi-Fi
#include <WebServer.h> // Синхронный веб-сервер (нужен для ElegantOTA)
#include <ElegantOTA.h> // Библиотека для OTA через веб-интерфейс
#include <Wire.h> // Для I2C-шины (подключение OLED)
#include <Adafruit_GFX.h> // Графическая библиотека для дисплеев
#include <Adafruit_SSD1306.h> // Драйвер для OLED-дисплея SSD1306
// Настройки OLED-дисплея (размеры 128x64, I2C-адрес 0x3C)
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Глобальные переменные для управления Wi-Fi
bool wifiConnected = false; // Флаг: подключены ли к Wi-Fi
unsigned long lastWiFiCheck = 0; // Время последней проверки Wi-Fi
const unsigned long CHECK_INTERVAL = 60000; // Интервал проверки: 60 секунд
// Настройки Wi-Fi сети
const char* ssid = "имя_сети";
const char* password = "пароль_сети";
// Создаём веб-сервер на порту 80
WebServer server(80);
// Функция setup() - инициализация контроллера
void setup() {
// Инициализация OLED-дисплея
// Если дисплей не отвечает по адресу 0x3C - зависаем в бесконечном цикле
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
while (true) {
delay(100);
}
}
// Вывод на OLED: "Initialization"
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("Initialization");
display.display();
// Настройка встроенного светодиода как выхода
pinMode(LED_BUILTIN, OUTPUT);
// Попытка подключения к Wi-Fi (неблокирующий режим)
WiFi.mode(WIFI_STA); // Режим клиента
WiFi.begin(ssid, password); // Запуск подключения
// Ждём до 5 секунд (20 попыток по 250 мс)
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(250);
attempts++;
display.print("."); // Визуальный индикатор ожидания на OLED
display.display();
}
// Проверка результата подключения
if (WiFi.status() == WL_CONNECTED) {
wifiConnected = true;
// Вывод информации об успешном подключении на OLED
display.setCursor(0, 20);
display.println("Wi-Fi connected!");
display.setCursor(0, 35);
display.println("IP address: ");
display.setCursor(0, 50);
display.println(WiFi.localIP());
display.display();
delay(3000);
// Запускаем ElegantOTA
ElegantOTA.begin(&server);
// При необходимости можно поставить пароль на вход в веб-интерфейс
// ElegantOTA.setAuth("admin", "password");
// Запускаем веб-сервер
server.begin();
} else {
wifiConnected = false;
// Вывод сообщения об ошибке подключения
display.setCursor(0, 20);
display.println("WiFi not connected.");
display.setCursor(0, 40);
display.println("Will retry later.");
display.display();
delay(3000);
}
// Основной экран (выводится всегда, независимо от Wi-Fi)
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("SPRYTRON.RU");
display.setCursor(0, 20);
display.println("Ready to work!");
if (!wifiConnected) {
display.setCursor(0, 40);
display.println("No Wi-Fi");
} else {
display.setCursor(0, 40);
display.println("Wi-Fi OK");
}
display.display();
// Запоминаем время последней проверки Wi-Fi
lastWiFiCheck = millis();
}
// Функция loop() - главный бесконечный цикл
void loop() {
// 1. Обработка OTA-событий (только если есть Wi-Fi)
if (wifiConnected) {
server.handleClient(); // Обработка клиентов веб-сервера (синхронный режим)
ElegantOTA.loop(); // Обработка OTA-запросов
}
// 2. Периодическая проверка Wi-Fi (каждые 60 секунд)
unsigned long currentMillis = millis();
if (currentMillis - lastWiFiCheck >= CHECK_INTERVAL) {
lastWiFiCheck = currentMillis;
// 2.1 Если соединение потеряно — пытаемся переподключиться
if (WiFi.status() != WL_CONNECTED) {
// Вывод на OLED: "Wi-Fi lost! Reconnecting..."
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("Wi-Fi lost!");
display.setCursor(0, 20);
display.println("Reconnecting...");
wifiConnected = false;
display.setCursor(0, 40);
display.println("No Wi-Fi");
display.display();
WiFi.reconnect(); // Переподключение
}
// 2.2 Если раньше не было сети, но теперь она появилась
else if (!wifiConnected) {
if (WiFi.status() == WL_CONNECTED) {
wifiConnected = true;
// Вывод на OLED: "Wi-Fi reconnected!"
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("Wi-Fi reconnected!");
display.setCursor(0, 40);
display.println("Wi-Fi OK");
display.display();
// Запускаем ElegantOTA и веб-сервер, если ещё не запущены
ElegantOTA.begin(&server);
server.begin();
}
}
}
// 3. Основной код приложения.
// Работает всегда, даже без Wi-Fi
digitalWrite(LED_BUILTIN, HIGH); // Включаем встроенный светодиод
delay(500);
digitalWrite(LED_BUILTIN, LOW); // Выключаем встроенный светодиод
delay(500);
}
5) Компиляция и экспорт bin
Скомпилируйте файл, нажав кнопку «Проверить» или комбинацию клавиш Ctrl + R. Если ошибок нет, то создайте bin-файл прошивки, выбрав в меню «Скетч» раздел «Экспортировать скомпилированный бинарный файл» (или комбинация клавиш Alt + Ctrl + S):
В папке с проектом появится директория build, которая содержит нужный бинарный файл:

6) Проверьте подключение к точке доступа
Убедитесь, что отладочная плата и компьютер подключены к одной точке доступа. Доступ к Интернету вовсе не обязателен, ведь это чисто локальная связь. Теперь вновь откройте в браузере веб-страницу, нажмите кнопку «Select File» и выберите файл, который оканчивается на «ino.bin»:

Нажав кнопку «Открыть», автоматически начнётся загрузка:

Через пару секунд заливка завершится:

В случае ошибки (обрыв соединения) также будет оповещение на веб-страничке:

7) Последующие изменения в коде потребуют пересборки bin-файла (шаг 5).
Заключение
Мы рассмотрели два надёжных и актуальных способа беспроводного обновления прошивки для ESP32:
– классический через Arduino IDE (библиотека ArduinoOTA);
– альтернативный через веб-интерфейс (ElegantOTA).
Самые важные моменты:
1) первая прошивка всегда заливается через USB-кабель;
2) каждая последующая прошивка обязана содержать код OTA-сервера;
3) неблокирующее управление подключением к Wi-Fi повышает автономность делает прибор устойчивым к временным сбоям сети.













