ESP32 OTA (Over-the-Air) Updates – AsyncElegantOTA using Arduino IDE

In this guide, you’ll learn how to do over-the-air (OTA) updates to your ESP32 boards using the AsyncElegantOTA library. This library creates a web server that allows you to upload new firmware (a new sketch) to your board without the need to make a serial connection between the ESP32 and your computer.

Additionally, with this library, you can also upload new files to the ESP32 filesystem (SPIFFS). The library is very easy to use, and it’s compatible with the ESPAsyncWebServer library that we often use to build web server projects.

ESP32 OTA Over-the-Air Updates AsyncElegantOTA using Arduino IDE Firmware Filesystem

By the end of this tutorial, you’ll be able to easily add OTA capabilities to your web server projects with the ESP32 to upload new firmware and files to the filesystem wirelessly in the future.

We have a similar tutorial for the ESP8266 NodeMCU board: ESP8266 NodeMCU OTA (Over-the-Air) Updates – AsyncElegantOTA using Arduino IDE

Watch the Video Tutorial

This project is available in video format and in written format. You can watch the video below or you can scroll down for the written instructions.

Overview

This tutorial covers:

We recommend that you follow all the tutorial steps to understand how ElegantOTA works and how you can use it in your projects. To demonstrate how to do this, we’ll upload files to build different web server projects.

ESP32 OTA (Over-the-Air) Programming

OTA (Over-the-Air) update is the process of loading new firmware to the ESP32 board using a Wi-Fi connection rather than a serial communication. This functionality is extremely useful in case of no physical access to the ESP32 board.

There are different ways to perform OTA updates. In this tutorial, we’ll cover how to do that using the AsyncElegantOTA library. In our opinion, this is one of the best and easiest ways to perform OTA updates.

The AsyncElegantOTA library creates a web server that you can access on your local network to upload new firmware or files to the filesystem (SPIFFS). The files you upload should be in .bin format. We’ll show you later in the tutorial how to convert your files to .bin format.

Async ElegantOTA Web Server How it Works ESP32

The only disadvantage of OTA programming is that you need to add the code for OTA in every sketch you upload so that you’re able to use OTA in the future. In the case of the AsyncElegantOTA library, it consists of just three lines of code.

AsyncElegantOTA Library

As mentioned previously, there are a bunch of alternatives for OTA programming with the ESP32 boards. For example, in the Arduino IDE, under the Examples folder, there is the BasicOTA example (that never worked well for us); the OTA Web Updater (works well, but it isn’t easy to integrate with web servers using the ESPAsyncWebServer library); and many other examples from different libraries.

Most of our web server projects with the ESP32 use the ESPAsyncWebServer library. So, we wanted a solution that was compatible with that library. The AsyncElegantOTA library is just perfect for what we want:

AsyncElegantOTA Library
  • It is compatible with the ESPAsyncWebServer library;
  • You just need to add three lines of code to add OTA capabilities to your “regular” Async Web Server;
  • It allows you to update not only new firmware to the board but also files to the ESP32 filesystem (SPIFFS);
  • It provides a beautiful and modern web server interface;
  • It works extremely well.

If you like this library and you’ll use it in your projects, consider supporting the developer’s work.

OTA Updates with AsyncElegantOTA Library – Quick Summary

To add OTA capabilities to your projects using the AsyncElegantOTA library, follow these steps:

  1. Install AsyncElegantOTA, AsyncTCP, and ESPAsyncWebServer libraries;
  2. Include AsyncElegantOTA library at the top of the Arduino sketch: #include <AsyncElegantOTA.h>;
  3. Add this line AsyncElegantOTA.begin(&server); before server.begin();
  4. Open your browser and go to http://<IPAddress>/update, where <IPAddress> is your ESP32 IP address.

Continue reading the tutorial for more detailed steps.

How does OTA Web Updater Work?

  • The first sketch should be uploaded via serial port. This sketch should contain the code to create the OTA Web Updater so that you are able to upload code later using your browser.
  • The OTA Web Updater sketch creates a web server you can access to upload a new sketch via web browser.
  • Then, you need to implement OTA routines in every sketch you upload so that you’re able to do the next updates/uploads over-the-air.
  • If you upload a code without an OTA routine, you’ll no longer be able to access the web server and upload a new sketch over-the-air.

Install AsyncElegantOTA Library

In this tutorial, the ESP32 will be programmed using Arduino IDE. If you want to learn how to do the same using VS Code + PlatformIO, follow the next tutorial: ESP32 OTA (Over-the-Air) Updates – AsyncElegantOTA (VS Code + PlatformIO)

You can install the AsyncElegantOTA library using the Arduino Library Manager. In your Arduino IDE, go to Sketch > Include Library > Manage Libraries… Search for “AsyncElegantOTA” and install it.

Install Async Elegant OTA Library Arduino IDE

Install AsyncTCP and ESPAsyncWebServer Libraries

You also need to install the AsyncTCP and the ESPAsyncWebServer libraries. Click the links below to download the libraries.

These libraries aren’t available to install through the Arduino Library Manager, so you need to copy the library files to the Arduino Installation Libraries folder. Alternatively, in your Arduino IDE, you can go to Sketch Include Library > Add .zip Library and select the libraries you’ve just downloaded.

AsyncElegantOTA ESP32 Basic Example

Let’s start with the basic example provided by the library. This example creates a simple web server with the ESP32. The root URL displays some text, and the /update URL displays the interface to update firmware and filesystem.

If you’re using an ESP32, you need to downgrade your ESP32 boards’ add-on to version 2.0.X. At the moment, the AsyncElegantOTA library is not compatible with version 3.X. If you want to use version 3.X, please use the newest version of the library: ElegantOTA V3.

ESP32 Boards Add-on install ESP32 board version 2

Copy the following code to your Arduino IDE.

/*
  Rui Santos
  Complete project details
   - Arduino IDE: https://RandomNerdTutorials.com/esp32-ota-over-the-air-arduino/
   - VS Code: https://RandomNerdTutorials.com/esp32-ota-over-the-air-vs-code/
  
  This sketch shows a Basic example from the AsyncElegantOTA library: ESP32_Async_Demo
  https://github.com/ayushsharma82/AsyncElegantOTA
*/

#include <Arduino.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <AsyncElegantOTA.h>

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

AsyncWebServer server(80);

void setup(void) {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  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());

  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send(200, "text/plain", "Hi! I am ESP32.");
  });

  AsyncElegantOTA.begin(&server);    // Start ElegantOTA
  server.begin();
  Serial.println("HTTP server started");
}

void loop(void) {

}

View raw code

Insert your network credentials and the code should work straight away:

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

How the Code Works

First, include the necessary libraries:

#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <AsyncElegantOTA.h>

Insert your network credentials in the following variables so that the ESP32 can connect to your local network.

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

Create an AsyncWebServer object on port 80:

AsyncWebServer server(80);

In the setup(), initialize the Serial Monitor:

Serial.begin(115200);

Initialize Wi-Fi:

WiFi.mode(WIFI_STA);
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());

Then, handle the client requests. The following lines, send some text Hi! I am ESP32. when you access the root (/) URL:

server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
  request->send(200, "text/plain", "Hi! I am ESP32.");
});

If your web server needs to handle more requests you can add them (we’ll show you in the next example).

Then, add the next line to start ElegantOTA:

AsyncElegantOTA.begin(&server);

Finally, initialize the server:

server.begin();

Access the Web Server

After uploading code to the board, open the Serial Monitor at a baud rate of 115200. Press the ESP32 on-board RST button. It should display the ESP IP address as follows (yours may be different):

AsyncElegantOTA Get ESP IP Address Serial Monitor

In your local network, open your browser and type the ESP32 IP address. You should get access the root (/) web page with some text displayed.

Async ElegantOTA Demo Example Web Server Root URL

Now, imagine that you want to modify your web server code. To do that via OTA, go to the ESP IP address followed by /update. The following web page should load.

Async ElegantOTA Update Page

Follow the next sections to learn how to upload new firmware using AsyncElegantOTA.

Upload New Firmware OTA (Over-the-Air) Updates – ESP32

Every file that you upload via OTA should be in .bin format. You can generate a .bin file from your sketch using the Arduino IDE.

With your sketch opened, you just need to go to Sketch > Export Compiled Binary. A .bin file will be generated from your sketch. The generated file will be saved under your project folder.

That’s that .bin file you should upload using the AsyncElegantOTA web page if you want to upload new firmware.

Upload a New Web Server Sketch – Example

