This is an old revision of the document!
This lab shows how to implement real remote firmware updates from a browser for an ESP32-C6 board using PlatformIO + pioarduino and an Async Web OTA page powered by ElegantOTA.
You will:
After completing this lab, you can:
What you are building:
Why two slots?
If an update fails mid-way (power loss, bad binary), the device still has a valid previous firmware to boot. This is the core reliability concept behind OTA on constrained devices.
Create a new PlatformIO project and create or replace your platformio.ini with:
[env:sparrow_c6] platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip board = esp32-c6-devkitm-1 framework = arduino monitor_speed = 115200 ; Use OTA-capable partition table board_build.partitions = sparrow_ota_4mb.csv ; Optional but often helpful build_flags = -D CORE_DEBUG_LEVEL=1 -D ARDUINO_USB_MODE=1 -D ARDUINO_USB_CDC_ON_BOOT=1 -D ESP32_C6_env -D ELEGANTOTA_USE_ASYNC_WEBSERVER=1 lib_deps = ESP32Async/AsyncTCP@^3.4.9 ESP32Async/ESPAsyncWebServer@^3.9.2 ayushsharma82/ElegantOTA adafruit/Adafruit NeoPixel lib_ignore = AsyncTCP_RP2040W
In your project root, next to platformio.ini, create sparrow_ota_4mb.csv.
Paste this:
# Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x5000, otadata, data, ota, 0xE000, 0x2000, app0, app, ota_0, 0x10000, 0x140000, app1, app, ota_1, , 0x140000, spiffs, data, spiffs, , 0xD0000,
This layout assumes 4MB internal flash and gives you two OTA application slots plus a small SPIFFS area.
Create src/main.cpp and paste the code from here then build and upload via USB.
You should see:
/updateIn your browser:
http:<device-ip>/
* http:<device-ip>/updateLog in with:
adminchange-meNow you will make a visible change and deliver it without USB.
Add a small LED pattern into main.cpp, you can get the new code from here.
You will upgrade your firmware update architecture from push OTA (human uploads a .bin to /update) to a more production-like pull OTA, where the device:
After completing this lab, you can:
Update server hosts:
manifest.jsonfirmware-<version>.binDevice workflow:
1. GET manifest 2. Parse JSON 3. If manifest.version > current_version: - download firmware - compute SHA-256 while streaming - compare expected vs computed - write to OTA slot - commit update + reboot 4. Record telemetry in NVS
Create a manifest file with the following structure:
{
"project": "sparrow-c6-lab",
"version": "1.0.1",
"url": "http://YOUR_PC_IP:8000/firmware-1.0.1.bin",
"sha256": "REPLACE_WITH_SHA256_HEX",
"notes": "NeoPixel speed fix + status endpoint."
}
Rules:
version uses semantic versioning: MAJOR.MINOR.PATCHurl must be reachable by the devicesha256 is lowercase hex of the binaryAfter building in PlatformIO, your binary is typically:
.pio/build/sparrow_c6/firmware.binCompute its SHA-256 on your computer:
Linux/macOS
shasum -a 256 firmware.bin
Windows PowerShell
Get-FileHash .\firmware.bin -Algorithm SHA256
Copy the hash into manifest.json.
In the directory containing:
manifest.jsonfirmware-1.0.1.binRun:
python3 -m http.server 8000
You should be able to open from your laptop browser http://YOUR_PC_IP:8000/manifest.json
Create/replace src/main.cpp with the template below.
You must edit:
MANIFEST_URL to your PC/server IP1) Flash this version once over USB (your pull-OTA baseline).
2) Build a new firmware with:
FW_VERSION = “1.0.1”Also update the homepage text so the new firmware is easy to confirm.
3) Build in PlatformIO and copy:
.pio/build/sparrow_c6/firmware.binRename it to:
firmware-1.0.1.bin
4) Compute SHA-256 and update manifest.json.
5) Host the files:
python -m http.server 8000
6) Reboot the device.
7) Open http:<device-ip>/status
Confirm telemetry fields exist.
===== Part D — Test cases =====
Complete these tests and record results.
==== Test 1: No update needed ====
* Manifest version equals device version.
Expected:
* Device prints “No update needed.”
* last_error clears or remains empty.
—-
==== Test 2: Successful update ====
* Manifest version is higher.
* Hash is correct.
Expected:
* Device updates and reboots.
* /status shows:
- last_result = “success”
- correct version transition values
==== Test 3: Hash mismatch ====
Intentionally change the manifest hash to a wrong value.
Expected:
* Device refuses update.
* last_error = “sha_mismatch”''
* Old firmware continues running.