OTA Example
This example shows how to use the espp::Ota component to update a device’s
firmware over three transports feeding the exact same engine:
USB vendor / WebUSB — an
espp::UsbDevicevendor interface (bInterfaceClass 0xFF, WebUSB + MS OS 2.0 descriptors) carrying the framed OTA stream protocol fromdetail/ota_stream_protocol.hpp. Update straight from a Chromium browser with the hosted espp OTA Console (source:components/ota/web/ota_console.html) — no driver, no network.WiFi HTTP push — an
esp_http_serverwithPOST /otathat streams the raw request body into the engine (Content-Length is the image size):curl --data-binary @build/ota_example.bin http://<ip>/ota # with EXAMPLE_OTA_HTTP_TOKEN configured: curl -H "Authorization: Bearer <token>" --data-binary @build/ota_example.bin http://<ip>/ota
Security: by default (empty
EXAMPLE_OTA_HTTP_TOKEN) this endpoint is unauthenticated — any peer that can reach the device can install a structurally-valid image; a warning is logged at startup. SetEXAMPLE_OTA_HTTP_TOKENin menuconfig to require a bearer token (the upload page has a matching field). A shared token is demo-grade gating only: for real deployments enable secure boot / signed images so the bootloader rejects unauthorized firmware regardless of transport.Browser HTTP upload —
GET /otaserves a tiny self-contained upload page (file picker + progress bar), so any browser on the LAN can update the board.
The HTTP server binds to every network interface, so the same code works
unchanged over the espp ethernet component (or any other esp_netif) —
Ethernet needs no separate OTA code path, just bring up its netif instead of
(or in addition to) espp::WifiSta.
How to use example
Hardware Required
An ESP32-S3 (the native USB-OTG peripheral is required for the USB / WebUSB
transport; the target is pinned in sdkconfig.defaults). Connect BOTH USB
connectors of a devkit: the USB-Serial-JTAG port carries the log console and
flashing, the USB-OTG port presents the vendor / WebUSB OTA interface.
Configure
idf.py menuconfig
Set the WiFi SSID / password under OTA Example Configuration (only needed for
the HTTP transports; the USB transport works without any network).
Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
idf.py -p PORT flash monitor
(To exit the serial monitor, type Ctrl-].)
The first flash writes the app into the ota_0 slot of the factory-less
partition table (partitions.csv: otadata + ota_0 + ota_1); every OTA
update alternates to the other slot.
Update over USB (WebUSB)
Open https://esp-cpp.github.io/espp/apps/ota_console.html in a Chromium browser (or open
components/ota/web/ota_console.htmlfrom disk).Connect to the “espp OTA” device, pick the new
build/ota_example.bin, and press Upload. The console streams BEGIN / DATA / FINISH frames (4 KiB max payload each, one in flight) with a progress bar; the device validates the image, replies OK, and restarts into it.
Update over HTTP (WiFi or Ethernet)
Browser: open
http://<ip>/ota, pick the.bin, upload.CLI:
curl --data-binary @build/ota_example.bin http://<ip>/ota— returns{"status":"ok",...}on success or a 4xx/5xx JSON error.
Rollback semantics
CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y is set, so a freshly-installed image
boots in the PENDING_VERIFY state. On boot the example logs the running
partition + version, and if the image is pending verification it runs a
(trivial) self-check and calls espp::Ota::mark_app_valid() — watch for the
“image marked VALID; rollback cancelled” log line on the first boot after an
update. If an updated app crashes/resets before marking itself valid, the
bootloader automatically rolls back to the previous slot. A failed
self-check would instead call mark_app_invalid_and_rollback() to return to
the old image immediately.
Note: espp::Ota::finish() only validates the image and sets the boot
partition; the restart is a separate explicit restart() call (this example
restarts ~750 ms after replying to the host, on any transport).
Example Output
I (608) OtaExample: Running 'ota_example' version 'v1.2.3-14-g35e120b' (built Aug 19 2026 12:34:56) from partition 'ota_0' (1966080 bytes)
I (618) OtaExample: Next update will target partition 'ota_1' (1966080 bytes)
I (668) espp_UsbDevice: USB device initialized (vendor/WebUSB interface ready)
I (5178) OtaExample: got IP: 192.168.1.23
I (5178) OtaExample: browser upload page: http://192.168.1.23/ota
I (5178) OtaExample: curl --data-binary @build/ota_example.bin http://192.168.1.23/ota
I (5188) OtaExample: HTTP OTA server ready: GET /ota (upload page), POST /ota (raw image)
I (5198) OtaExample: OTA example ready; transports: USB vendor/WebUSB, HTTP POST /ota (WiFi/Ethernet)
I (42198) Ota: incoming firmware: project 'ota_example', version 'v1.2.4', built Aug 20 2026 09:00:00 (IDF v6.0)
I (55123) Ota: finish: 1204224 bytes validated; boot partition set to 'ota_1' — call restart() to boot the new image
...reboot...
W (612) OtaExample: This image is PENDING VERIFY (first boot after an OTA update)
I (614) Ota: running app marked valid; rollback cancelled
I (615) OtaExample: Self-check passed -> image marked VALID; rollback cancelled