Let’s see a practical example. Imagine that after uploading the previous sketch, you want to upload a new one that allows you to control an LED via a web interface like this project. Here’s the steps you need to follow:

1. Copy the following code to your Arduino IDE. Don’t forget to insert your network credentials.

/*
  Rui Santos
  Complete project details
   - Arduino IDE: https://RandomNerdTutorials.com/esp32-ota-over-the-air-arduino/
   - VS Code: https://RandomNerdTutorials.com/esp32-ota-over-the-air-vs-code/
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.
  
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*/

// Import required libraries
#include <Arduino.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <AsyncElegantOTA.h>

// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

bool ledState = 0;
const int ledPin = 2;

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <title>ESP Web Server</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" href="data:,">
  <style>
  html {
    font-family: Arial, Helvetica, sans-serif;
    text-align: center;
  }
  h1 {
    font-size: 1.8rem;
    color: white;
  }
  h2{
    font-size: 1.5rem;
    font-weight: bold;
    color: #143642;
  }
  .topnav {
    overflow: hidden;
    background-color: #143642;
  }
  body {
    margin: 0;
  }
  .content {
    padding: 30px;
    max-width: 600px;
    margin: 0 auto;
  }
  .card {
    background-color: #F8F7F9;;
    box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5);
    padding-top:10px;
    padding-bottom:20px;
  }
  .button {
    padding: 15px 50px;
    font-size: 24px;
    text-align: center;
    outline: none;
    color: #fff;
    background-color: #0f8b8d;
    border: none;
    border-radius: 5px;
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    -webkit-tap-highlight-color: rgba(0,0,0,0);
   }
   /*.button:hover {background-color: #0f8b8d}*/
   .button:active {
     background-color: #0f8b8d;
     box-shadow: 2 2px #CDCDCD;
     transform: translateY(2px);
   }
   .state {
     font-size: 1.5rem;
     color:#8c8c8c;
     font-weight: bold;
   }
  </style>
<title>ESP Web Server</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
</head>
<body>
  <div class="topnav">
    <h1>ESP WebSocket Server</h1>
  </div>
  <div class="content">
    <div class="card">
      <h2>Output - GPIO 2</h2>
      <p class="state">state: <span id="state">%STATE%</span></p>
      <p><button id="button" class="button">Toggle</button></p>
    </div>
  </div>
<script>
  var gateway = `ws://${window.location.hostname}/ws`;
  var websocket;
  window.addEventListener('load', onLoad);
  function initWebSocket() {
    console.log('Trying to open a WebSocket connection...');
    websocket = new WebSocket(gateway);
    websocket.onopen    = onOpen;
    websocket.onclose   = onClose;
    websocket.onmessage = onMessage; // <-- add this line
  }
  function onOpen(event) {
    console.log('Connection opened');
  }
  function onClose(event) {
    console.log('Connection closed');
    setTimeout(initWebSocket, 2000);
  }
  function onMessage(event) {
    var state;
    if (event.data == "1"){
      state = "ON";
    }
    else{
      state = "OFF";
    }
    document.getElementById('state').innerHTML = state;
  }
  function onLoad(event) {
    initWebSocket();
    initButton();
  }
  function initButton() {
    document.getElementById('button').addEventListener('click', toggle);
  }
  function toggle(){
    websocket.send('toggle');
  }
</script>
</body>
</html>)rawliteral";

void notifyClients() {
  ws.textAll(String(ledState));
}

void handleWebSocketMessage(void *arg, uint8_t *data, size_t len) {
  AwsFrameInfo *info = (AwsFrameInfo*)arg;
  if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
    data[len] = 0;
    if (strcmp((char*)data, "toggle") == 0) {
      ledState = !ledState;
      notifyClients();
    }
  }
}

void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type,
             void *arg, uint8_t *data, size_t len) {
  switch (type) {
    case WS_EVT_CONNECT:
      Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str());
      break;
    case WS_EVT_DISCONNECT:
      Serial.printf("WebSocket client #%u disconnected\n", client->id());
      break;
    case WS_EVT_DATA:
      handleWebSocketMessage(arg, data, len);
      break;
    case WS_EVT_PONG:
    case WS_EVT_ERROR:
      break;
  }
}

void initWebSocket() {
  ws.onEvent(onEvent);
  server.addHandler(&ws);
}

String processor(const String& var){
  Serial.println(var);
  if(var == "STATE"){
    if (ledState){
      return "ON";
    }
    else{
      return "OFF";
    }
  }
  return String();
}

void setup(){
  // Serial port for debugging purposes
  Serial.begin(115200);

  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);
  
  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }

  // Print ESP Local IP Address
  Serial.println(WiFi.localIP());

  initWebSocket();

  // Route for root / web page
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", index_html, processor);
  });

  // Start ElegantOTA
  AsyncElegantOTA.begin(&server);
  // Start server
  server.begin();
}

void loop() {
  ws.cleanupClients();
  digitalWrite(ledPin, ledState);
}

View raw code

This is the same code used in this project, but it contains the needed lines of code to handle ElegantOTA:

#include <AsyncElegantOTA.h>
AsyncElegantOTA.begin(&server);

2. Save your sketch: File > Save and give it a name. For example: Web_Server_LED_OTA_ESP32.

3. Generate a .bin file from your sketch. Go to Sketch > Export Compiled Binary. Several .bin files will be created under the project folder. You should upload the file with the ino.bin extension.

4. Now, you need to upload that file using the ElegantOTA page. Go to your ESP IP address followed by /update. Make sure you have the firmware option selected. Click on Choose File and select the .bin file you’ve just generated.

Update New Firmware Elegant OTA

5. When it’s finished, click on the Back button.

Upload New Firmware AsyncElegantOTA success

6. Then, you can go to the root (/) URL to access the new web server. This is the page you should see when you access the ESP IP address on the root (/) URL.

ESP32 Websocket Server Control Outputs

You can click on the button to turn the ESP32 on-board LED on and off.

ESP32 board Built in LED turned on HIGH

Because we’ve also added OTA capabilities to this new web server, we can upload a new sketch in the future if needed. You just need to go to the ESP32 IP address followed by /update.

Congratulations, you’ve uploaded new code to your ESP32 via Wi-Fi using AsyncElegantOTA.

Continue reading if you want to learn how to upload files to the ESP32 filesystem (SPIFFS) using AsyncElegantOTA.

Upload Files to Filesystem OTA (Over-the-Air) Updates – ESP32

In this section you’ll learn to upload files to the ESP32 filesystem (SPIFFS) using AsyncElegantOTA.

ESP32 Filesystem Upload Plugin

Before proceeding, you need to have the ESP32 Uploader Plugin installed in your Arduino IDE. Follow the next tutorial before proceeding:

Web Server with Files from SPIFFS

Imagine the scenario that you need to upload files to the ESP32 filesystem, for example: configuration files; HTML, CSS and JavaScript files to update the web server page; or any other file that you may want to save in SPIFFS via OTA.

To show you how to do this, we’ll create a new web server that serves files from SPIFFS: HTML, CSS and JavaScript files to build a web page that controls the ESP32 GPIOs remotely.

Before proceeding make sure you have the Arduino_JSON library by Arduino version 0.1.0 installed. You can install this library in the Arduino IDE Library Manager. Just go to Sketch Include Library > Manage Libraries and search for the library name as follows: Arduino_JSON.

Copy the following code to your Arduino IDE.

/*
  Rui Santos
  Complete project details
   - Arduino IDE: https://RandomNerdTutorials.com/esp32-ota-over-the-air-arduino/
   - VS Code: https://RandomNerdTutorials.com/esp32-ota-over-the-air-vs-code/
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.
  
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*/

// Import required libraries
#include <Arduino.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include "SPIFFS.h"
#include <Arduino_JSON.h>
#include <AsyncElegantOTA.h>

// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);

// Create a WebSocket object
AsyncWebSocket ws("/ws");

// Set number of outputs
#define NUM_OUTPUTS  4

// Assign each GPIO to an output
int outputGPIOs[NUM_OUTPUTS] = {2, 4, 12, 14};

// Initialize SPIFFS
void initSPIFFS() {
  if (!SPIFFS.begin(true)) {
    Serial.println("An error has occurred while mounting SPIFFS");
  }
  Serial.println("SPIFFS mounted successfully");
}

// Initialize WiFi
void initWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.print("Connecting to WiFi ..");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    delay(1000);
  }
  Serial.println(WiFi.localIP());
}

