# OTA API On-Demand Example
Updating when the application decides to, not when it boots.
The device connects to the network and then just runs, exposing a small console
on the serial port. An update happens only when you ask for one, through
`ota_api_update()` — the **blocking** half of the API, which returns an
`esp_err_t` to the caller instead of rebooting on its own.
That difference is the whole example:
| | [basic](../basic) (`ota_api_start_task`) | this example (`ota_api_update`) |
| --- | --- | --- |
| Trigger | at boot | console command, any time |
| On failure | task logs and exits | returns an error, device keeps running, retry allowed |
| Reboot | done by the component | done by the application, after its own cleanup |
## Console commands
```text
ota> help
ota> version
ota> ota
ota> ota https://192.168.0.3:8070/my_firmware.bin
```
- `ota` without an argument uses **Example Configuration -> default firmware
upgrade url endpoint**.
- `ota <url>` overrides it at runtime — no rebuild, no `menuconfig`.
- `version` prints the running project name, version, build date and partition,
which is how you confirm an update actually took effect.
The update runs on a dedicated worker task: the console REPL task's stack is
nowhere near enough for a TLS download. A second `ota` while one is in flight
is rejected rather than queued.
## Configuration
```bash
idf.py menuconfig
```
- **Example Connection Configuration** — Wi-Fi SSID/password or Ethernet.
- **Example Configuration -> default firmware upgrade url endpoint** — URL used
by a bare `ota` command.
- **Example Configuration -> Skip server certificate CN fieldcheck** — leave
off. The certificate generated by `ota_server.py` carries the server IP in
`subjectAltName`, so validation succeeds without it.
The console is the standard UART REPL. On a board configured for USB Serial
JTAG instead, switch **Component config -> ESP System Settings -> Channel for
console output** back to UART, or the prompt will not appear.
## Local HTTPS server
```bash
python ../common/ota_server.py
```
Serves `examples/common/ota/` on port `8070`, prints the exact URL to paste
after `ota`, and generates the self-signed certificate itself — no `openssl`
required, on Windows or Linux. See
[examples/common/README.md](../common/README.md).
Rebuild and reflash whenever the script reports a newly generated certificate,
since it is baked into the binary at build time.
## Build and Flash
```bash
idf.py -p PORT flash monitor
```
(Replace `PORT` with the serial port name, e.g., `/dev/ttyUSB0` or `COM3`.
To exit the serial monitor, type `Ctrl-]`.)
## Expected session
```text
I (...) ota_on_demand: Console ready. Type 'help' for the command list, or 'ota' to update now
ota> version
project : ota_api_on_demand_example
version : 1
partition : ota_0 at offset 0x00010000
ota> ota https://192.168.0.3:8070/hello_world.bin
Requesting update from https://192.168.0.3:8070/hello_world.bin
I (...) ota-api: Downloading update from https://192.168.0.3:8070/hello_world.bin
I (...) ota-api: Update written, new firmware boots on next restart
I (...) ota_on_demand: Update written, shutting the connection down before rebooting
```
Point `ota` at a URL that does not exist to see the other half — the error is
reported and the prompt comes back:
```text
ota> ota https://192.168.0.3:8070/nope.bin
E (...) ota_on_demand: Update failed (ESP_ERR_HTTP_CONNECT). Still running, type 'ota <url>' to retry
ota>
```
## Going further
[advanced](../advanced) builds on this one: same console-driven trigger, plus a
live percentage bar, `abort` to cancel a download in flight, a version check
that refuses firmware already installed, and a rollback you confirm by hand.
## Project Structure
```text
examples/on_demand/
├── CMakeLists.txt
├── sdkconfig.defaults # OTA partition table + certificate bundle
├── sdkconfig.defaults.esp32h2
├── README.md
└── main/
├── CMakeLists.txt
├── idf_component.yml # ota-api + protocol_examples_common deps
├── Kconfig.projbuild # Example Configuration menu
└── main.c
```
The HTTPS server, certificates and firmware images live in
[examples/common/](../common), shared with the other examples.
To create a project from this example, run:
idf.py create-project-from-example "pedroluisdionisiofraga/ota-api=0.2.0:on_demand"