Compare commits
2 Commits
main
...
849d126ce0
| Author | SHA1 | Date | |
|---|---|---|---|
| 849d126ce0 | |||
| ad65bf520b |
5
Provider/.agents/rules/how-to-work-with-user.md
Normal file
5
Provider/.agents/rules/how-to-work-with-user.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
trigger: always_on
|
||||
---
|
||||
|
||||
The way you must work with the human user is simple. When you finish a task, tell him what you did, what you think you should do next and ask for review and confirmation. Never go rogue.
|
||||
@@ -1,3 +1,5 @@
|
||||
#include <cstddef>
|
||||
|
||||
// SDK
|
||||
#include "cJSON.h"
|
||||
#include "esp_http_server.h"
|
||||
@@ -7,6 +9,7 @@
|
||||
// Project
|
||||
#include "appstate.hpp"
|
||||
#include "types.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
internal esp_err_t api_ota_status_handler(httpd_req_t *req)
|
||||
{
|
||||
@@ -17,22 +20,24 @@ internal esp_err_t api_ota_status_handler(httpd_req_t *req)
|
||||
|
||||
cJSON_AddNumberToObject(root, "active_slot", g_Active_WWW_Partition);
|
||||
|
||||
const char *partitions[] = {"www_0", "www_1"};
|
||||
constexpr const char *kPartitions[] = {"www_0", "www_1", "ota_0", "ota_1",
|
||||
"factory"};
|
||||
constexpr size_t kPartitionCount = ArrayCount(kPartitions);
|
||||
cJSON *parts_arr = cJSON_AddArrayToObject(root, "partitions");
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
for (size_t i = 0; i < kPartitionCount; i++)
|
||||
{
|
||||
cJSON *p_obj = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_obj, "label", partitions[i]);
|
||||
cJSON_AddStringToObject(p_obj, "label", kPartitions[i]);
|
||||
|
||||
const esp_partition_t *p = esp_partition_find_first(
|
||||
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, partitions[i]);
|
||||
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, kPartitions[i]);
|
||||
if (p)
|
||||
{
|
||||
cJSON_AddNumberToObject(p_obj, "size", p->size);
|
||||
|
||||
size_t total = 0, used = 0;
|
||||
if (esp_littlefs_info(partitions[i], &total, &used) == ESP_OK)
|
||||
if (esp_littlefs_info(kPartitions[i], &total, &used) == ESP_OK)
|
||||
{
|
||||
cJSON_AddNumberToObject(p_obj, "used", used);
|
||||
cJSON_AddNumberToObject(p_obj, "free", total - used);
|
||||
|
||||
8
Provider/main/utils.hpp
Normal file
8
Provider/main/utils.hpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <types.hpp>
|
||||
|
||||
template <typename T, size_t N> constexpr size_t ArrayCount(T (&)[N])
|
||||
{
|
||||
return N;
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x6000,
|
||||
phy_init, data, phy, 0xf000, 0x1000,
|
||||
factory, app, factory, 0x10000, 1M,
|
||||
otadata, data, ota, , 0x2000,
|
||||
phy_init, data, phy, , 0x1000,
|
||||
factory, app, factory, , 2M,
|
||||
ota_0, app, ota_0, , 2M,
|
||||
ota_1, app, ota_1, , 2M,
|
||||
www_0, data, littlefs, , 1M,
|
||||
www_1, data, littlefs, , 1M,
|
||||
|
||||
|
72
Provider/tdd/firmware_ota.md
Normal file
72
Provider/tdd/firmware_ota.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Firmware 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 main firmware of the ESP32-S3. The update must:
|
||||
- Update the core application logic without requiring a physical USB connection.
|
||||
- Provide a reliable fallback if an update fails (Rollback capability via A/B slots).
|
||||
- Provide a permanent "factory" fallback as an extreme safety measure.
|
||||
- Integrate seamlessly with the existing Svelte frontend UI for a push-based update experience.
|
||||
- Maintain a clear versioning scheme visible to the user.
|
||||
|
||||
## 2. Chosen Approach
|
||||
|
||||
We implemented a **Dual-Partition Image Flash (A/B slots) with Factory Fallback** strategy using ESP-IDF's native OTA mechanisms.
|
||||
|
||||
The build process generates a single `.bin` firmware image. This image is uploaded via the frontend UI and streamed directly to the inactive OTA flash partition (`ota_0` or `ota_1`). Upon successful transfer and validation, the bootloader is instructed to boot from the new partition on the next restart.
|
||||
|
||||
## 3. Design Decisions & Trade-offs
|
||||
|
||||
### 3.1. Why Dual-Partition (A/B) with Factory?
|
||||
- **Safety**: A failed or interrupted upload never "bricks" the device.
|
||||
- **Factory Fallback**: By maintaining a dedicated 2MB `factory` partition alongside the two 2MB OTA partitions (`ota_0`, `ota_1`), we ensure that even if both OTA slots are irrecoverably corrupted, the device can always boot into a known-good state. This requires an initial USB flash to set up but provides maximum long-term reliability.
|
||||
- **Storage Allocation**: With 16MB of total flash on the ESP32-S3, dedicating 6MB to application code (3x 2MB) is a worthwhile trade-off for extreme resilience, while still leaving ample room for the frontend (`www_0`, `www_1`) and NVS.
|
||||
|
||||
### 3.2. Automatic App Rollback
|
||||
We rely on ESP-IDF's built-in "App Rollback" feature.
|
||||
- **The Mechanism**: When the ESP32 boots a newly OTA-flashed firmware, it is marked as "Pending Verify". If the application crashes, resets, or fails to explicitly mark itself as "valid" during this initial boot, the bootloader automatically reverts to the previous working partition on the subsequent boot.
|
||||
- **Validation Point**: We consider the firmware "valid" (and stop the rollback timer) only after it successfully establishes a network connection (Ethernet or WiFi). This ensures that a bad update won't permanently disconnect the device from future OTA attempts.
|
||||
|
||||
### 3.3. Push vs. Pull Updates
|
||||
- **Decision**: We implemented a "Push" mechanism where the user manually uploads the `.bin` file via the web UI.
|
||||
- **Rationale**: This matches the existing frontend OTA workflow and is simpler to implement initially. It avoids the need for external update servers, manifest files, and polling mechanisms. A "Pull" mechanism can be added later if fleet management becomes a requirement.
|
||||
|
||||
### 3.4. Versioning Strategy
|
||||
- **Decision**: We extract the firmware version directly from the ESP-IDF natively embedded `esp_app_desc_t` structure.
|
||||
- **Rationale**: This ensures the version reported by the API (`GET /api/system/info`) is exactly the version compiled by CMake (`PROJECT_VER`), eliminating the risk of manual mismatches or external version files getting out of sync.
|
||||
|
||||
## 4. Final Architecture
|
||||
|
||||
### 4.1. The Partition Table
|
||||
```csv
|
||||
# Name, Type, SubType, Offset, Size
|
||||
nvs, data, nvs, 0x9000, 0x6000
|
||||
otadata, data, ota, , 0x2000
|
||||
phy_init, data, phy, , 0x1000
|
||||
factory, app, factory, , 2M
|
||||
ota_0, app, ota_0, , 2M
|
||||
ota_1, app, ota_1, , 2M
|
||||
www_0, data, littlefs, , 1M
|
||||
www_1, data, littlefs, , 1M
|
||||
```
|
||||
|
||||
### 4.2. Backend Components
|
||||
- `main/api/ota/firmware.cpp`: The endpoint (`POST /api/ota/firmware`) handling the streaming ingestion of the `.bin` file using standard ESP-IDF `esp_ota` functions.
|
||||
- `main/api/system/system.cpp`: The endpoint querying `esp_app_get_description()` to expose the unified version payload to the frontend.
|
||||
- `main/main.cpp`: The orchestrator that calls `esp_ota_mark_app_valid_cancel_rollback()` post-network connection.
|
||||
|
||||
### 4.3. UI/UX Implementation
|
||||
- The Frontend OTA update component (`OTAUpdate.svelte`) is expanded to include a parallel "Firmware Update" section.
|
||||
- This UI section handles file selection, upload progress visualization, and system reboot confirmation, providing parity with the existing frontend update UX.
|
||||
|
||||
## 5. Summary
|
||||
|
||||
We use **ESP-IDF's native OTA APIs** with a **Factory + Dual A/B Partition** layout for maximum reliability. The system leverages **Automatic App Rollback** to prevent network lockouts from bad firmware. Versioning is natively controlled via the **CMake build descriptions**, and the entire update process is driven centrally from the **Svelte Frontend UI** via a Push-based REST endpoint.
|
||||
|
||||
---
|
||||
*Created by Antigravity - Last Updated: 2026-03-03*
|
||||
Reference in New Issue
Block a user