Механизм OTA обновлений прошивки «по воздуху» для ESP32 и ESP-IDF
Добрый день, уважаемые читатели! В этой статье я расскажу, как достаточно просто и легко выполняются OTA-обновления на микроконтроллере ESP32 и фреймворке ESP-IDF.
Совсем не сложно провернуть то же самое и на ESP8266 и Arduino. И я, быть может, даже расскажу об этом в следующей статье. Но в данном тексте речь пойдет только об ESP32 и ESP-IDF.
Что такое ОТА?
OTA — это обновление устройств, которое устанавливается при помощи Wi-Fi соединения или мобильного интернета (в случае со смартфонами). Аббревиатура OTA произошла от английского «Over the Air», а еще раньше это называлось «Firmware Over The Air» — что в переводе с английского означает «микропрограмма по воздуху».
Согласитесь, что намного удобнее обновлять «прошивки» умных устройств удаленно, без необходимости физического подключения кабелем к плате микроконтроллера. Особенно если это устройство установлено где-то глубоко в недрах кухонной вытяжки или, например, на чердаке. А особенно удобно обновлять (и добавлять новые функции, кстати) устройства, которые вообще находятся «за тридевять земель» и возможности физического подключения к ним нет вообще.
В общем с какой стороны не глянь — одни плюсы. Но на самом деле это не так, минусы то же имеются. Обо всем этом и поговорим в данной статье.
Способы доставки ОТА-обновлений
Чтобы использовать технологию ОТА-обновлений, необходимо как минимум скомпилировать исходники и «доставить» полученный двоичный файл с прошивкой непосредственно на устройство. Я знаю как минимум два способа доставки бинарного файла на устройства:
- Отправить файл с прошивкой напрямую на устройство, используя предварительно открытый специальный порт на устройстве. Вроде бы удобно, но этот способ можно использовать только в локальной сети. И возникает необходимость постоянно держать открытым порт на устройстве и мониторить его. Насколько я помню, этот способ реализован в Arduino IDE.
- Использовать промежуточный сервер в сети интернет. В этом случае файл с прошивкой предварительно загружается на этот сервер (например хостинг). Затем устройству отправляется прямая ссылка на загруженный файл любым удобным способом — через MQTT, telegram, web-интерфейс. Либо запускаем процесс обновления по расписанию и т.д. и т.п. В этом случае обновление можно «доставить» на целевое устройство в любую точку планеты, где есть интернет.
Следует учитывать, что файл с прошивкой должен быть размещен на таком ресурсе, где он доступен для скачивания по прямой ссылке. Яндекс Диск, Google Disk, Dropbox для этих целей не подойдут, так как ссылка на файл в этом случае ведёт не на сам файл, а на промежуточную страницу!
Есть и ещё способы отправить прошивку на устройство с использованием сторонних сервисов, но я ими не пользовался.
Я использую второй способ (с использованием промежуточного сервера), используя в качестве него виртуальный хостинг, на котором расположен мой сайт. И именно про этот способ пойдет речь в этой статье.
Подготовка разметки flash-памяти
Прежде чем начинать эксперименты с OTA-обновлениями, необходимо соответствующим образом подготовить разметку flash-памяти устройства.
Для OTA-обновлений потребуется как минимум два отдельных раздела с типом app / ota_x под хранение данных прошивок:
- С одного раздела запускается устройство в текущий момент, этот раздел называется активным .
- В другой раздел ( неактивный ) может записываться новая прошивка при получении её через OTA без риска нарушения работоспособности текущей прошивки.
Размеры этих разделов должны быть не менее, чем это требуется для загрузки всей микропрограммы, которую вы создали. Можно создать и больше ota разделов — например три или пять, максимум 16. Но лично я не вижу в этом особого смысла, всё равно работать будет только одна.
Кроме того, обязательно потребуется создать ещё один раздел otadata с типом data / ota , в котором загрузчик хранит служебные данные об активном разделе. После того, как данные новой прошивки были успешно получены и записаны в неактивный раздел, загрузчик помечает активным только что обновленный раздел и после программного перезапуска пробует загрузиться уже с новой прошивкой.
Если вдруг «что-то пошло не так», и новая прошивка не работает как требуется, ESP-IDF предоставляет возможность автоматически откатится на предыдущую версию путем отката активного раздела на предыдущий. Более подробно об этом будет рассказано ниже.
Дополнительно (если позволяет размер вашей прошивки и свободное место на flash-памяти) можно создать раздел factory с типом app / factory , в котором содержится «заводская» прошивка. Этот раздел будет использован, если служебный раздел otadata чист и не содержит никаких сведений об активной прошивке. Но это не обязательно — если они отсутствует, то будет использован раздел ota_0 .
Пример переключения между разделами ОТА: 1 — состояние после прошивки «кабелем» 2 — состояние после первого OTA обновления 3 — состояние после второго ОТА-обновления 4 — состояние после третьего ОТА-обновления или если второе завершилось не удачно
В рамках данной статьи я не буду касаться тонкостей создания файла разметки разделов, этому нужно посвятить отдельную статью. Для целей проверки OTA вполне достаточно использовать стандартную разметку » Factory app, two OTA definitions «, которая выглядит следующим образом:
Эта разметка подходит для модулей с доступной flash памятью 4МБ (самых распространенных). Как видите, здесь выделено по 1МБ выделено под два OTA-раздела, еще 1МБ под заводскую прошивку, и остальные 1МБ распределены под загрузчик, otadata, nvs и данные физической инициализации. На мой взгляд, это далеко не оптимальная разметка, но для тестовых целей вполне сгодится.
Итак, подготавливаем разделы
Заходим в sdkconfig , находим раздел (Top) → Partition Table → Partition Table и выбираем пункт » Factory app, two OTA definitions «:
Не забываем сохранить конфигурацию перед выходом.
После этого компилируем проект (хотя-бы пустой) и загружаем его в устройство через USB или COM — порт . Загрузчик и таблица разделов записываются только при «физической» прошивке.
«Потом» через ОТА изменить таблицу разделов не получится, равно как и изменить параметры отката неудачной прошивки, так как за все это отвечает именно загрузчик. Поэтому выполняем этот этап максимально тщательно, особенно если физического доступа к устройству после установки на постоянного место работы не планируется.
После того, как новая таблица разделов и настроенный загрузчик записаны на flash, уже таки можно приступать к программированию OTA.
Код загрузки новой прошивки через HTTPS
Как ни страшно звучит OTA с первого взгляда, код для загрузки новой прошивки по заданному URL выглядит достаточно просто:
1. Первым шагом настраиваем самое обычное HTTPS-подключение , но обязательно с TLS-шифрованием (без HTTPS прошивка загружаться отказывается). Через поле cfgHTTPS.url = otaSource ; передаем прямую ссылку на двоичный файл с новой версией прошивки .
Приведенный на скриншоте код позволяет в зависимости от настроек в файле конфигурации выбрать либо сертификат в буфере, либо пакет, либо централизованное хранилище.
Прошивка esp32 — по воздуху, через wi-fi
Каждый, кто хоть раз занимался хобби-проектами на основе микроконтроллеров, знает, что такая идея проходит множество итераций, прежде чем займёт своё законное место на пыльной полке будет служить верой и правдой.
Обновление программного обеспечения при этом довольно часто становится одной из основных задач. В этой статье мы попробуем испытать способ, который позволит любому разработчику обновлять своё программное обеспечение легко и просто — прямо через сеть wi-fi! При этом задача существенно облегчится, если мы возьмём для экспериментов замечательный микроконтроллер esp32, так как он уже имеет в своём составе wifi-адаптер.
Обновление по воздуху (англ. over-the-air, OTA) предполагает разные методы распространения новых версий программ, настроек и обновлений ключей шифрования для телефонов, ресиверов и устройств зашифрованной передачи речи (двухканальные рации с шифрованием).
Что же касается esp32, то обновление по воздуху является весьма интересной «фишкой», так как позволяет обновлять программу на устройствах, доступ к которым осложнён или невозможен. Например, если устройство расположено где-то высоко (на дереве или фонарном столбе).
Интересным моментом такого рода обновлений является возможность постоянно апдейтить приложения на целой сети устройств, используя для этого центральный сервер с программным обеспечением.
Давайте попробуем реализовать нечто подобное.
Заранее хочу оговориться, что все необходимые файлы находятся внизу под статьёй, и вы можете скачать их по предложенным ссылкам. Все материалы полностью рабочие и протестированы лично.
Сначала нам необходимо зайти на официальный сайт и скачать python версии 2.7.15. Возможно, будет работать и с другой версией, но я пробовал только с этой. Я скачивал 64-битную версию под windows 7 (вот такой я ретроград! 🙂 ).
Далее вам необходимо установить её на компьютер для всех пользователей:
На следующем шаге необходимо выбрать опцию, как я показал на рисунке ниже:
Проблема в том, что изначально плата esp32 совсем не обладает функционалом для обновления программ по воздуху, поэтому нам необходимо выбрать из примеров опцию ArduinoOTA, как показано ниже:
Далее в предложенный ниже код мы должны вставить название своей точки доступа, к которой мы будем подключаться, а также её пароль. После чего прошьём этим скетчем свою плату:
#include #include #include #include const char* ssid = "testNet"; const char* password = "parole"; void setup() < Serial.begin(115200); Serial.println("Booting"); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); while (WiFi.waitForConnectResult() != WL_CONNECTED) < Serial.println("Connection Failed! Rebooting. "); delay(5000); ESP.restart(); >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"); >); ArduinoOTA.begin(); Serial.println("Ready"); Serial.print("IP address: "); Serial.println(WiFi.localIP()); > void loop()
Дальше вам будет необходимо запустить монитор порта, предварительно выбрав сам порт, к которому подключена плата и который мы будем мониторить.
Если кто не знает, делается это так, как показано на картинке ниже, то есть можно выбрать среди доступных COM-портов тот самый, к которому у вас подключена esp32. Скорее всего, у вас там будет отображаться несколько портов, один из которых — ваш нужный. Ничего страшного, если вы немного «потыкаетесь» и найдёте методом тыка нужный порт. Однако, если вы хотите поступить более правильным образом, следует нажать на значок компьютера на рабочем столе (повторяю, у меня – Win’7) правой кнопкой мыши и пройти по пути, как показано ниже. Таким образом вы сможете выяснить порт, к которому подключена у вас esp32, это нужно, чтобы вы знали, какой порт необходимо мониторить.
Если в мониторе порта ничего не показывается — то вам необходимо нажать на кнопку «EN» на плате esp32, что приведёт к перезагрузке платы, и в монитор порта выведется вся необходимая информация.
Если всё прошло успешно, то в конце этой информации будет показан IP-адрес, который был выдан вашей плате точкой доступа wi-fi, к которой плата подключилась:
Теперь мы внесём изменения в этот скетч, чтобы при его повторной загрузке в плату, что-то происходило. Например, сделаем так, чтобы встроенный в плату светодиод мерцал с некоторым интервалом.
Для этого в приведённый выше код были внесены некоторые изменения, которые показаны на рисунке ниже:
Сам изменённый код будет выглядеть следующим образом:
#include #include #include #include const char* ssid = "testNet"; const char* password = "parole"; //переменные для мигания светодиодом через millis const int led = 2; unsigned long previousMillis = 0; const long interval = 1000; int ledState = LOW; void setup() < pinMode(led, OUTPUT); Serial.begin(115200); Serial.println("Booting"); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); while (WiFi.waitForConnectResult() != WL_CONNECTED) < Serial.println("Connection Failed! Rebooting. "); delay(5000); ESP.restart(); >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"); >); ArduinoOTA.begin(); Serial.println("Ready"); Serial.print("IP address: "); Serial.println(WiFi.localIP()); > void loop() < ArduinoOTA.handle(); unsigned long currentMillis = millis(); if (currentMillis - previousMillis >= interval) < previousMillis = currentMillis; ledState = not(ledState); digitalWrite(led, ledState); >>
Загрузим этот код в нашу плату. И тут возникает один очень интересный момент: у вас в списке COM-портов появляется сетевой порт, имеющий IP адрес — это и есть наша плата esp32!
Выберем её и нажмём на загрузку скетча. В результате в нижнем окошке появится следующий статус происходящего: будет увеличиваться в длину индикатор загрузки и расти процент:
Кстати сказать, во время этой загрузки у меня плата была подключена к power-банку, т.е. была отключена от компьютера, и загрузка производилась непосредственно через сеть wi-fi.
В качестве небольшого примечания следует сказать, что код, поддерживающий беспроводную загрузку (первый кусок кода в этой статье), должен быть всегда включён в ваш скетч (который выполняет какую-либо полезную деятельность для вас). Если этого не сделать, то вы потеряете возможность загружать скетчи «по воздуху».
P.S. Если кого-то интересует прошивка по воздуху предыдущей версии платы (esp8266), то вот тут есть весьма подробный мануал.
Загрузки:
НЛО прилетело и оставило здесь промокод для читателей нашего блога:
— 15% на все тарифы VDS (кроме тарифа Прогрев) — HABRFIRSTVDS .