diff --git a/data-src/index.js b/data-src/index.js
index 6dd60fd..06a09ec 100644
--- a/data-src/index.js
+++ b/data-src/index.js
@@ -4834,7 +4834,8 @@ class Firmware {
updateFirmware() {
let div = this.createFileUploader('/updateFirmware');
let inst = div.querySelector('div[id=divInstText]');
- let html = '
Select a binary file [SomfyController.ino.esp32.bin] containing the device firmware then press the Upload File button.
';
+ let chip = document.getElementById('divContainer').getAttribute('data-chipmodel') || '';
+ let html = `Select a binary file [SomfyController.ino.esp32${chip}.bin] containing the device firmware then press the Upload File button.
`;
if (this.isMobile()) {
html += `WARNING
`;
html += '
This browser does not support automatic backups. It is highly recommended that you back up your configuration using the backup button before proceeding.
';
@@ -4849,7 +4850,8 @@ 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.
';
+ let chip = document.getElementById('divContainer').getAttribute('data-chipmodel') || '';
+ inst.innerHTML = `Select a binary file [SomfyController.littlefs.esp32${chip}.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/docs/KNOWN_ISSUES.md b/docs/KNOWN_ISSUES.md
index 402fe61..ed15935 100644
--- a/docs/KNOWN_ISSUES.md
+++ b/docs/KNOWN_ISSUES.md
@@ -267,3 +267,10 @@ On 2026-10-19 the log shows `web GET /controller from 192.168.1.197 total took 2
- **Fixed: a command for an unknown shade left no trace.** `handleShadeCommand`, `handleTiltCommand` and `handleGroupCommand` in [src/entryPoints/Web.cpp](../src/entryPoints/Web.cpp) now log a `not found` warning. On 2026-10-18 Home Assistant sent 10 commands to a device that had no shades yet, and the log showed only the response times.
- **Fixed: `last ping 0ms ago` meant the client never pinged.** The disconnect line in [src/outputs/SocketEmitter.cpp](../src/outputs/SocketEmitter.cpp) now says `never pinged`. Browsers don't ping, so they always showed it.
- **Open: `.gz does not exist` errors are noise.** `serveStatic` tries `.gz` first. For files stored without one (`appversion`, `icon.png`), LittleFS logs an `E`-level `does not exist, no permits for creation` line on every request. The fix would be for `minify.py` to gzip the plain files too. `setTryGzipFirst(false)` doesn't help: the error comes from `exists()` probing the missing variant, so it would only move the noise to the gzip-only files.
+
+## Installing "Main" from the update dialog always fails
+
+**Status:** open. Found 2026-10-20 while reviewing PR #72.
+**Location:** `GitUpdater::beginUpdate` in [src/GitOTA.cpp](../src/GitOTA.cpp).
+
+The update dialog offers a "Main" entry next to the releases. For it, the updater downloads from `https://raw.githubusercontent.com/cjkas/ESPSomfy-RTS/master/`, but the repository holds no `.bin` files, so the firmware download 404s. Nothing is written and the device keeps running, so it does no harm, but the option can't work. Either drop the entry or point it at a build that publishes per-chip images.
diff --git a/src/GitOTA.cpp b/src/GitOTA.cpp
index 9b13d9c..2c09b9f 100644
--- a/src/GitOTA.cpp
+++ b/src/GitOTA.cpp
@@ -3,7 +3,6 @@
#include
#include
#include
-#include
#include "esp_log.h"
#include "ConfigSettings.h"
#include "GitOTA.h"
@@ -43,7 +42,9 @@ 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;
+ char fsFile[64];
+ releaseAssetName(fsFile, sizeof(fsFile), "littlefs", settings.chipModel);
+ if(strcmp(val, fsFile) == 0) this->hasFS = true;
else if(strstr(val, "ino.esp32.bin")) {
if(strlen(this->hwVersions)) strcat(this->hwVersions, ",");
strcat(this->hwVersions, "32");
@@ -353,25 +354,6 @@ void GitUpdater::emitDownloadProgress(uint8_t num, size_t total, size_t loaded,
sockEmit.loop();
webServer.loop();
}
-void GitUpdater::setFirmwareFile() {
- 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;
- }
-}
-
bool GitUpdater::beginUpdate(const char *version) {
ESP_LOGI(TAG, "Begin update called...");
if(strcmp(version, "Main") == 0) strcpy(this->baseUrl, "https://raw.githubusercontent.com/cjkas/ESPSomfy-RTS/master/");
@@ -379,14 +361,14 @@ bool GitUpdater::beginUpdate(const char *version) {
strcpy(this->targetRelease, version);
this->emitUpdateCheck();
- this->setFirmwareFile();
+ releaseAssetName(this->currentFile, sizeof(this->currentFile), "ino", settings.chipModel);
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", settings.chipModel);
this->partition = U_SPIFFS;
this->lockFS = true;
this->error = this->downloadFile();
@@ -405,8 +387,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", settings.chipModel);
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..0fc4a0a 100644
--- a/src/Utils.cpp
+++ b/src/Utils.cpp
@@ -150,6 +150,9 @@ bool worthRoaming(bool connected, int currentRssi, int apRssi, int minGain) {
bool otaProven(bool networkUp, unsigned long uptimeMs, unsigned long minUptimeMs) {
return networkUp && uptimeMs >= minUptimeMs;
}
+void releaseAssetName(char *buf, size_t size, const char *kind, const char *chipModel) {
+ snprintf(buf, size, "SomfyController.%s.esp32%s.bin", kind, chipModel);
+}
const char *wifiAuthModeName(uint8_t authMode) {
switch(authMode) {
case 0: return "OPEN";
diff --git a/src/Utils.h b/src/Utils.h
index 27c9ba9..7dfbb31 100644
--- a/src/Utils.h
+++ b/src/Utils.h
@@ -86,6 +86,10 @@ 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..esp32.bin", kind
+// "ino" for the firmware or "littlefs" for the web UI image, chipModel as in
+// ConfigSettings ("" for the ESP32, "s3", "c3", "c6").
+void releaseAssetName(char *buf, size_t size, const char *kind, const char *chipModel);
// 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..e549f73 100644
--- a/test/test_utils/test_utils.cpp
+++ b/test/test_utils/test_utils.cpp
@@ -272,6 +272,26 @@ static void test_otaProven_not_without_network() {
TEST_ASSERT_FALSE(otaProven(false, 3600000, 60000));
}
+/*********************************************************************
+ * releaseAssetName
+ ********************************************************************/
+static void test_releaseAssetName_matches_the_release_workflow() {
+ // .github/workflows/build.yaml fwname and fsname, from ConfigSettings::chipModel.
+ char buf[64];
+ releaseAssetName(buf, sizeof(buf), "ino", "");
+ TEST_ASSERT_EQUAL_STRING("SomfyController.ino.esp32.bin", buf);
+ releaseAssetName(buf, sizeof(buf), "littlefs", "s3");
+ TEST_ASSERT_EQUAL_STRING("SomfyController.littlefs.esp32s3.bin", buf);
+ releaseAssetName(buf, sizeof(buf), "littlefs", "c6");
+ TEST_ASSERT_EQUAL_STRING("SomfyController.littlefs.esp32c6.bin", buf);
+}
+static void test_releaseAssetName_keeps_c3() {
+ // C3 is out of the IDF 5.5 builds for now (ADR 0003) but is coming back.
+ char buf[64];
+ releaseAssetName(buf, sizeof(buf), "ino", "c3");
+ TEST_ASSERT_EQUAL_STRING("SomfyController.ino.esp32c3.bin", buf);
+}
+
/*********************************************************************
* wifiAuthModeName
********************************************************************/
@@ -402,6 +422,8 @@ 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_keeps_c3);
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);