OTA (Over-the-Air Firmware Update)

The Ota class is a transport-agnostic OTA firmware update engine wrapping ESP-IDF’s esp_ota_ops: a single mutex-serialized update session (begin() -> write() … -> finish() / abort()) with all failures reported via std::error_code. It performs no I/O itself — feed it image bytes from any transport and it streams them into the next OTA app partition. The first chunk is validated against the ESP image magic byte (0xE9) and the incoming application descriptor (project name, version, build date) is extracted, logged and exposed; finish() runs the full image validation (including the appended SHA-256) and sets the boot partition, while the restart is a separate explicit restart() call.

Rollback helpers (is_pending_verify(), mark_app_valid(), mark_app_invalid_and_rollback()) integrate with the bootloader’s app rollback support (CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE): an app booted pending-verify must call mark_app_valid() after its own health checks or the bootloader rolls back to the previous image on the next reset.

For OTA over a raw byte stream (such as the usb_device vendor / WebUSB interface), the header detail/ota_stream_protocol.hpp provides a host-testable framed protocol — CRC-32-verified little-endian frames (BEGIN / DATA / FINISH / ABORT and OK / ERROR / PROGRESS replies) with an incremental, resynchronizing parser and a bounded 4096-byte maximum payload. The hosted espp OTA Console web app speaks this protocol over WebUSB directly from a Chromium browser.

The frame codec itself lives in the reusable Stream Frame APIs component (detail/ota_stream_protocol.hpp re-exports it and layers the OTA message types on top); to run OTA alongside other protocols (crash-dump, CAN, …) on one stream, register it as a module with the Dispatcher APIs — the ota example does exactly this (OTA is module id 0).

Command line: build → OTA

The ota component ships a project_include.cmake and a pure-Python host tool (components/ota/python/espp_ota), so any project using it can build and OTA-flash over USB in one step — the OTA counterpart to idf.py flash:

pip install pyusb      # once (needs a libusb backend)
idf.py ota-usb        # builds the app, then OTAs it over USB

The tool draws a live progress bar (percent, size, transfer speed, ETA) and colorizes its output. Because idf.py captures the target’s output, the bar is drawn straight to the controlling terminal so it still animates in place:

espp_ota flashing an image over USB via idf.py ota-usb espp_ota reporting a completed OTA over USB

For full control (a specific serial, chunk size, discovery probe) run it directly with python -m espp_ota flash build/<app>.bin — see components/ota/python/README.md.

API Reference

Header File

Classes

class Ota : public espp::BaseComponent

Transport-agnostic OTA (over-the-air) firmware update engine.

Wraps ESP-IDF’s `esp_ota_ops` in an idiomatic espp API: no exceptions, all failures reported via `std::error_code`, and a single mutex-serialized update session (`begin()` -> `write()`… -> `finish()` / `abort()`). The component performs no I/O of its own — feed it image bytes from ANY transport (USB vendor / WebUSB stream, HTTP request body, TCP socket, UART, SD card, …) and it streams them into the next OTA app partition.

On the first chunk of `write()` the image is sanity-checked (the ESP image magic byte 0xE9) and the embedded application descriptor (`esp_app_desc_t`: project name, version, build date) is extracted, logged and exposed via `incoming_app_description()`; with `Config::reject_same_version` set, an image whose version matches the running app is rejected. `finish()` runs the full image validation (including the appended SHA-256) via `esp_ota_end()` and sets the boot partition, but does NOT restart — call `restart()` when the application is ready to reboot into the new image.

Rollback

The rollback helpers (`is_pending_verify()`, `mark_app_valid()`, `mark_app_invalid_and_rollback()`) require the bootloader rollback support to be compiled in (`CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y`). With rollback enabled, a freshly-updated app boots in the `ESP_OTA_IMG_PENDING_VERIFY` state and MUST call `mark_app_valid()` after its own health checks pass — otherwise the bootloader rolls back to the previous image on the next reset. Call `mark_app_invalid_and_rollback()` to actively reject the new image and reboot into the previous one.

