Download PDF Parts List View on YouTube Download Code

Today we’re working with something that will change the way you manage your ESP32 projects: Over-The-Air (OTA) firmware updates. OTA is the ability to push new code to a microcontroller wirelessly – no USB cable required.

Introduction

If you’ve spent any time working with ESP32s, you’re familiar with the usual development cycle. First, you connect the board via USB, compile your sketch in the Arduino IDE, and then click Upload. During initial project development, this method makes sense, as having the ESP32 on your workbench alongside the Arduino IDE is convenient.

But once you deploy your project, updates become a bit trickier.  Yes, you can “uninstall” the ESP32 and bring it back to the workbench when it needs an update, or it may be easier to bring a notebook computer with the Arduino IDE to the ESP32.  Both methods are awkward and unprofessional!

OTA changes all that. Once you configure your sketch with OTA code, you can update it from anywhere on your network.

OTA updates become especially powerful once your ESP32s are deployed in the real world. Imagine a home automation network with half a dozen ESP32s scattered around – one monitoring the garage temperature, another controlling irrigation in the garden, and a third monitoring the front door. Without OTA, fixing a bug means physically tracking down each board, plugging it in, and flashing it. That’s tedious at best, and completely impractical when boards are sealed inside enclosures or tucked behind walls. With OTA, you update all of them from your desk.

In this article, we’ll explore three distinct OTA methods, each suited to a different scenario. The first method lets you upload directly from the Arduino IDE over Wi-Fi, just as naturally as you’d upload via USB. The second method has the ESP32 host its own web page with a file-upload form (I’ll show you how to make a .bin file to flash teh ESP32 with). And teh third method has the ESP32 check a local file server for a newer version and download it automatically.

Each method has its own strengths and ideal use cases, and we’ll cover them all in detail.  But first, let’s see how the ESP32 performs OTA updates.

How ESP32 OTA Updates Work

The key to understanding the OTA update process is to examine the ESP32’s Flash memory partition table.  A snapshot of the Flash memory of a Seeduino XIAO ESP32-S3 is shown here:

There are three partitions central to the OTA process.

  • The app0 (OTA0) partition – This is the primary application slot — this is where your sketch lives after an initial USB upload.
  • The app1 (OTA1) partition – This is the secondary application slot, where incoming OTA firmware is written during an update.
  • The otadata partition – This is a small metadata area that tracks which of the two application partitions is currently active. It acts like a pointer, telling the bootloader which slot to run at startup.

Here is how the partitions are used:

Before an update, the ESP32 is running code from app0; the otadata partition points to app0 as the active slot.

When a new OTA update begins, new firmware starts uploading via Wi-Fi while the ESP32 continues running the existing app0 code without interruption. The new firmware is written into the idle app1 partition.

Once the download is complete and verified, otadata is updated to point to app1, and the ESP32 reboots into the new code.

On the next update, the process reverses: new firmware goes into app0, and after a successful update, otadata flips back to app0. This alternating cycle continues indefinitely.

One important thing to remember is that every OTA firmware version must include OTA code. If you flash a sketch without OTA support, the board loses its wireless update capability and must be recovered via USB. Keep the OTA code in every firmware release.

Hardware and Experiment Setup

I will be using an ESP32-S3 for the experiments today, specifically a Seeeduino XIA ESP32-S3 board. This board has 8 MB of Flash memory, which will allow for two suitably large OTA partitions. The board uses a dual-core Xtensa LX7 processor running at 240 MHz and also has 8 MB of PSRAM.

To make the hookup easier, I have chosen to use the Seeduino XIAO Expansion Board V1, which is available from the same retailer that sold you the Seeduino XIAO ESP32-S3. This handy expansion board turns the XIAO into an all-in-one development platform, providing a built-in SSD1306 OLED display (128×64 pixels) for status messages, a push button ready to use, LiPo battery support with a charging circuit (essential for powering the ESP32 without USB), a real-time clock with battery backup, and Grove connectors and DuPont headers for easy peripheral attachment.

If you don’t have the expansion board, all you need is a standard SSD1306 OLED display (the common 128×64 I²C variant) and a momentary push button. Wire everything up as shown below:

The same hardware hookup is used for all three demo sketches.

Software and Library Setup

All three sketches are written for the Arduino IDE using the ESP32 board package.

Select the Seeeduino XIAO ESP32-S3 in the board manager and make sure you’re using a partition scheme that supports OTA – the default XIAO ESP32-S3 setup provides an OTA-capable layout, but it’s always worth confirming this in the Tools menu.

