В предыдущих статьях серии «Клуб самоделкиных» мы собрали метеостанцию, автополив, весы и GPS-трекер. У всех проектов общая боль: чтобы поправить одну строчку в коде, нужно ехать в поле с ноутбуком и USB-кабелем. Зимой — ещё ладно, техника рядом. А летом, когда ESP32 стоит в теплице за 15 километров? Когда датчик на удалённом пастбище? Когда трекер в кабине комбайна, который не стоит на месте?
OTA (Over-The-Air) решает эту проблему: вы загружаете новую прошивку по Wi-Fi прямо из дома. Открыли браузер, выбрали файл, нажали «Загрузить» — через 30 секунд ESP32 перезагрузился с новым кодом. Без поездки, без кабеля, без потерянного рабочего дня.
Как это работает: 30 секунд теории
ESP32 запускает крошечный веб-сервер на порту 80. Вы открываете его IP-адрес в браузере и видите форму загрузки. Выбираете скомпилированный файл прошивки (.bin), нажимаете «Загрузить». ESP32 принимает файл, записывает его во второй раздел flash-памяти, проверяет целостность и перезагружается. Если новая прошивка не стартует — ESP32 откатывается на предыдущую. Всё это встроено в библиотеку ArduinoOTA и модуль Update — ничего дополнительного ставить не нужно.
Важно: OTA работает через Wi-Fi. ESP32 должен быть подключён к той же сети, что и ваш компьютер, или быть доступен через интернет (об этом — в конце статьи).
Подготовка: 5 минут на библиотеку
Нам понадобятся две встроенные библиотеки, которые уже есть в пакете ESP32 для Arduino IDE: WebServer и Update. Дополнительно ничего устанавливать не нужно — если вы уже прошивали ESP32 по USB (а если вы читаете эту статью — прошивали), всё готово.
Единственное ограничение: прошивка должна поместиться в половину flash-памяти ESP32. У стандартной платы 4 МБ flash, из которых ~1.5 МБ доступно для приложения. Это более чем достаточно для любого IoT-проекта с датчиками, MQTT и Telegram.
Код: OTA-сервер в 80 строк
Берём любой существующий проект — метеостанцию, автополив, трекер — и добавляем к нему OTA-модуль. Ниже — полный рабочий код. Его можно встроить в любой скетч.
#include <WiFi.h>
#include <WebServer.h>
#include <Update.h>
// === НАСТРОЙКИ === //
const char* WIFI_SSID = "Название_вашей_сети";
const char* WIFI_PASSWORD = "пароль_от_wifi";
const char* OTA_USERNAME = "admin";
const char* OTA_PASSWORD = "секретный_пароль_ota";
WebServer server(80);
// Страница загрузки прошивки
const char* uploadPage = R"rawliteral(
<!DOCTYPE html><html><head>
<meta charset="utf-8">
<title>OTA Update</title>
<style>
body{font-family:sans-serif;max-width:480px;margin:40px auto;padding:20px}
h2{color:#184225} input[type=file]{margin:16px 0}
button{background:#2E7D4A;color:#fff;border:0;padding:12px 24px;
border-radius:8px;font-size:16px;cursor:pointer}
button:hover{background:#184225}
.progress{width:100%;height:24px;background:#eee;border-radius:12px;margin:16px 0}
.bar{height:100%;background:#2E7D4A;border-radius:12px;transition:width 0.3s}
</style>
</head><body>
<h2>🔄 Обновление прошивки</h2>
<form method="POST" action="/update" enctype="multipart/form-data">
<input type="file" name="firmware" accept=".bin"><br>
<button type="submit">Загрузить прошивку</button>
</form>
<p id="status"></p>
</body></html>
)rawliteral";
void setup() {
Serial.begin(115200);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 40) {
delay(500);
attempts++;
}
Serial.println("IP: " + WiFi.localIP().toString());
// Авторизация: без пароля прошивку не зальёшь
server.on("/", HTTP_GET, []() {
if (!server.authenticate(OTA_USERNAME, OTA_PASSWORD)) {
return server.requestAuthentication();
}
server.send(200, "text/html", uploadPage);
});
// Приём прошивки
server.on("/update", HTTP_POST,
// По завершении загрузки
[]() {
server.send(200, "text/plain",
Update.hasError() ? "ОШИБКА обновления!" : "OK! Перезагрузка...");
delay(1000);
ESP.restart();
},
// Приём файла чанками
[]() {
HTTPUpload& upload = server.upload();
if (upload.status == UPLOAD_FILE_START) {
Serial.println("OTA: начало загрузки");
if (!Update.begin(UPDATE_SIZE_UNKNOWN)) {
Update.printError(Serial);
}
} else if (upload.status == UPLOAD_FILE_WRITE) {
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
Update.printError(Serial);
}
} else if (upload.status == UPLOAD_FILE_END) {
if (Update.end(true)) {
Serial.println("OTA: успешно, " + String(upload.totalSize) + " байт");
} else {
Update.printError(Serial);
}
}
}
);
server.begin();
}
void loop() {
server.handleClient();
// ... ваш основной код (датчики, MQTT, алерты) ...
}
Что менять: WIFI_SSID и WIFI_PASSWORD — данные Wi-Fi. OTA_USERNAME и OTA_PASSWORD — логин и пароль для страницы обновления. Никогда не оставляйте OTA без пароля — иначе любой в той же сети сможет залить произвольный код на ваше устройство.
Первая прошивка: единственный раз по кабелю
Парадокс OTA: чтобы прошивать удалённо, первый раз нужно прошить по USB. Это логично — ESP32 «из коробки» не знает о вашей Wi-Fi сети и не имеет OTA-сервера.
Подключаете ESP32 кабелем, заливаете код через Arduino IDE — как обычно. После перезагрузки открываете Serial Monitor, видите строку IP: 192.168.1.XXX. Запоминаете адрес.
Открываете браузер, вводите http://192.168.1.XXX. Появляется окно ввода логина/пароля (те, что в коде). Вводите — видите страницу с формой загрузки. Готово, OTA работает.
Как отправить обновление
Допустим, вы хотите изменить порог температуры в метеостанции — было 35°C, ставите 38°C. Меняете строку в коде, нажимаете в Arduino IDE: Sketch → Export Compiled Binary (или Ctrl+Alt+S). В папке проекта появится файл *.ino.esp32.bin.
Открываете браузер, заходите на IP ESP32, выбираете этот .bin файл, жмёте «Загрузить прошивку». Через 20–40 секунд ESP32 перезагрузится с новым кодом. Вы даже не вставали со стула.
Если новая прошивка «сломалась» (не стартует Wi-Fi, зависла) — ESP32 автоматически откатится на предыдущую версию. Это встроенная защита модуля Update.
Интеграция с существующими проектами
Добавить OTA в уже работающий скетч — три шага:
- Шаг 1. Добавьте
#include <WebServer.h>и#include <Update.h>в начало файла - Шаг 2. Скопируйте блок с
server.on("/", ...)иserver.on("/update", ...)в вашу функциюsetup(), после подключения к Wi-Fi - Шаг 3. Добавьте
server.handleClient();в началоloop()
Всё. Ваш проект продолжает работать как раньше — датчики, алерты, MQTT — но теперь вы можете обновить код удалённо.
Один нюанс: WebServer и HTTPClient используют один и тот же стек Wi-Fi. Если у вас одновременно идёт отправка в Telegram и кто-то открыл OTA-страницу — ничего не сломается, но загрузка может быть чуть медленнее. На практике это незаметно.
Безопасность: три обязательных правила
OTA — это мощный инструмент, но и потенциальная уязвимость. Без защиты любой человек в вашей Wi-Fi сети может залить произвольный код на устройство. Три правила, которые нельзя игнорировать:
- Пароль обязателен. Код выше использует HTTP Basic Auth. Это минимум. Без пароля — не деплойте
- Не выставляйте OTA-порт наружу. ESP32 должен быть доступен только из локальной сети. Если нужен доступ из интернета — через VPN или SSH-туннель, но не через проброс порта на роутере
- Обновляйте пароль периодически. Его можно зашить в код и менять при каждом обновлении — иронично, но обновление пароля делается через OTA
OTA из интернета: когда ESP32 далеко
Локальная сеть — это хорошо, когда теплица рядом с домом. А если ESP32 стоит на поле, подключённом к мобильному роутеру с SIM-картой? Или в коровнике на другом конце района?
Два рабочих варианта:
VPN. WireGuard на вашем домашнем сервере или VPS. ESP32 подключается к VPN-сети — и вы видите его IP из любой точки мира. Настройка WireGuard на ESP32 — тема отдельной статьи, но это вполне реально.
HTTP-pull. Вместо того чтобы вы заходили на ESP32, ESP32 сам проверяет сервер на наличие обновлений. Раз в час делает GET-запрос на ваш сервер: «Есть новая прошивка?» Если есть — скачивает и обновляется. Это называется «pull OTA» и используется в промышленных IoT-системах.
// Pull OTA — ESP32 сам проверяет обновления
#include <HTTPClient.h>
#include <Update.h>
void checkForUpdate() {
HTTPClient http;
http.begin("http://ваш-сервер.ru/firmware/latest.bin");
http.addHeader("X-Device-ID", WiFi.macAddress());
int httpCode = http.GET();
if (httpCode == 200) {
int contentLength = http.getSize();
if (contentLength > 0 && Update.begin(contentLength)) {
WiFiClient* stream = http.getStreamPtr();
size_t written = Update.writeStream(*stream);
if (written == contentLength && Update.end(true)) {
Serial.println("OTA pull: успешно!");
ESP.restart();
}
}
}
http.end();
}
Вызываете checkForUpdate() раз в час из loop(). На сервере просто кладёте новый .bin файл — и все устройства обновятся автоматически. Когда у вас 20 ESP32 в разных теплицах — это единственный разумный способ.
Что дальше
OTA — это фундамент для серьёзной IoT-инфраструктуры. Без неё каждое обновление — это поездка. С ней — 30 секунд в браузере.
В следующих статьях серии:
- MQTT + VK бот — когда датчиков больше десяти, HTTP-запросы в Telegram перестают справляться. MQTT-брокер + бот ВКонтакте = единый хаб управления всей фермой
- Дашборд на Nuxt — собственный веб-интерфейс с графиками, алертами и push-уведомлениями. Без Grafana, без облака, полностью под вашим контролем
Код из статьи — рабочий. Добавляете в любой существующий проект, прошиваете один раз по кабелю — и дальше обновляете удалённо. Если что-то не получилось — пишите в комментариях.