OTA Example (USB / WebUSB + WiFi / Ethernet HTTP)

  namespace proto = espp::detail::ota_stream;

  // --- The OTA engine (transport-agnostic) -----------------------------------
  espp::Ota ota({.reject_same_version = false,
                 .progress_callback =
                     [&logger](size_t written, size_t total) {
                       // log every ~64 KiB so a big image doesn't spam the log
                       if (total > 0 && (written % (64 * 1024)) < 4096)
                         logger.info("OTA progress: {} / {} bytes", written, total);
                     },
                 .log_level = espp::Logger::Verbosity::INFO});

  const auto running = ota.running_app_description();
  logger.info("Running '{}' version '{}' (built {} {}) from partition '{}' ({} bytes)",
              running.project_name, running.version, running.date, running.time,
              ota.running_partition_label(), ota.running_partition_size());
  logger.info("Next update will target partition '{}' ({} bytes)", ota.update_partition_label(),
              ota.update_partition_size());

  // --- Rollback handling ------------------------------------------------------
  // With CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE, an app booted right after an
  // OTA update is in the PENDING_VERIFY state: it will ROLL BACK to the previous
  // image on the next reset unless it is confirmed. This example demonstrates
  // HOST-DRIVEN confirmation: it deliberately does NOT mark itself valid here.
  // Instead it stays pending and lets the host confirm it (MARK_VALID over the
  // OTA protocol) once the host has verified the device is healthy — a broken
  // build could otherwise self-validate right before failing. The ota-console web
  // app / `espp-ota` CLI do this after reconnecting.
  //
  // (If your own product prefers device self-validation, run your health checks
  // here and call ota.mark_app_valid() / ota.mark_app_invalid_and_rollback().)
  if (ota.is_pending_verify()) {
    logger.warn("This image is PENDING VERIFY (first boot after an OTA update). Waiting for the "
                "host to confirm it (MARK_VALID); it rolls back on the next reset if not.");
  }

  // --- Transport 1: USB vendor / WebUSB (espp::UsbDevice) --------------------
  // The vendor interface carries the framed OTA stream protocol (see
  // detail/ota_stream_protocol.hpp); the hosted web app
  // https://esp-cpp.github.io/espp/apps/ota_console.html speaks it in the
  // browser. RX bytes arrive in the TinyUSB task context, so they are queued
  // and dispatched from a worker task below (esp_ota_begin's flash erase can
  // take seconds and must not block the USB stack).
  espp::UsbDevice::Config usb_cfg;
  usb_cfg.manufacturer = "espp";
  usb_cfg.product = "espp OTA";
  usb_cfg.log_level = espp::Logger::Verbosity::INFO;
  espp::UsbDevice::VendorFunction vendor;
  vendor.interface_name = "espp OTA (WebUSB)";
  vendor.webusb = true; // advertise BOS / WebUSB / MS OS 2.0 descriptors
  vendor.landing_page_url = "esp-cpp.github.io/espp/apps/ota_console.html";
  usb_cfg.vendor = vendor;
  // Add a CDC-ACM function so the SAME native USB cable also carries the log
  // console. `route_console = true` makes UsbDevice redirect the ESP console
  // (stdout: printf / ESP_LOG / espp::Logger) to this CDC interface at the end of
  // initialize(), teeing to the UART0 console so `idf.py monitor` still works. So
  // one native USB cable carries both the OTA vendor stream and the logs -- no
  // manual VFS plumbing in the app. CDC uses 1 interrupt IN + 1 bulk IN + 1 bulk
  // OUT; with the vendor function's bulk IN + OUT that is 3 IN / 2 OUT endpoints,
  // within the ESP32-S3 budget.
  espp::UsbDevice::CdcFunction cdc;
  cdc.interface_name = "espp OTA console";
  cdc.route_console = true; // redirect the console to this CDC interface (tee to UART0)
  usb_cfg.cdc = cdc;
  espp::UsbDevice usb(usb_cfg);

  std::mutex usb_rx_mutex;
  std::condition_variable usb_rx_cv;
  std::deque<std::vector<uint8_t>> usb_rx_queue;
  size_t usb_rx_queued_bytes = 0;
  bool usb_rx_overflow = false;
  // The protocol is one-frame-in-flight (the host waits for OK/ERROR before
  // the next DATA), so a well-behaved host queues at most ~one frame while the
  // worker is busy. Cap the queue anyway: the worker can legitimately block
  // for seconds inside esp_ota_begin()/end() (flash erase / SHA validation),
  // and a misbehaving host that pipelines OUT transfers must not be able to
  // exhaust device RAM. 8 max-size frames of headroom is far more than the
  // protocol ever needs.
  static constexpr size_t kMaxQueuedRxBytes = 8 * espp::detail::ota_stream::kMaxFrameSize;
  usb.set_vendor_receive_callback([&](std::span<const uint8_t> data) {
    // TinyUSB task context: just queue the bytes and wake the worker.
    {
      std::lock_guard<std::mutex> lock(usb_rx_mutex);
      if (usb_rx_queued_bytes + data.size() > kMaxQueuedRxBytes) {
        // Overflow: drop everything (partial frames are useless once bytes
        // are missing) and let the worker abort + resynchronize + reply.
        usb_rx_queue.clear();
        usb_rx_queued_bytes = 0;
        usb_rx_overflow = true;
      } else {
        usb_rx_queue.emplace_back(data.begin(), data.end());
        usb_rx_queued_bytes += data.size();
      }
    }
    usb_rx_cv.notify_one();
  });

  std::error_code usb_ec;
  if (!usb.initialize(usb_ec))
    logger.error("Failed to initialize USB device: {}", usb_ec.message());
  // On success the console is now on USB-CDC (cdc.route_console above), teed to
  // UART0 -- this and later logs travel over the native USB cable and UART0.

  // Route the vendor stream through a Dispatcher: OTA occupies module id 0 (its
  // opcodes are 0x0X). Other protocols (e.g. a crash-dump service on module 4)
  // could register alongside on the same stream and would be routed
  // independently; frames for unregistered modules are ignored rather than
  // mis-handled as malformed OTA frames.
  espp::Dispatcher dispatcher;
  bool restart_pending = false;
  // The OTA engine serializes sessions across ALL transports, but that alone
  // is not enough here: without ownership tracking a USB DATA/FINISH/ABORT
  // could append to / activate / cancel a session that HTTP started. Set only
  // after a successful USB BEGIN; cleared on every terminal path (FINISH and
  // ABORT end the session in all outcomes, and a failed write() aborts it).
  // If the host unplugs mid-session the flag stays set, so a reconnecting
  // host's ABORT is still honored (BEGIN would correctly fail busy first).
  bool usb_owns_session = false;
  auto handle_usb_frame = [&](const proto::Frame &frame) {
    // The device only handles requests; OTA replies (OK/ERROR/PROGRESS) share
    // module 0, so ignore any reply-flagged frame (e.g. a loopback echo) rather
    // than treating it as an unknown request.
    if (frame.is_reply())
      return;
    std::error_code ec;
    auto reply_error = [&](const std::error_code &err, const std::string &context) {
      usb.write_vendor(
          proto::make_error(static_cast<uint32_t>(err.value()), context + ": " + err.message()));
    };
    switch (static_cast<proto::MessageType>(frame.type)) {
    case proto::MessageType::Begin: {
      const auto image_size = proto::parse_u32_payload(frame);
      if (!image_size.has_value()) {
        reply_error(std::make_error_code(std::errc::invalid_argument), "malformed BEGIN");
        break;
      }
      if (ota.begin(*image_size, ec)) {
        usb_owns_session = true;
        usb.write_vendor(proto::make_ok(0));
      } else {
        // busy = another transport's session; ownership stays false
        reply_error(ec, "begin failed");
      }
      break;
    }
    case proto::MessageType::Data:
      if (!usb_owns_session) {
        reply_error(std::make_error_code(std::errc::operation_not_permitted),
                    "no USB-owned update session (send BEGIN first)");
        break;
      }
      if (ota.write(frame.payload, ec)) {
        usb.write_vendor(proto::make_ok(static_cast<uint32_t>(ota.bytes_written())));
      } else {
        usb_owns_session = false; // write() aborted the session on failure
        reply_error(ec, "write failed");
      }
      break;
    case proto::MessageType::Finish: {
      if (!usb_owns_session) {
        reply_error(std::make_error_code(std::errc::operation_not_permitted),
                    "no USB-owned update session (send BEGIN first)");
        break;
      }
      const auto written = static_cast<uint32_t>(ota.bytes_written());
      usb_owns_session = false; // finish() ends the session in all outcomes
      if (ota.finish(ec)) {
        usb.write_vendor(proto::make_ok(written));
        restart_pending = true; // reply first; the worker restarts shortly
      } else {
        reply_error(ec, "finish (validate/activate) failed");
      }
      break;
    }
    case proto::MessageType::Abort: {
      if (!usb_owns_session) {
        reply_error(std::make_error_code(std::errc::operation_not_permitted),
                    "no USB-owned update session to abort");
        break;
      }
      const auto written = static_cast<uint32_t>(ota.bytes_written());
      usb_owns_session = false; // session over either way
      if (ota.abort(ec))
        usb.write_vendor(proto::make_ok(written));
      else
        reply_error(ec, "abort failed");
      break;
    }
    case proto::MessageType::GetStatus: {
      // Report rollback status + the running firmware (so the host can show what
      // is now running before confirming it). Session-independent (no BEGIN).
      uint8_t flags = 0;
#if defined(CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE)
      flags |= proto::kStatusRollbackSupported;
      if (ota.is_pending_verify())
        flags |= proto::kStatusPendingVerify;
#endif
      const auto desc = ota.running_app_description();
      usb.write_vendor(proto::make_status(flags, desc.version, desc.project_name));
      break;
    }
    case proto::MessageType::MarkValid:
      // The HOST confirms the running image after its own health checks — the app
      // must not confirm itself. Cancels the pending rollback.
      if (ota.mark_app_valid(ec))
        usb.write_vendor(proto::make_ok(0));
      else
        reply_error(ec, "mark valid failed");
      break;
    case proto::MessageType::MarkInvalid:
      // Reject the running image: roll back to the previous app and reboot.
      // mark_app_invalid_and_rollback() does NOT return on success (the device
      // reboots), so DON'T pre-send OK: the reboot / USB disconnect IS the
      // success signal to the host. It only returns on *failure* (e.g. no valid
      // image to roll back to), so the reply below is reached only then and an
      // ERROR is the sole reply. Sending OK first would let the host report
      // success even when rollback was refused, leaving a stale ERROR on the
      // stream.
      ota.mark_app_invalid_and_rollback(ec);
      reply_error(ec, "rollback failed"); // only reached on failure
      break;
    default:
      reply_error(std::make_error_code(std::errc::not_supported), "unknown message type");
      break;
    }
  };

  // OTA is module id 0. The Dispatcher routes each frame for that module here.
  // Advertise it (name / web app / description) so the browser Device Hub can
  // discover and link it, and answer discovery queries over the vendor stream.
  dispatcher.register_module(
      proto::kModule, [&](const proto::Frame &frame) { handle_usb_frame(frame); },
      {.name = "OTA", .app = "ota_console.html", .description = "Firmware update over USB"});
  dispatcher.set_device_info(usb_cfg.product);
  dispatcher.serve_discovery([&](std::span<const uint8_t> frame) { usb.write_vendor(frame); });

  espp::Task usb_task(
      {.callback = [&](std::mutex &, std::condition_variable &) -> bool {
         std::vector<std::vector<uint8_t>> chunks;
         bool overflowed = false;
         {
           std::unique_lock<std::mutex> lock(usb_rx_mutex);
           usb_rx_cv.wait_for(lock, 100ms,
                              [&] { return !usb_rx_queue.empty() || usb_rx_overflow; });
           chunks.assign(std::make_move_iterator(usb_rx_queue.begin()),
                         std::make_move_iterator(usb_rx_queue.end()));
           usb_rx_queue.clear();
           usb_rx_queued_bytes = 0;
           overflowed = usb_rx_overflow;
           usb_rx_overflow = false;
         }
         if (overflowed) {
           // Bytes were dropped: any in-flight frame/image is
           // unusable. Abort a USB-owned session, resync the
           // parser, and tell the host to start over.
           if (usb_owns_session) {
             std::error_code abort_ec;
             ota.abort(abort_ec);
             usb_owns_session = false;
           }
           dispatcher.reset();
           usb.write_vendor(proto::make_error(
               static_cast<uint32_t>(std::make_error_code(std::errc::no_buffer_space).value()),
               "RX overflow: frames dropped; transfer aborted -- wait for OK "
               "replies between frames and restart the update"));
           return false; // dropped chunks are gone; skip parse
         }
         for (const auto &chunk : chunks)
           dispatcher.feed(chunk);
         if (restart_pending) {
           // give the final OK reply time to reach the host
           std::this_thread::sleep_for(750ms);
           ota.restart();
         }
         return false; // don't stop the task
       },
       .task_config = {.name = "ota_usb", .stack_size_bytes = 8192}});
  usb_task.start();

  // --- Transports 2 & 3: WiFi (or Ethernet) + HTTP push -----------------------
  // NVS is required by the WiFi stack.
  esp_err_t nvs_err = nvs_flash_init();
  if (nvs_err == ESP_ERR_NVS_NO_FREE_PAGES || nvs_err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
    ESP_ERROR_CHECK(nvs_flash_erase());
    nvs_err = nvs_flash_init();
  }
  ESP_ERROR_CHECK(nvs_err);

  espp::WifiSta wifi_sta({.ssid = CONFIG_ESP_WIFI_SSID,
                          .password = CONFIG_ESP_WIFI_PASSWORD,
                          .num_connect_retries = CONFIG_ESP_MAXIMUM_RETRY,
                          .on_connected = nullptr,
                          .on_disconnected = nullptr,
                          .on_got_ip =
                              [&logger](ip_event_got_ip_t *eventdata) {
                                logger.info("got IP: {}.{}.{}.{}", IP2STR(&eventdata->ip_info.ip));
                                logger.info("  browser upload page: http://{}.{}.{}.{}/ota",
                                            IP2STR(&eventdata->ip_info.ip));
                                logger.info("  curl --data-binary @build/ota_example.bin "
                                            "http://{}.{}.{}.{}/ota",
                                            IP2STR(&eventdata->ip_info.ip));
                              },
                          .log_level = espp::Logger::Verbosity::WARN});

  // The HTTP server binds to every netif, so this exact same code serves OTA
  // over the espp `ethernet` component as well -- to use Ethernet, simply
  // bring up its netif (see the ethernet example) instead of WifiSta above.
  httpd_handle_t http_server = nullptr;
  httpd_config_t http_cfg = HTTPD_DEFAULT_CONFIG();
  http_cfg.stack_size = 8192; // OTA handler streams through a 4 KiB buffer
  if (httpd_start(&http_server, &http_cfg) == ESP_OK) {
    const httpd_uri_t get_uri = {
        .uri = "/ota", .method = HTTP_GET, .handler = ota_get_handler, .user_ctx = nullptr};
    const httpd_uri_t post_uri = {
        .uri = "/ota", .method = HTTP_POST, .handler = ota_post_handler, .user_ctx = &ota};
    // Status + host-driven rollback endpoints backing the upload page's status
    // card (running firmware, pending-verify state, mark-valid / rollback).
    const httpd_uri_t status_uri = {
        .uri = "/status", .method = HTTP_GET, .handler = ota_status_handler, .user_ctx = &ota};
    const httpd_uri_t mark_valid_uri = {.uri = "/mark-valid",
                                        .method = HTTP_POST,
                                        .handler = ota_mark_valid_handler,
                                        .user_ctx = &ota};
    const httpd_uri_t rollback_uri = {
        .uri = "/rollback", .method = HTTP_POST, .handler = ota_rollback_handler, .user_ctx = &ota};
    httpd_register_uri_handler(http_server, &get_uri);
    httpd_register_uri_handler(http_server, &post_uri);
    httpd_register_uri_handler(http_server, &status_uri);
    httpd_register_uri_handler(http_server, &mark_valid_uri);
    httpd_register_uri_handler(http_server, &rollback_uri);
    logger.info("HTTP OTA server ready: GET /ota (upload page + status), POST /ota (raw image), "
                "GET /status, POST /mark-valid, POST /rollback");
    if constexpr (sizeof(CONFIG_EXAMPLE_OTA_HTTP_TOKEN) <= 1) {
      logger.warn("POST /ota is UNAUTHENTICATED (demo default): any peer that can reach this "
                  "device can install structurally-valid firmware. Set EXAMPLE_OTA_HTTP_TOKEN in "
                  "menuconfig to require a bearer token, and enable secure boot / signed images "
                  "for real deployments.");
    }
  } else {
    logger.error("Failed to start HTTP server");
  }

