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

@ -160,10 +160,6 @@
<input id="cbSsdpBroadcast" name="ssdpBroadcast" type="checkbox" data-bind="general.ssdpBroadcast" style="display:inline-block;" />
<label for="cbSsdpBroadcast" style="display:inline-block;cursor:pointer;">Broadcast uPnP over SSDP</label>
</div>
<div class="field-group" style="margin-top:-12px;">
<input id="cbCheckForUpdate" type="checkbox" data-bind="general.checkForUpdate" style="display:inline-block;" />
<label for="cbCheckForUpdate" style="display:inline-block;cursor:pointer;">Auto Check for Updates</label>
</div>
<div class="button-container">
<button id="btnSaveGeneral" type="button" onclick="general.setGeneral();">
Save
@ -251,9 +247,6 @@
</div>
</div>
<div class="button-container">
<button id="btnUpdateGithub" type="button" onclick="firmware.updateGithub();">
<i class="icss-github-o" style="font-size:1.7em;margin-top:-14px;margin-bottom:-10px;margin-right:7px;"></i><span>Github Update</span>
</button>
<button id="btnUpdateFirmware" type="button" onclick="firmware.updateFirmware();">
Update Firmware
</button>

View file

@ -579,9 +579,6 @@ async function initSockets() {
case 'memStatus':
firmware.procMemoryStatus(msg);
break;
case 'updateProgress':
firmware.procUpdateProgress(msg);
break;
case 'fwStatus':
firmware.procFwStatus(msg);
break;
@ -4569,268 +4566,11 @@ class Firmware {
}
procFwStatus(rel) {
console.log(rel);
let div = document.getElementById('divFirmwareUpdate');
if (rel.available && rel.status === 0 && rel.checkForUpdate !== false) {
div.style.color = 'black';
div.innerHTML = `<span>Firmware ${rel.fwVersion.name} Installed<span><span style="color:red"> ${rel.latest.name} Available</span>`;
}
else {
switch (rel.status) {
case 2: // Awaiting update.
div.style.color = 'red';
div.innerHTML = `Preparing firmware update`;
break;
case 3: // Updating -- this will be set by the update progress.
break;
case 4: // Complete
if (rel.error !== 0) {
div.style.color = 'red';
let e = errors.find(x => x.code === rel.error) || { code: rel.error, desc: 'Unspecified error' };
let inst = document.getElementById('divGitInstall');
if (inst) {
inst.remove();
ui.errorMessage(e.desc);
}
div.innerHTML = e.desc;
}
else {
div.innerHTML = `Firmware update complete`;
// Throw up a wait message this will be cleared on the reload.
ui.waitMessage(document.getElementById('divContainer'));
}
break;
case 5:
div.style.color = 'red';
div.innerHTML = `Cancelling firmware update`;
break;
case 6:
div.style.color = 'red';
div.innerHTML = `Firmware update cancelled`;
break;
default:
div.style.color = 'black';
div.innerHTML = `Firmware ${rel.fwVersion.name} Installed`;
break;
}
}
div.style.color = 'black';
div.innerHTML = `Firmware ${rel.fwVersion.name} Installed`;
div.style.display = '';
}
procUpdateProgress(prog) {
let pct = Math.round((prog.loaded / prog.total) * 100);
let file = prog.part === 100 ? 'Application' : 'Firmware';
let div = document.getElementById('divFirmwareUpdate');
if (div) {
div.style.color = 'red';
div.innerHTML = `Updating ${file} to ${prog.ver} ${pct}%`;
}
general.reloadApp = true;
let git = document.getElementById('divGitInstall');
if (git) {
// Update the status on the client that started the install.
if (pct >= 100 && prog.part === 100) git.remove();
else {
if (prog.part === 100) {
document.getElementById('btnCancelUpdate').style.display = 'none';
}
let p = prog.part === 100 ? document.getElementById('progApplicationDownload') : document.getElementById('progFirmwareDownload');
if (p) {
p.style.setProperty('--progress', `${pct}%`);
p.setAttribute('data-progress', `${pct}%`);
}
}
}
}
async installGitRelease(div) {
if (!this.isMobile()) {
console.log('Starting backup');
try {
await firmware.backup();
console.log('Backup Complete');
}
catch (err) {
ui.serviceError(el, err);
return;
}
}
let obj = ui.fromElement(div);
console.log(obj);
putJSONSync(`/downloadFirmware?ver=${obj.version}`, {}, (err, ver) => {
if (err) ui.serviceError(err);
else {
general.reloadApp = true;
// Change the display and allow the percentage to be shown when the socket emits the progress.
let html = `<div>Installing ${ver.name}</div><div style="font-size:.7em;margin-top:4px;">Please wait as the files are downloaded and installed. Once the application update process starts you may no longer cancel the update as this will corrupt the downloaded files.</div>`;
html += `<div class="progress-bar" id="progFirmwareDownload" style="--progress:0%;margin-top:10px;text-align:center;"></div>`;
html += `<label for="progFirmwareDownload" style="font-size:10pt;">Firmware Install Progress</label>`;
html += `<div class="progress-bar" id="progApplicationDownload" style="--progress:0%;margin-top:10px;text-align:center;"></div>`;
html += `<label for="progFirmwareDownload" style="font-size:10pt;">Application Install Progress</label>`;
html += `<hr></hr><div class="button-container" style="text-align:center;">`;
html += `<button id="btnCancelUpdate" type="button" style="width:40%;padding-left:20px;padding-right:20px;display:inline-block;" onclick="firmware.cancelInstallGit(document.getElementById('divGitInstall'));">Cancel</button>`;
html += `</div>`;
div.innerHTML = html;
}
});
}
cancelInstallGit(div) {
putJSONSync(`/cancelFirmware`, {}, (err, ver) => {
if (err) ui.serviceError(err);
else console.log(ver);
div.remove();
});
}
updateGithub() {
getJSONSync('/getReleases', (err, rel) => {
if (err) ui.serviceError(err);
else {
console.log(rel);
let div = document.createElement('div');
let chip = document.getElementById('divContainer').getAttribute('data-chipmodel');
div.setAttribute('id', 'divGitInstall')
div.setAttribute('class', 'inst-overlay');
div.style.width = '100%';
div.style.alignContent = 'center';
// Sort the releases so that the pre-releases are at the bottom.
rel.releases.sort((a, b) => a.preRelease === b.preRelease && b.draft === a.draft ? 0 : a.preRelease ? 1 : -1);
let html = `<div>Select a version from the repository to install using the dropdown below. Then press the update button to install that version.</div><div style="font-size:.7em;margin-top:4px;">Select Main to install the most recent alpha version from the repository.</div>`;
html += `<div id="divPrereleaseWarning" style="display:none;width:100%;color:red;text-align:center;font-weight:bold;"><span style="margin-top:7px;width:100%;padding:3px;display:inline-block;border-radius:5px;background:white;">WARNING<span><hr style="margin:0px" /><div style="font-size:.7em;padding-left:1em;padding-right:1em;color:black;font-weight:normal;">You have selected a pre-released beta version that has not been fully tested or published for general use.</div></div>`;
html += `<div class="field-group" style="text-align:center;">`;
html += `<select id="selVersion" data-bind="version" style="width:70%;font-size:2em;color:white;text-align-last:center;" onchange="firmware.gitReleaseSelected(document.getElementById('divGitInstall'));">`
for (let i = 0; i < rel.releases.length; i++) {
if (rel.releases[i].hwVersions.length === 0 || rel.releases[i].hwVersions.indexOf(chip) >= 0)
html += `<option style="text-align:left;font-size:.5em;color:black;" data-prerelease="${rel.releases[i].preRelease}" value="${rel.releases[i].version.name}">${rel.releases[i].name}${rel.releases[i].preRelease ? ' - Pre' : ''}</option>`
}
html += `</select><label for="selVersion">Select a version</label></div>`;
html += `<div class="button-container" id="divReleaseNotes" style="text-align:center;margin-top:-20px;display:none;"><button type="button" onclick="firmware.showReleaseNotes(document.getElementById('selVersion').value);" style="display:inline-block;width:auto;padding-left:20px;padding-right:20px;">Release Notes</button></div>`;
if (this.isMobile()) {
html += `<div style="width:100%;color:red;text-align:center;font-weight:bold;"><span style="margin-top:7px;width:100%;background:yellow;padding:3px;display:inline-block;border-radius:5px;background:white;">WARNING<span></div>`;
html += '<hr/><div style="font-size:14px;margin-bottom:10px;">This browser does not support automatic backups. It is highly recommended that you back up your configuration using the backup button before proceeding.</div>';
}
else {
html += '<hr/><div style="font-size:14px;margin-bottom:10px;">A backup file for your configuration will be downloaded to your browser. If the firmware update process fails please restore this file using the restore button after going through the onboarding process.</div>'
}
html += `<hr></hr><div class="button-container" style="text-align:center;">`;
if (this.isMobile()) {
html += `<button id="btnBackupCfg" type="button" style="display:inline-block;width:calc(80% + 7px);padding-left:20px;padding-right:20px;margin-right:4px;" onclick="firmware.backup();">Backup</button>`;
}
html += `<button id="btnUpdate" type="button" style="width:40%;padding-left:20px;padding-right:20px;display:inline-block;margin-right:7px;" onclick="firmware.installGitRelease(document.getElementById('divGitInstall'));">Update</button>`;
html += `<button id="btnClose" type="button" style="width:40%;padding-left:20px;padding-right:20px;display:inline-block;" onclick="document.getElementById('divGitInstall').remove();">Cancel</button>`;
html += `</div></div>`;
div.innerHTML = html;
document.getElementById('divContainer').appendChild(div);
this.gitReleaseSelected(div);
}
});
}
gitReleaseSelected(div) {
let obj = ui.fromElement(div);
let divNotes = div.querySelector('#divReleaseNotes');
let divPre = div.querySelector('#divPrereleaseWarning');
let sel = div.querySelector('#selVersion');
if (sel && sel.selectedIndex !== -1 && makeBool(sel.options[sel.selectedIndex].getAttribute('data-prerelease'))) {
if (divPre) divPre.style.display = '';
}
else
if (divPre) divPre.style.display = 'none';
if (divNotes) {
if (!obj.version || obj.version === 'main' || obj.version === '') divNotes.style.display = 'none';
else divNotes.style.display = '';
}
}
async getReleaseInfo(tag) {
let overlay = ui.waitMessage(document.getElementById('divContainer'));
try {
let ret = {};
ret.resp = await fetch(`https://api.github.com/repos/cjkas/espsomfy-rts/releases/tags/${tag}`);
if (ret.resp.ok)
ret.info = await ret.resp.json();
return ret;
}
catch (err) {
return { err: err };
}
finally { overlay.remove(); }
}
async showReleaseNotes(tagName) {
console.log(tagName);
let r = await this.getReleaseInfo(tagName);
console.log(r);
let fnToItem = (txt, tag) => { }
if (r.resp.ok) {
// Convert this to html.
let lines = r.info.body.split('\r\n');
let ctx = { html: '', llvl: 0, lines: r.info.body.split('\r\n'), ndx: 0 };
ctx.toHead = function (txt) {
let num = txt.indexOf(' ');
return `<h${num}>${txt.substring(num).trim()}</h${num}>`;
};
ctx.toUL = function () {
let txt = this.lines[this.ndx++];
let tok = this.token(txt);
this.html += `<ul>${this.toLI(tok.txt)}`;
while (this.ndx < this.lines.length) {
txt = this.lines[this.ndx];
let t = this.token(txt);
if (t.ch === '*') {
if (t.indent !== tok.indent) this.toUL();
else {
this.html += this.toLI(t.txt);
this.ndx++;
}
}
else break;
}
this.html += '</ul>';
};
ctx.toLI = function (txt) { return `<li>${txt.trim()}</li>`; }
ctx.token = function (txt) {
let tok = { ch: '', indent: 0, txt:'' }
for (let i = 0; i < txt.length; i++) {
if (txt[i] === ' ') tok.indent++;
else {
tok.ch = txt[i];
let tmp = txt.substring(tok.indent);
tok.txt = tmp.substring(tmp.indexOf(' '));
break;
}
}
return tok;
};
ctx.next = function () {
if (this.ndx >= this.lines.length) return false;
let tok = this.token(this.lines[this.ndx]);
switch (tok.ch) {
case '#':
this.html += this.toHead(this.lines[this.ndx]);
this.ndx++;
break;
case '*':
this.toUL();
break;
case '':
this.ndx++;
this.html += `<br/><div>${tok.txt}</div>`;
break;
default:
this.ndx++;
break;
}
return true;
};
while (ctx.next());
console.log(ctx);
ui.infoMessage(ctx.html);
}
}
updateFirmware() {
let div = this.createFileUploader('/updateFirmware');
let inst = div.querySelector('div[id=divInstText]');

View file

@ -239,7 +239,6 @@ bool ConfigSettings::toJSON(JsonObject &obj) {
obj["hostname"] = this->hostname;
obj["connType"] = static_cast<uint8_t>(this->connType);
obj["chipModel"] = this->chipModel;
obj["checkForUpdate"] = this->checkForUpdate;
return true;
}
bool ConfigSettings::requiresAuth() { return this->Security.type != security_types::None; }
@ -247,7 +246,6 @@ bool ConfigSettings::fromJSON(JsonObject &obj) {
if(!obj["ssdpBroadcast"].isNull()) this->ssdpBroadcast = obj["ssdpBroadcast"];
if(!obj["hostname"].isNull()) this->parseValueString(obj, "hostname", this->hostname, sizeof(this->hostname));
if(!obj["connType"].isNull()) this->connType = static_cast<conn_types_t>(obj["connType"].as<uint8_t>());
if(!obj["checkForUpdate"].isNull()) this->checkForUpdate = obj["checkForUpdate"];
return true;
}
void ConfigSettings::print() {

View file

@ -183,6 +183,8 @@ class ConfigSettings: BaseSettings {
appver_t fwVersion;
appver_t appVersion;
bool ssdpBroadcast = true;
// Nothing reads it since the GitHub update went (docs/adr/0007-no-github-update.md).
// NVS and the backup file still carry it so their formats stay the same.
bool checkForUpdate = true;
uint8_t status;
IPSettings IP;

View file

@ -1,520 +0,0 @@
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <Update.h>
#include <HTTPClient.h>
#include "TaskWdt.h"
#include "esp_log.h"
#include "ConfigSettings.h"
#include "GitOTA.h"
#include "Utils.h"
#include "outputs/SocketEmitter.h"
#include "Somfy.h"
#include "entryPoints/Web.h"
#include "WResp.h"
#include "ESPNetwork.h"
extern ConfigSettings settings;
extern SocketEmitter sockEmit;
extern SomfyShadeController somfy;
extern rebootDelay_t rebootDelay;
extern Web webServer;
extern ESPNetwork net;
static const char *TAG = "OTA";
#define MAX_BUFF_SIZE 4096
void GitRelease::setReleaseProperty(const char *key, const char *val) {
if(strcmp(key, "id") == 0) this->id = atol(val);
else if(strcmp(key, "draft") == 0) this->draft = toBoolean(val, false);
else if(strcmp(key, "prerelease") == 0) this->preRelease = toBoolean(val, false);
else if(strcmp(key, "name") == 0) strlcpy(this->name, val, sizeof(this->name));
else if(strcmp(key, "tag_name") == 0) {
this->version.parse(val);
}
else if(strcmp(key, "published_at") == 0) {
//Serial.printf("Key:[%s] Value:[%s]\n", key, val);
this->releaseDate = Timestamp::parseUTCTime(val);
}
}
void GitRelease::setAssetProperty(const char *key, const char *val) {
if(strcmp(key, "name") == 0) {
//Serial.println(val);
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");
}
else if(strstr(val, "ino.esp32s3.bin")) {
if(strlen(this->hwVersions)) strcat(this->hwVersions, ",");
strcat(this->hwVersions, "s3");
}
else if(strstr(val, "ino.esp32s2.bin")) {
if(strlen(this->hwVersions)) strcat(this->hwVersions, ",");
strcat(this->hwVersions, "s2");
}
else if(strstr(val, "ino.esp32c3.bin")) {
if(strlen(this->hwVersions)) strcat(this->hwVersions, ",");
strcat(this->hwVersions, "c3");
}
else if(strstr(val, "ino.esp32c2.bin")) {
if(strlen(this->hwVersions)) strcat(this->hwVersions, ",");
strcat(this->hwVersions, "c2");
}
else if(strstr(val, "ino.esp32c6.bin")) {
if(strlen(this->hwVersions)) strcat(this->hwVersions, ",");
strcat(this->hwVersions, "c6");
}
else if(strstr(val, "ino.esp32h2.bin")) {
if(strlen(this->hwVersions)) strcat(this->hwVersions, ",");
strcat(this->hwVersions, "h2");
}
}
}
#define ERR_CLIENT_OFFSET -50
int16_t GitRepo::getReleases(uint8_t num) {
WiFiClientSecure sclient;
sclient.setInsecure();
sclient.setHandshakeTimeout(3);
uint8_t ndx = 0;
uint8_t count = min((uint8_t)GIT_MAX_RELEASES, num);
char url[128];
memset(this->releases, 0x00, sizeof(GitRelease) * GIT_MAX_RELEASES);
sprintf(url, "https://api.github.com/repos/cjkas/espsomfy-rts/releases?per_page=%d&page=1", count);
GitRelease *main = &this->releases[GIT_MAX_RELEASES];
main->releaseDate = Timestamp::now();
main->id = 1;
main->main = true;
strcpy(main->version.name, "main");
strcpy(main->name, "Main");
strcpy(main->hwVersions, "32,s3");
HTTPClient https;
https.setReuse(false);
if(https.begin(sclient, url)) {
resetTaskWdt();
int httpCode = https.GET();
ESP_LOGD(TAG, "[HTTPS] GET... code: %d", httpCode);
if(httpCode > 0) {
int len = https.getSize();
ESP_LOGD(TAG, "[HTTPS] GET... code: %d - %d", httpCode, len);
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
WiFiClient *stream = https.getStreamPtr();
uint8_t buff[128] = {0};
char jsonElem[32] = "";
char jsonValue[128] = "";
int arrTok = 0;
int objTok = 0;
bool inQuote = false;
bool inElem = false;
bool inValue = false;
bool awaitValue = false;
bool inAss = false;
while(https.connected() && (len > 0 || len == -1) && ndx < count) {
size_t size = stream->available();
if(size) {
resetTaskWdt();
int c = stream->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size));
//Serial.write(buff, c);
if(len > 0) len -= c;
// Now we should have some data.
for(uint8_t i = 0; i < c; i++) {
// Read the buffer a byte at a time until we have a key value pair.
char ch = static_cast<char>(buff[i]);
if(ch == '[') {
arrTok++;
if(arrTok == 2 && strcmp(jsonElem, "assets") == 0) {
inElem = inValue = awaitValue = false;
inAss = true;
//Serial.printf("%s: %d\n", jsonElem, arrTok);
}
else if(arrTok < 2) inAss = false;
}
else if(ch == ']') {
arrTok--;
if(arrTok < 2) inAss = false;
}
else if(ch == '{') {
objTok++;
if(objTok != 1 && !inAss) inElem = inValue = awaitValue = false;
}
else if(ch == '}') {
objTok--;
if(objTok == 0) ndx++;
}
else if(objTok == 1 || inAss) {
// We only want data from the root object.
//if(inAss) Serial.print(ch);
if(ch == '\"') {
inQuote = !inQuote;
if(inElem) {
inElem = false;
awaitValue = true;
}
else if(inValue) {
inValue = false;
inElem = false;
awaitValue = false;
if(inAss)
this->releases[ndx].setAssetProperty(jsonElem, jsonValue);
else
this->releases[ndx].setReleaseProperty(jsonElem, jsonValue);
memset(jsonElem, 0x00, sizeof(jsonElem));
memset(jsonValue, 0x00, sizeof(jsonValue));
}
else if(awaitValue) inValue = true;
else {
inElem = true;
awaitValue = false;
}
}
else if(awaitValue) {
if(ch != ' ' && ch != ':') {
strncat(jsonValue, &ch, 1);
awaitValue = false;
inValue = true;
}
}
else if((!inQuote && ch == ',') || ch == '\r' || ch == '\n') {
inElem = inValue = awaitValue = false;
if(strlen(jsonElem) > 0) {
if(inAss)
this->releases[ndx].setAssetProperty(jsonElem, jsonValue);
else
this->releases[ndx].setReleaseProperty(jsonElem, jsonValue);
}
memset(jsonElem, 0x00, sizeof(jsonElem));
memset(jsonValue, 0x00, sizeof(jsonValue));
}
else {
if(inElem) {
if(strlen(jsonElem) < sizeof(jsonElem) - 1) strncat(jsonElem, &ch, 1);
}
else if(inValue) {
if(strlen(jsonValue) < sizeof(jsonValue) - 1) strncat(jsonValue, &ch, 1);
}
}
}
}
delay(1);
}
//else break;
}
}
else {
https.end();
sclient.stop();
return httpCode;
}
}
https.end();
sclient.stop();
}
settings.printAvailHeap();
return 0;
}
#define UPDATE_ERR_OFFSET 20
#define ERR_DOWNLOAD_HTTP -40
#define ERR_DOWNLOAD_BUFFER -41
#define ERR_DOWNLOAD_CONNECTION -42
void GitUpdater::loop() {
if(!net.connected()) return;
if(this->status == GIT_STATUS_READY) {
if(settings.checkForUpdate &&
(millis() > net.connectTime + 60000) && // Wait a minute before checking after connection.
(this->lastCheck + 86400000 < millis() || this->lastCheck == 0) && !rebootDelay.reboot) { // 1 day
this->checkForUpdate();
}
}
else if(this->status == GIT_AWAITING_UPDATE) {
ESP_LOGI(TAG, "Starting update process.........");
this->status = GIT_UPDATING;
this->beginUpdate(this->targetRelease);
this->status = GIT_STATUS_READY;
this->emitUpdateCheck();
}
else if(this->status == GIT_UPDATE_CANCELLING) {
ESP_LOGI(TAG, "Cancelling update process..........");
if(!this->lockFS) {
this->status = GIT_UPDATE_CANCELLED;
this->cancelled = true;
this->emitUpdateCheck();
}
}
}
void GitUpdater::checkForUpdate() {
if(this->status != 0) return; // If we are already checking.
ESP_LOGI(TAG, "Check github for updates...");
this->status = GIT_STATUS_CHECK;
settings.printAvailHeap();
this->lastCheck = millis();
if(this->checkInternet() == 0) {
GitRepo repo;
this->updateAvailable = false;
this->error = repo.getReleases(2);
if(this->error == 0) { // Get 2 releases so we can filter our pre-releases
this->setCurrentRelease(repo);
}
else {
this->emitUpdateCheck();
}
}
this->status = GIT_STATUS_READY;
}
void GitUpdater::setCurrentRelease(GitRepo &repo) {
this->updateAvailable = false;
for(uint8_t i = 0; i < 2; i++) {
if(repo.releases[i].draft || repo.releases[i].preRelease || repo.releases[i].id == 0) continue;
// Compare the versions.
this->latest.copy(repo.releases[i].version);
if(repo.releases[i].version.compare(settings.fwVersion) > 0) {
// We have a new release.
this->updateAvailable = true;
}
break;
}
this->emitUpdateCheck();
}
void GitUpdater::emitUpdateCheck(uint8_t num) {
JsonSockEvent *json = sockEmit.beginEmit("fwStatus");
json->beginObject();
json->addElem("available", this->updateAvailable);
json->addElem("status", this->status);
json->addElem("error", (int32_t)this->error);
json->addElem("cancelled", this->cancelled);
json->addElem("checkForUpdate", settings.checkForUpdate);
json->addElem("inetAvailable", this->inetAvailable);
json->beginObject("fwVersion");
settings.fwVersion.toJSON(json);
json->endObject();
json->beginObject("appVersion");
settings.appVersion.toJSON(json);
json->endObject();
json->beginObject("latest");
this->latest.toJSON(json);
json->endObject();
json->endObject();
sockEmit.endEmit(num);
}
int GitUpdater::checkInternet() {
int err = 500;
uint32_t t = millis();
WiFiClientSecure sclient;
sclient.setInsecure();
sclient.setHandshakeTimeout(3);
resetTaskWdt();
HTTPClient https;
https.setReuse(false);
if(https.begin(sclient, "https://github.com/cjkas/ESPSomfy-RTS")) {
https.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
https.setTimeout(3000);
resetTaskWdt();
int httpCode = https.sendRequest("HEAD");
resetTaskWdt();
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY || httpCode == HTTP_CODE_FOUND) {
err = 0;
ESP_LOGI(TAG, "Internet is Available: %ldms", millis() - t);
this->inetAvailable = true;
}
else {
err = httpCode;
ESP_LOGE(TAG, "Internet is Unavailable: %d: %ldms", err, millis() - t);
this->inetAvailable = false;
}
https.end();
sclient.stop();
}
resetTaskWdt();
return err;
}
void GitUpdater::emitDownloadProgress(size_t total, size_t loaded, const char *evt) { this->emitDownloadProgress(255, total, loaded, evt); }
void GitUpdater::emitDownloadProgress(uint8_t num, size_t total, size_t loaded, const char *evt) {
JsonSockEvent *json = sockEmit.beginEmit(evt);
json->beginObject();
json->addElem("ver", this->targetRelease);
json->addElem("part", (int32_t)this->partition);
json->addElem("file", this->currentFile);
json->addElem("total", (uint32_t)total);
json->addElem("loaded", (uint32_t)loaded);
json->addElem("error", (uint32_t)this->error);
json->endObject();
sockEmit.endEmit(num);
/*
char buf[420];
snprintf(buf, sizeof(buf), "{\"ver\":\"%s\",\"part\":%d,\"file\":\"%s\",\"total\":%d,\"loaded\":%d, \"error\":%d}", this->targetRelease, this->partition, this->currentFile, total, loaded, this->error);
if(num >= 255) sockEmit.sendToClients(evt, buf);
else sockEmit.sendToClient(num, evt, buf);
*/
sockEmit.loop();
webServer.loop();
}
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/");
else sprintf(this->baseUrl, "https://github.com/cjkas/ESPSomfy-RTS/releases/download/%s/", version);
strcpy(this->targetRelease, version);
this->emitUpdateCheck();
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();
releaseAssetName(this->currentFile, sizeof(this->currentFile), "littlefs", settings.chipModel);
this->partition = U_SPIFFS;
this->lockFS = true;
this->error = this->downloadFile();
this->lockFS = false;
if(this->error == 0) {
settings.fwVersion.parse(version);
delay(100);
ESP_LOGI(TAG, "Committing Configuration...");
somfy.commit();
}
rebootDelay.reboot = true;
rebootDelay.rebootTime = millis() + 500;
}
this->status = GIT_UPDATE_COMPLETE;
this->emitUpdateCheck();
return true;
}
bool GitUpdater::recoverFilesystem() {
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;
this->error = this->downloadFile();
this->lockFS = false;
if(this->error == 0) {
delay(100);
ESP_LOGI(TAG, "Committing Configuration...");
somfy.commit();
}
this->status = GIT_UPDATE_COMPLETE;
rebootDelay.reboot = true;
rebootDelay.rebootTime = millis() + 500;
return true;
}
bool GitUpdater::endUpdate() { return true; }
int8_t GitUpdater::downloadFile() {
ESP_LOGI(TAG, "Begin update %s", this->currentFile);
WiFiClientSecure sclient;
sclient.setInsecure();
HTTPClient https;
char url[196];
sprintf(url, "%s%s", this->baseUrl, this->currentFile);
ESP_LOGD(TAG, "%s", url);
resetTaskWdt();
if(https.begin(sclient, url)) {
https.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
ESP_LOGD(TAG, "[HTTPS] GET...");
int httpCode = https.GET();
if(httpCode > 0) {
size_t len = https.getSize();
size_t total = 0;
uint8_t pct = 0;
ESP_LOGD(TAG, "[HTTPS] GET... code: %d - %d", httpCode, len);
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY || httpCode == HTTP_CODE_FOUND) {
WiFiClient *stream = https.getStreamPtr();
if(!Update.begin(len, this->partition)) {
ESP_LOGE(TAG, "Update Error detected!!!!!");
https.end();
return -(Update.getError() + UPDATE_ERR_OFFSET);
}
uint8_t *buff = (uint8_t *)malloc(MAX_BUFF_SIZE);
if(buff) {
this->emitDownloadProgress(len, total);
int timeouts = 0;
while(https.connected() && (len > 0 || len == -1) && total < len) {
size_t size = stream->available();
resetTaskWdt();
if(size) {
timeouts = 0;
if(this->cancelled && !this->lockFS) {
Update.abort();
free(buff);
https.end();
return -(Update.getError() + UPDATE_ERR_OFFSET);
}
int c = stream->readBytes(buff, ((size > MAX_BUFF_SIZE) ? MAX_BUFF_SIZE : size));
total += c;
//Serial.println(total);
if (Update.write(buff, c) != c) {
ESP_LOGE(TAG, "Upload of %s aborted invalid size %d", url, c);
free(buff);
https.end();
sclient.stop();
return -(Update.getError() + UPDATE_ERR_OFFSET);
}
// Calculate the percentage.
uint8_t p = (uint8_t)floor(((float)total / (float)len) * 100.0f);
if(p != pct) {
pct = p;
ESP_LOGD(TAG, "LEN:%d TOTAL:%d %d%%", len, total, pct);
this->emitDownloadProgress(len, total);
}
delay(1);
if(total >= len) {
if(!Update.end(true)) {
ESP_LOGE(TAG, "Error downloading update...");
}
else {
ESP_LOGI(TAG, "Update.end Called...");
}
https.end();
sclient.stop();
}
}
else {
timeouts++;
if(timeouts >= 500) {
Update.abort();
https.end();
free(buff);
ESP_LOGE(TAG, "Stream timeout!!!");
return -43;
}
sockEmit.loop();
webServer.loop();
delay(100);
}
}
free(buff);
if(len > total) {
Update.abort();
somfy.commit();
ESP_LOGE(TAG, "Error downloading file!!!");
return -42;
}
else
ESP_LOGI(TAG, "Update %s complete", this->currentFile);
}
else {
// TODO: memory allocation error.
ESP_LOGE(TAG, "Unable to allocate memory for update!!!");
}
}
else {
ESP_LOGE(TAG, "Invalid HTTP Code... %d", httpCode);
return httpCode;
}
}
else {
ESP_LOGE(TAG, "Invalid HTTP Code: %d", httpCode);
}
https.end();
sclient.stop();
ESP_LOGI(TAG, "End update %s", this->currentFile);
}
resetTaskWdt();
return 0;
}

