Каждый, кто хоть раз занимался хобби-проектами на основе микроконтроллеров, знает, что такая идея проходит множество итераций, прежде чем займёт своё законное место на пыльной полке будет служить верой и правдой.

Обновление программного обеспечения при этом довольно часто становится одной из основных задач. В этой статье мы попробуем испытать способ, который позволит любому разработчику обновлять своё программное обеспечение легко и просто — прямо через сеть wi-fi! При этом задача существенно облегчится, если мы возьмём для экспериментов замечательный микроконтроллер esp32, так как он уже имеет в своём составе wifi-адаптер.

Обновление по воздуху (англ. over-the-air, OTA) предполагает разные методы распространения новых версий программ, настроек и обновлений ключей шифрования для телефонов, ресиверов и устройств зашифрованной передачи речи (двухканальные рации с шифрованием).

Что же касается esp32, то обновление по воздуху является весьма интересной «фишкой», так как позволяет обновлять программу на устройствах, доступ к которым осложнён или невозможен. Например, если устройство расположено где-то высоко (на дереве или фонарном столбе).

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

Давайте попробуем реализовать нечто подобное.

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

Сначала нам необходимо зайти на официальный сайт и скачать python версии 2.7.15. Возможно, будет работать и с другой версией, но я пробовал только с этой. Я скачивал 64-битную версию под windows 7 (вот такой я ретроград! :-) ).



Далее вам необходимо установить её на компьютер для всех пользователей:



На следующем шаге необходимо выбрать опцию, как я показал на рисунке ниже:



Проблема в том, что изначально плата esp32 совсем не обладает функционалом для обновления программ по воздуху, поэтому нам необходимо выбрать из примеров опцию ArduinoOTA, как показано ниже:



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

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

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() {
  ArduinoOTA.handle();
}


Дальше вам будет необходимо запустить монитор порта, предварительно выбрав сам порт, к которому подключена плата и который мы будем мониторить.

Если кто не знает, делается это так, как показано на картинке ниже, то есть можно выбрать среди доступных COM-портов тот самый, к которому у вас подключена esp32. Скорее всего, у вас там будет отображаться несколько портов, один из которых — ваш нужный. Ничего страшного, если вы немного «потыкаетесь» и найдёте методом тыка нужный порт. Однако, если вы хотите поступить более правильным образом, следует нажать на значок компьютера на рабочем столе (повторяю, у меня – Win’7) правой кнопкой мыши и пройти по пути, как показано ниже. Таким образом вы сможете выяснить порт, к которому подключена у вас esp32, это нужно, чтобы вы знали, какой порт необходимо мониторить.



Если в мониторе порта ничего не показывается — то вам необходимо нажать на кнопку «EN» на плате esp32, что приведёт к перезагрузке платы, и в монитор порта выведется вся необходимая информация.

Если всё прошло успешно, то в конце этой информации будет показан IP-адрес, который был выдан вашей плате точкой доступа wi-fi, к которой плата подключилась:



Теперь мы внесём изменения в этот скетч, чтобы при его повторной загрузке в плату, что-то происходило. Например, сделаем так, чтобы встроенный в плату светодиод мерцал с некоторым интервалом.

Для этого в приведённый выше код были внесены некоторые изменения, которые показаны на рисунке ниже:



Сам изменённый код будет выглядеть следующим образом:

#include <WiFi.h>
#include <ESPmDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
                                                                                                                                              	 
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.

Комментарии (10)


  1. GenkaOk
    14.03.2022 12:14

    Для домашних поделок отлично подходит ESPHome с включенным web server и OTA

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


  1. FGV
    14.03.2022 13:11
    +9

    И все? Где про разметку флэшки? Где подробности как ota вообще работает? Собственно сам ota позволяет заливать прошивку при работающем esp32/8266 через любой канал связи, и это не обязательно wifi.


  1. LAVBoy
    14.03.2022 15:30
    +5

    А причем тут питон?


    1. TR1K
      15.03.2022 08:27

      Без Python не работает сама среда и ее сервисы...


      1. LAVBoy
        15.03.2022 10:58
        +1

        Среда Arduino? Я не флейма ради, сразу говорю, просто реально не понимаю и не вижу связи.


    1. fiego
      15.03.2022 17:29

      Python нужен для работы ESP-IDF, поверх которого работает библиотека Arduino для ESP32.


      1. Dmitry2019
        16.03.2022 10:57
        +1

        Странно, я Arduino IDE пользуюсь и для ESP8266 и для ESP32 и я никогда Питон не ставил..


        1. d3vil_st
          16.03.2022 14:53

          Тулы для прошивки ESP и работы с ней написаны на Питоне, если вы Питон не ставили, это не значит, что компонент Arduino не скачал их за вас.


          1. Dmitry2019
            16.03.2022 16:17

            Действительно, Питон был поставлен автоматически.


  1. mxkmn
    16.03.2022 20:20

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

    А по самой фиче - в личном проекте я пробовал подключать ArduinoOTA, и работало это крайне так себе: на (тогда ещё) Windows 10 плата постоянно не высвечивалась в сетевых портах, не помогала даже перезагрузка ПК. Вывод доступного порта случался редко, можно пересчитать случаи по пальцам руки. Подобных жалоб много, и кажется, что всё работает хорошо только на старых системах (хотя про Linux и macOS ничего не скажу - уже не помню как там). При этом ESP8266 (не проверял с ESP32) начинает постоянно взаимодействовать с вайфаем, что даёт постоянные просадки на пинах (болячка микроконтроллера) - в случае применения LCD1602 символы или подсветка начинают мерцать, большая боль. А, ну и древний Python еще устанавливать...

    В общем, в отсутствии условий вроде "на дереве висит" я вычистил фичу с OTA из своего проекта, ведь она представляла из себя скорее проблему, чем удобную вещь.