Public Types

using progress_callback_fn = std::function<void(size_t written, size_t total)>

Progress callback, invoked (with the session mutex held, so keep it short) after every successful write().

Param written:

Total bytes written to the update partition so far.

Param total:

Total expected image size in bytes (0 if unknown / streaming).

Public Functions

inline explicit Ota(const Config &config)

Construct the OTA engine. Does not touch the flash until begin().

Parameters:

config – Configuration parameters.

inline ~Ota()

Aborts any still-active update session.

inline bool begin(size_t image_size, std::error_code &ec)

Start an update session targeting the next OTA app partition (esp_ota_get_next_update_partition()).

Parameters:
  • image_size – Expected image size in bytes, or 0 if unknown / streaming (OTA_SIZE_UNKNOWN — the WHOLE update partition is erased up front, which can take several seconds; with a known size only the required range is erased).

  • ec[out] Set on failure: a session is already active (device_or_resource_busy), no OTA partition exists (no_such_device), the image is larger than the partition (no_space_on_device), or the flash erase failed (io_error).

Returns:

true if the session started, false otherwise (ec is set).

inline bool write(std::span<const uint8_t> data, std::error_code &ec)

Stream image bytes into the update partition.

The first chunk(s) are additionally used to validate the ESP image magic byte (0xE9) and — once enough bytes have arrived — to extract and log the incoming `esp_app_desc_t` (see incoming_app_description()). On ANY failure the session is aborted (a partially-written image is useless), so after an error the caller may simply start over with begin().

