ESP32 программирование по воздуху (OTA) – веб-обновление в Arduino IDE

Краткое руководство, которое показывает, как выполнять программирование по воздуху (OTA) с ESP32, используя OTA Web Updater в Arduino IDE. OTA Web Updater позволяет обновлять/загружать новый код на ESP32 через браузер, без необходимости последовательного подключения между ESP32 и вашим компьютером.

ESP32 OTA Web Updates

OTA-программирование полезно, когда вам нужно обновить код на платах ESP32, к которым нет удобного доступа. Пример, который мы покажем здесь, работает, когда ESP32 и ваш браузер находятся в одной локальной сети.

Единственный недостаток OTA Web Updater заключается в том, что вам необходимо добавлять код для OTA в каждый скетч, который вы загружаете, чтобы иметь возможность использовать OTA в будущем.

Как работает OTA Web Updater?

  • Первый скетч должен быть загружен через последовательный порт. Этот скетч должен содержать код для создания OTA Web Updater, чтобы вы могли загружать код позже через браузер.

  • Скетч OTA Web Updater создает веб-сервер, к которому вы можете обратиться для загрузки нового скетча через веб-браузер.

  • Затем вам нужно реализовать процедуры OTA в каждом загружаемом скетче, чтобы иметь возможность выполнять следующие обновления/загрузки по воздуху.

  • Если вы загрузите код без процедуры OTA, вы больше не сможете получить доступ к веб-серверу и загрузить новый скетч по воздуху.

Предварительные требования

Перед тем как продолжить это руководство, у вас должно быть установлено дополнение ESP32 в вашей Arduino IDE. Следуйте одному из следующих руководств для установки ESP32 в Arduino IDE, если вы еще этого не сделали.

ESP32 OTA Web Updater

Когда вы устанавливаете дополнение ESP32 для Arduino IDE, оно автоматически устанавливает библиотеку ArduinoOTA. Перейдите в File > Examples > ArduinoOTA > OTAWebUpdater.

OTA Web Updater пример в Arduino IDE

Должен загрузиться следующий код.

/*
 * OTAWebUpdater.ino Example from ArduinoOTA Library
 * Rui Santos
 * Complete Project Details https://randomnerdtutorials.com
 */

#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Update.h>

const char* host = "esp32";
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

WebServer server(80);

/*
 * Login page
 */
const char* loginIndex =
 "<form name='loginForm'>"
    "<table width='20%' bgcolor='A09F9F' align='center'>"
        "<tr>"
            "<td colspan=2>"
                "<center><font size=4><b>ESP32 Login Page</b></font></center>"
                "<br>"
            "</td>"
            "<br>"
            "<br>"
        "</tr>"
        "<td>Username:</td>"
        "<td><input type='text' size=25 name='userid'><br></td>"
        "</tr>"
        "<br>"
        "<br>"
        "<tr>"
            "<td>Password:</td>"
            "<td><input type='Password' size=25 name='pwd'><br></td>"
            "<br>"
            "<br>"
        "</tr>"
        "<tr>"
            "<td><input type='submit' onclick='check(this.form)' value='Login'></td>"
        "</tr>"
    "</table>"
"</form>"
"<script>"
    "function check(form)"
    "{"
    "if(form.userid.value=='admin' && form.pwd.value=='admin')"
    "{"
    "window.open('/serverIndex')"
    "}"
    "else"
    "{"
    " alert('Error Password or Username')/*displays error message*/"
    "}"
    "}"
"</script>";

/*
 * Server Index Page
 */

const char* serverIndex =
"<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>"
"<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>"
   "<input type='file' name='update'>"
        "<input type='submit' value='Update'>"
    "</form>"
 "<div id='prg'>progress: 0%</div>"
 "<script>"
  "$('form').submit(function(e){"
  "e.preventDefault();"
  "var form = $('#upload_form')[0];"
  "var data = new FormData(form);"
  " $.ajax({"
  "url: '/update',"
  "type: 'POST',"
  "data: data,"
  "contentType: false,"
  "processData:false,"
  "xhr: function() {"
  "var xhr = new window.XMLHttpRequest();"
  "xhr.upload.addEventListener('progress', function(evt) {"
  "if (evt.lengthComputable) {"
  "var per = evt.loaded / evt.total;"
  "$('#prg').html('progress: ' + Math.round(per*100) + '%');"
  "}"
  "}, false);"
  "return xhr;"
  "},"
  "success:function(d, s) {"
  "console.log('success!')"
 "},"
 "error: function (a, b, c) {"
 "}"
 "});"
 "});"
 "</script>";

/*
 * setup function
 */
