feat: remove the GitHub update

The release check, release list, download, cancel and
/recoverFilesystem go, with the GitHub button and the Auto Check for
Updates setting in the web UI (docs/adr/0007-no-github-update.md).
The HTTPS session it needed failed for want of a 35-40 KB block (#76),
it checked no certificate, and the web UI's buttons had been getting
404.  Updates are uploads only (/updateFirmware, /updateApplication).

The old socket still sends fwStatus with the installed versions, which
the web UI and Home Assistant read.  checkForUpdate stays in NVS and the
backup file so their formats don't change.

The release image shrinks by 126.5 KB: 142 KB of the OTA slot is free.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cjkas 2026-10-22 21:28:23 +02:00
parent 539a49eb77
commit 83c946dd5f
14 changed files with 25 additions and 1009 deletions

View file

@ -18,7 +18,6 @@ void registerApiRoutes(AsyncWebServer &server) {
server.on("/room", HTTP_GET, [](AsyncWebServerRequest *r) { webServer.handleRoom(r); });
server.on("/shade", HTTP_GET, [](AsyncWebServerRequest *r) { webServer.handleShade(r); });
server.on("/group", HTTP_GET, [](AsyncWebServerRequest *r) { webServer.handleGroup(r); });
server.on("/downloadFirmware", WebRequestMethodComposite(HTTP_GET) | HTTP_POST, [](AsyncWebServerRequest *r) { webServer.handleDownloadFirmware(r); });
server.on("/backup", WebRequestMethodComposite(HTTP_GET) | HTTP_POST, [](AsyncWebServerRequest *r) { webServer.handleBackup(r); });
server.on("/reboot", WebRequestMethodComposite(HTTP_POST) | HTTP_PUT, [](AsyncWebServerRequest *r) { webServer.handleReboot(r); });
// JSON body endpoints

View file

@ -17,7 +17,6 @@
#include "Web.h"
#include "WebRoutes.h"
#include "MqttConnection.h"
#include "GitOTA.h"
#include "ESPNetwork.h"
#include "Timing.h"
#include <AsyncTCP.h>
@ -32,7 +31,6 @@ extern LoopStats loopStats;
extern SomfyShadeController somfy;
extern Web webServer;
extern MqttConnection mqtt;
extern GitUpdater git;
extern ESPNetwork net;
extern LoopQueue loopQueue;
extern SnapshotSlots snapshotSlots;
@ -203,38 +201,13 @@ void serializeAppVersion(JsonFormatter &json, appver_t &ver) {
json.addElem("build", ver.build);
json.addElem("suffix", ver.suffix);
}
void serializeGitVersion(JsonFormatter &json) {
json.addElem("available", git.updateAvailable);
json.addElem("status", git.status);
json.addElem("error", (int32_t)git.error);
json.addElem("cancelled", git.cancelled);
json.addElem("checkForUpdate", settings.checkForUpdate);
json.addElem("inetAvailable", git.inetAvailable);
void serializeVersion(JsonFormatter &json) {
json.beginObject("fwVersion");
serializeAppVersion(json, settings.fwVersion);
json.endObject();
json.beginObject("appVersion");
serializeAppVersion(json, settings.appVersion);
json.endObject();
json.beginObject("latest");
serializeAppVersion(json, git.latest);
json.endObject();
}
void serializeGitRelease(GitRelease *rel, JsonFormatter &json) {
Timestamp ts;
char buff[20];
sprintf(buff, "%llu", rel->id);
json.addElem("id", buff);
json.addElem("name", rel->name);
json.addElem("date", ts.getISOTime(rel->releaseDate));
json.addElem("draft", rel->draft);
json.addElem("preRelease", rel->preRelease);
json.addElem("main", rel->main);
json.addElem("hasFS", rel->hasFS);
json.addElem("hwVersions", rel->hwVersions);
json.beginObject("version");
serializeAppVersion(json, rel->version);
json.endObject();
}
// The parts of /controller that aren't in the shade model.
@ -251,7 +224,7 @@ static void controllerHeader(JsonFormatter &json, const model_snapshot_t &snap)
json.endObject();
json.endObject();
json.beginObject("version");
serializeGitVersion(json);
serializeVersion(json);
json.endObject();
}
// The parts of /discovery that aren't in the shade model.
@ -262,14 +235,12 @@ static void discoveryHeader(JsonFormatter &json, const model_snapshot_t &snap) {
else if(net.connType == conn_types_t::wifi) connType = "Wifi";
json.addElem("serverId", settings.serverId);
json.addElem("version", settings.fwVersion.name);
json.addElem("latest", git.latest.name);
json.addElem("model", "ESPSomfyRTS");
json.addElem("hostname", settings.hostname);
json.addElem("authType", static_cast<uint8_t>(settings.Security.type));
json.addElem("permissions", settings.Security.permissions);
json.addElem("chipModel", settings.chipModel);
json.addElem("connType", connType);
json.addElem("checkForUpdate", settings.checkForUpdate);
json.beginObject("memory");
json.addElem("max", ESP.getMaxAllocHeap());
json.addElem("free", ESP.getFreeHeap());
@ -697,40 +668,6 @@ void Web::handleSetSensor(AsyncWebServerRequest *request, JsonVariant &json) {
}
else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"shadeId was not provided\"}"));
}
void Web::handleDownloadFirmware(AsyncWebServerRequest *request) {
if(request->method() == HTTP_OPTIONS) { request->send(200); return; }
if(!this->isAuthenticated(request)) return;
GitRepo repo;
GitRelease *rel = nullptr;
int8_t err = repo.getReleases();
ESP_LOGI(TAG, "Async downloadFirmware called...");
if(err == 0) {
if(asyncHasParam(request, "ver")) {
String ver = asyncParam(request, "ver");
if(ver == "latest") rel = &repo.releases[0];
else if(ver == "main") rel = &repo.releases[GIT_MAX_RELEASES];
else {
for(uint8_t i = 0; i < GIT_MAX_RELEASES; i++) {
if(repo.releases[i].id == 0) continue;
if(strcmp(repo.releases[i].name, ver.c_str()) == 0) { rel = &repo.releases[i]; break; }
}
}
if(rel) {
AsyncJsonResp resp;
resp.beginResponse(request, g_async_content, sizeof(g_async_content));
resp.beginObject();
serializeGitRelease(rel, resp);
resp.endObject();
resp.endResponse();
strcpy(git.targetRelease, rel->name);
git.status = GIT_AWAITING_UPDATE;
}
else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Release not found in repo.\"}"));
}
else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Release version not supplied.\"}"));
}
else request->send(err, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Error communicating with Github.\"}"));
}
void Web::handleBackup(AsyncWebServerRequest *request) {
if(!this->isAuthenticated(request)) return;
bool attach = false;

View file

@ -33,7 +33,6 @@ class Web {
void handleRepeatCommand(AsyncWebServerRequest *request, JsonVariant &json);
void handleSetPositions(AsyncWebServerRequest *request, JsonVariant &json);
void handleSetSensor(AsyncWebServerRequest *request, JsonVariant &json);
void handleDownloadFirmware(AsyncWebServerRequest *request);
void handleBackup(AsyncWebServerRequest *request);
void handleReboot(AsyncWebServerRequest *request);
void handleNotFound(AsyncWebServerRequest *request);

View file

@ -4,7 +4,6 @@
#include "Somfy.h"
#include "LoopQueue.h"
#include "ModelSnapshot.h"
#include "GitOTA.h"
// What WebUiRoutes, ApiRoutes and the handlers both of them call (Web.cpp)
// share. Internal to the web servers.
@ -40,6 +39,6 @@ void serializeShade(SomfyShade *shade, JsonFormatter &json);
void serializeShade(SomfyShade *shade, JsonFormatter &json, bool paired, uint16_t lastRollingCode);
void serializeTransceiverConfig(JsonFormatter &json);
void serializeAppVersion(JsonFormatter &json, appver_t &ver);
void serializeGitVersion(JsonFormatter &json);
void serializeGitRelease(GitRelease *rel, JsonFormatter &json);
// The installed firmware and web application versions.
void serializeVersion(JsonFormatter &json);
#endif

View file

@ -16,7 +16,6 @@
#include "Web.h"
#include "WebRoutes.h"
#include "MqttConnection.h"
#include "GitOTA.h"
#include "ESPNetwork.h"
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
@ -31,7 +30,6 @@ extern rebootDelay_t rebootDelay;
extern SomfyShadeController somfy;
extern Web webServer;
extern MqttConnection mqtt;
extern GitUpdater git;
extern ESPNetwork net;
extern LoopQueue loopQueue;
@ -189,50 +187,6 @@ void registerWebUiRoutes(AsyncWebServer &server) {
request->send(LittleFS, "/shades.tmp", _encoding_text);
});
server.on("/getReleases", HTTP_GET, [](AsyncWebServerRequest *request) {
GitRepo repo;
repo.getReleases();
git.setCurrentRelease(repo);
AsyncJsonResp resp;
resp.beginResponse(request, g_async_content, sizeof(g_async_content));
resp.beginObject();
// Inline GitRepo::toJSON
resp.beginObject("fwVersion");
serializeAppVersion(resp, settings.fwVersion);
resp.endObject();
resp.beginObject("appVersion");
serializeAppVersion(resp, settings.appVersion);
resp.endObject();
resp.beginArray("releases");
for(uint8_t i = 0; i < GIT_MAX_RELEASES + 1; i++) {
if(repo.releases[i].id == 0) continue;
resp.beginObject();
serializeGitRelease(&repo.releases[i], resp);
resp.endObject();
}
resp.endArray();
resp.endObject();
resp.endResponse();
});
server.on("/downloadFirmware", HTTP_GET, [](AsyncWebServerRequest *request) { webServer.handleDownloadFirmware(request); });
server.on("/cancelFirmware", HTTP_GET, [](AsyncWebServerRequest *request) {
if(!git.lockFS) {
git.status = GIT_UPDATE_CANCELLING;
AsyncJsonResp resp;
resp.beginResponse(request, g_async_content, sizeof(g_async_content));
resp.beginObject();
serializeGitVersion(resp);
resp.endObject();
resp.endResponse();
git.cancelled = true;
}
else {
request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Cannot cancel during filesystem update.\"}"));
}
});
server.on("/backup", HTTP_GET, [](AsyncWebServerRequest *request) { webServer.handleBackup(request); });
server.on("/restore", HTTP_POST,
@ -550,10 +504,6 @@ void registerWebUiRoutes(AsyncWebServer &server) {
// updateShadeConfig - file upload
server.on("/updateShadeConfig", HTTP_POST,
[](AsyncWebServerRequest *request) {
if(git.lockFS) {
request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Filesystem update in progress\"}"));
return;
}
request->send(200, _encoding_json, "{\"status\":\"ERROR\",\"desc\":\"Updating Shade Config: \"}");
},
[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
@ -759,11 +709,9 @@ void registerWebUiRoutes(AsyncWebServer &server) {
return;
}
JsonObject obj = json.as<JsonObject>();
if(!obj["hostname"].isNull() || !obj["ssdpBroadcast"].isNull() || !obj["checkForUpdate"].isNull()) {
bool checkForUpdate = settings.checkForUpdate;
if(!obj["hostname"].isNull() || !obj["ssdpBroadcast"].isNull()) {
settings.fromJSON(obj);
settings.save();
if(settings.checkForUpdate != checkForUpdate) git.emitUpdateCheck();
if(!obj["hostname"].isNull()) net.updateHostname();
}
if(!obj["ntpServer"].isNull() || !obj["posixZone"].isNull()) {
@ -872,7 +820,6 @@ void registerWebUiRoutes(AsyncWebServer &server) {
resp.addElem("hostname", settings.hostname);
resp.addElem("connType", static_cast<uint8_t>(settings.connType));
resp.addElem("chipModel", settings.chipModel);
resp.addElem("checkForUpdate", settings.checkForUpdate);
resp.addElem("ntpServer", settings.NTP.ntpServer);
resp.addElem("posixZone", settings.NTP.posixZone);
resp.endObject();
@ -952,17 +899,6 @@ void registerWebUiRoutes(AsyncWebServer &server) {
resp.endResponse();
});
server.on("/recoverFilesystem", WebRequestMethodComposite(HTTP_GET) | HTTP_POST, [](AsyncWebServerRequest *request) {
if(git.status == GIT_UPDATING)
request->send(200, "application/json", "{\"status\":\"OK\",\"desc\":\"Filesystem is updating. Please wait!!!\"}");
else if(git.status != GIT_STATUS_READY)
request->send(200, "application/json", "{\"status\":\"ERROR\",\"desc\":\"Cannot recover file system at this time.\"}");
else {
git.recoverFilesystem();
request->send(200, "application/json", "{\"status\":\"OK\",\"desc\":\"Recovering filesystem from github please wait!!!\"}");
}
});
server.onNotFound([](AsyncWebServerRequest *r) {
if(r->method() == HTTP_OPTIONS) { r->send(200); return; }
webServer.handleNotFound(r);