String getOutputStates(){
  JSONVar myArray;
  for (int i =0; i<NUM_OUTPUTS; i++){
    myArray["gpios"][i]["output"] = String(outputGPIOs[i]);
    myArray["gpios"][i]["state"] = String(digitalRead(outputGPIOs[i]));
  }
  String jsonString = JSON.stringify(myArray);
  return jsonString;
}

void notifyClients(String state) {
  ws.textAll(state);
}

void handleWebSocketMessage(void *arg, uint8_t *data, size_t len) {
  AwsFrameInfo *info = (AwsFrameInfo*)arg;
  if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
    data[len] = 0;
    if (strcmp((char*)data, "states") == 0) {
      notifyClients(getOutputStates());
    }
    else{
      int gpio = atoi((char*)data);
      digitalWrite(gpio, !digitalRead(gpio));
      notifyClients(getOutputStates());
    }
  }
}

void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client,AwsEventType type,
             void *arg, uint8_t *data, size_t len) {
  switch (type) {
    case WS_EVT_CONNECT:
      Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str());
      break;
    case WS_EVT_DISCONNECT:
      Serial.printf("WebSocket client #%u disconnected\n", client->id());
      break;
    case WS_EVT_DATA:
      handleWebSocketMessage(arg, data, len);
      break;
    case WS_EVT_PONG:
    case WS_EVT_ERROR:
      break;
  }
}

void initWebSocket() {
    ws.onEvent(onEvent);
    server.addHandler(&ws);
}

void setup(){
  // Serial port for debugging purposes
  Serial.begin(115200);

  // Set GPIOs as outputs
  for (int i =0; i<NUM_OUTPUTS; i++){
    pinMode(outputGPIOs[i], OUTPUT);
  }
  initSPIFFS();
  initWiFi();
  initWebSocket();

  // Route for root / web page
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(SPIFFS, "/index.html", "text/html",false);
  });

  server.serveStatic("/", SPIFFS, "/");

  // Start ElegantOTA
  AsyncElegantOTA.begin(&server);
  
  // Start server
  server.begin();
}

void loop() {
  ws.cleanupClients();
}

View raw code

Insert your network credentials in the following variables and save the code.

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

Update Firmware

Create a .bin file from this sketch as shown previously (this sketch includes the needed lines of code to provide OTA capabilities).

Go to the ESP32 IP address followed by /update and upload the new firmware.

Next, we’ll see how to upload the files.

Update Filesystem

Under the project folder, create a folder called data and paste the following HTML, CSS, and JavaScript files (click on the links to download the files):

To find your project folder, you can simply go to Sketch > Show Sketch Folder.

This is where your data folder should be located and how it looks:

Data Folder Structure ESP32 Async ElegantOTA Example

After this, with the ESP32 disconnected from your computer (that’s the whole purpose of OTA), click on ESP32 Data Sketch Upload.

ESP32 Sketch Data Upload Arduino IDE SPIFFS FS Filesystem

You’ll get an error because there isn’t any ESP32 board connected to your computer – don’t worry.

