Add single-click auto-update functionality

This commit is contained in:
Djuri Baars 2024-09-11 20:27:40 +02:00
parent d00c216126
commit 023ff29131
6 changed files with 72 additions and 53 deletions

2
data

@ -1 +1 @@
Subproject commit 1c2d8dcdd0efd846f39b0f24977c982a96f16392 Subproject commit 6c40b54273b7f7c7d6c2624d3c2a066435f27756

View file

@ -4,6 +4,8 @@ TaskHandle_t taskOtaHandle = NULL;
bool isOtaUpdating = false; bool isOtaUpdating = false;
QueueHandle_t otaQueue; QueueHandle_t otaQueue;
void setupOTA() void setupOTA()
{ {
if (preferences.getBool("otaEnabled", DEFAULT_OTA_ENABLED)) if (preferences.getBool("otaEnabled", DEFAULT_OTA_ENABLED))
@ -65,7 +67,7 @@ void onOTAStart()
vTaskSuspend(workerTaskHandle); vTaskSuspend(workerTaskHandle);
vTaskSuspend(taskScreenRotateTaskHandle); vTaskSuspend(taskScreenRotateTaskHandle);
vTaskSuspend(ledTaskHandle); // vTaskSuspend(ledTaskHandle);
vTaskSuspend(buttonTaskHandle); vTaskSuspend(buttonTaskHandle);
// stopWebServer(); // stopWebServer();
@ -81,7 +83,18 @@ void handleOTATask(void *parameter)
{ {
if (xQueueReceive(otaQueue, &msg, 0) == pdTRUE) if (xQueueReceive(otaQueue, &msg, 0) == pdTRUE)
{ {
int result = downloadUpdateHandler(msg.updateType); if (msg.updateType == UPDATE_ALL) {
int resultWebUi = downloadUpdateHandler(UPDATE_WEBUI);
int resultFw = downloadUpdateHandler(UPDATE_FIRMWARE);
if (resultWebUi == 0 && resultFw == 0) {
ESP.restart();
} else {
queueLedEffect(LED_FLASH_ERROR);
vTaskDelay(pdMS_TO_TICKS(3000));
ESP.restart();
}
}
} }
ArduinoOTA.handle(); // Allow OTA updates to occur ArduinoOTA.handle(); // Allow OTA updates to occur
@ -89,7 +102,7 @@ void handleOTATask(void *parameter)
} }
} }
String getLatestRelease(const String &fileToDownload) ReleaseInfo getLatestRelease(const String &fileToDownload)
{ {
String releaseUrl = "https://api.github.com/repos/btclock/btclock_v3/releases/latest"; String releaseUrl = "https://api.github.com/repos/btclock/btclock_v3/releases/latest";
WiFiClientSecure client; WiFiClientSecure client;
@ -100,7 +113,7 @@ String getLatestRelease(const String &fileToDownload)
int httpCode = http.GET(); int httpCode = http.GET();
String downloadUrl = ""; ReleaseInfo info = {"", ""};
if (httpCode > 0) if (httpCode > 0)
{ {
@ -113,15 +126,26 @@ String getLatestRelease(const String &fileToDownload)
for (JsonObject asset : assets) for (JsonObject asset : assets)
{ {
if (asset["name"] == fileToDownload) String assetName = asset["name"].as<String>();
if (assetName == fileToDownload)
{
info.fileUrl = asset["browser_download_url"].as<String>();
}
else if (assetName == fileToDownload + ".sha256")
{
info.checksumUrl = asset["browser_download_url"].as<String>();
}
if (!info.fileUrl.isEmpty() && !info.checksumUrl.isEmpty())
{ {
downloadUrl = asset["browser_download_url"].as<String>();
break; break;
} }
} }
Serial.printf("Latest release URL: %s\r\n", downloadUrl.c_str()); Serial.printf("Latest release URL: %s\r\n", info.fileUrl.c_str());
Serial.printf("Checksum URL: %s\r\n", info.checksumUrl.c_str());
} }
return downloadUrl; http.end();
return info;
} }
int downloadUpdateHandler(char updateType) int downloadUpdateHandler(char updateType)
@ -131,7 +155,7 @@ int downloadUpdateHandler(char updateType)
HTTPClient http; HTTPClient http;
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
String latestRelease = ""; ReleaseInfo latestRelease;
switch (updateType) switch (updateType)
{ {
@ -143,25 +167,22 @@ int downloadUpdateHandler(char updateType)
case UPDATE_WEBUI: case UPDATE_WEBUI:
{ {
latestRelease = getLatestRelease("littlefs.bin"); latestRelease = getLatestRelease("littlefs.bin");
updateWebUi(latestRelease, U_SPIFFS); // updateWebUi(latestRelease.fileUrl, U_SPIFFS);
return 0; // return 0;
} }
break; break;
} }
if (latestRelease.isEmpty())
{
return 503;
}
// First, download the expected SHA256 // First, download the expected SHA256
String expectedSHA256 = downloadSHA256(getFirmwareFilename()); String expectedSHA256 = downloadSHA256(latestRelease.checksumUrl);
if (expectedSHA256.isEmpty()) if (expectedSHA256.isEmpty())
{ {
Serial.println("Failed to get SHA256 checksum. Aborting update."); Serial.println("Failed to get SHA256 checksum. Aborting update.");
return false; return false;
} }
http.begin(client, latestRelease); http.begin(client, latestRelease.fileUrl);
http.setUserAgent(USER_AGENT); http.setUserAgent(USER_AGENT);
int httpCode = http.GET(); int httpCode = http.GET();
@ -215,19 +236,21 @@ int downloadUpdateHandler(char updateType)
Update.onProgress(onOTAProgress); Update.onProgress(onOTAProgress);
int updateType = (updateType == UPDATE_WEBUI) ? U_SPIFFS : U_FLASH;
if (Update.begin(contentLength, updateType)) if (Update.begin(contentLength, updateType))
{ {
size_t written = Update.writeStream(*stream); onOTAStart();
size_t written = Update.write(firmware, contentLength);
if (written == contentLength) if (written == contentLength)
{ {
Serial.println("Written : " + String(written) + " successfully"); Serial.println("Written : " + String(written) + " successfully");
free(firmware);
} }
else else
{ {
Serial.println("Written only : " + String(written) + "/" + String(contentLength) + ". Retry?"); Serial.println("Written only : " + String(written) + "/" + String(contentLength) + ". Retry?");
free(firmware);
return 503;
} }
if (Update.end()) if (Update.end())
@ -236,26 +259,33 @@ int downloadUpdateHandler(char updateType)
if (Update.isFinished()) if (Update.isFinished())
{ {
Serial.println("Update successfully completed. Rebooting."); Serial.println("Update successfully completed. Rebooting.");
ESP.restart(); // ESP.restart();
} }
else else
{ {
Serial.println("Update not finished? Something went wrong!"); Serial.println("Update not finished? Something went wrong!");
free(firmware);
return 503;
} }
} }
else else
{ {
Serial.println("Error Occurred. Error #: " + String(Update.getError())); Serial.println("Error Occurred. Error #: " + String(Update.getError()));
free(firmware);
return 503;
} }
} }
else else
{ {
Serial.println("Not enough space to begin OTA"); Serial.println("Not enough space to begin OTA");
free(firmware);
return 503;
} }
} }
else else
{ {
Serial.println("Invalid content length"); Serial.println("Invalid content length");
return 503;
} }
} }
else else
@ -265,7 +295,7 @@ int downloadUpdateHandler(char updateType)
} }
http.end(); http.end();
return 200; return 0;
} }
void updateWebUi(String latestRelease, int command) void updateWebUi(String latestRelease, int command)
@ -380,9 +410,8 @@ bool getIsOTAUpdating()
return isOtaUpdating; return isOtaUpdating;
} }
String downloadSHA256(const String &filename) String downloadSHA256(const String &sha256Url)
{ {
String sha256Url = getLatestRelease(filename + ".sha256");
if (sha256Url.isEmpty()) if (sha256Url.isEmpty())
{ {
Serial.println("Failed to get SHA256 file URL"); Serial.println("Failed to get SHA256 file URL");

View file

@ -15,6 +15,11 @@ typedef struct {
extern QueueHandle_t otaQueue; extern QueueHandle_t otaQueue;
struct ReleaseInfo {
String fileUrl;
String checksumUrl;
};
void setupOTA(); void setupOTA();
void onOTAStart(); void onOTAStart();
void handleOTATask(void *parameter); void handleOTATask(void *parameter);
@ -23,9 +28,10 @@ void onOTAProgress(unsigned int progress, unsigned int total);
void onOTAError(ota_error_t error); void onOTAError(ota_error_t error);
void onOTAComplete(); void onOTAComplete();
int downloadUpdateHandler(char updateType); int downloadUpdateHandler(char updateType);
String getLatestRelease(const String& fileToDownload); ReleaseInfo getLatestRelease(const String& fileToDownload);
bool getIsOTAUpdating(); bool getIsOTAUpdating();
void updateWebUi(String latestRelease, int command); void updateWebUi(String latestRelease, int command);
String downloadSHA256(const String& filename); String downloadSHA256(const String& filename);

View file

@ -9,6 +9,7 @@
#include <GxEPD2.h> #include <GxEPD2.h>
#include <GxEPD2_BW.h> #include <GxEPD2_BW.h>
#include <mbedtls/md.h> #include <mbedtls/md.h>
#include <Update.h>
#include <mutex> #include <mutex>
#include <utils.hpp> #include <utils.hpp>
@ -69,9 +70,9 @@ const int usPerMinute = 60 * usPerSecond;
extern const char *github_root_ca; extern const char *github_root_ca;
const PROGMEM char UPDATE_FIRMWARE = 0; const PROGMEM char UPDATE_FIRMWARE = U_FLASH;
const PROGMEM char UPDATE_WEBUI = 1; const PROGMEM char UPDATE_WEBUI = U_SPIFFS;
const PROGMEM char UPDATE_ALL = 99;
struct ScreenMapping { struct ScreenMapping {
int value; int value;

View file

@ -86,8 +86,7 @@ void setupWebserver()
{ {
server.on("/upload/firmware", HTTP_POST, onFirmwareUpdate, asyncFirmwareUpdateHandler); server.on("/upload/firmware", HTTP_POST, onFirmwareUpdate, asyncFirmwareUpdateHandler);
server.on("/upload/webui", HTTP_POST, onFirmwareUpdate, asyncWebuiUpdateHandler); server.on("/upload/webui", HTTP_POST, onFirmwareUpdate, asyncWebuiUpdateHandler);
// server.on("/update/webui", HTTP_GET, onUpdateWebUi); server.on("/api/firmware/auto_update", HTTP_GET, onAutoUpdateFirmware);
// server.on("/update/firmware", HTTP_GET, onUpdateFirmware);
} }
server.on("/api/restart", HTTP_GET, onApiRestart); server.on("/api/restart", HTTP_GET, onApiRestart);
@ -142,29 +141,16 @@ void onFirmwareUpdate(AsyncWebServerRequest *request)
request->send(response); request->send(response);
} }
void onUpdateWebUi(AsyncWebServerRequest *request) void onAutoUpdateFirmware(AsyncWebServerRequest *request)
{ {
UpdateMessage msg = {UPDATE_WEBUI}; UpdateMessage msg = {UPDATE_ALL};
if (xQueueSend(otaQueue, &msg, 0) == pdTRUE) if (xQueueSend(otaQueue, &msg, 0) == pdTRUE)
{ {
request->send(200, "text/plain", "WebUI update triggered"); request->send(200, "application/json", "{\"msg\":\"Firmware update triggered\"}");
} }
else else
{ {
request->send(503, "text/plain", "Update already in progress"); request->send(503,"application/json", "{\"msg\":\"Update already in progress\"}");
}
}
void onUpdateFirmware(AsyncWebServerRequest *request)
{
UpdateMessage msg = {UPDATE_FIRMWARE};
if (xQueueSend(otaQueue, &msg, 0) == pdTRUE)
{
request->send(200, "text/plain", "Firmware update triggered");
}
else
{
request->send(503, "text/plain", "Update already in progress");
} }
} }

View file

@ -26,10 +26,6 @@ bool processEpdColorSettings(AsyncWebServerRequest *request);
void onApiStatus(AsyncWebServerRequest *request); void onApiStatus(AsyncWebServerRequest *request);
void onApiSystemStatus(AsyncWebServerRequest *request); void onApiSystemStatus(AsyncWebServerRequest *request);
void onApiSetWifiTxPower(AsyncWebServerRequest *request); void onApiSetWifiTxPower(AsyncWebServerRequest *request);
void onUpdateWebUi(AsyncWebServerRequest *request);
void onUpdateFirmware(AsyncWebServerRequest *request);
void onApiScreenNext(AsyncWebServerRequest *request); void onApiScreenNext(AsyncWebServerRequest *request);
void onApiScreenPrevious(AsyncWebServerRequest *request); void onApiScreenPrevious(AsyncWebServerRequest *request);
@ -58,6 +54,7 @@ void onFirmwareUpdate(AsyncWebServerRequest *request);
void asyncFirmwareUpdateHandler(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); void asyncFirmwareUpdateHandler(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
void asyncFileUpdateHandler(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final, int command); void asyncFileUpdateHandler(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final, int command);
void asyncWebuiUpdateHandler(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); void asyncWebuiUpdateHandler(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
void onAutoUpdateFirmware(AsyncWebServerRequest *request);
void onIndex(AsyncWebServerRequest *request); void onIndex(AsyncWebServerRequest *request);
void onNotFound(AsyncWebServerRequest *request); void onNotFound(AsyncWebServerRequest *request);