The Wi-Fi, OTA, and HTTP libraries used in these sketches are all included with the ESP32 Arduino core and require no additional installation.

The only libraries you may need to add via the Library Manager are the Adafruit SSD1306 and Adafruit GFX libraries for the OLED display. Search for “Adafruit SSD1306” in the Library Manager; it will automatically prompt you to install the GFX dependency.

Now that we have our ESP32 attached to our workstation and the IDE configured correctly, we can begin with our first OTA method.

Method 1 — ArduinoOTA

The ArduinoOTA method is the simplest method for anyone who already uses the Arduino IDE. Once the OTA-enabled sketch is running on the board, the ESP32 shows up as a network port in the Arduino IDE, right alongside your regular USB COM ports. You simply select it and upload, exactly as you would via USB.

One thing to note is that the wireless connection cannot be used for the Arduino IDE Serial Monitor.

ArduinoOTA Demo

We will demonstrate ArduinoOTA using a simple sketch that prints to the OLED and flashes the XIAO’s built-in LED.  After uploading it to the XIAO and verifying proper operation, we will remove the USB cable and connect to the XIAO via wireless. We will then modify the sketch and perform an OTA update directly from the Arduino IDE.

Here is the sketch we will be using to demonstrate ArduinoOTA:

Key Code Concepts

The ESP32 must be in station mode, connecting to your existing Wi-Fi network rather than creating its own access point. This is set with WiFi.mode(WIFI_STA).

A critical entry is WiFi.setSleep(false), which keeps the Wi-Fi radio awake at all times. Without this, the radio enters a power-saving sleep mode between packets and will miss incoming OTA connections, causing slow or completely failed uploads. This is one of the most common sources of ArduinoOTA problems, particularly on the ESP32-S3.

Each device should have a unique hostname set with ArduinoOTA.setHostname(). This is what appears in the Arduino IDE port list, allowing you to distinguish between multiple boards on the same network.

Before calling ArduinoOTA.begin() you must register your onStart, onEnd, onProgress, and onError callbacks. In this sketch, we set them to display status on the OLED and log progress to Serial.

Finally, ArduinoOTA.handle() must be called at the very top of every loop() iteration. This is what lets the library receive and process incoming OTA connection requests.

Use Non-Blocking Code!

This is one of the most important things to understand when working with any type of OTA update.

You will note that we do not use the delay() function to blink our LED. The delay() function completely blocks the microcontroller for its entire duration, and nothing else runs, including ArduinoOTA.handle(). If an OTA upload request arrives while the MCU is inside a delay(), it will be missed entirely, and the update will fail or time out.

The demo sketch blinks the onboard LED using millis() rather than delay() – the LED state is toggled only when enough time has elapsed, leaving the processor free to handle OTA requests between checks.

You’ll need to make sure not to use any blocking code statements in a design that uses OTA.

ArduinoOTA Testing

Load the sketch to the Arduino using the conventional USB cable method. You should see the version number printed on the OLED and observe the LED flashing once per second.

Now disconnect the USB cable and power the ESP32 with an alternate power supply. I used a LiPo battery connected to the Expansion Board, but you could also use a USB power supply.

Make sure your workstation is on the same Wi-Fi network as the ESP32; this is critical!

Look in the ports of the Arduino IDE. You should see a new “Network” section, with a wireless connection available. If you don’t see it, try closing and opening the Arduino IDE. If you are on Windows, you may need to allow the connection through your Windows Firewall.

After connecting to the wireless network, you may need to tell the Arduino IDE that the board is a XIAO ESP32-S3.  After you do that, you can use the connection just as you would a USB connection.

Make some edits to the sketch:

  • Change the version number to “1.1”
  • Change the blink delay to “300”

Now save and upload the sketch.

The first time you upload, you will be prompted for a password. You can enter any character you like in the password box to continue. A password is not required, but the Arduino IDE stores one in case it is.

The upload should proceed as usual, except for a different font color in the IDE.

Once the upload is completed, the ESP32 will reset itself and display the new version number. You will also note the LED blinking much faster.

The ArduinoOTA method has advantages and disadvantages, as shown here:

If all you want to do is use the Arduino IDE to perform occasional OTA updates, the ArduinoOTA method may be all you need.

Method 2 — Web OTA

