# 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 `POST`ed 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.