example

Example of the component espp/ota v1.2.0
# 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**:

1. **USB vendor / WebUSB** — an `espp::UsbDevice` vendor interface
   (bInterfaceClass 0xFF, WebUSB + MS OS 2.0 descriptors) carrying the framed
   OTA stream protocol from `detail/ota_stream_protocol.hpp`. Update straight
   from a Chromium browser with the hosted
   [espp OTA Console](https://esp-cpp.github.io/espp/apps/ota_console.html)
   (source: [`components/ota/web/ota_console.html`](../web/ota_console.html)) —
   no driver, no network.
2. **WiFi HTTP push** — an `esp_http_server` with `POST /ota` that streams the
   raw request body into the engine (Content-Length is the image size):

   ```bash
   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. Set
   > `EXAMPLE_OTA_HTTP_TOKEN` in 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.

3. **Browser HTTP upload** — `GET /ota` serves 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

```bash
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:

```bash
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)

1. Open <https://esp-cpp.github.io/espp/apps/ota_console.html> in a Chromium
   browser (or open `components/ota/web/ota_console.html` from disk).
2. 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
```

To create a project from this example, run:

idf.py create-project-from-example "espp/ota=1.2.0:example"

or download archive (~13.27 KB)