Web OTA takes a completely different approach to updating the ESP32 over the air. Instead of requiring the Arduino IDE on the update computer, the ESP32 itself hosts a small web server with a firmware-upload form. Any device with a web browser on the same network can trigger an update, with no programming environment required.

This makes it an excellent option for distributing firmware updates to customers, colleagues, or less-technical users with whom you’d rather not share your source code.

Web OTA .bin File

The firmware update file is a binary or “.bin” file. You can easily generate one using the Arduino IDE.

  • Make edits with the IDE and save your sketch (make sure to retain the OTA code in the new version).
  • Open the Sketch menu at the top of the Arduino IDE.
  • Select “Export Compiled Binary“.

The sketch will compile as usual. Once it finishes compiling, it will just stop; no message will be displayed other than the standard compiler messages.

So where is the output? Go to your sketch folder (usually under your Arduino folder), and you should see a “build” folder. Inside this folder you will see several files, including many with “.bin” extensions.

The one you want is the sketchname.ino.bin file. If you are using the example sketch, the file will be named 2_WebOTA.ino.bin. This is the file you will use to upload your updates via the web page created by the ESP32.

Web OTA Sketch

Here is the sketch that we will be using to demonstrate Web OTA:

The sketch uses the ESP32’s built-in WebServer and Update libraries.

The buildPage() function dynamically assembles the HTML status page, pulling in the current firmware version, IP address, uptime counter, and the PAGE_ACCENT color variable. The upload form posts the binary file to the /update endpoint via an XHR request with a progress event listener, which drives the progress bar in the browser.

Two key server handlers drive the OTA process. handleUpdateUpload() is called repeatedly as binary data chunks arrive; it feeds those chunks to the Update library with Update.write().

When the upload is complete, handleUpdateDone() sends a response to the browser and, if the update was successful, calls ESP.restart() to reboot into the new firmware. The Update.end(true) call in the upload handler verifies the written image before the reboot proceeds.

Demonstrating Web OTA

Load the sketch to the ESP32 using a USB cable (you could also load it via the OTA connection if you are still connected from the first experiment).  Note that the screen displays the version number, IP address, and uptime.

Now disconnect the USB cable (if you used one) and power the ESP32 with an alternate power source.

Open a web browser on a computer connected to the same Wi-Fi network as the ESP32, then go to the IP address displayed on the OLED. You should see an upload page.

We will need a binary file to upload to update our ESP32.  Back in the Arduino IDE, edit the following parameters in the sketch:

  • The version number
  • The screen color (use the suggested color or one of your own)

Now save the sketch and compile it to a binary file using the instructions provided earlier. Use the Choose File button on the upload web page to select the correct .bin file. Then click the Upload and Flash button to start the update. You will see a progress bar while the upload is occurring.

When the upload is done, the ESP32 will restart. Note the version number on the OLED, and the color of the web page ( you may need to manually refresh the web page).

As with the previous method, there are pros and cons to using Web OTA:

This is a great way to update the ESP32 project you build and distribute to others. All you need to do is provide a .bin file to update or refresh the firmware.

Method 3 — Server Pull OTA

Server Pull OTA is the most professional of the three methods and the one most commonly used in production IoT systems. Instead of an update being pushed to the ESP32 from outside, the ESP32 itself reaches out to a file server, checks whether a newer version is available, and, if so, downloads and installs it automatically.

The file server hosts just two files: latest.txt, which contains the current version string (e.g., 1.1), and firmware.bin, which is the compiled application binary. When an update check is triggered, the ESP32 fetches the latest.txt file, compares the version string to its LOCAL_VERSION constant, and calls the update routine only if the server version is newer.

In our demo, the check is triggered by pressing the hardware button, but the same checkForUpdate() function can just as easily be called at boot, on a timer, or at any other point in your application logic, making the entire process fully automatic.

Setting Up the Python File Server

Any computer on your local 2.4 GHz Wi-Fi network can act as the file server. We’ll use Python’s built-in HTTP server module, which requires zero configuration and serves files from any folder you point it at.

Python 3 is pre-installed on most Linux and macOS systems. On Windows, download the installer from python.org and run it (make sure to tick “Add Python to PATH” during setup), and reboot after installation before proceeding.

Follow these steps to get the server running. C

  • Create a dedicated folder to serve files (the sketch download from dronebotworkshop.com includes a ready-made server subfolder for this purpose).
  • Inside that folder, create a plain text file named latest.txt. Its entire content should be just the version string: 1.0 — no quotes, no extra lines.
  • Open a terminal (Linux/macOS) or Command Prompt (Windows) and navigate to that folder. Most operating systems let you open the terminal directly in the folder using your file manager.
  • Find your machine’s IP address on the Wi-Fi network using ipconfig on Windows or ifconfig on Linux/macOS. Look for the IPv4 address on your wireless adapter, not any Ethernet or virtual adapter addresses.