void setup(void) {
  Serial.begin(115200);

  // Connect to WiFi network
  WiFi.begin(ssid, password);
  Serial.println("");

  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  /*use mdns for host name resolution*/
  if (!MDNS.begin(host)) { //http://esp32.local
    Serial.println("Error setting up MDNS responder!");
    while (1) {
      delay(1000);
    }
  }
  Serial.println("mDNS responder started");
  /*return index page which is stored in serverIndex */
  server.on("/", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", loginIndex);
  });
  server.on("/serverIndex", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", serverIndex);
  });
  /*handling uploading firmware file */
  server.on("/update", HTTP_POST, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
    ESP.restart();
  }, []() {
    HTTPUpload& upload = server.upload();
    if (upload.status == UPLOAD_FILE_START) {
      Serial.printf("Update: %s\n", upload.filename.c_str());
      if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size
        Update.printError(Serial);
      }
    } else if (upload.status == UPLOAD_FILE_WRITE) {
      /* flashing firmware to ESP*/
      if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
        Update.printError(Serial);
      }
    } else if (upload.status == UPLOAD_FILE_END) {
      if (Update.end(true)) { //true to set the size to the current progress
        Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
      } else {
        Update.printError(Serial);
      }
    }
  });
  server.begin();
}

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

Исходный код

Вам нужно изменить следующие строки в коде, чтобы указать ваши учетные данные сети:

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

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

Загрузите приведенный выше код на вашу плату ESP32. Не забудьте ввести свои учетные данные сети и выбрать правильную плату и последовательный порт.

Кнопка загрузки Arduino IDE

После загрузки кода откройте монитор порта на скорости 115200 бод, нажмите кнопку сброса ESP32, и вы должны получить IP-адрес ESP32:

IP-адрес ESP32

Теперь вы можете загружать код на ESP32 по воздуху, используя браузер в вашей локальной сети.

Чтобы протестировать OTA Web Updater, вы можете отключить ESP32 от компьютера и запитать его, например, от повербанка (это необязательно, мы предлагаем это для имитации ситуации, когда ESP32 не подключен к вашему компьютеру).

Обновление нового кода с помощью OTA Web Updater

Откройте браузер в вашей сети и введите IP-адрес ESP32. Вы должны увидеть следующее:

Страница входа ESP32 OTA Web Updater

Введите имя пользователя и пароль:

  • Имя пользователя: admin

  • Пароль: admin

Вы можете изменить имя пользователя и пароль в коде.

Примечание

После ввода имени пользователя и пароля вы будете перенаправлены на URL /serverIndex. Вам не нужно вводить имя пользователя и пароль для доступа к URL /serverIndex. Поэтому, если кто-то знает URL для загрузки нового кода, имя пользователя и пароль не защищают веб-страницу от доступа посторонних.

Должна открыться новая вкладка на URL /serverIndex. Эта страница позволяет загрузить новый код на ваш ESP32. Вам нужно загружать файлы .bin (мы увидим, как это сделать, чуть позже).

Страница загрузки OTA Web Updater ESP32

Подготовка нового скетча

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

Для обучения давайте загрузим новый код, который мигает светодиодом (без delay). Скопируйте следующий код в вашу Arduino IDE.

/*
 * Rui Santos
 * Complete Project Details https://randomnerdtutorials.com
 */

#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Update.h>

const char* host = "esp32";
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

//variabls to blink without delay:
const int led = 2;
unsigned long previousMillis = 0;        // will store last time LED was updated
const long interval = 1000;           // interval at which to blink (milliseconds)
int ledState = LOW;             // ledState used to set the LED

WebServer server(80);

/*
 * Login page
 */

const char* loginIndex =
 "<form name='loginForm'>"
    "<table width='20%' bgcolor='A09F9F' align='center'>"
        "<tr>"
            "<td colspan=2>"
                "<center><font size=4><b>ESP32 Login Page</b></font></center>"
                "<br>"
            "</td>"
            "<br>"
            "<br>"
        "</tr>"
        "<td>Username:</td>"
        "<td><input type='text' size=25 name='userid'><br></td>"
        "</tr>"
        "<br>"
        "<br>"
        "<tr>"
            "<td>Password:</td>"
            "<td><input type='Password' size=25 name='pwd'><br></td>"
            "<br>"
            "<br>"
        "</tr>"
        "<tr>"
            "<td><input type='submit' onclick='check(this.form)' value='Login'></td>"
        "</tr>"
    "</table>"
"</form>"
"<script>"
    "function check(form)"
    "{"
    "if(form.userid.value=='admin' && form.pwd.value=='admin')"
    "{"
    "window.open('/serverIndex')"
    "}"
    "else"
    "{"
    " alert('Error Password or Username')/*displays error message*/"
    "}"
    "}"