Scroll up on the debugging window until you find the .spiffs.bin file location. That’s that file that you should upload (in our case the file is called Web_Server_OTA_ESP32_Example_2.spiffs.bin.

Get SPIFFS bin binary File Path

And this is the path where our file is located:

C:\Users\sarin\AppData\Local\Temp\arduino_build_675367\Web_server_OTA_ESP32_Example_2.spiffs.bin

To access that file on my computer, I need to make hidden files visible (the AppData folder was not visible). Check if that’s also your case.

Arduino IDE ESP32 ESP8266 NodeMCU Board Add-on fix install

Once you reach the folder path, you want to get the file with .spiffs.bin extension.

SPIFFS Bin Folder Arduino IDE

To make things easier you can copy that file to your project folder.

Now that we have a .bin file from the data folder, we can upload that file. Go to your ESP32 IP address followed by /update. Make sure you have the Filesystem option selected.

Upload Files to Filesystem AsyncElegantOTA

Then, select the file with the .spiffs.bin extension.

After successfully uploading, click the Back button. And go to the root (/) URL again. You should get access to the following web page that controls the ESP32 outputs using Web Socket protocol.

Control Multiple ESP32 ESP8266 Outputs Websocket Web Server

To see the web server working, you can connect 4 LEDs to your ESP32 on GPIOS: 2, 4, 12, and 14. You should be able to control those outputs from the web server.

If you need to update something on your project, you just need to go to your ESP32 IP address followed by /update.

Congratulations! You’ve successfully uploaded files to the ESP32 filesystem using AsyncElegantOTA.

Wrapping Up

In this tutorial you’ve learned how to add OTA capabilities to your Async Web Servers using the AsyncElegantOTA library. This library is super simple to use and allows you to upload new firmware or files to SPIFFS effortlessly using a web page. In our opinion, the AsyncElegantOTA library is one of the best options to handle OTA web updates.

We hope you’ve found this tutorial useful.

Learn more about the ESP32 with our resources:

Thanks for reading.



Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »
Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »

Enjoyed this project? Stay updated by subscribing our newsletter!

135 thoughts on “ESP32 OTA (Over-the-Air) Updates – AsyncElegantOTA using Arduino IDE”

  1. This is great. I have been (trying) to use OTA for years and very rarely has it worked. This seems to work every time (sort of) so that’s a plus. The only thing odd is that after loading the “Web Server Sketch – Example” it will not connect to the Wifi. I see “Connecting to WiFi..” repeating but it never connects unless I reboot/reset the ESP32, then it connects every other time. In other words, after uploading the code it doesn’t work until I reboot the ESP32. Then it connects. If I reboot it again, it doesn’t connect. If I reboot it again, it works. Very odd behavior. Any thoughts.

    Reply
    • Hi Bruce.
      That’s indeed a very odd behavior.
      We’ve experimented with the library with different examples, and it never failed.
      We also tried it with the ESP8266, and everything went fine.
      Do you have another board to experiment with?
      Regards,
      Sara

      Reply
      • experimenting too. currently I have a a program (on an ESP8266 using the proper ESP8266 code) with both STA and AP,Async webserver that connects with static IP and the elegantOTA does not seem to want to start. Have not figured out why yet. disabling the AP makes no difference. Will try to find it (the example runs fine)

        Reply
        • OK I found the problem. As you can see, most of the examples, make their WiFi connection, then issue a ‘server,on(“/”……..’ request, and then do their elegant ota and server.begin() requests, placing it at the end of setup().

          That is what I did…….but I had a couple more server requests,,,,one of them called ‘update’.
          need i say more 🙂

          Reply
  2. Hi Sara
    An excellent description – as usual!
    I’m wondering if this works with LittleFS too as the SPIFF file system seems to be deprecated for the ESP32. Have you tried it and it it possible to make it work in a similar way?
    Regards, Juerg

    Reply
    • Hi.
      SPIFFS is only deprecated for the ESP8266.
      This tutorial also works with littlefs (at least with the ESP8266).
      We’ll publish a similar tutorial for the ESP8266 by the end of this week (with littlefs).
      Regards,
      Sara

      Reply
      • Hi Sara
        I have just tested the OTA library with an ESP32 with LITTLEFS: Indeed everything works fine and smooth, even with a username and password for the OTA website (IoT security). The ESP32 just had to be rebooted after the LITTLEFS update, after a firmware update the ESP32 restarted itself.
        Best Regards, Juerg

        Reply
  3. Great,I have been trying this with the ESP8266 and works very well.
    Never had real trouble with the regular OTA, including webOTA, but had the occasional odd behaviour. Main advantage here is that it can be used with Async webpages and it looks a lot better.

    Very well explained

    Reply
  4. Great example. I will definitely use this.
    I am a little concerned that just about anyone can corrupt your esp32 server by uploading a new (possibly a virus) program to it. There seems to be no security and a very generic “update” catch phrase to access it.

    Reply
  5. I clicked on view raw of the second example
    link https://github.com/RuiSantosdotme/Random-Nerd-Tutorials/raw/master/Projects/ESP32/AsyncElegantOTA/ESP32_Web_Server_LED_OTA/ESP32_Web_Server_LED_OTA.ino

    and did a <ctrl-a, ctrl-c ctrl-v
    then I tried to compile the example but I get a lot of error-messages

    Do you RE-test EVERYTHING by following your tutorial?
    I guess not otherwise you would have recognized the bugs yourself

    exit status 1

    control reaches end of non-void function [-Werror=return-type]

    Reply
    • Hi.
      Can you provide more details about the error?
      I’ve just compiled the code again and it was fine.
      Regards,
      Sara

      Reply
  6. Hi Sara,
    thank you for answering so quick. Did you do a Copy & paste from the website and then compile it?
    The I have actovated erbose output so the complete output is more than the forum-software allows

    I guess this part is important
    C:\Users\Stefan\Documents\Arduino\Web_Server_LED_OTA_ESP32\Web_Server_LED_OTA_ESP32.ino: In function ‘String processor(const String&)’:

    Web_Server_LED_OTA_ESP32:202:1: error: control reaches end of non-void function [-Werror=return-type]
    }
    cc1plus.exe: some warnings being treated as errors
    Multiple libraries were found for “WiFi.h”

    Used: P:\Portable-Apps\arduino1.8.13\portable\packages\esp32\hardware\esp32\1.0.4\libraries\WiFi

    Not used: P:\Portable-Apps\arduino1.8.13\libraries\WiFi

    Using library WiFi at version 1.0 in folder: P:\Portable-Apps\arduino1.8.13\portable\packages\esp32\hardware\esp32\1.0.4\libraries\WiFi

    Using library AsyncTCP at version 1.1.1 in folder: C:\Users\Stefan\Documents\Arduino\libraries\AsyncTCP

    Using library ESPAsyncWebServer at version 1.2.3 in folder: C:\Users\Stefan\Documents\Arduino\libraries\ESPAsyncWebServer

    Using library FS at version 1.0 in folder: P:\Portable-Apps\arduino1.8.13\portable\packages\esp32\hardware\esp32\1.0.4\libraries\FS

    Using library AsyncElegantOTA at version 2.2.5 in folder: C:\Users\Stefan\Documents\Arduino\libraries\AsyncElegantOTA

    Using library Update at version 1.0 in folder: P:\Portable-Apps\arduino1.8.13\portable\packages\esp32\hardware\esp32\1.0.4\libraries\Update

    exit status 1

    control reaches end of non-void function [-Werror=return-type]

    best regards Stefan

    Reply
    • Hi.

      Yes, I did that and it compiles fine for me.
      What’s the Arduino IDE and esp32 boards version that you have?

      Modify your processor() function to be like this:

      String processor(const String& var){
      Serial.println(var);
      if(var == "STATE"){
      if (ledState){
      return "ON";
      }
      else{
      return "OFF";
      }
      }
      return String();
      }

      Let me know if this solves your issue.
      Regards,
      Sara

      Reply
  7. I did modify the first code and tested the OTA with this modified first code-version.
    The one that just says Hi! I am ESP32.
    This worked.

    When I inserted your version of String processor
    there was no formatting. no indentions so I pressed ctrl-t for autoformatting. but nothing changed.

    then I moved the function String processor above the html-code and voila there autoformatting worked.

    So my conclusion is that something is written “non-international” inside the rawliteral.
    Something inside the raw-literal has a syntactical error. No idea what this could be as I have never worked with html-code.

    Now idea if – when I post the html-code section here that all the character-translations done by the forum-software keeps the error

    anyway I post it here

    // Create AsyncWebServer object on port 80
    AsyncWebServer server(80);
    AsyncWebSocket ws(“/ws”);

    const char index_html[] PROGMEM = R”rawliteral(

    ESP Web Server

    html {
    font-family: Arial, Helvetica, sans-serif;
    text-align: center;
    }
    h1 {
    font-size: 1.8rem;
    color: white;
    }
    h2{
    font-size: 1.5rem;
    font-weight: bold;
    color: #143642;
    }
    .topnav {
    overflow: hidden;
    background-color: #143642;
    }
    body {
    margin: 0;
    }
    .content {
    padding: 30px;
    max-width: 600px;
    margin: 0 auto;
    }
    .card {
    background-color: #F8F7F9;;
    box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5);
    padding-top:10px;
    padding-bottom:20px;
    }
    .button {
    padding: 15px 50px;
    font-size: 24px;
    text-align: center;
    outline: none;
    color: #fff;
    background-color: #0f8b8d;
    border: none;
    border-radius: 5px;
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    -webkit-tap-highlight-color: rgba(0,0,0,0);
    }
    /.button:hover {background-color: #0f8b8d}/
    .button:active {
    background-color: #0f8b8d;
    box-shadow: 2 2px #CDCDCD;
    transform: translateY(2px);
    }
    .state {
    font-size: 1.5rem;
    color:#8c8c8c;
    font-weight: bold;
    }

    ESP Web Server

    ESP WebSocket Server

    Output – GPIO 2
    state: %STATE%
    Toggle

    var gateway = `ws://${window.location.hostname}/ws`;
    var websocket;
    window.addEventListener(‘load’, onLoad);
    function initWebSocket() {
    console.log(‘Trying to open a WebSocket connection…’);
    websocket = new WebSocket(gateway);
    websocket.onopen = onOpen;
    websocket.onclose = onClose;
    websocket.onmessage = onMessage; // <– add this line
    }
    function onOpen(event) {
    console.log('Connection opened');
    }
    function onClose(event) {
    console.log('Connection closed');
    setTimeout(initWebSocket, 2000);
    }
    function onMessage(event) {
    var state;
    if (event.data == "1"){
    state = "ON";
    }
    else{
    state = "OFF";
    }
    document.getElementById('state').innerHTML = state;
    }
    function onLoad(event) {
    initWebSocket();
    initButton();
    }
    function initButton() {
    document.getElementById('button').addEventListener('click', toggle);
    }
    function toggle(){
    websocket.send('toggle');
    }

    )rawliteral”;

    void notifyClients() {
    ws.textAll(String(ledState));
    }
    best regards Stefan

    Reply
  8. Posting html-code in this commenting-software SUCKS,
    The comment-software eats half of the code

    to be honest: Sara and Rui you should consider using a completely DIFFERENT commenting-software. All this is about programming. Which means SOURCECODE is a very important part.

    So the Commenting-part should be able to show Sourcecode as Sourcecode
    with a FIXED-DISTANCE-font and as CODE-SECTIONS like ANY other programming-user-forum does.

    I did some reading about PROGMEN and rawliterals and found the syntactical error.
    Then I did do the following:
    I took my mouse holding down the left mousebutton to start marking the sourcecode of the seconds sourcecode (the one that is called Web_Server_LED_OTA_ESP32)

    I finished the marking of the source-code by keeping the shift-key-pressed using cursor down to mark all characters that belong to the sourcecode.

    Then I pasted this into the Arduino-IDE. As a cross-checking I pasted it into UltraEdit, Notepad++ , and standard-notepad.

    Always the same result:

    At the end of the html-code there is

    )rawliteral”;

    “)” is one line BELOW the “>”

    the closing bracket of the command rawliteral is one line below the closing “edgy” bracket “>” of the html-tag

    it is the same inside the “RAW-Code”-page

    as soon as I removed the CR so the source-code looks like this
    )rawliteral”;

    the closing-tag and closing bracket and rawliteral all in the SAME line

    the code compiled.

    This is why I highly doubt that you did do an EXACT copy and paste into Arduino-IDE without any additional hand-formatting

    How would it be possible that your Arduino-IDE could remove a CR (a carriage-return) while my version and any other texteditor does not?

    I developed the habit of testing / repeating ALL steps (if I say ALL steps I mean ALL steps!!)
    before uploading code into a user-forum. Even after changing a single character.

    Which means for example I upload a code-example to the Arduino-forum as a code-section. I use the select all link to copy the code into the clipboard
    I paste this clipboard-content into an absolutely empty new opened sketch and do a REAL upload into the microcontroller and testing the code and watch the serial output does the code behave like expected?

    So this means I do ALL steps another user will do if he tries my code.

    Maybe you have tested it with PlatForm-IO or did remove the CR because you thought it was a type by you

    There is nothing more frustrating for newbees as if a tutorial pretends to explain everything step by step and therefore seems to be fool-proof and then it does not work because of an error in the description.

    best regards Stefan

    Reply
    • I’ve also tested the exact code on my computer right now and it works fine for me. I honestly don’t know what’s missing either.

      I’ve copied the exact code from our website again to both Arduino IDE and VS Code and both compiled just fine.

      Reply
  9. again this forumsoftware eat up the html-code so I add it modified by inserting underlines between each character of the html-code-part
    your codeversion looks like this
    </_h_t_m_l>_)
    rawliteral”;

    it should be in ONE line
    </_h_t_m_l>_)rawliteral”;

    best regards Stefan

    Reply
    • Hi Stefan.
      I’m sorry for the issue.
      But I did test the code, as I told you.
      I copied the raw code, paste it into Arduino IDE, and recompiled it. It went fine, as you can see here: https://imgur.com/ylc6n11.
      This is all very weird, and I can’t find any explanation about this. What’s the version of Arduino IDE that you are using? I’m using 1.8.13.
      All I want is that the tutorials work for everyone straight away.
      I’ll try to investigate a bit more about this issue.
      Regards,
      Sara

      Reply
  10. I tried it with Arduino Ide 1.8.12 and 1.8.13 both showed the same problem.
    Then I tried it with Arduino-IDE 1.8.13 on another computer same problem.

    the combination of
    – changing the code of function “processor” to return String()
    with
    – putting the html-tag and the keyword rawliteral in the same line of code makes the compiler compiling.

    Beside this weird syntax-pickiness this OTA-functionality is great.
    Thank you very much for providing this.

    What really astonished me was the fact that – if I call the ESP32’s website with the LED-toggle-button from multiples computers that on each computer the written state of the LED gets updated the same tenth second I click on the button.
    No reloading of the website requiered. That is really great!

    Do you happen to know a WYSIWYG-website designer that makes it easier to create the HTML-code?

    Or do you have a tutorial that shows how the HTML-elements like buttons, sliders etc. are programmed?
    I mean giving an example and explaining the details through variations:

    positioning the button
    button-size
    button color
    button-text
    how to evaluate the button-click
    how to change the button text/color on run-time
    etc.

    best regards Stefan

    Reply
    • Hi.

      That’s very strange behavior. Rui also tested the code, and it went fine.
      What operating system do you use? We are on Windows.

      Yes, the synchronization of all clients is a great feature, thanks to the WebSocket protocol.

      I’m not familiar with software like that. We design our own webpages.
      In our latest course, “Build Web Servers with ESP32 and ESP8266,” we go into more detail about writing the HTML and CSS for your web pages, handling the HTML elements, and how to use JavaScript.
      Whenever I have a doubt about HTML elements and how to style them, I usually refer to the w3schools website.
      Regards,
      Sara

      Reply
  11. Hi Sara,

    thank you very much.
    Now a simple copy & paste compiles.
    From both “sources” the Website-section (with the colored code) and the raw code (just text)

    OK I’m gonna take a look into your Build Web Servers with ESP32-course

    best regards Stefan

    Reply
  12. Really great tutorial – excellent level of clarity.
    Where I cam unstuck was my Arduino IDE was an Ubuntu SNAP package and for the life of me I could not find the …SPIFFS.bin file. Solved by installing the IDE from the web site but then had to solve all the python2 – python3 problems. Anyway working really well so now I need to think of a use for it!

    Reply
  13. H Sara,
    I get the following error message when attempting to run the Arduino IDE:

    Arduino: 1.8.13 (Mac OS X), Board: “Adafruit ESP32 Feather, 80MHz, 921600, None, Default”

    Traceback (most recent call last):
    File “esptool.py”, line 57, in
    File “/Library/Python/2.7/site-packages/PyInstaller/loader/pyimod03_importers.py”, line 389, in load_module
    File “serial/tools/list_ports.py”, line 29, in
    File “/Library/Python/2.7/site-packages/PyInstaller/loader/pyimod03_importers.py”, line 389, in load_module
    File “serial/tools/list_ports_posix.py”, line 31, in
    File “/Library/Python/2.7/site-packages/PyInstaller/loader/pyimod03_importers.py”, line 389, in load_module
    File “serial/tools/list_ports_osx.py”, line 32, in
    ValueError: dlsym(RTLD_DEFAULT, kIOMasterPortDefault): symbol not found
    Failed to execute script esptool
    Multiple libraries were found for “WiFi.h”
    Used: /Users/TinkersHome/Library/Arduino15/packages/esp32/hardware/esp32/1.0.4/libraries/WiFi
    Not used: /private/var/folders/5y/zlr8vhg579vbb3glg_x9dfsr0000gn/T/AppTranslocation/1E1F157B-C1FB-4E6D-B760-D8182C3AD58B/d/Arduino.app/Contents/Java/libraries/WiFi
    exit status 255
    /private/var/folders/5y/zlr8vhg579vbb3glg_x9dfsr0000gn/T/AppTranslocation/1E1F157B-C1FB-4E6D-B760-D8182C3AD58B/d/Arduino.app/Contents/Java/arduino-builder returned 255
    Error compiling for board Adafruit ESP32 Feather.

    Would you please tell me what my problem is? Thanks

    Reply
  14. I downloaded esp32 and esp 8266 but it didn’t go into Arduino IDE library. It went somewhere .when I re download
    It said its already been installed.I have windows 10. What can be done?

    Reply
    • “I downloaded ESP32 and ESP8266” is a pretty vague description of what you have
      might done. For programming ESP32/8266 you need to add two additional board-definition url into the preferences. So the minimum is that you describe in detail what you have really have done. And I guess you tried to compile he code. You have to activate verbose output and then analyse the output for the errors that were found during compilation and post the errors here.

      best regards Stefan

      Reply
  15. Another excellent tutorial – many thanks
    Can I suggest changing:
    request->send(200, “text/plain”, “Hi! I am ESP32.”);
    to:
    request->send(200, “text/plain”,FILE “\nCompiled: ” DATE ” ” TIME);

    This will return the path and INO name currently loaded IDE and when it was compiled.
    Also… recommend using fixed IP address otherwise you need to connect to USB to find IP address which defeats the use of OTA

    Reply
    • If you encounter a problem or if you want a new feature do a 5 minute-research.
      5 minutes is almost nothing and in a part of all cases you have success in just 5 minutes.

      I developed this habit. For googling and for coding.

      So I took a look into the file AsyncElegantOTA.h
      and VOILĂ€:
      you can find there two lines for setting up a username and a password

      I haven’t tested this myself yet.

      best regards Stefan

      Reply
      • Thanks Stefan

        I did indeed google but had no joy. Looking at the Code I thought that the credentials referred to the Wifi Password… my coding is a tad rusty 🙂

        I will do a bit more digging in the *.h file.

        Thanks again.

        Reply
        • Ok so this works i.e. adding credentials to this line, “void begin(AsyncWebServer server, const char username = “username”, const char* password = “password”)

          I tried setting them where they are declared in at the bottom of the file but that did not work.

          I just need to now work out how to log off as once you have logged on it appears to keep you logged in.

          Reply
  16. Hello, very good article. A question would be possible, say on any device with ESp32, installed on a user, company client …, to change / register a new wifi network and password on esp32 through the use of the web server page, when so desired?

    Reply
  17. Hello Sara and Rui.

    I’m trying to upload my .bin file into ESP32CAM through OTA, but is occurring a problem…The web page shows up well and I can choose the .bin file, however after show up 100%, doesn’t appear the message OTA Succes and the button BACK. The web page stay on 100% message always.
    After that the ESP32 CAM reboot and run the old firmware.
    Can you help me with this issue?

    Reply
    • I am also trying to upload the LED .bin file via the OTA on a ESP32-CAM board.
      Problem is that when I try to browse to the IP: 192.168.0.114/update after a little delay, I just get a blank screen – ie no web page.
      However, when I browse to 192.168.0.114 I get the expected: “I am ESP32”.
      Could be a problem unique to the ESP32cam board – will try it with a standard ESP32.

      Reply
  18. Very neat solution! A little typo: You explain that the update interface is started by:

    Now, you need to upload that file using the ElegantOTA page. Go to your ESP IP address followed by /updated.

    It should be /update only – without the final “d” – else you get a blank screen.

    Thanks and regards

    Reply
  19. Hi Sara,
    Thank you for sharing the awesome technology. Excellent code base.
    I just have 2 queries

    Can we update firmware and files from intermate, Like if I kept both .bin file on my google drive, every power on Node MCU will check if files are updated and update itself with new .bin files of firmware and other files(html, java, css, image)
    I need to work on some data in csv file, how I can get the csv file from my google drive to my Node MCU

    Reply
    • Hi.
      Thanks for reaching out.
      I think that should be possible, but I don’t have any tutorials about that subject.
      Regards,
      Sara

      Reply
  20. I am trying to use AsyncElegantOTA with an existing sketch, but am getting a bunch of “Multiple definition ” errors.
    I have the sketch set up to allow me to switch Access points, so, I am also including <WebServer.h>. No matter what I tried, I could not get away from multiple errors, so, I loaded your scetch in a new project, It compiles fine.
    But, as soon as I add
    #include <WebServer.h>
    to you sketch, I get a host of errors, leading me to believe WebServer.h and AsyncElegantOTA.h are causing problems for each other.
    Is there a way to include both?
    Is

    sketch\menu.cpp.o:(.bss.AsyncElegantOTA+0x0): multiple definition of AsyncElegantOTA'
    sketch\AsyncIOTWebServer.ino.cpp.o:(.bss.AsyncElegantOTA+0x0): first defined here
    sketch\pin_manager.cpp.o:(.bss.AsyncElegantOTA+0x0): multiple definition of
    AsyncElegantOTA’
    sketch\AsyncIOTWebServer.ino.cpp.o:(.bss.AsyncElegantOTA+0x0): first defined here
    sketch\system.cpp.o:(.bss.AsyncElegantOTA+0x0): multiple definition of AsyncElegantOTA'
    sketch\AsyncIOTWebServer.ino.cpp.o:(.bss.AsyncElegantOTA+0x0): first defined here
    sketch\temperatures.cpp.o:(.bss.AsyncElegantOTA+0x0): multiple definition of
    AsyncElegantOTA’
    sketch\AsyncIOTWebServer.ino.cpp.o:(.bss.AsyncElegantOTA+0x0): first defined here
    sketch\wifi_code.cpp.o:(.bss.AsyncElegantOTA+0x0): multiple definition of `AsyncElegantOTA’
    sketch\AsyncIOTWebServer.ino.cpp.o:(.bss.AsyncElegantOTA+0x0): first defined here
    collect2.exe: error: ld returned 1 exit status
    exit status 1
    Error compiling for board ESP32 Dev Module.

    Reply
  21. Thanks for the answer Sara, will this also be compatible with <ESPAsyncWebServer.h>?

    It may seem as if I cannot make up my mind what I want to use, but, I am only using “WebServer.h” to add new WiFi credentials if I am not within range of a currently known AP, but the project runs on <ESPAsyncWebServer.h> once an AP has been established.

    Reply
  22. OK, Thanks, I will try it, I have already been round and round with so many different ways of trying to acomplish this, one more attempt it is. You & Rui have been an excellent source of quality information!
    Thanks!

    Reply
  23. Hi, nice tutorial and just what i need. I consider myself a novice where esp32 is concerned. I have just 1 question, i need to upload temperature logs to a server hosted outside of my network. So, i will do an http get request with temp values. Can i use the OTA library as well as do the above mentioned tasks. Temp logs get uploaded every 5 minutes

    Reply
  24. OTA means a compiled binary file that contains a PROGRAM will be transferred over WiFi into a reserved part of the ESP32-flash-memory. After storing the new compiled program into the OTA-area successfully, this compiled program is transferred into the regular program-flash-memory.

    compiled binary PROGRAM——>——-any Computer——–>WiFi———–>ESP32——Flash-PROGRAM-memory

    That is a completely different thing than
    ESP32–temperature-Data-in-RAM-Memory—–>——WiFi——->——–external Server

    different direction: Computer—>–ESP32 versus ESP32—–>—-Server
    different datalocation: flash / versus RAM
    different dataNATURE: PROGRAM versus TemperatureDATA

    there could not much more be different between those two tasks

    Reply
    • I know this is years late, but I honestly have to say you have the kindest bedside manner and patience. Rarely do I see individuals not only answer questions but explain almost every detail of the hows and whys of the specific behavior relevant to the question. All the while simultaneously able to be almost combative or hostile with your wit in terms of any blatant lack of competence of the question itself. I applaud the work you do here, sincerely, without any satire. Your expertise in these areas is just as astonishing as your ability to explain/teach/assist. 100x better than the levels I’ve experienced from most EE undergrad professors.

      Best regards
      Travis

      Reply
  25. Hi Sara,

    I am using the ESP32-CAM for my test. All programs work fine on it
    When I do OTA update it hangs at 58% (several attempts done)
    Reloading the web page gives me the update page again with Browse again.
    Do I need to change something for ESP32-CAM?

    Reply
  26. I had problems when update firmware “abort() was called at pc 0x40143f45 on core 1”. ESP32 immediately reset. What do I need to do to make it work properly?

    Reply
    • one possible reason for aborted updates is a wrong partition scheme
      I’m using 4MB 1MB FS OTA ~1019KB
      This means your ESP has to have a 4MB flash-chip
      best regards Stefan

      Reply
  27. Hi
    Thanks a lot for your tutorials, I have ElegantOTA working on my ESP32 project now.

    Wondering if there is a way to write the filesystem to SD instead of SPIFFS?

    I have a webserver running with files on a micro SD card (working thanks to your other tutorials), and would like to make OTA updates to the html files.

    Randy

    Reply
    • FWIW, I gave up trying to save files to a micro sd card using elegant OTA.

      I changed my code to use store and read the html files to/from SPIFFS.
      This is working very well, its now really easy to make changes to the webpage, and then load them with elegantOTA.

      Still using the uSD card to store files, no problem using SPIFFS and uSD at the same time.

      Reply
  28. Hi Sara
    I get error for ws.cleanupClient();

    AsyscWebSocket has no member named cleanupClients

    I’m using a Mac an Arduini 1.8.15

    Reply
  29. I have followed along with the tutorial and updating firmware is working very well except that the progress bar always reads 100%. Then I tried the update file SPIFFS example and I’m stuck. After disconnecting the ESP32 and clicking Tools/ESP32 Sketch Data Upload it fails as expected but the Debug window is blank. I have searched for .spiffs.bin but haven’t found any files. I am using MacOS 10.15.5 and Arduino 1.8.15.

    Reply
  30. HI, after I upload files with the file system,The uploaded file name cannot be found when use “spiffs.opendir (folder_name)”. i don’t know why.

    Reply
  31. Hello,
    always the best compliments for the resources and the work done. I use a machine linux mint 20.xx and, I often have difficulties in the paths of the files, in fact, obviously, different from windows. A description for linux would also be useful for us. That said, I stuck on the point “After this, with the ESP32 disconnected from your computer (that’s the whole purpose of OTA), click on ESP32 Data Sketch Upload” in fact it describes the path for windows (C: \ Users \ sarin \ AppData \ Local \ Temp \ arduino_build_675367 \ Web_server_OTA_ESP32_Example_2.spiffs.bin) but not for linux. Where should I find the affected.bin file? I did some research but they didn’t show, exactly the file you described.
    any help would be appreciated, thanks.

    Reply
    • The filename is the same as your *.ino-file name just with the extension *.bin
      On a windows machine you would type a part of the filename and the search-function would show you all files containing the characters you have searched for.

      Does Linux not have something similar?
      Me personal I use the a tool named “everything.exe” which is listening to the filesystem all the time and recognises any change in realtime.

      So a simple search for *.bin
      over ALL folders of my 2TB hardisk with 1.3 million ! files
      and sorting the files newest first
      would find the compiled binary-file within one second

      best regards Stefan

      Reply
      • 680 / 5000
        Risultati della traduzione
        Thanks for the reply. I apologize for any errors and misunderstandings but I use google translator, I’m Italian. also for this reason, something is lost in the translation of the examples. I seemed to understand that, in windows the “constant” folder is “\ AppData \ Local \” the rest is variable. In this case is there an equivalent in linux? I am not an expert, otherwise I would not follow the tutorials. in addition are you sure that the name of the generated file corresponds to the name of the project.bin (in linux)? I ask because the research shows eg. projectName.ino.doitESP32devkitV1.bin, and again projectName.ino.bin and many other places in different places. It becomes a stressful quest, in my case.

        Reply
        • Hi Gianni,

          you can test this by choosing a very specific name for your *.ino-file
          example:
          I-want-to-find-you-xyz.ino
          and the board is an ESP32-board
          then the filename will be
          I-want-to-find-you-xyz.ino.esp32.bin
          and this *.bin-file is stored in the eat same folder as the *.ino-file

          This is how it works in windows.
          If this is different on linux. Just another thing that makes Linux beginner-UN-friendly

          If you sort the files by date and the last thing you did was creating this *.bin-file
          t should be on top of the *.bin-files or at least in the top 10

          best regards Stefan

          Reply
          • Thanks for the replies.
            after a day of research I came to some conclusions / solutions for me which place, could be useful to others:
            As said I am using a machine with OS linux mint 20.
            Regarding the project, at the point of creation “sketch data upload, upload failed” look for the description of the binary file, as described in the tutorial. take note only of the partial name (my full name in arduino IDE is: [SPIFFS] upload: /tmp/arduino_build_462571/SPIFFS_AsyncElegantOTA_051221.spiffs.bin) copy the partial: arduino_build_462571.
            I open the file manager “NEMO” I go to the / proc folder and look for: arduino_build_462571. in a few seconds it displays the folder containing the file.bin (SPIFFS_AsyncElegantOTA_051221.spiffs.bin in my case)
            Considerations:
            / proc is a virtual folder with many other folders inside that will be deleted and recreated, with different names at each restart. So / proc you will always find it but the inside will always be different.
            As I said, look for the arduino_build_xxxxxx reference “always” in / proc.
            Use “nemo” and in a few seconds you will find the file.
            do not use “caja, nautilus etc” as it would take hours to find it among the dozens and dozens of folders.
            Do not use other managers eg. fserch, they will not read virtual folders, I have tried 7 of the most famous but have not found anything.
            That’s it, I hope it helps someone by avoiding going crazy like me.

  32. Hi Rui/Sara

    Thanks for the tutorial. I was using arduion 2 beta, but it didn’t support elegantOTA file uploads using the ESP32_Sketch_Data_Upload tool, so I dropped back to ardruino ide 1.8.16, which does support the tool (linux desktop). However, I can export the firmware binary and upload it via ElegantOTA webserver on the ESP32, but when I run the ESP32_Sketch_Data_Upload tool to update spiffs files, it immediately says “SPIFFS Error: serial port not defined!” and stops compilation/uploading. So, it never creates the spiffs.bin file that I need to upload via OTA to the ESP32. Any suggestions other than possibly going backwards further with the IDE?? I’d really like to be able to update the firmware and files remotely or I’ll have to continually disconnect my esp32 to reprogram it for updates via USB. Thanks in advance!

    Reply
  33. Thank you for your excellent work and assistance over the past 5 years!

    I have an ESP32 AI-thinker, with a wifi http client script. that works super standalone.

    The script runs fine with either OTAWebUpdater, or ElegantOTA.

    With either OTA approach, on the AI-THINKER esp32 (my required platform). both _abort() on core 1, apparently identically. I tried putting some debug statements in Update.cpp on the OTAWebUpdater _abort() calls, but didnt get anything useful. I moved ot ElegantOTA and had the same problem.

    I haven’t exercised the camera.

    Thank you for your help. the magic esp32 cam AI-thinker is required, with its cool sd card too!

    Bradshaw in Buzzards Bay MA

    23:14:47.477 ->
    23:14:47.477 -> abort() was called at PC 0x40082066 on core 1
    23:14:47.477 ->
    23:14:47.477 ->
    23:14:47.477 -> Backtrace:0x4008372d:0x3ffccc000x4008ca71:0x3ffccc20 0x40091ce1:0x3ffccc40 0x40082066:0x3ffcccc0 0x400ecf47:0x3ffccd10 0x400df3a9:0x3ffccd30 0x400dca46:0x3ffccd50 0x400dcc09:0x3ffccd70 0x400d3192:0x3ffccda0 0x400d32a9:0x3ffcce00 0x400d8941:0x3ffcce60 0x40162366:0x3ffccec0 0x400d4fbe:0x3ffccef0 0x400d68b2:0x3ffccf70 0x400d6a55:0x3ffccfc0 0x400dbc66:0x3ffccfe0 0x400dbce5:0x3ffcd010 0x400dc362:0x3ffcd030
    23:14:47.980 ->
    23:14:47.980 ->
    23:14:47.980 ->
    23:14:47.980 ->
    23:14:47.980 -> ELF file SHA256: 0000000000000000
    23:14:47.980 ->
    23:14:47.980 -> Rebooting…
    23:14:47.980 -> ets Jun 8 2016 00:22:57

    Reply
  34. abort() was called at PC 0x40082066 on core 1

    is there a way to convert the PC back to a source line, or library containing the code? That would facilitate my debugging.

    Bradshaw

    Reply
  35. elegantOTA is working on an esp32 dev, see for OTA and minimal spiffs.

    I dont seem to have the same set the memory model with the AI-thinker esp32 or the esp32s-cam boards.

    Seems I need to learn more about the memory model specification. I bet that is in boards.txt.

    Thanks Bradshaw at Buzzards Bay.

    Reply
  36. Hi. Excellent tutorial. I have followed the steps to perform the basic firmware update and everything goes well until I perform the actual update. The process starts and it jumps from 0 to “25%” quickly and then just stalls. Not sure what’s happening but I have repeated several times with no luck. Using Arduino IDE 1.8.10 on Mac with an ESP32 by Heltec. Any thoughts? Thanks!

    Reply
    • Did you check if the WiFi-signal was strong enough? In my experience if you have a very weak signal RSSI below -75dbi it becomes difficult to do OTA-updates.

      Second thing to check: what is the partition-scheme that you are using?
      it should be a partition scheme that has 1MB for OTA
      best regards Stefan

      Reply
      • I am having the same problem as reported by Andrew Clark. I don’t have a signalling issue.

        The .bin code is 880 KB (note, the actual code is less than 50 KB – the rest is the ESP32 bloatware). I just wonder where is this going? There is not enough sram or flash to fit this file.

        If this is a partition issue, how do I set the partition scheme to 1 MB? And how does this solve the lack of memory problem?

        Thanks
        Bernie

        Reply
    • Problem solved (with the help of Ayush).

      The issue I was having is that my application (vs1053 chip) produces 1,250 external interrupts per second. When you have such high rates of external or timer interrupts, the bootloader refuses to complete without a warning.

      So I had the vs1053 stop producing interrupts before the upload (this can be done automatically by ElegantOA as well), and the uploads all completed.

      Reply
  37. I loaded up the both codes for ESP32 from your tutorial (basic example and websocket server with LED).
    Updated them with each other to test the OTA function. Update function worked well, but after update new sketch does not loaded to run without a reset button push. But in this case I should go to the controller to reset microcontroller the OTA function is less useful.
    What should I do to restart automatically after update and avoid manual restart by reset button?

    Reply
  38. Hi Sara,

    Is there any way that you know of that I can upload both FW.bin and SPIFFS.bin files OTA via an HTTPS webpage? I have successfully done it with FW.bin files using the generic HttpsOTAUpdate.h library but have not been able to get spiffs files to work! I would like to have multiple ESP32 devices update themselves from the same github repository when the reference FW version.text file is changed. Again this I have achieved but only for FW and not SPIFFS

    Reply
  39. Hi
    I have to warn you.
    The zipped elegant OTA Webpage Code in the file elegantWebpage.h in the Variable ELEGANT_HTML[] PROGMEM is obfuscated. For this you can’t know what it really do. My noscript Addon in the Browser informed me, that the Code connect to an CDN for buymeaCoffee Webpage. If this Page will be one time be hacked, then all IoT Devices who use the AsyncElegantOTA Library are in the danger of infection by a Virus or can be hacked.
    Please check the Source Code for your own to decide if this is a problem for you.
    Greeting Cornflake

    Reply
    • Hi Cornflake,

      This is Ayush, creator & maintainer of ElegantOTA, I came to know about your comment when a dear follower of mine sent me a email stating your comment. I would just like to issue a bit of guidance for you:

      The ELEGANT_HTML code you see is NOT obfuscated. It’s simply a gzipped file in bytes array. It has no potential of harming anyone. The whole webpage source is on the github repository itself for transparency.

      Please don’t spread fake news before doing complete research on the topic. You could have compiled the webpage yourself and see if the hash of file was different. Open source is built on trust and I have no intention of disrupting it.

      On a side note, the noscript tag refers to “Buy me a coffee” SDK which uses HTTPS and their own domain. For someone to spoof “Buy me a coffee” domain, they have to be on your LAN network which in itself makes it a useless target because the attacker is already inside your network.

      Reply
      • Hi.
        Thanks for clarifying this.
        I love your libraries. Keep up the great work.
        I have bought you several coffees some time ago:)
        Regards,
        Sara

        Reply
  40. Problem of Elegant OTA uploading failing and freezing in the middle of the upload is solved (with the help of Ayush). See January 1, 2022 issue by Andrew Clark.

    This problem happens only for very large uploads, such as 1 MB binary files. (Note AsyncWebServer is at least 800 KB).

    The issue I had is that my application (vs1053 chip) produces 1,250 external interrupts per second. When you have such high rates of external or timer interrupts, the bootloader refuses to complete without a warning and freezes.

    So I had the vs1053 stop producing interrupts before the upload (this can be done automatically by ElegantOA as well), and the uploads all completed.

    Reply
  41. I ask You
    on Twitter to run as Stand alone Server. You say is won`t work but thats wrong.

    The Solution is:

    //*********SSID and Pass for AP**************/
    static const char *ap_ssid = “Esp_32”;
    static const char *ap_pass = “temp_pass”;

    AsyncWebServer server(80);

    void setup(void) {
    Serial.begin(115200);

    WiFi.softAP(ap_ssid, ap_pass);
    Serial.print(“Access point running. IP address: “);
    Serial.print(WiFi.softAPIP());
    Serial.println(“”);

    // server.begin();
    server.on(“/”, HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send(200, “text/plain”, “Hi! I am ESP32.”);
    });

    AsyncElegantOTA.begin(&server); // Start ElegantOTA
    server.begin();
    Serial.println(“HTTP server started”);
    }

    Reply
  42. Hello. I have a question.
    Is it possible to loosen the LOOP () loop even if the microcontroller does not connect to the wifi network? It seems that the cycle in the setup () section waits for registration in the wifi network and only then initializes loop (). I need the main program to run independently of the connection. I am new to the field and I welcome any advice. I apologize for the imperfect English I’m Czech and I took the help of a google translator.

    Reply
    • I think You could put that part in the loop like :

      void loop() {

      // Wait for connection
      If (WiFi.status() = WL_CONNECTED) {

      Serial.println(“”);
      Serial.print(“Connected to “);
      Serial.println(ssid);
      Serial.print(“IP address: “);
      Serial.println(WiFi.localIP());

      server.on(“/”, HTTP_GET, [](AsyncWebServerRequest *request) {
      request->send(200, “text/plain”, “Hi! I am ESP32.”);
      });

      AsyncElegantOTA.begin(&server); // Start ElegantOTA
      server.begin();
      Serial.println(“HTTP server started”);
      }

      // put Your code here

      }

      Reply
  43. So had this all working wonderfully on a load of devices then upgraded to Ubuntu 22.04! Now in case anyone else hits this the OTA update will not work on the version of Firefox bundled with Ubuntu because it is a snap package. You need to remove it and install a stand alone version as per https://www.omgubuntu.co.uk/2022/04/how-to-install-firefox-deb-apt-ubuntu-22-04

    There is also another little problem that crops up with ESPAsyncWebserver due to recent changes in ESP32 framework so if you end up with compiler errors involving mbedtls when using version 2.0.3 try this solution https://github.com/philbowles/ESPAsyncWebServer/issues/3

    Reply
  44. I can’t login to the ip address even though the serial monitor port prints out: HTTP server started. Can someone tell me how to fix it?

    Reply
  45. Each time I move to the next iteration of the code, it breaks. I’ve had to update files in the library because it won’t compile, or it breaks the OTA update part, or once I fix that it won’t toggle the relays.

    Reply
  46. Well done, it works very well if only one server port 80 is used, in my case, I have a personal weather station feeding data to weatherunderground.com, its configuration has another server (80) entry, so they conflict each other.

    conflicting declaration ‘AsyncWebServer server’

    I’d like to find a solution for this.

    Thank you,
    Alex

    Reply
    • Hello. I’m using another port without anyproblem.

      AsyncWebServer server(8080);

      And later in code :

      //For OTA updates ! …
      AsyncElegantOTA.begin(&server);

      server.begin();

      AsyncElegantOTA.loop() is no longer required with lastest release. That’s it and it’s working like a charm. So simple ! awesome.

      Reply
  47. Thank you for all this Great information. I am just starting out in coding ESP32s. I was able to get this working and it does update my ESP32. My question is that the Elegant OTA web page seems to generate a MD5 hash and send to the ESP32 but I don’t see where the ESP32 recalculates a hash for the received file and verifies it against the received hash before applying the update. Is this a feature?

    Reply
    • I was able to solve the issue my self by designing a custom web page to replace the elegant OTA page that does “POST” a hash of the file being uploaded.
      I was able to change the code on the server side so that the MD5 hash is checked and if it doesn’t match the update returns Fail and the current firmware isn’t changed.

      Reply
  48. Great stuff, as usual!

    Question: How would I format a GET request to obtain the status of one of the LEDs? I can’t see how the variable is stored locally or accessed remotely…

    Thanks…

    Reply
  49. I would not recommend using AsyncElegantOTA. First because the server side is poorly written and second because it’s using an embedded framework (Vue.js) just to display an upload button, thus using a lot of valuable space.

    Reply
  50. With ArduinoUpdate, it was possible to upload the bin directly from the Arduino IDE. The IDE added a virtual serial port and redirected the upload to an IP (wifi).

    Is it still possible, also with this library? Just to have it locally and not over Arduino cloud.

    Reply
  51. Has anyone managed to get this to work with a reverse proxy server from outside the firewall? I have a home network with a Raspberry Pi and a few ESP32’s and ESP8266 MCU’s. The devices all have local IP addresses 192.168.x.x. I have setup an Apache2 as a reverse proxy server on my Pi and pointed it to the device at “192.168.1.9/update”. I can access other services from outside the firewall tunnelled through a VPN, such as the Apache web server running on the Pi as well as all the applications running in docker containers on my Pi. But I cannot access ElegantOTA from beyond the firewall only from a local IP address such as 192.168.1.x.

    Reply
    • I have it working with a NGINX reverse proxy and the proxy adds SSL to the communication to the outside. This way I have the proxy do the authentication as well. I had to change the proxy config to allow large files or else the update was failing.

      Reply
  52. It works fine on my standard ESP32 boards but not on the 3 esp32 CAM boards that I have. The ElegantOTA page shows 58% immediately after choosing the binary file and does not change. In case there was a problem with the sketch (eg I had changed the Led to 4) I simply tried the basic example changing the text to say “ESP32 CAM” and that produced the same result.

    Reply
  53. Good day friends,

    Anybody using esp32 S3 with OTA? I try so many times but i can not successfully update the firmware through OTA

    Reply
    • It does work! But if you are not using an aerial you will almost certainly fail. Try sitting everything next to your wifi router to get as strong a signal as you can and see if that works.

      Reply
  54. I tried this on VSCode with PlatformIO and it’s ridiculously slow to compile.
    I tried this with Arduino IDE and I kept getting “espasyncwebserver.h: no such file or directory”

    The ESPAsyncWebServer library is not on the Arduino library manager search. You must manually download it from https://github.com/me-no-dev/ESPAsyncWebServer and “Add .ZIP LIbrary” on the Arduino IDE.

    My word I hate Arduino so much. Coming from Microchip, STM and Nordic development, this is so sluggish and heavy on Arduino IDE and VSCode.

    Anyway, hope this helps someone.

    Reply
  55. I have followed your tutorial and all works great.
    I would like to use OTA to update a project for the ESP32.
    But it is not clear for me how to implement OTA to an exsisting project?
    Could you give me a little guidance please?

    Reply
  56. Hi Sara!
    Thanks for this great tutorial.

    May I ask why did you choose ElegantOTA over ArduinoOTA for this tutorial? Which one would you use for a more serious project?

    Reply
  57. My most pressing question is how much code and data space I need to be able to have left in an existing project to implement ElegantOTA . For example, if my ESP project reports having used 1110589 bytes (84%) of program storage space. (Maximum is 1310720 bytes.) and Global variables use 62220 bytes (18%) of dynamic memory (leaving 265460 bytes for local variables. Maximum is 327680 bytes.), to that seems like there’s not much room for all the ElegantOTA library needs to do its job. But if you tell me the approximate space requirements, maybe I can plan my next project to be more compact.

    Reply
  58. Hi Sara

    Excellent tutorial, but sadly, in my case it failed at the very last point. I am using the latest versions of Arduino & Arduino IDE on a Mac using latest version of Sonoma.

    I have found and tried to upload my project EnergyMiser_2023.spiffs.bin file and went to IPxxx/upload to select the file. Choose file but got this error :- [HTTP ERROR] Bad Request
    Any suggestions or help please.

    Andrew

    Reply
  59. Hi, thanks for another wonderful tutorial !
    I have a question: is it possible to have the same OTA update functionality with an esp32 running as a wifi access point (not connected to an existing wifi) ?
    I have a project that is for a location that has no wifi (or internet access), and would like to esp32 to act as wifi access point, and have the option to connect to it for updating.
    Any tips or directions are appreciated.
    Thanks again,
    Ken.

    Reply
  60. Hi Sara,
    I am trying using BLE and OTA together in esp32 but I am facing issue that OTA webpage in not loading can you help in this?

    Reply
  61. Hey,
    I tried your OTA code, and followed the same procedure but the problem is my web page for OTA upload is not loading, and instead of that some random file named “update” start downloading. Can you please help me.

    Reply

Leave a Reply to Sara Santos Cancel reply

Download Our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.