Then start the HTTP server with the following command.

Test it by opening a browser on any device on the network and navigating to http://<your-ip>:8080. You should see a directory listing showing latest.txt. If you open that file, it should show 1.0. Your server is ready.

Now that we have a web server, we can concentrate on the code we will use to work with it.

Server-Pull OTA Code

The sketch for using Server-Pull OTA is a bit more complex than the previous two; however, it is pretty easy to understand once you break it into sections.

The sketch is organized into several helper functions, each handling one part of the update pipeline.

fetchServerVersion() opens an HTTP connection to baseURL() + “/latest.txt” using an explicit WiFiClient object. It includes a single automatic retry because the first HTTP request after boot occasionally returns -1 while the connection is still settling.

The returned string is passed through sanitizeVersion(), which strips everything except digits and dots. These can become an issue when you use text editors like Notepad to edit the version file.

isNewer() performs a proper dotted-numeric version comparison using sscanf() to parse the major and minor version numbers separately, so 1.9 is correctly identified as older than 1.10  (a comparison that would fail with a simple string equality check).

doUpdate() calls the httpUpdate library to stream firmware.bin directly into the inactive flash partition, with an onProgress callback that throttles OLED redraws to every 10% (updating the display on every packet would stall the transfer, since full I²C redraws are slow. Every 10% is still visually pleasing).

checkForUpdate() ties all of these together: it calls fetchServerVersion(), compares the result to LOCAL_VERSION, and either triggers doUpdate() or reports that the board is already up to date.

Testing the Server-Pull OTA

As with the other two OTA demonstrations, we will start by uploading the sketch to the XIAO ESP32-S3 using a USB cable (it is also possible to use one of the previous OTA methods).   Make sure the ESP32 is on the same network as the web server and that you can access it at the designated IP address (don’t forget the “8080” at the end of the URL).

In my demonstration, I left the USB cable connected to the ESP32 to monitor the Serial Monitor traffic.

Test the board by pressing the pushbutton. This will cause the ESP32 to read the version file on the server and compare it to its own internal version. At the beginning, it should be version 1.0, so you should see a message on the OLED and Serial Monitor confirming that the ESP32 is at the latest version.

Now make some changes to the file; the version number is sufficient. Compile this into a binary file. Copy that binary file to the web server and rename it to firmware.bin (that last step is very important).

You also need to edit the latest.txt file to show the latest version. Change the 1.0 to 1.1.

Now repeat the test on the ESP32. When you press the pushbutton, the ESP32 should check for the latest version and see if one is available. It should then download the update and restart the ESP32.  The progress will be shown on both the OLED and the Serial monitor.

In our demonstration, the check for updates was initiated with the pushbutton, but you could just as easily have used a timer to do this automatically at a preset interval.

Conclusion

Using the ESP32’s OTA Update capabilities will really take your projects to the next level. With minimal code and setup, your ESP32 projects can continue to benefit from updates and improvements for years. And if you’re tasked with keeping a fleet of ESP32 devices up to date, your job just became a lot easier.

Hope you enjoyed the article. Make sure to check out the associated YouTube video to see the three OTA demonstrations in action.

Parts List

Here are some components that you might need to complete the experiments in this article. Please note that some of these links may be affiliate links, and the DroneBot Workshop may receive a commission on your purchases. This does not increase your cost and is a way to support this ad-free website.

XIAO ESP32-S3               Seeed Studio         Mouser

XIAO Expansion Board       Seeed Studio     Mouser

 

Resources

Code for this Article – All of the code used in the article, in a handy ZIP file. Includes Server files

PDF Version – The article in a PDF format

OTA Updates – Espressif’s guide to Over-the-Air updates.

 

 

 

 

ESP32 OTA (Over The Air) Updates
Summary
ESP32 OTA Updates
Article Name
ESP32 OTA Updates
Description
Learn how Over-The_Air or OTA updates work with the ESP32. We will look at three different methods of updating your ESP32 without using a USB cable.
Author
Publisher Name
DroneBot Workshop
Publisher Logo
Tagged on:

1 Comment
Oldest
Newest
Paul
7 days ago

Great article! Thanks Bill.