Skip to main content

OTA Updates

Development and production defaults now enable CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y. Older units need a bootloader update over USB before automatic rollback is available; updating only Safi.bin through OTA cannot enable rollback in an existing bootloader.

An OTA system has one non-negotiable requirement: no input, no timing, and no power cut may leave the device unbootable. Everything in this chapter descends from that sentence.

A/B slots and rollback, from zero​

The flash carries two application slots (partition layout): ota_0 and ota_1. There is no separate factory application partition. An update never touches the running slot; it writes the other OTA slot, then flips a pointer:

The pointer lives in the tiny otadata partition, written by esp_ota_set_boot_partition() as two redundant copies with sequence counters, so a power cut mid-flip preserves a consistent choice. (This partition's absence was an instructive production bug; the story is told with the partition table.)

Rollback requires a bootloader built with rollback enabled. A pending image starts HTTPS and opens card storage, waits 30 seconds, and checks that card storage remains available before confirming itself. A failed check restarts the device so the bootloader can return to the previous valid slot. This checks startup services; it does not prove audio, NFC, or battery hardware performance.

Card changes are blocked during this verification window. Migration copies records without deleting the source and publishes its new index only after the image is accepted. If an update rolls back before acceptance, another attempt reads the source records again. Physical power-cut and rollback testing is still required before release.

A power cut during download is even less dramatic: the running firmware was never left, and the half-written spare slot is garbage that the next attempt overwrites.

Two transports, one pipeline​

Both dashboard paths converge on esp_https_ota / esp_ota_* writes with progress events (EVENT_OTA_PROGRESS, mirrored to the display and every WebSocket client):

Upload (POST /api/ota/upload): the browser streams Safi.bin up; the handler writes it chunk-by-chunk to the spare slot. No staging buffer exists or could: the image (2+ MB) outweighs all RAM. For the person standing next to the device, this path has no external dependencies at all.

Pull (POST /api/ota/pull with a URL): the device downloads the image itself, for updating devices you are not next to. Pull is where the security model concentrates, because a URL is an instruction to fetch and execute code:

  • HTTPS with full certificate verification against the CA bundle, non-negotiable in release builds. The development-only escape hatch (CONFIG_SAFI_ALLOW_INSECURE_OTA_PULL, for a local HTTP server on the bench) is structurally excluded from production: the production gates make the build fail if both are set, a compile error being the one warning nobody can scroll past.
  • Optional URL prefix pinning (CONFIG_SAFI_OTA_SERVER_URL_PREFIX): the device refuses pulls from anywhere but your release host, shrinking "attacker with a valid TLS cert somewhere" to "attacker controlling your release host".
  • Rate limited to 2 attempts per minute like every expensive endpoint.

Image authenticity beyond TLS comes from secure boot when enabled: a signed-image chain means even a compromised release host cannot produce a bootable malicious image; see security.

Progress and failure UX​

The OTA task publishes state transitions (connecting, downloading with percent, error with the esp_err name) consumed by GET /api/ota/status, the WebSocket, and the display's progress screen with its "do not power off" warning (which is politeness, not protection; the design above survives ignoring it). Errors are terminal per attempt and leave the device exactly as it was, which is the only failure UX an updater should ever have.

What is deliberately absent​

No automatic update checking, no phoning home: an appliance in a bedroom fetches code when and only when its owner says so. No delta updates: full images at 2 MB over LAN or broadband finish in seconds, and delta reconstruction on-device would add a failure mode to the one subsystem that must not have any. Boring is the feature.