"</script>";

/*
 * Server Index Page
 */

const char* serverIndex =
"<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>"
"<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>"
   "<input type='file' name='update'>"
        "<input type='submit' value='Update'>"
    "</form>"
 "<div id='prg'>progress: 0%</div>"
 "<script>"
  "$('form').submit(function(e){"
  "e.preventDefault();"
  "var form = $('#upload_form')[0];"
  "var data = new FormData(form);"
  " $.ajax({"
  "url: '/update',"
  "type: 'POST',"
  "data: data,"
  "contentType: false,"
  "processData:false,"
  "xhr: function() {"
  "var xhr = new window.XMLHttpRequest();"
  "xhr.upload.addEventListener('progress', function(evt) {"
  "if (evt.lengthComputable) {"
  "var per = evt.loaded / evt.total;"
  "$('#prg').html('progress: ' + Math.round(per*100) + '%');"
  "}"
  "}, false);"
  "return xhr;"
  "},"
  "success:function(d, s) {"
  "console.log('success!')"
 "},"
 "error: function (a, b, c) {"
 "}"
 "});"
 "});"
 "</script>";

/*
 * setup function
 */
void setup(void) {
  pinMode(led, OUTPUT);

  Serial.begin(115200);

  // Connect to WiFi network
  WiFi.begin(ssid, password);
  Serial.println("");

  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  /*use mdns for host name resolution*/
  if (!MDNS.begin(host)) { //http://esp32.local
    Serial.println("Error setting up MDNS responder!");
    while (1) {
      delay(1000);
    }
  }
  Serial.println("mDNS responder started");
  /*return index page which is stored in serverIndex */
  server.on("/", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", loginIndex);
  });
  server.on("/serverIndex", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", serverIndex);
  });
  /*handling uploading firmware file */
  server.on("/update", HTTP_POST, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
    ESP.restart();
  }, []() {
    HTTPUpload& upload = server.upload();
    if (upload.status == UPLOAD_FILE_START) {
      Serial.printf("Update: %s\n", upload.filename.c_str());
      if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size
        Update.printError(Serial);
      }
    } else if (upload.status == UPLOAD_FILE_WRITE) {
      /* flashing firmware to ESP*/
      if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
        Update.printError(Serial);
      }
    } else if (upload.status == UPLOAD_FILE_END) {
      if (Update.end(true)) { //true to set the size to the current progress
        Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
      } else {
        Update.printError(Serial);
      }
    }
  });
  server.begin();
}

void loop(void) {
  server.handleClient();
  delay(1);

  //loop to blink without delay
  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= interval) {
    // save the last time you blinked the LED
    previousMillis = currentMillis;

    // if the LED is off turn it on and vice-versa:
    ledState = not(ledState);

    // set the LED with the ledState of the variable:
    digitalWrite(led, ledState);
  }
}

Исходный код

Как видите, мы добавили код «мигание без delay» к коду OTAWebUpdater, чтобы иметь возможность делать обновления в дальнейшем.

После копирования кода в Arduino IDE вам нужно сгенерировать файл .bin.

Генерация файла .bin в Arduino IDE

Сохраните ваш скетч как LED_Web_Updater.

Чтобы сгенерировать файл .bin из вашего скетча, перейдите в Sketch > Export compiled Binary

Экспорт бинарного файла в Arduino IDE

В папке скетча должен появиться новый файл. Перейдите в Sketch > Show Sketch Folder. В папке скетча должно быть два файла: файл .ino и файл .bin. Вам нужно загрузить файл .bin через OTA Web Updater.

Файл .bin в папке скетча

Загрузка нового скетча по воздуху на ESP32

В вашем браузере на странице ESP32 OTA Web Updater нажмите кнопку Choose File. Выберите файл .bin, сгенерированный ранее, и нажмите Update.

Через несколько секунд код должен быть успешно загружен.

Файл .bin успешно загружен

Встроенный светодиод ESP32 должен начать мигать.

Мигающий светодиод ESP32

Поздравляем! Вы загрузили новый код на ESP32 по воздуху.

Заключение

Обновления по воздуху полезны для загрузки нового кода на плату ESP32, когда к ней нет удобного доступа. Код OTA Web Updater создает веб-сервер, к которому вы можете обратиться для загрузки нового кода на плату ESP32 через веб-браузер в вашей локальной сети.

Надеемся, что эта статья была для вас интересной. Если вам нравится ESP32, вам также могут понравиться:


Источник: ESP32 Over-the-air (OTA) Programming – Web Updater Arduino IDE (Random Nerd Tutorials)