feat: say when the firmware is flashed over USB

Builds with one app slot (C6, debug, and the C5 to come) have nowhere
for /updateFirmware to write. It answered "Error updating firmware: "
and rebooted.

- ConfigSettings::otaSlot, from esp_ota_get_next_update_partition().
- The header sends otaSlot, default 1, so only those builds send it.
- /updateFirmware refuses with 400 and a reason, and doesn't stop the
  radio or reboot.
- The web UI's Update Firmware says to flash the onboard image over USB,
  with a backup first. Firmware before the field leaves it undefined,
  which keeps the uploader.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cjkas 2026-10-28 22:06:26 +01:00
parent 6db449e878
commit 5721d34a34
10 changed files with 55 additions and 5 deletions

View file

@ -7,6 +7,7 @@
#include "Utils.h"
#include "Secrets.h"
#include "esp_chip_info.h"
#include "esp_ota_ops.h"
#include "esp_log.h"
static const char *TAG = "Config";
@ -144,6 +145,7 @@ bool ConfigSettings::begin() {
break;
}
ESP_LOGD(TAG, "Chip Model ESP32-%s", this->chipModel);
this->otaSlot = esp_ota_get_next_update_partition(nullptr) != nullptr;
this->fwVersion.parse(FW_VERSION);
uint64_t mac = ESP.getEfuseMac();
for(int i=0; i<17; i=i+8) {

View file

@ -163,6 +163,9 @@ class ConfigSettings: BaseSettings {
char serverId[10] = "";
char hostname[32] = "ESPSomfyRTS";
char chipModel[10] = "ESP32";
// Whether the partition table has a second app slot for /updateFirmware
// to write. Builds with one 3 MB slot (C5, C6, debug) are flashed over USB.
bool otaSlot = true;
conn_types_t connType = conn_types_t::unset;
appver_t fwVersion;
appver_t appVersion;

View file

@ -169,6 +169,7 @@
FIELD(hostname, "hn", 's', 0) \
FIELD(model, "md", 's', 0) \
FIELD(chipModel, "cp", 's', 0) \
FIELD(otaSlot, "os", 'b', 1) \
FIELD(connType, "ct", 's', 0) \
/* memory, wifi and ethernet */ \
FIELD(freeHeap, "fr", 'i', 0) \

View file

@ -164,6 +164,11 @@ void registerHttpRoutes(AsyncWebServer &server) {
// updateFirmware - file upload
server.on(AsyncURIMatcher::exact("/updateFirmware"), HTTP_POST,
[](AsyncWebServerRequest *request) {
// Nothing was written or stopped, so there is nothing to reboot for.
if(!settings.otaSlot) {
request->send(400, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"This device has no OTA slot, so its firmware is flashed over USB.\"}"));
return;
}
if(Update.hasError())
request->send(500, _encoding_json, "{\"status\":\"ERROR\",\"desc\":\"Error updating firmware: \"}");
else
@ -172,7 +177,7 @@ void registerHttpRoutes(AsyncWebServer &server) {
rebootDelay.rebootTime = millis() + 500;
},
[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
if(!uploadAllowed(request, index)) return;
if(!uploadAllowed(request, index) || !settings.otaSlot) return;
if(index == 0) {
webServer.uploadSuccess = false;
ESP_LOGI(TAG, "Update: %s", filename.c_str());

View file

@ -77,6 +77,7 @@ static void writeHeader(ProtoWriter &w) {
w.add(proto_field_t::hostname, settings.hostname);
w.add(proto_field_t::model, "ESPSomfyRTS");
w.add(proto_field_t::chipModel, settings.chipModel);
w.add(proto_field_t::otaSlot, settings.otaSlot);
w.add(proto_field_t::fw, settings.fwVersion.name);
w.add(proto_field_t::connType, net.connTypeName());
}