View file

@ -1,67 +0,0 @@
#ifndef GITOTA_H
#define GITOTA_H
#include <Arduino.h>
#include <ArduinoJson.h>
#include <time.h>
#include "ConfigSettings.h"
#include "WResp.h"
#define GIT_MAX_RELEASES 5
#define GIT_STATUS_READY 0
#define GIT_STATUS_CHECK 1
#define GIT_AWAITING_UPDATE 2
#define GIT_UPDATING 3
#define GIT_UPDATE_COMPLETE 4
#define GIT_UPDATE_CANCELLING 5
#define GIT_UPDATE_CANCELLED 6
class GitRelease {
public:
uint64_t id = 0;
bool draft = false;
bool preRelease = false;
bool main = false;
bool hasFS = false;
char hwVersions[128] = "";
time_t releaseDate;
char name[32] = "";
appver_t version;
void setReleaseProperty(const char *key, const char *val);
void setAssetProperty(const char *key, const char *val);
};
class GitRepo {
public:
int16_t getReleases(uint8_t num = GIT_MAX_RELEASES);
GitRelease releases[GIT_MAX_RELEASES + 1];
};
class GitUpdater {
public:
bool lockFS = false;
bool canCancel = true;
uint8_t status = 0;
uint32_t lastCheck = 0;
bool updateAvailable = false;
bool inetAvailable = false;
appver_t latest;
bool cancelled = false;
int16_t error = 0;
char targetRelease[32];
char currentFile[64] = "";
char baseUrl[128] = "";
int partition = 0;
void checkForUpdate();
bool beginUpdate(const char *release);
bool endUpdate();
int8_t downloadFile();
void setCurrentRelease(GitRepo &repo);
void loop();
bool recoverFilesystem();
int checkInternet();
void emitUpdateCheck(uint8_t num=255);
void emitDownloadProgress(size_t total, size_t loaded, const char *evt = "updateProgress");
void emitDownloadProgress(uint8_t num, size_t total, size_t loaded, const char *evt = "updateProgress");
};
#endif