Parameters:
  • data – Image bytes (any chunk size; empty is a no-op).

  • ec[out] Set on failure: no active session (operation_not_permitted), bad image magic (illegal_byte_sequence), same-version rejection (file_exists, when Config::reject_same_version is set), the chunk crossing a declared image size (file_too_large &#8212; only that range was erased by begin()), or a flash write failure (io_error).

Returns:

true if the bytes were written, false otherwise (ec is set).

inline bool finish(std::error_code &ec)

Finish the update: validate the complete image (esp_ota_end() checks the image structure and its appended SHA-256, plus the signature when secure boot is enabled) and set it as the boot partition.

Does NOT restart the device — call restart() when ready, so the application controls the timing (e.g. after flushing a reply to the host). The session is over after this call, whether it succeeds or fails.

Parameters:

ec[out] Set on failure: no active session (operation_not_permitted), image validation failed (illegal_byte_sequence), or setting the boot partition failed (io_error).

Returns:

true if the new image is validated and set to boot, false otherwise.

inline bool abort(std::error_code &ec)

Abort the active update session (esp_ota_abort()) and reset the session state. Idempotent: succeeds as a no-op if no session is active.

Parameters:

ec[out] Set on failure (io_error if esp_ota_abort() fails).

Returns:

true on success (or no-op), false otherwise (ec is set).

inline void restart()

Restart the device (esp_restart()); does not return. Call after a successful finish() to boot the new image.

inline bool is_pending_verify() const

Whether the RUNNING app is in the ESP_OTA_IMG_PENDING_VERIFY state, i.e. it was just installed by an OTA update and must call mark_app_valid() after its health checks, or the bootloader will roll back on the next reset.

Note

Only meaningful with CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y; without it this always returns false.

inline bool mark_app_valid(std::error_code &ec)

Mark the running app valid and cancel a pending rollback (esp_ota_mark_app_valid_cancel_rollback()). An app booted in the pending-verify state must call this once its own health checks pass.

Parameters:

ec[out] Set on failure (io_error).

Returns:

true on success, false otherwise (ec is set).

inline bool mark_app_invalid_and_rollback(std::error_code &ec)

Mark the running app invalid and reboot into the previous image (esp_ota_mark_app_invalid_rollback_and_reboot()). Does not return on success.

Parameters:

ec[out] Set on failure (io_error, e.g. no valid app to roll back to or rollback support not enabled).

Returns:

false (only returns on failure; ec is set).

inline std::string running_partition_label() const

Label of the partition the current app is running from (”” if unknown).

inline size_t running_partition_size() const

Size in bytes of the partition the current app is running from (0 if unknown).

inline std::string boot_partition_label() const

Label of the currently-configured BOOT partition (”” if unknown). After a successful finish() this is the just-written partition.

inline std::string update_partition_label() const

Label of the partition the next update session will (or the active one does) target (”” if the partition table has no OTA slot).

inline size_t update_partition_size() const

Size in bytes of the update target partition (0 if none) — the maximum image size an update can carry.

inline AppDescription running_app_description() const

Description (project name / version / build date) of the RUNNING app.

inline std::optional<AppDescription> incoming_app_description() const

Description of the INCOMING image, available once enough of the image (the first 288 bytes) has been written in the current / latest session; std::nullopt before then or if the image carries no valid app descriptor.

inline bool session_active() const

Whether an update session is active (begin() succeeded and neither finish() nor abort() has ended it).

inline size_t bytes_written() const

Bytes written to the update partition in the current session.

inline size_t image_size() const

Expected image size passed to begin() (0 if unknown / streaming).

inline const std::string &get_name() const

Get the name of the component

Note

This is the tag of the logger

Returns:

A const reference to the name of the component

inline void set_log_tag(const std::string_view &tag)

Set the tag for the logger

Parameters:

tag – The tag to use for the logger

inline espp::Logger::Verbosity get_log_level() const

Get the log level for the logger

Returns:

The verbosity level of the logger

inline void set_log_level(espp::Logger::Verbosity level)

Set the log level for the logger

Parameters:

level – The verbosity level to use for the logger

inline void set_log_verbosity(espp::Logger::Verbosity level)

Set the log verbosity for the logger

See also

set_log_level

Note

This is a convenience method that calls set_log_level

Parameters:

level – The verbosity level to use for the logger

inline espp::Logger::Verbosity get_log_verbosity() const

Get the log verbosity for the logger

See also

get_log_level

Note

This is a convenience method that calls get_log_level

Returns:

The verbosity level of the logger

inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)

Set the rate limit for the logger

Note

Only calls to the logger that have _rate_limit suffix will be rate limited

Parameters:

rate_limit – The rate limit to use for the logger

struct AppDescription

Application description extracted from an app image’s `esp_app_desc_t`.

Public Members

std::string project_name

Project name (CMake project / PROJECT_NAME).

std::string version

Application version (git describe / PROJECT_VER).

std::string date

Compile date.

std::string time

Compile time.

std::string idf_version

ESP-IDF version the image was built with.

struct Config

Configuration for the Ota engine.

Public Members

bool reject_same_version = {false}

Reject an incoming image whose `esp_app_desc_t` version string matches the running app’s version (checked on the first chunk of write()).

progress_callback_fn progress_callback = {nullptr}

Optional progress callback (see progress_callback_fn).

espp::Logger::Verbosity log_level = {espp::Logger::Verbosity::WARN}

Logger verbosity.

Header File