Files
Calendink/Provider/tdd/frontend_ota.md

4.1 KiB

Frontend OTA Strategy for ESP32-S3 Provider

Authored by Antigravity Date: 2026-03-03


1. Goal

Implement a robust Over-The-Air (OTA) update mechanism specifically for the Svelte frontend assets served by the ESP32-S3. The update must:

  • Update the frontend code without requiring a full firmware re-flash.
  • Provide a reliable fallback if an update fails (Rollback capability).
  • Handle updates gracefully within the ESP32's available RAM limitations.
  • Provide a dedicated UI for the user to upload new frontend binaries.

2. Chosen Approach

We have opted for a Dual-Partition Image Flash (A/B slots) strategy using LittleFS.

Instead of updating individual files (HTML, JS, CSS) over HTTP, the build process will generate a single, pre-packaged .bin image of the entire www directory. This image will be streamed directly to an inactive flash partition, mimicking the safety of standard firmware OTA.

3. Why Dual-Partition Image Flash?

Image Flash vs. Individual File Uploads

Image Flash (LittleFS .bin) Individual File Uploads
Integrity High (Flash whole partition, verify, switch) Low (A failure mid-upload leaves a broken site)
Simplicity (Backend) Easy: Stream bytes to raw flash partition Hard: Manage file creation, deletion, truncation
Speed Faster (One contiguous flash write) Slower (Multiple HTTP requests, VFS overhead)

Dual-Partition (A/B) vs. Single Partition

Dual-Partition (A/B) Single Partition
Rollback Yes: Revert to previous slot if new one fails No: Broken update bricks the UI
Flash Usage Higher (Requires 2x space) Lower

Decision: Because we have a 16MB flash chip, allocating two 1MB partitions for the frontend (www_0 and www_1) is trivial and provides crucial safety guarantees.

4. Architecture & Workflow

4.1. The Partition Table

The partitions.csv will be modified to include two 1MB data partitions for LittleFS:

  • www_0
  • www_1

4.2. State Management (NVS)

The active partition index (0 or 1) will be stored in Non-Volatile Storage (NVS).

  • On factory flash via serial, www_0 is populated.
  • During boot (app_main), the ESP32 reads the NVS key. If the key is empty, it defaults to 0 and mounts www_0 to the /www VFS path.

4.3. The Update Process (Backend)

  1. Identify Slot: The ESP32 determines which slot is currently inactive.
  2. Stream Upload: The new LittleFS image (.bin) is POSTed to /api/ota/frontend.
  3. Write to Flash: The HTTP handler streams the payload directly to the raw, unmounted inactive partition using esp_partition_erase_range and esp_partition_write, bypassing LittleFS entirely to save RAM and CPU.
  4. Switch: Once the upload completes successfully, the NVS pointer is updated to point to the newly flashed partition.
  5. Reboot: The ESP32 reboots. The bootloader reads the new NVS value, mounts the updated partition, and the new frontend is served.

Design Note: We chose an explicit reboot over a hot-swap (unmounting and remounting at runtime) because a reboot is very fast (~2-3 seconds) and guarantees a clean state, closing any open file handles.

4.4. Security Decisions

Authentication and security for the /api/ota/frontend endpoint are deferred. The device operates exclusively on a local, trusted network, making immediate authentication overhead unnecessary for this iteration.

5. Implementation Steps

  1. Partition Table: Update partitions.csv with www_0 and www_1 (1MB each).
  2. Boot Logic: Update main.cpp and http_server.cpp to read the active partition from NVS and mount the correct label.
  3. API Endpoints:
    • Add GET /api/ota/status to report the current active slot.
    • Add POST /api/ota/frontend to handle the binary stream.
  4. Frontend UI: Create a standalone "Update" page in the Svelte app that fetches the status and provides a file picker and progress bar for the upload.
  5. Build Automation: Add mklittlefs to the Node.js build pipeline to generate www.bin alongside the standard dist output.