View file

@ -4,14 +4,12 @@
#include "ConfigSettings.h"
#include "Somfy.h"
#include "ConfigFile.h"
#include "GitOTA.h"
#include "ModelEditor.h"
static const char *TAG = "Somfy";
extern SomfyShadeController somfy;
extern ConfigSettings settings;
extern GitUpdater git;
void SomfyShadeController::end() { this->transceiver.disableReceive(); }
SomfyShadeController::SomfyShadeController() {
@ -77,7 +75,6 @@ bool SomfyShadeController::begin() {
return true;
}
void SomfyShadeController::commit() {
if(git.lockFS) return;
resetTaskWdt(); // Make sure we don't reset inadvertently.
ShadeConfigFile file;
file.begin();
@ -87,7 +84,6 @@ void SomfyShadeController::commit() {
this->lastCommit = millis();
}
void SomfyShadeController::writeBackup() {
if(git.lockFS) return;
resetTaskWdt(); // Make sure we don't reset inadvertently.
this->backupData = "";
this->backupData.reserve(16384);

View file

@ -13,7 +13,6 @@
#include "ShadeRequests.h"
#include "SomfyShadeOps.h"
#include "ModelSnapshot.h"
#include "GitOTA.h"
#include "esp_core_dump.h"
#include "esp_system.h"
#include "esp_app_desc.h"
@ -42,7 +41,6 @@ ESPNetwork net;
rebootDelay_t rebootDelay;
SomfyShadeController somfy;
MqttConnection mqtt;
GitUpdater git;
SomfyShadeOps shadeOps;
ShadeRequests shadeRequests(shadeOps);
LoopQueue loopQueue;
@ -133,7 +131,6 @@ void setup() {
delay(1000);
net.setup();
somfy.begin();
//git.checkForUpdate();
// The core starts the watchdog with a 5s timeout before setup(), so
// esp_task_wdt_init() would fail and keep that.
const esp_task_wdt_config_t wdt_config = { .timeout_ms = 15000, .idle_core_mask = 1, .trigger_panic = true };
@ -173,11 +170,6 @@ void loop() {
logIfSlow(TAG, "Loop changes", timing.lap(), LOOP_SLOW_MS);
esp_task_wdt_reset();
if(net.connected() || net.softAPOpened) {
if(!rebootDelay.reboot && net.connected() && !net.softAPOpened) {
git.loop();
logIfSlow(TAG, "Loop git", timing.lap(), LOOP_SLOW_MS);
esp_task_wdt_reset();
}
webServer.loop();
logIfSlow(TAG, "Loop web server", timing.lap(), LOOP_SLOW_MS);
esp_task_wdt_reset();

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);

View file

@ -6,7 +6,6 @@
#include "ConfigSettings.h"
#include "Somfy.h"
#include "ESPNetwork.h"
#include "GitOTA.h"
#include "Timing.h"
#include "Utils.h"
@ -16,7 +15,6 @@ extern ConfigSettings settings;
extern ESPNetwork net;
extern SomfyShadeController somfy;
extern SocketEmitter sockEmit;
extern GitUpdater git;
AsyncWebServer wsServer(8080);
AsyncWebSocket ws("/");
@ -147,6 +145,20 @@ uint8_t SocketEmitter::activeClients(uint8_t room) {
if(room < SOCK_MAX_ROOMS) return this->rooms[room].activeClients();
return 0;
}
// The installed versions, in the fwStatus event the web UI and Home Assistant
// read them from.
static void emitFirmwareVersion(uint8_t num) {
JsonSockEvent *json = sockEmit.beginEmit("fwStatus");
json->beginObject();
json->beginObject("fwVersion");
settings.fwVersion.toJSON(json);
json->endObject();
json->beginObject("appVersion");
settings.appVersion.toJSON(json);
json->endObject();
json->endObject();
sockEmit.endEmit(num);
}
void SocketEmitter::initClients() {
for(uint8_t i = 0; i < sizeof(this->newClients); i++) {
uint8_t slot = this->newClients[i];
@ -162,8 +174,8 @@ void SocketEmitter::initClients() {
somfy.emitState(slot);
logElapsed(TAG, "Socket init somfy state", step.lap());
if(!ws.hasClient(asyncId)) { this->newClients[i] = 255; continue; }
git.emitUpdateCheck(slot);
logElapsed(TAG, "Socket init update check", step.lap());
emitFirmwareVersion(slot);
logElapsed(TAG, "Socket init firmware version", step.lap());
if(!ws.hasClient(asyncId)) { this->newClients[i] = 255; continue; }
net.emitSockets(slot);
logElapsed(TAG, "Socket init network", step.lap());