From ab7eae3d94ec15037517c597fc65097719224074 Mon Sep 17 00:00:00 2001 From: cjkas Date: Tue, 20 Oct 2026 20:43:00 +0200 Subject: [PATCH] fix: download the per-chip LittleFS image in online updates Releases publish SomfyController.littlefs..bin, but the updater asked for SomfyController.littlefs.bin. The download 404'd and the device rebooted into the new firmware with the old web UI. Also: hasFS now matches per-chip images, C6 picks its own firmware file, and recoverFilesystem() points at cjkas instead of rstrouse. Co-Authored-By: Claude Opus 5.5 --- data-src/index.js | 2 +- src/GitOTA.cpp | 30 ++++++++++++------------------ src/GitOTA.h | 1 - src/Utils.cpp | 8 ++++++++ src/Utils.h | 6 ++++++ test/test_utils/test_utils.cpp | 32 ++++++++++++++++++++++++++++++++ 6 files changed, 59 insertions(+), 20 deletions(-) diff --git a/data-src/index.js b/data-src/index.js index 6dd60fd..2055e5e 100644 --- a/data-src/index.js +++ b/data-src/index.js @@ -4849,7 +4849,7 @@ class Firmware { let div = this.createFileUploader('/updateApplication'); general.reloadApp = true; let inst = div.querySelector('div[id=divInstText]'); - inst.innerHTML = '
Select a binary file [SomfyController.littlefs.bin] containing the littlefs data for the application then press the Upload File button.
'; + inst.innerHTML = '
Select a binary file [SomfyController.littlefs.<chip>.bin, e.g. SomfyController.littlefs.esp32.bin] containing the littlefs data for the application then press the Upload File button.
'; if (this.isMobile()) { inst.innerHTML += `
WARNING
`; inst.innerHTML += '
This browser does not support automatic backups. It is highly recommended that you back up your configuration using the backup button before proceeding.
'; diff --git a/src/GitOTA.cpp b/src/GitOTA.cpp index 9b13d9c..9391624 100644 --- a/src/GitOTA.cpp +++ b/src/GitOTA.cpp @@ -43,7 +43,7 @@ void GitRelease::setReleaseProperty(const char *key, const char *val) { void GitRelease::setAssetProperty(const char *key, const char *val) { if(strcmp(key, "name") == 0) { //Serial.println(val); - if(strstr(val, "littlefs.bin")) this->hasFS = true; + if(isFsAsset(val)) this->hasFS = true; else if(strstr(val, "ino.esp32.bin")) { if(strlen(this->hwVersions)) strcat(this->hwVersions, ","); strcat(this->hwVersions, "32"); @@ -353,22 +353,16 @@ void GitUpdater::emitDownloadProgress(uint8_t num, size_t total, size_t loaded, sockEmit.loop(); webServer.loop(); } -void GitUpdater::setFirmwareFile() { +// The chip part of this board's release asset names. +static const char *chipName() { esp_chip_info_t ci; esp_chip_info(&ci); switch(ci.model) { - case esp_chip_model_t::CHIP_ESP32S3: - strcpy(this->currentFile, "SomfyController.ino.esp32s3.bin"); - break; - case esp_chip_model_t::CHIP_ESP32S2: - strcpy(this->currentFile, "SomfyController.ino.esp32s2.bin"); - break; - case esp_chip_model_t::CHIP_ESP32C3: - strcpy(this->currentFile, "SomfyController.ino.esp32c3.bin"); - break; - default: - strcpy(this->currentFile, "SomfyController.ino.esp32.bin"); - break; + case esp_chip_model_t::CHIP_ESP32S3: return "esp32s3"; + case esp_chip_model_t::CHIP_ESP32S2: return "esp32s2"; + case esp_chip_model_t::CHIP_ESP32C3: return "esp32c3"; + case esp_chip_model_t::CHIP_ESP32C6: return "esp32c6"; + default: return "esp32"; } } @@ -379,14 +373,14 @@ bool GitUpdater::beginUpdate(const char *version) { strcpy(this->targetRelease, version); this->emitUpdateCheck(); - this->setFirmwareFile(); + releaseAssetName(this->currentFile, sizeof(this->currentFile), "ino", chipName()); this->partition = U_FLASH; this->lockFS = this->cancelled = false; this->error = 0; this->error = this->downloadFile(); if(this->error == 0 && !this->cancelled) { somfy.commit(); - strcpy(this->currentFile, "SomfyController.littlefs.bin"); + releaseAssetName(this->currentFile, sizeof(this->currentFile), "littlefs", chipName()); this->partition = U_SPIFFS; this->lockFS = true; this->error = this->downloadFile(); @@ -405,8 +399,8 @@ bool GitUpdater::beginUpdate(const char *version) { return true; } bool GitUpdater::recoverFilesystem() { - sprintf(this->baseUrl, "https://github.com/rstrouse/ESPSomfy-RTS/releases/download/%s/", settings.fwVersion.name); - strcpy(this->currentFile, "SomfyController.littlefs.bin"); + sprintf(this->baseUrl, "https://github.com/cjkas/ESPSomfy-RTS/releases/download/%s/", settings.fwVersion.name); + releaseAssetName(this->currentFile, sizeof(this->currentFile), "littlefs", chipName()); this->status = GIT_UPDATING; this->partition = U_SPIFFS; this->lockFS = true; diff --git a/src/GitOTA.h b/src/GitOTA.h index 6996f83..7a02112 100644 --- a/src/GitOTA.h +++ b/src/GitOTA.h @@ -55,7 +55,6 @@ class GitUpdater { bool beginUpdate(const char *release); bool endUpdate(); int8_t downloadFile(); - void setFirmwareFile(); void setCurrentRelease(GitRepo &repo); void loop(); diff --git a/src/Utils.cpp b/src/Utils.cpp index 75c77f0..2651f2a 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -150,6 +150,14 @@ bool worthRoaming(bool connected, int currentRssi, int apRssi, int minGain) { bool otaProven(bool networkUp, unsigned long uptimeMs, unsigned long minUptimeMs) { return networkUp && uptimeMs >= minUptimeMs; } +bool releaseAssetName(char *buf, size_t size, const char *kind, const char *chip) { + int n = snprintf(buf, size, "SomfyController.%s.%s.bin", kind, chip); + return n >= 0 && (size_t)n < size; +} +bool isFsAsset(const char *name) { + size_t len = strlen(name); + return strstr(name, ".littlefs.") && len >= 4 && strcmp(name + len - 4, ".bin") == 0; +} const char *wifiAuthModeName(uint8_t authMode) { switch(authMode) { case 0: return "OPEN"; diff --git a/src/Utils.h b/src/Utils.h index 27c9ba9..723d9d5 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -86,6 +86,12 @@ heap_emit_t heapEmitTarget(uint8_t num, uint32_t sinceLastMs, uint32_t intervalM // update has proven itself: it has run for minUptimeMs and the network is // up. Until then a reset rolls back to the previous firmware. bool otaProven(bool networkUp, unsigned long uptimeMs, unsigned long minUptimeMs); +// A release asset's name: "SomfyController...bin", kind "ino" for +// the firmware or "littlefs" for the web UI image, chip e.g. "esp32s3". False +// when it doesn't fit in size. +bool releaseAssetName(char *buf, size_t size, const char *kind, const char *chip); +// Whether a release asset is a LittleFS image, for any chip. +bool isFsAsset(const char *name); // How well loop() keeps up: the passes it made per second and its longest // pass, over the window since the last memStatus broadcast. Only the loop // task calls record(), rate(), maxUs() and publish(). publish() closes the diff --git a/test/test_utils/test_utils.cpp b/test/test_utils/test_utils.cpp index b17d0e0..2e5db6e 100644 --- a/test/test_utils/test_utils.cpp +++ b/test/test_utils/test_utils.cpp @@ -272,6 +272,34 @@ static void test_otaProven_not_without_network() { TEST_ASSERT_FALSE(otaProven(false, 3600000, 60000)); } +/********************************************************************* + * releaseAssetName / isFsAsset + ********************************************************************/ +static void test_releaseAssetName_matches_the_release_workflow() { + // .github/workflows/build.yaml fwname and fsname. + char buf[64]; + TEST_ASSERT_TRUE(releaseAssetName(buf, sizeof(buf), "ino", "esp32")); + TEST_ASSERT_EQUAL_STRING("SomfyController.ino.esp32.bin", buf); + TEST_ASSERT_TRUE(releaseAssetName(buf, sizeof(buf), "littlefs", "esp32s3")); + TEST_ASSERT_EQUAL_STRING("SomfyController.littlefs.esp32s3.bin", buf); +} +static void test_releaseAssetName_too_small_buffer() { + char buf[16]; + TEST_ASSERT_FALSE(releaseAssetName(buf, sizeof(buf), "littlefs", "esp32c6")); + TEST_ASSERT_EQUAL(sizeof(buf) - 1, strlen(buf)); +} +static void test_isFsAsset_per_chip_images() { + TEST_ASSERT_TRUE(isFsAsset("SomfyController.littlefs.esp32.bin")); + TEST_ASSERT_TRUE(isFsAsset("SomfyController.littlefs.esp32c6.bin")); + TEST_ASSERT_TRUE(isFsAsset("SomfyController.littlefs.bin")); +} +static void test_isFsAsset_not_the_other_assets() { + TEST_ASSERT_FALSE(isFsAsset("SomfyController.ino.esp32.bin")); + TEST_ASSERT_FALSE(isFsAsset("SomfyController.onboard.esp32.bin.zip")); + TEST_ASSERT_FALSE(isFsAsset("SomfyController.littlefs.bin.sha256")); + TEST_ASSERT_FALSE(isFsAsset("")); +} + /********************************************************************* * wifiAuthModeName ********************************************************************/ @@ -402,6 +430,10 @@ int main(int argc, char **argv) { RUN_TEST(test_otaProven_after_the_minimum_uptime_with_network); RUN_TEST(test_otaProven_not_before_the_minimum_uptime); RUN_TEST(test_otaProven_not_without_network); + RUN_TEST(test_releaseAssetName_matches_the_release_workflow); + RUN_TEST(test_releaseAssetName_too_small_buffer); + RUN_TEST(test_isFsAsset_per_chip_images); + RUN_TEST(test_isFsAsset_not_the_other_assets); RUN_TEST(test_wifiAuthModeName_matches_the_idf_enum); RUN_TEST(test_wifiAuthModeName_unknown_past_the_shared_values); RUN_TEST(test_heapEmitTarget_one_client_always_gets_it);