Improved firmware upgrade

This commit is contained in:
Gunnar Skjold 2023-04-15 07:44:33 +02:00
parent 9c8788225d
commit be116d5b35
14 changed files with 169 additions and 101 deletions

View File

@ -11,6 +11,7 @@
#define CONFIG_SYSTEM_START 8
#define CONFIG_METER_START 32
#define CONFIG_UPGRADE_INFO_START 216
#define CONFIG_UI_START 248
#define CONFIG_GPIO_START 266
#define CONFIG_ENTSOE_START 290
@ -213,6 +214,13 @@ struct TempSensorConfig {
bool common;
};
struct UpgradeInformation {
char fromVersion[8];
char toVersion[8];
int16_t exitCode;
int16_t errorCode;
}; // 20
class AmsConfiguration {
public:
bool hasConfig();
@ -297,6 +305,10 @@ public:
bool isSensorAddressEqual(uint8_t a[8], uint8_t b[8]);
bool getUpgradeInformation(UpgradeInformation&);
bool setUpgradeInformation(int16_t exitCode, int16_t errorCode, const char* currentVersion, const char* nextVersion);
void clearUpgradeInformation(UpgradeInformation&);
void clear();
protected:

View File

@ -7,6 +7,6 @@
String toHex(uint8_t* in);
String toHex(uint8_t* in, uint16_t size);
void fromHex(uint8_t *out, String in, uint16_t size);
void stripNonAscii(uint8_t* in, uint16_t size, bool extended = false);
bool stripNonAscii(uint8_t* in, uint16_t size, bool extended = false);
#endif

View File

@ -679,6 +679,45 @@ void AmsConfiguration::clearUiConfig(UiConfig& config) {
config.showTemperaturePlot = 2;
}
bool AmsConfiguration::setUpgradeInformation(int16_t exitCode, int16_t errorCode, const char* currentVersion, const char* nextVersion) {
UpgradeInformation upinfo;
upinfo.exitCode = exitCode;
upinfo.errorCode = errorCode;
strcpy(upinfo.fromVersion, currentVersion);
strcpy(upinfo.toVersion, nextVersion);
stripNonAscii((uint8_t*) upinfo.fromVersion, 8);
stripNonAscii((uint8_t*) upinfo.toVersion, 8);
EEPROM.begin(EEPROM_SIZE);
EEPROM.put(CONFIG_UPGRADE_INFO_START, upinfo);
bool ret = EEPROM.commit();
EEPROM.end();
return ret;
}
bool AmsConfiguration::getUpgradeInformation(UpgradeInformation& upinfo) {
if(hasConfig()) {
EEPROM.begin(EEPROM_SIZE);
EEPROM.get(CONFIG_UPGRADE_INFO_START, upinfo);
EEPROM.end();
if(stripNonAscii((uint8_t*) upinfo.fromVersion, 8) || stripNonAscii((uint8_t*) upinfo.toVersion, 8)) {
clearUpgradeInformation(upinfo);
}
return true;
} else {
clearUpgradeInformation(upinfo);
return false;
}
}
void AmsConfiguration::clearUpgradeInformation(UpgradeInformation& upinfo) {
upinfo.exitCode = -1;
upinfo.errorCode = 0;
memset(upinfo.fromVersion, 0, 8);
memset(upinfo.toVersion, 0, 8);
}
void AmsConfiguration::clear() {
EEPROM.begin(EEPROM_SIZE);
@ -734,6 +773,10 @@ void AmsConfiguration::clear() {
clearUiConfig(ui);
EEPROM.put(CONFIG_UI_START, ui);
UpgradeInformation upinfo;
clearUpgradeInformation(upinfo);
EEPROM.put(CONFIG_UPGRADE_INFO_START, upinfo);
EEPROM.put(EEPROM_CONFIG_ADDRESS, EEPROM_CLEARED_INDICATOR);
EEPROM.commit();
EEPROM.end();

View File

@ -22,7 +22,8 @@ void fromHex(uint8_t *out, String in, uint16_t size) {
}
}
void stripNonAscii(uint8_t* in, uint16_t size, bool extended) {
bool stripNonAscii(uint8_t* in, uint16_t size, bool extended) {
bool ret = false;
for(uint16_t i = 0; i < size; i++) {
if(in[i] == 0) { // Clear the rest with null-terminator
memset(in+i, 0, size-i);
@ -30,9 +31,12 @@ void stripNonAscii(uint8_t* in, uint16_t size, bool extended) {
}
if(extended && (in[i] < 32 || in[i] == 127 || in[i] == 129 || in[i] == 141 || in[i] == 143 || in[i] == 144 || in[i] == 157)) {
memset(in+i, ' ', 1);
ret = true;
} else if(in[i] < 32 || in[i] > 126) {
memset(in+i, ' ', 1);
ret = true;
}
}
memset(in+size-1, 0, 1); // Make sure the last character is null-terminator
return ret;
}

File diff suppressed because one or more lines are too long

View File

@ -26,6 +26,13 @@ let sysinfo = {
upgrading: false,
ui: {},
security: 0,
boot_reason: 0,
upgrade: {
x: -1,
e: 0,
f: null,
t: null
},
trying: null
};
export const sysinfoStore = writable(sysinfo);

View File

@ -25,7 +25,7 @@
s.upgrading = true;
return s;
});
upgrade(nextVersion);
upgrade(nextVersion.tag_name);
}
}
}

