Welcome to ESP32 Core Board repository
12. WIFI OTA Upgrade
WIFI OTA Upgrade
WIFI OTA Upgrade1. OTA Overview2. Core Concepts2.1 ESP32 Flash Partition Table2.2 OTA Upgrade Flow2.3 Version Number Extraction — Magic Number Search Method2.4 OTA Core API3. RGB LED Color Semantics4. Project Architecture and Flow5. Hardware Dependencies and Third-Party Components6. Source Code Analysis6.1 Macro Configuration6.2 WiFi Initialization (Review)6.3 compare_version() — Version Number Comparison6.4 get_server_version_from_bin() — Extract Version from Firmware Header6.5 vOtaTask() — OTA Main Task (Download + Write Part)6.6 Preparing Upgraded Version Firmware7. Operation Flow7.1 Build and Flash Flow7.2 Pre-Operation Notice (Must Know)7.3 OTA Upgrade Operation Steps7.4 Normal Log — OTA Upgrade Full Flow8. Common Problems
1. OTA Overview
OTA (Over-The-Air) is a core operational capability for IoT devices—download new firmware over WiFi network, write to OTA partition in Flash, then automatically restart and switch to new firmware. No USB cable needed, no JTAG programmer, product can still be updated after deployment.
Importance: OTA is the "lifeline" of mass-produced IoT products—without OTA, every bug fix or feature update requires manual disassembly and flashing, unacceptable cost. Smart home, industrial sensors, agricultural monitoring, and all remotely deployed embedded devices rely on OTA for continuous iteration.
Application Scenarios: Smart bulb firmware upgrades, industrial sensor algorithm updates, agricultural monitoring node remote patches, wearable device feature push, etc.
| Upgrade Method | Operation | Applicable Stage | Physical Wiring Required |
|---|---|---|---|
| USB serial flash | USB cable + idf.py flash | Development/debugging | Yes |
| JTAG flash | JTAG debugger | Development/debugging | Yes |
| WiFi OTA | HTTP download + partition switch | Mass production | No |
This project implements complete OTA upgrade flow: WiFi connect → HTTP Range read server firmware header to extract version → version comparison → if server version newer, fully download firmware → write to OTA partition → switch boot partition → restart. Onboard WS2812 RGB LED uses color to indicate current version and upgrade status.
2. Core Concepts
2.1 ESP32 Flash Partition Table
ESP32 Flash is divided into multiple partitions. OTA requires at least two APP partitions for alternating use:
┌────────────────────────────────────────────────────────┐│ Flash Layout (simplified) │├───────────┬──────────┬──────────┬──────────┬───────────┤│ Bootloader│ OTA Data│ factory │ ota_0 │ ota_1 ││ │ (2x4KB) │ (app) │ (app) │ (app) │└───────────┴──────────┴──────────┴──────────┴───────────┘↑ ↑ ↑ ↑Fixed First factory OTA alternate partitions (A/B switch)
factory: Factory firmware (only this one APP partition without OTA)
ota_0 / ota_1: Two APP partitions used alternately. Currently running ota_0 → OTA writes to ota_1 → restart and boot from ota_1 → next OTA writes to ota_0
OTA Data: Metadata recording "which partition to boot from next"
2.2 OTA Upgrade Flow
xxxxxxxxxx┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐│ 1. Version check │ → │ 2. Download firmware │ → │ 3. Write partition │ → │ 4. Switch reboot ││ Range 1KB │ │ HTTP GET full │ │ esp_ota_write│ │ set_boot_ ││ read version │ │ firmware bin file │ │ write block │ │ partition + ││ │ │ │ │ │ │ esp_restart │└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
2.3 Version Number Extraction — Magic Number Search Method
To avoid downloading entire firmware just to know the version number, use HTTP Range request to download only the first 1024 bytes of firmware, locate version number by searching magic number of esp_app_desc_t struct:
xxxxxxxxxxesp_app_desc_t struct layout in firmware bin:┌──────────────┬─────────────────────────────────┐│ Offset │ Content │├──────────────┼─────────────────────────────────┤│ 0x00 │ magic_word = 0xABCD5432 ││ │ Little-endian: 32 54 CD AB │├──────────────┼─────────────────────────────────┤│ 0x10 │ version[32] version string ││ │ e.g. "1.0.0\0..." │└──────────────┴─────────────────────────────────┘
Search algorithm:
HTTP Range
bytes=0-1023download firmware header 1KBByte-by-byte match magic byte sequence
{0x32, 0x54, 0xCD, 0xAB}Magic found → offset +0x10 read 32-byte version string
Verify first character is digit ('0'~'9'), prevent false match
2.4 OTA Core API
x// Get "next" writable OTA partition (the one besides current running partition)const esp_partition_t *esp_ota_get_next_update_partition(NULL);
// Begin OTA writeesp_err_t esp_ota_begin(const esp_partition_t *partition, size_t image_size, // OTA_SIZE_UNKNOWN = unknown size esp_ota_handle_t *handle);
// Write block by block (max 4096 bytes each time, aligned with HTTP read buffer size)esp_err_t esp_ota_write(esp_ota_handle_t handle, const void *data, size_t size);
// End write (verify + close)esp_err_t esp_ota_end(esp_ota_handle_t handle);
// Set next boot partitionesp_err_t esp_ota_set_boot_partition(const esp_partition_t *partition);3. RGB LED Color Semantics
Onboard WS2812 RGB LED (GPIO 48) indicates firmware version and upgrade status:
| Color | RGB | Meaning | Trigger |
|---|---|---|---|
| Red | (255, 0, 0) | Initial version v1.0.0 | First run after app_main flash |
| Green | (0, 255, 0) | Upgraded version v2.0.0 | When compiling upgraded firmware, comment out red line, uncomment green |
| Blue | (0, 0, 255) | Upgrade success | After OTA download + write complete, lights for 3 seconds before restart |
Color is manually switched by commenting/uncommenting lines in
rgb_led_set()in code, visually demonstrating version change before and after OTA.
4. Project Architecture and Flow
xxxxxxxxxxapp_main()├─ nvs_flash_init() ← Init NVS flash├─ rgb_led_init() ← Init WS2812 RGB LED├─ rgb_led_set(255,0,0) ← Light red (initial version, or change to green)├─ wifi_init_sta() ← WiFi STA init│└─ xTaskCreate(vOtaTask, stack=20KB, prio=5)└─ vOtaTask():├─ Wait for WiFi connection (WIFI_CONNECTED_BIT, 30s)│├─ 1. Get local firmware version → esp_app_get_description()->version│├─ 2. Get server firmware version (retry up to 3 times)│ └─ get_server_version_from_bin(OTA_URL, ...):│ ├─ HTTP Range bytes=0-1023 download firmware header 1KB│ ├─ Search magic 0xABCD5432 (little-endian: 32 54 CD AB)│ └─ Magic offset +0x10 → version string│├─ 3. compare_version(server_ver, cur_ver)│ ├─ server ≤ current → No upgrade needed, delete task│ └─ server > current → Continue OTA│├─ 4. OTA download and write:│ ├─ esp_http_client_open(OTA_URL)│ ├─ esp_ota_get_next_update_partition() → ota_part│ ├─ esp_ota_begin(ota_part, OTA_SIZE_UNKNOWN, &handle)│ ├─ while(1):│ │ ├─ esp_http_client_read(buf, 4096) → len│ │ ├─ len == 0 → break (download complete)│ │ ├─ esp_ota_write(handle, buf, len)│ │ └─ Print progress bar every 10%│ └─ esp_ota_end(handle)│├─ 5. esp_ota_set_boot_partition(ota_part) → Set boot partition├─ 6. rgb_led_set(0, 0, 255) → Light blue├─ 7. vTaskDelay(3000ms) → Display blue for 3s└─ 8. esp_restart() → Restart to new firmware
5. Hardware Dependencies and Third-Party Components
Hardware Connection: WiFi uses onboard antenna; RGB LED connected to GPIO 48 (WS2812, onboard)
Required Components:
esp_http_client,esp_ota_ops,esp_app_desc,led_strip(espressifled_strip),esp_delta_ota(espressifesp_delta_ota),esp_wifi,esp_netif,esp_event,nvs_flash
This tutorial uses third-party libraries again: For detailed installation, review (Installation of Third-Party Libraries: 2.Basic Peripherals -> 10.RGB LED Effects -> RGB LED Effects)
Note:
led_stripandesp_delta_otaare third-party components from ESP-IDF Component Registry, not built into IDF. Before using, must declare inmain/idf_component.yml:xxxxxxxxxx## IDF Component Manager Manifest Filedependenciesidfversion'>=4.1.0'espressif/led_strip^3.0.0espressif/esp_delta_ota^1.1.4After declaration, execute
idf.py build, component manager will automatically download tomanaged_components/directory.
6. Source Code Analysis
Full source: Source Code → ESP-IDF → 5.Network_Protocol → 8.WiFi_OTA
6.1 Macro Configuration
These macros centrally manage WiFi connection parameters and OTA firmware URL. Modify to adapt to different network environments and server addresses.
xxxxxxxxxx//192.168.11.183:8080/WiFi_OTA.bin"// Event group connection bit6.2 WiFi Initialization (Review)
The WiFi STA connection code in this experiment (wifi_init_sta(), event callback, NVS init, etc.) is identical to 5.2 WIFI-STA Mode. app_main() first connects WiFi to get IP → then xTaskCreate() creates OTA upgrade task.
For details: 5. Network Protocols and Wireless Communication -> 2. WIFI-STA Mode -> WIFI-STA Mode
6.3 compare_version() — Version Number Comparison
Parse x.y.z format version string segment by segment, convert each to integer and compare in order. If first segment determines size difference, return immediately without parsing subsequent segments; if all segments equal, return 0.
xxxxxxxxxxstatic int compare_version(const char *v1, const char *v2){ int n1 = 0, n2 = 0; int i = 0, j = 0;
while (1) { n1 = 0; // Parse current segment of v1 while (v1[i] && v1[i] != '.') { n1 = n1 * 10 + (v1[i] - '0'); i++; } n2 = 0; // Parse current segment of v2 while (v2[j] && v2[j] != '.') { n2 = n2 * 10 + (v2[j] - '0'); j++; } if (n1 > n2) return 1; if (n1 < n2) return -1; // Both end simultaneously = versions equal if (!v1[i] && !v2[j]) break; // Skip '.' i++; j++; } return 0;}6.4 get_server_version_from_bin() — Extract Version from Firmware Header
Use HTTP Range request to download only the first 1KB of firmware, search for esp_app_desc_t struct magic 0xABCD5432 (little-endian 32 54 CD AB), locate and offset +0x10 to read version string. Takes less than 1 second to get server version, no need to download full firmware.
xxxxxxxxxxstatic esp_err_t get_server_version_from_bin(const char *url, char *version, size_t max_len){ esp_http_client_config_t config = { .url = url, .timeout_ms = 10000, }; esp_http_client_handle_t client = esp_http_client_init(&config); esp_http_client_set_header(client, "Range", "bytes=0-1023");
esp_http_client_open(client, 0); esp_http_client_fetch_headers(client);
int status = esp_http_client_get_status_code(client); // 200 = full response, 206 = partial content if (status != 200 && status != 206) { esp_http_client_cleanup(client); return ESP_FAIL; }
// Read response body (max 1KB) uint8_t buf[1024] = {0}; int total = 0, len; while (total < 1024) { len = esp_http_client_read(client, (char *)buf + total, 1024 - total); if (len <= 0) break; total += len; } esp_http_client_close(client); esp_http_client_cleanup(client);
// Search magic 0xABCD5432 (little-endian: 32 54 CD AB) const uint8_t magic[] = {0x32, 0x54, 0xCD, 0xAB}; for (int i = 0; i < total - 4; i++) { if (memcmp(buf + i, magic, 4) == 0) { // Version offset int ver_off = i + 0x10; if (ver_off + 32 <= total) { strncpy(version, (char *)buf + ver_off, max_len - 1); version[max_len - 1] = '\0'; // Verify first char is digit if (version[0] >= '0' && version[0] <= '9') { ESP_LOGI(TAG, "Server firmware version: %s", version); return ESP_OK; } } } } ESP_LOGE(TAG, "Magic word not found in %d bytes", total); return ESP_FAIL;}6.5 vOtaTask() — OTA Main Task (Download + Write Part)
After version comparison, if server is newer, fully download firmware and write block by block to OTA partition. Progress bar refreshes every 10%. After write complete, switch boot partition and restart, light blue LED for 3 seconds before restart to indicate upgrade complete.
xxxxxxxxxx// ========== OTA Download & Write ==========esp_http_client_config_t config = { .url = OTA_URL, .timeout_ms = 10000,};esp_http_client_handle_t client = esp_http_client_init(&config);esp_http_client_open(client, 0);esp_http_client_fetch_headers(client);
// Get firmware sizeint total_size = esp_http_client_get_content_length(client);
// Get next writable OTA partitionconst esp_partition_t *ota_part = esp_ota_get_next_update_partition(NULL);esp_ota_handle_t ota_handle;esp_ota_begin(ota_part, OTA_SIZE_UNKNOWN, &ota_handle);
// Download and write block by blockuint8_t buf[4096];int len, downloaded = 0, last_pct = -1;while (1) { len = esp_http_client_read(client, (char*)buf, sizeof(buf)); if (len < 0) { // Download failed → abort esp_ota_abort(ota_handle); return; } if (len == 0) break; esp_ota_write(ota_handle, buf, len); downloaded += len;
// Print progress every 10% if (total_size > 0) { int pct = downloaded * 100 / total_size; if (pct >= last_pct + 10 || pct >= 100) { last_pct = pct; int bars = pct / 5; ESP_LOGI(TAG, "[%.*s%*s] %d%% (%d/%d KB)", bars, "====================", 20 - bars, "", pct, downloaded / 1024, total_size / 1024); } }}
// End OTA writeesp_ota_end(ota_handle);// Switch bootesp_ota_set_boot_partition(ota_part);
// Blue = successrgb_led_set(0, 0, 255);vTaskDelay(3000 / portTICK_PERIOD_MS);esp_restart();6.6 Preparing Upgraded Version Firmware
Before compiling upgraded firmware, modify two places: version number (PROJECT_VER in CMakeLists.txt) and LED indicator color, to visually distinguish before and after upgrade.
xxxxxxxxxx// Version indicator (pick one)rgb_led_set(0, 255, 0); // Upgraded version → Green7. Operation Flow
7.1 Build and Flash Flow
ESP-IDF Flash Flow: See ESP-IDF → 1. Before Development → 3. Build and Flash.
7.2 Pre-Operation Notice (Must Know)
If routine continuously fails, try connecting phone to same WiFi, open browser and search OTA_URL in code: http://192.168.xx.xx:8080/WiFi_OTA.bin. If access fails, close firewall and try again!!!!
7.3 OTA Upgrade Operation Steps
Step 1 — Prepare Initial Firmware (v1.0.0)
Compile
WiFi_OTAproject, confirmPROJECT_VERinCMakeLists.txtis"1.0.0"Confirm
app_main()LED is redrgb_led_set(255, 0, 0)Flash to ESP32, onboard RGB LED lights red, indicating initial version
Step 2 — Deploy Firmware Server (choose one) (Windows version)
Method 1: Manual command line
Open cmd/PowerShell on PC, cd to firmware directory (i.e.,
OTA_FOLDERfolder)Execute
python -m http.server 8080to start HTTP serverPut compiled upgraded firmware
WiFi_OTA.binin this directory
Method 2: Double-click .bat one-click start (Recommended)
Put compiled upgraded firmware
WiFi_OTA.binintoOTA_FOLDERfolderDouble-click
OTA_HTTP_Server.bat, automatically starts HTTP server (port 8080)


Window shows local LAN IP, fill this IP into code
OTA_URLmacro
OTA_HTTP_Server.batcontent is essentiallypython -m http.server 8080, saves manual typing:
xxxxxxxxxx@echo offchcp 65001 >nultitle ESP32 OTA HTTP Serverecho ==============================================echo ESP32 OTA HTTP Serverecho ==============================================echo.echo [Instructions]echo 1. Place the compiled WiFi_OTA.bin in this folderecho 2. Double-click to run this fileecho 3. Copy the IP address shown below to ESP32 codeecho.echo Firmware URL format:echo http://YOUR_IP:8080/WiFi_OTA.binecho (Version auto-extracted from firmware, no version.txt needed)echo.echo ==============================================echo.for /f "tokens=2 delims:" %%a in ('ipconfig ^| findstr /i "IPv4" ^| findstr /v "169.254."') do set IP=%%aset IP=%IP: =%if "%IP%"=="" (echo Unable to detect LAN IP. Run ipconfig manually to find it.) else (echo Local IP: %IP%:8080)echo HTTP Port: 8080echo.echo Server is running. Do NOT close this window!echo ==============================================echo.python -m http.server 8080pause
Step 3 — Deploy Firmware Server (Linux version)
① Open Linux terminal, enter: sudo +~/path to your saved files.../OTA_Server.sh

② Enter permission password (boot password)

③ Server starts

Step 3 — Prepare Upgraded Firmware (v1.7.0)
Modify
CMakeLists.txt:set(PROJECT_VER "x.x.x")

Modify
app_main(): comment out red line, uncomment green lineAfter modifying version and LED color, compile, then go to build folder → find WiFi_OTA.bin → move to folder where server.bat is located, then open .bat file → ready for upgrade

Compiled
WiFi_OTA.bin, put in PC HTTP server directory

Step 4 — Trigger OTA Upgrade
Restart ESP32 development board (press RST key)
ESP32 automatically connects to WiFi, gets server firmware version, finds v1.7.0 (v2.0.0) > v1.0.0
Starts downloading firmware, serial displays progress bar
Download complete → RGB LED turns blue for 3 seconds → ESP32 automatically restarts
After restart, LED lights green, version number is v2.0.0 → Upgrade successful
7.4 Normal Log — OTA Upgrade Full Flow
xxxxxxxxxxI (1738) esp_netif_handlers: sta ip: 192.168.11.181, mask: 255.255.255.0, gw: 192.168.11.1I (1738) WIFI_OTA: WiFi connected successfully, starting version checkI (1738) WIFI_OTA: Current Version: 1.7.0I (1738) WIFI_OTA: Connecting to check server firmware version...I (1758) wifi:<ba-add>idx:1 (ifx:0, 80:8f:1d:20:9d:f7), tid:0, ssn:0, winSize:64I (1808) WIFI_OTA: HTTP status: 200I (1838) WIFI_OTA: Read 1024 bytes from firmware headerI (1838) WIFI_OTA: Server firmware version: 1.8.9I (1838) WIFI_OTA: New version detected, starting upgradeI (1868) WIFI_OTA: Firmware size: 923904 bytes (902.2 KB)I (3148) WIFI_OTA: [= ] 9% (84/902 KB)I (3878) WIFI_OTA: [=== ] 19% (172/902 KB)I (4368) WIFI_OTA: [===== ] 29% (264/902 KB)I (5008) WIFI_OTA: [======= ] 39% (352/902 KB)I (5618) WIFI_OTA: [========= ] 49% (444/902 KB)I (6138) WIFI_OTA: [=========== ] 59% (536/902 KB)I (6598) WIFI_OTA: [============= ] 69% (624/902 KB)I (7288) WIFI_OTA: [=============== ] 79% (716/902 KB)I (7768) WIFI_OTA: [================= ] 89% (804/902 KB)I (8348) WIFI_OTA: [=================== ] 99% (896/902 KB)I (8548) WIFI_OTA: [====================] 100% (902/902 KB)--- Reboot ---I (1728) esp_netif_handlers: sta ip: 192.168.11.181, mask: 255.255.255.0, gw: 192.168.11.1I (1728) WIFI_OTA: WiFi connected successfully, starting version checkI (1728) WIFI_OTA: Current Version: 1.8.9I (1728) WIFI_OTA: Connecting to check server firmware version...I (1768) wifi:<ba-add>idx:1 (ifx:0, 80:8f:1d:20:9d:f7), tid:0, ssn:0, winSize:64I (1798) WIFI_OTA: HTTP status: 200I (1808) WIFI_OTA: Read 1024 bytes from firmware headerI (1808) WIFI_OTA: Server firmware version: 1.8.9I (1808) WIFI_OTA: The current version is already the latest and no upgrade is needed.





8. Common Problems
| Symptom | Cause | Solution |
|---|---|---|
| Magic search failed "Magic word not found" | bin file format wrong / corrupted | Confirm WiFi_OTA.bin is complete firmware generated by idf.py build |
| HTTP status 404 | bin file not in server root directory | Check PC HTTP server file path |
| Web server port firewall blocked | PC firewall blocking port 8080 | Close Windows firewall or add port inbound rule |
esp_ota_begin() failed | Partition table missing ota_0/ota_1 partitions | menuconfig → Partition Table → select "Factory + 2 OTA definitions" |
| OTA download fails halfway | WiFi signal unstable | Move closer to router and retry; code already has 3x version check retry |
| Still old version after restart | esp_ota_set_boot_partition() not called or failed | Check this API return value, confirm partition write complete |
| Version comparison always thinks no upgrade needed | Version string parsing error | Confirm version format is pure digits + dots (e.g. 1.0.0, don't include v prefix) |
| LED doesn't light | GPIO pin config wrong / WS2812 not powered | Confirm RGB_LED_GPIO is 48 (onboard LED), some core boards may not have onboard WS2812 |
content_length is -1 | HTTP server didn't return Content-Length header | Normal, progress bar percentage won't display, download unaffected |
Deadlock after esp_restart() | New firmware has bug | Need USB to reflash old version to recover |
| NVS error ret=4353 NVS partition open failed, namespace can't mount | 1. Power loss / improper reset timing 2. Frequent WiFi config read/write 3. Firmware upgrade (OTA) didn't clear NVS | Re-erase flash with flash tool |