ESP32 Over-the-air (OTA) Programming – Web Updater Arduino IDE

Quick guide that shows how to do over-the-air (OTA) programming with the ESP32 using the OTA Web Updater in Arduino IDE. The OTA Web Updater allows you to update/upload new code to your ESP32 using a browser, without the need to make a serial connection between the ESP32 and your computer.

esp32-ota-web-updates

OTA programming is useful when you need to update code to ESP32 boards that are not easily accessible. The example we’ll show here works when the ESP32 and your browser are on your local network.

The only disadvantage of the OTA Web Updater is that you have to add the code for OTA in every sketch you upload, so that you’re able to use OTA in the future.

How does OTA Web Updater Works?

  • 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 a OTA routine you’ll no longer be able to access the web server and upload a new sketch over-the-air.

Prerequisites

Before proceeding with this tutorial you should have the ESP32 add-on installed in your Arduino IDE. Follow one of the next tutorials to install the ESP32 on the Arduino IDE, if you haven’t already.

ESP32 OTA Web Updater

When you install the ESP32 add-on for the Arduino IDE, it will automatically install the ArduinoOTA library. Go to File > Examples >ArduinoOTA> OTAWebUpdater.

This is our complete guide to program the ESP32 with Arduino IDE, including projects, tips, and tricks!  SIGN UP NOW »

This is our complete guide to program the ESP32 with Arduino IDE, including projects, tips, and tricks! SIGN UP NOW »

otawebupdater-arduino-ide

The following code should load.