View File

@ -155,6 +155,11 @@
<div class="my-2">
Installed version: {sysinfo.version}
</div>
{#if sysinfo.upgrade.t && sysinfo.upgrade.t != sysinfo.version}
<div class="my-2">
<div class="bd-yellow">Previous upgrade attempt ({sysinfo.upgrade.t}) does not match current version ({sysinfo.version}) [{sysinfo.upgrade.x}/{sysinfo.upgrade.e}]</div>
</div>
{/if}
{#if nextVersion}
<div class="my-2 flex">
Latest version:

View File

@ -2,8 +2,8 @@ export function upgradeWarningText(board) {
return 'WARNING: ' + board + ' must be connected to an external power supply during firmware upgrade. Failure to do so may cause power-down during upload resulting in non-functioning unit.'
}
export async function upgrade() {
const response = await fetch('/upgrade', {
export async function upgrade(expected_version) {
const response = await fetch('/upgrade?expected_version='+expected_version, {
method: 'POST'
});
await response.json();

View File

@ -17,18 +17,18 @@ export default defineConfig({
plugins: [svelte()],
server: {
proxy: {
"/data.json": "http://192.168.233.235",
"/energyprice.json": "http://192.168.233.235",
"/dayplot.json": "http://192.168.233.235",
"/monthplot.json": "http://192.168.233.235",
"/temperature.json": "http://192.168.233.235",
"/sysinfo.json": "http://192.168.233.235",
"/configuration.json": "http://192.168.233.235",
"/tariff.json": "http://192.168.233.235",
"/save": "http://192.168.233.235",
"/reboot": "http://192.168.233.235",
"/configfile": "http://192.168.233.235",
"/upgrade": "http://192.168.233.235"
"/data.json": "http://192.168.233.244",
"/energyprice.json": "http://192.168.233.244",
"/dayplot.json": "http://192.168.233.244",
"/monthplot.json": "http://192.168.233.244",
"/temperature.json": "http://192.168.233.244",
"/sysinfo.json": "http://192.168.233.244",
"/configuration.json": "http://192.168.233.244",
"/tariff.json": "http://192.168.233.244",
"/save": "http://192.168.233.244",
"/reboot": "http://192.168.233.244",
"/configfile": "http://192.168.233.244",
"/upgrade": "http://192.168.233.244"
}
}
})

View File

@ -97,6 +97,7 @@ private:
void handleSave();
void reboot();
void upgrade();
void upgradeFromUrl(String url, String nextVersion);
void firmwareHtml();
void firmwarePost();
void firmwareUpload();

View File

@ -37,5 +37,11 @@
"s": %d
},
"security": %d,
"boot_reason": %d
"boot_reason": %d,
"upgrade": {
"x": %d,
"e": %d,
"f": "%s",
"t": "%s"
}
}

View File

@ -254,6 +254,9 @@ void AmsWebServer::sysinfoJson() {
UiConfig ui;
config->getUiConfig(ui);
UpgradeInformation upinfo;
config->getUpgradeInformation(upinfo);
String meterModel = meterState->getMeterModel();
if(!meterModel.isEmpty())
meterModel.replace("\\", "\\\\");
@ -314,10 +317,14 @@ void AmsWebServer::sysinfoJson() {
ui.showTemperaturePlot,
webConfig.security,
#if defined(ESP32)
rtc_get_reset_reason(0)
rtc_get_reset_reason(0),
#else
ESP.getResetInfoPtr()->reason
ESP.getResetInfoPtr()->reason,
#endif
upinfo.exitCode,
upinfo.errorCode,
upinfo.fromVersion,
upinfo.toVersion
);
stripNonAscii((uint8_t*) buf, size+1);
@ -1542,44 +1549,61 @@ void AmsWebServer::upgrade() {
if(server.hasArg(F("version"))) {
url += "/" + server.arg(F("version"));
}
WiFiClient client;
#if defined(ESP8266)
String chipType = F("esp8266");
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
String chipType = F("esp32s2");
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
String chipType = F("esp32c3");
#elif defined(ESP32)
#if defined(CONFIG_FREERTOS_UNICORE)
String chipType = F("esp32solo");
#else
String chipType = F("esp32");
#endif
#endif
#if defined(ESP8266)
ESPhttpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
t_httpUpdate_return ret = ESPhttpUpdate.update(client, url, VERSION);
#elif defined(ESP32)
HTTPUpdate httpUpdate;
httpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
HTTPUpdateResult ret = httpUpdate.update(client, url, String(VERSION) + "-" + chipType);
#endif
switch(ret) {
case HTTP_UPDATE_FAILED:
debugger->printf(PSTR("Update failed"));
break;
case HTTP_UPDATE_NO_UPDATES:
debugger->printf(PSTR("No Update"));
break;
case HTTP_UPDATE_OK:
debugger->printf(PSTR("Update OK"));
break;
}
upgradeFromUrl(url, server.arg(F("expected_version")));
}
}
void AmsWebServer::upgradeFromUrl(String url, String nextVersion) {
config->setUpgradeInformation(0xFF, 0xFF, VERSION, nextVersion.c_str());
WiFiClient client;
#if defined(ESP8266)
String chipType = F("esp8266");
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
String chipType = F("esp32s2");
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
String chipType = F("esp32c3");
#elif defined(ESP32)
#if defined(CONFIG_FREERTOS_UNICORE)
String chipType = F("esp32solo");
#else
String chipType = F("esp32");
#endif
#endif
#if defined(ESP8266)
ESP8266HTTPUpdate httpUpdate = ESP8266HTTPUpdate(60000);
String currentVersion = VERSION;
#elif defined(ESP32)
HTTPUpdate httpUpdate = HTTPUpdate(60000);
String currentVersion = String(VERSION) + "-" + chipType;
#endif
httpUpdate.rebootOnUpdate(false);
httpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
HTTPUpdateResult ret = httpUpdate.update(client, url, currentVersion);
int lastError = httpUpdate.getLastError();
config->setUpgradeInformation(ret, ret == HTTP_UPDATE_OK ? 0 : lastError, VERSION, nextVersion.c_str());
switch(ret) {
case HTTP_UPDATE_FAILED:
debugger->printf(PSTR("Update failed\n"));
break;
case HTTP_UPDATE_NO_UPDATES:
debugger->printf(PSTR("No Update\n"));
break;
case HTTP_UPDATE_OK:
debugger->printf(PSTR("Update OK\n"));
debugger->flush();
#if defined(ESP8266)
ESP.reset();
#elif defined(ESP32)
ESP.restart();
#endif
break;
}
}
void AmsWebServer::firmwareHtml() {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(PSTR("Serving /firmware.html over http..."));
@ -1607,41 +1631,7 @@ void AmsWebServer::firmwarePost() {
if(!url.isEmpty() && (url.startsWith(F("http://")) || url.startsWith(F("https://")))) {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(PSTR("Custom firmware URL was provided"));
WiFiClient client;
#if defined(ESP8266)
String chipType = F("esp8266");
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
String chipType = F("esp32s2");
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
String chipType = F("esp32c3");
#elif defined(ESP32)
#if defined(CONFIG_FREERTOS_UNICORE)
String chipType = F("esp32solo");
#else
String chipType = F("esp32");
#endif
#endif
#if defined(ESP8266)
ESPhttpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
t_httpUpdate_return ret = ESPhttpUpdate.update(client, url, VERSION);
#elif defined(ESP32)
HTTPUpdate httpUpdate;
httpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
HTTPUpdateResult ret = httpUpdate.update(client, url, String(VERSION) + "-" + chipType);
#endif
switch(ret) {
case HTTP_UPDATE_FAILED:
debugger->printf(PSTR("Update failed"));
break;
case HTTP_UPDATE_NO_UPDATES:
debugger->printf(PSTR("No Update"));
break;
case HTTP_UPDATE_OK:
debugger->printf(PSTR("Update OK"));
break;
}
upgradeFromUrl(url, "");
server.send(200, MIME_PLAIN, "OK");
return;
}

View File

@ -30,7 +30,7 @@ ADC_MODE(ADC_VCC);
#if defined(ESP32)
#include <esp_task_wdt.h>
#endif
#define WDT_TIMEOUT 60
#define WDT_TIMEOUT 90
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3)
#include <driver/uart.h>