GUIDE / ESPFLASH

A practical walkthrough for enabling Over-The-Air firmware updates on ESP32 and pushing new builds from your Android phone with ESPFlash.

How to Set Up ESP32 OTA Updates with ESPFlash

Flashing firmware over a USB cable is simple when your development board sits on a workbench. But once your ESP32 is deployed inside an IP67 weatherproof enclosure, mounted in a ceiling sensor housing, or installed in an industrial cabinet, plugging in a cable for every bug fix is impractical.

This is where Over-The-Air (OTA) updates become essential. OTA allows an ESP32 to receive and write new firmware images wirelessly over Wi-Fi without any physical connection.

With ESPFlash, embedded developers already have a reliable way to flash ESP32 chips directly from Android phones using USB OTG. Now, ESPFlash is bringing OTA support soon, creating an entirely mobile wireless deployment workflow.

This guide explains how ESP32 OTA works, how to configure the dual-partition layout, how to prepare your code, and how to push wireless updates from Android.

Why OTA matters for ESP32 projects

Traditional wired updates create friction once devices leave the lab:

  • Enclosure disassembly: Opening sealed outdoor cases risks damaging waterproof gaskets and requires tools and physical access.
  • Hardware wear: Repeatedly connecting micro-USB or USB-C plugs can loosen surface-mount connectors.
  • Fleet deployment: Updating multiple sensor nodes across a site via cable takes hours. With OTA, updates happen across the local network in minutes.
  • Fail-safe rollback: The ESP32's dual-partition scheme supports automated rollback. If a new firmware build crashes on boot, the bootloader automatically reverts to the previous working build.

Prerequisites for ESP32 OTA

Before setting up wireless updates, make sure you have:

  1. An ESP32 with at least 4MB flash: Standard ESP32, ESP32-S3, ESP32-C3, and ESP32-C6 boards with 4MB or larger flash are supported.
  2. An initial wired flash: Blank flash memory cannot receive OTA updates. You must flash the initial bootloader, partition table, and OTA-capable firmware via USB OTG using ESPFlash.
  3. Local Wi-Fi connectivity: The ESP32 and your Android device must reside on the same Wi-Fi network.
  4. SerialFlow Terminal: A portable Android serial monitor to inspect boot logs, verify IP allocation, and confirm partition switching.

Step 1: Configure the partition table for OTA

Standard ESP32 factory firmware uses a single app partition. For OTA, flash memory must be divided into two application slots (ota_0 and ota_1), plus an otadata partition that tracks which slot is active.

When an update is pushed:

  1. The new binary writes to the inactive partition slot.
  2. The image hash is validated.
  3. The bootloader marks the new partition active in otadata.
  4. The device reboots into the updated partition.

Here is a standard 4MB partition table (partitions.csv):

# Name,   Type, SubType, Offset,   Size,     Flags
nvs,      data, nvs,     0x9000,   0x4000,
otadata,  data, ota,     0xd000,   0x2000,
phy_init, data, phy,     0xf000,   0x1000,
ota_0,    app,  ota_0,   0x10000,  0x1E0000,
ota_1,    app,  ota_1,   0x1F0000, 0x1E0000,

Each app partition gets 0x1E0000 (roughly 1.9MB) of executable space. In Arduino IDE or PlatformIO, select an OTA partition profile like Minimal SPIFFS (1.9MB APP with OTA).

Step 2: Prepare your ESP32 firmware for OTA

Your firmware must run an OTA listener to receive incoming update packets.

Option A: Using the Arduino framework (ArduinoOTA)

The ArduinoOTA library provides an easy-to-use network flashing listener:

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

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

void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("\nConnected. IP: " + WiFi.localIP().toString());

  ArduinoOTA.setHostname("esp32-node");

  ArduinoOTA.onStart([]() {
    Serial.println("OTA update starting...");
  });
  ArduinoOTA.onEnd([]() {
    Serial.println("\nOTA update finished.");
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  });
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("OTA Error[%u]\n", error);
  });

  ArduinoOTA.begin();
}

void loop() {
  ArduinoOTA.handle();
  // Keep loop non-blocking to prevent OTA timeouts
}

Important: Avoid long blocking delay() calls in loop() so ArduinoOTA.handle() can service network transfer packets without timing out.

Option B: Using the ESP-IDF Native OTA API

In ESP-IDF projects, use the native esp_https_ota and esp_ota_ops components. These stream binary chunks directly to the inactive partition and switch the active slot using esp_ota_set_boot_partition().

Step 3: Push OTA updates from Android with ESPFlash

ESPFlash is bringing OTA support soon, enabling mobile developers to update wireless ESP32 devices without a computer.

The upcoming mobile OTA workflow:

  1. Build the firmware: Compile your updated project and save the resulting .bin file to your Android phone or cloud storage.
  2. Open ESPFlash: Switch to the upcoming OTA tab.
  3. Select device: Scan the local network via mDNS (e.g., esp32-node.local) or enter the board's static IP.
  4. Choose binary: Select the compiled .bin file from your phone.
  5. Flash OTA: Tap Start Update. ESPFlash streams the binary over Wi-Fi with live progress and byte counters.
  6. Reboot: Upon successful verification, the ESP32 switches partitions and boots into the new firmware.

Troubleshooting common OTA issues

Issue Cause Solution
"Image size exceeds partition size" Binary size is larger than the 1.9MB app partition slot Use a partition table with smaller SPIFFS/LittleFS allocation, or strip unused libraries.
Wi-Fi drop during transfer Modem power saving causes frame drops Call WiFi.setSleep(false); in setup() to disable Wi-Fi sleep during transfers.
MD5 mismatch error Network packet corruption during transfer Retry the upload in an area with good Wi-Fi signal (-70 dBm or better).
Rolls back to previous version Anti-rollback triggered because app didn't validate Call esp_ota_mark_app_valid_cancel_rollback() in application setup after boot checks pass.
Board not found on network Phone and ESP32 are on isolated subnets Ensure both devices share the same local Wi-Fi, or enter the board IP directly.

Verifying updates with SerialFlow Terminal

After completing an OTA update, verify that the board boots cleanly into the new partition.

Connect the board or view debug logs using SerialFlow Terminal. The bootloader confirms the new active partition:

I (142) boot: Loaded app from partition at offset 0x1F0000
I (153) cpu_start: Pro cpu up.

Using SerialFlow's real-time filters and regex matching, you can verify Wi-Fi re-association and peripheral initialization without hunting through routine log traffic.

Summary

Combining USB OTG flashing for initial provisioning with wireless OTA updates provides a complete mobile workflow:

  • Use ESPFlash for USB OTG setup, and look forward to its upcoming wireless OTA update capabilities.
  • Use SerialFlow Terminal for real-time serial debugging, log filtering, and telemetry monitoring.

Together, they provide a full embedded development and maintenance toolchain right on your phone.