/*
 * 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);
}

View raw code

You should change the following lines on the code to include your own network credentials:

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

The OTAWebUpdater example for the ESP32 creates an asynchronous web server where you can upload new code to your board without the need for a serial connection.

Upload the previous code to your ESP32 board. Don’t forget to enter your network credentials and select the right board and serial port.

After uploading the code, open the Serial Monitor at a baud rate of 115200, press the ESP32 enable button, and you should get the ESP32 IP address:

Now, you can upload code to your ESP32 over-the-air using a browser on your local network.

To test the OTA Web Updater you can disconnect the ESP32 from your computer and power it using a power bank, for example (this is optional, we’re suggesting this to mimic a situation in which the ESP32 is not connected to your computer).

Update New Code using OTA Web Updater

Open a browser in your network and enter the ESP32 IP address. You should get the following:

Download our Free eBooks and Resources

otawebupdater-user-pass

Enter the username and the password:

  • Username: admin
  • Password: admin

You can change the username and password on the code.

Note: After you enter the username and password, you are redirected to the /serverIndex URL. You don’t need to enter the username and password to access the /serverIndex URL. So, if someone knows the URL to upload new code, the username and password don’t protect the web page from being accessible from others.

A new tab should open on the /serverIndex URL. This page allows you to upload a new code to your ESP32. You should upload .bin files (we’ll see how to do that in a moment).

ota-web-updater-esp32

Preparing the New Sketch

When uploading a new sketch over-the-air, you need to keep in mind that you need to add code for OTA in your new sketch, so that you can always overwrite any sketch with a new one in the future. So, we recommend that you modify the OTAWebUpdater sketch to include your own code.

For learning purposes let’s upload a new code that blinks an LED (without delay). Copy the following code to your 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);
  }
}

View raw code

As you can see, we’ve added the “blink without delay” code to the OTAWebUpdater code, so that we’re able to make updates later on.

After copying the code to your Arduino IDE, you should generate a .bin file.

Generate a .bin file in Arduino IDE

Save your sketch as LED_Web_Updater.

To generate a .bin file from your sketch, go to Sketch > Export compiled Binary

export-bin-file-arduino-ide

A new file on the folder sketch should be created. Go to Sketch > Show Sketch Folder. You should have two files in your Sketch folder: the .ino and the .bin file. You should upload the .bin file using the OTA Web Updater.

Upload a new sketch over-the-air to the ESP32

In your browser, on the ESP32 OTA Web Updater page, click the Choose File button. Select the .bin file generated previously, and then click Update.

After a few seconds, the code should be successfully uploaded.

The ESP32 built-in LED should be blinking.

esp32-blinking-led

Congratulations! You’ve uploaded a new code to your ESP32 over-the-air.

Wrapping Up

Over-the-air updates are useful to upload new code to your ESP32 board when it is not easily accessible. The OTA Web Updater code creates a web server that you can access to upload new code to your ESP32 board using a web browser on your local network.

We hope you’ve found this article interesting. If you like ESP32 you may also like:


Learn how to program and build projects with the ESP32 and ESP8266 using MicroPython firmware DOWNLOAD »

Learn how to program and build projects with the ESP32 and ESP8266 using MicroPython firmware DOWNLOAD »


Enjoyed this project? Stay updated by subscribing our weekly newsletter!

34 thoughts on “ESP32 Over-the-air (OTA) Programming – Web Updater Arduino IDE”

  1. Thanks, very useful!
    Would it be possible in zhis example to access the server from outside the local net?
    If not, how could this be achieved?
    Thanks again!

    • Paul,
      You can do this in a couple of steps:

      1. Use a dynamic DNS service to assign a domain name to your home network. So the outside world can get the your home LAN using something like: my router.dnsservice.com
      Google: dynamic DNS home router

      2. Enable port forwarding on your home router. So a web browser request can be sent to ”myrouter.dnsservice.com:5000” and your router will pass it along from Port 5000 to the IP address assigned to your Arduino/ESP32 device.
      Google: port forwarding

      The are some security implications in all of this – but Google searches will highlight these for you.

      -Kim

    • Hi.
      I just understand Portuguese and English (I’ve used Google Translator to understand what you mean).
      At the moment, we don’t have an example like this is micropython. But I’ll do a research about that, and probably write a blog post about that soon.
      Thank you so much for supporting our work.
      Regards,
      Sara 🙂

  2. Rui,

    How difficult would it be to make this work on a ESP8266 featherwing?

    This solution is what I am looking for to allow clients to update the ESP8266 without having to connect a USB cable.

  3. Your tutorial is great but there is some ambiguity in your explanation. I am not certain if “REPLACE_WITH_YOUR_SSID” and “REPLACE_WITH_YOUR_PASSWORD” refers to the SSID and password on my local network (the router wifi login credentials) or one I am creating for my ESP device. If it is my local router does that mean that I can only update the ESP when I am in range of that router? What I need is the ability to distribute ESP devices to many different remote locations and the users at those locations need to be able to get updates whenever they are available. Also, I do not have a web server that they can connect to. So what I need is to email them the latest BIN file which they then save on their local PC. Then they connect their PC to the ESP via WiFi using the SSID and password set up in the ESP code. Then they enter the IP set up in the ESP code (192.168.1.114 for example). Then the ESP serves then an initial menu page with everything that they can do with their ESP, including updating the ESP firmware. When they choose that option, the ESP web server serves them the same page that you showed by pointing the browser to 192.168.1.148/serverIndex, allowing them to point to the location that they stored the BIN file. And when they click the Update button, the update proceeds. Could you show a way to accomplish this? It would be immensely helpful!

    • Hi.
      SSID and Password refers to the router wifi login credentials. With this example, you can only update code when the ESP is in the range of the router.
      I think you’ve just described what you need to do. I don’t understand what is really your question.
      Regards,
      Sara.

  4. vc precisará abrir uma porta no firewall da rede, ou no roteador que tem em casa, endereçar esta porta na hora de chamar a pagina. Exemplo: http://www.suarede.com/xxx onde suarede é o endereço IP perante o mundo (endereço que sua rede tem para o mundo e não a rede interna) e xxx é o numero da porta que vc liberou no seu roteador

  5. Great Tutorial Guys! Thanks a lot!
    In regards to OTA updates, is there a way to only update Config parameters and not write the entire FW? For example to simple update the reporting interval or specific Config settings/Config file from the server without being on the same network?

    Thanks and keep the great tutorials coming! 🙂

  6. For some reason this site will not let me reply to your reply to my question so I am posting it as a top level question.

    Sorry I wasn’t clear. I need to distribute identical ESPs to many different people, all with router logins that I do not and cannot know. Since your code requires knowing their SSIDs and passwords and entering them into the code, I cannot use your code as written. Is there a way to do this without knowing the SSID and password of my clients’ routers? If so, could you show the code? Thank you!

  7. Is it possible /serverIndex to open in the same tab? Also if the login failure happens, the address bar to NOT show the whole link including the pass and userid?

    In case I need to include some small icons, which directory/where do I need to put them in the code?

    Thank you!

    • This method is not secure, so I wouldn’t rely on the login, because anyone with the URL we’ll be able to access the web page. This sketch comes with the OTA library. In the future, I plan to create a real login to prevent anyone to access and upload a new firmware. Thanks for asking.

  8. Boa tarde!

    O código funciona bem quando fazemos upload de um sketch pela primeira vez, mas se fizermos upload de outro sketch deixa de funcionar.
    É possível solucionar este problema?

    • Olá Francisco.
      Quando faz upload the um novo sketch, esse sketch precisa de ter o código para OTA.
      Se fizer upload de um sketch que não tem a parte de código para OTA, deixa de existir OTA a correr na ESP32.
      Espero que tenha sido claro.
      Cumprimentos,
      Sara 🙂

      • Sim, já estava a falar de sketch’s que incluíam o código OTA.
        Mas mesmo assim o segundo upload não funciona.

        Cumprimentos,

        Francisco

  9. Hello, I’m a little new to the Esp32 and OTA updates. Can someone explain how the server.on callback works with the /update? I see two inline functions, where the first one has the ESP.restart. How does the ESP32 collect the data from the second inline function before performing the software restart?

  10. Is it possible to use this OTA capability along with a web server application? I have an IOT application that uses a server to provide a web page for control of a device (similar to your server example). I’d like to be able to use OTA updates so the devices would not need to be accessed to update the software. How could these be integrated together?
    Thanks,
    Leon

    • Hi Leon.
      The OTA example we provide here uses the and libraries to create a web server in which you can upload new code on the /serverIndex URL.
      One solution is to build your web server on a different URL using these libraries. However, I don’t have any example or tutorial about this.
      Anyway, I hope this suggestion can help you.
      Regards,
      Sara

  11. This works great. Now I want to use OTA reprogramming on an ESP32 that is asleep in light sleep mode most of the time, and not easily accessible to plug in with wires. How long does it need to be awake for to be able to reprogram it?

    • Hi Steve.
      I haven’t tested how much time it needs to completely update the code via OTA.
      You need to test it yourself. But I think that maybe 20 to 30 seconds is enough (it probably needs less time).
      Sorry that I can’t help much.
      Thank you for supporting our work.
      Regards.

  12. Hi All,

    I’ve done an Android app that communicates with my ESP32 via BLE. I am thinking of using the Android app to download a new software image to the ESP32. My thought are to store the ESP32 image on Firebase. The phone will check every so often to see if there is a new image. If there is, it will download it onto itself and then program the ESP32.
    Questions:
    1) Has anyone done this before?
    2) Are there better ways of doing it?
    3) How do I transfer the image from the phone to the esp32? (Some server listening for an image or a command to download the file as described above or a direct Wifi connection?
    4) Any code available?
    Any suggestions will be appreciated

    • Hi.
      That’s quite of a project!
      Unfortunately, we don’t have any tutorials related with what you want to do, and I’m not familiar with those specific subjects to give you advice.
      I’m sorry that I can’t help much.
      Regards,
      Sara

  13. Nice article – I was able to learn many new things here. Simple Understandable Instructions with snapshots. Was easy to learn.
    Good Luck,

    My question : I want to blink GPIO 26 instead of the onboard LED.
    How do I go about it – I tried “const int led=26” – did not work .

    Appreciate a reply.
    Shashi

    • Hi Shashi.
      Have you changed anything else in the code?
      Changing the pin to 26 should work. Please make sure that you’ve wired the LED correctly.
      Regards,
      Sara

Leave a Comment

Download our Free eBooks and Resources

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