From 8b290d535f3421e58815ef9f58744d4ef354cd49 Mon Sep 17 00:00:00 2001 From: alufers Date: Tue, 11 Jun 2024 13:22:47 +0200 Subject: [PATCH] Add firmware image updates from emegency mode Needs a reboot --- src/core/main/app/LocalOTAEndpoints.cpp | 37 +++++- src/core/main/app/ServiceTask.cpp | 4 +- .../UpdateFirmwareFromUploadServiceJob.cpp | 111 ++++++++++++++++++ src/core/main/app/emergency.html | 26 +++- .../main/app/include/FirmwareImageUpdater.h | 1 - .../UpdateFirmwareFromUploadServiceJob.h | 27 +++++ .../main/app/ESP32FirmwareImageUpdater.cpp | 16 ++- .../app/include/ESP32FirmwareImageUpdater.h | 1 + 8 files changed, 213 insertions(+), 10 deletions(-) create mode 100644 src/core/main/app/UpdateFirmwareFromUploadServiceJob.cpp create mode 100644 src/core/main/app/include/UpdateFirmwareFromUploadServiceJob.h diff --git a/src/core/main/app/LocalOTAEndpoints.cpp b/src/core/main/app/LocalOTAEndpoints.cpp index efdfcaae..7872d7c1 100644 --- a/src/core/main/app/LocalOTAEndpoints.cpp +++ b/src/core/main/app/LocalOTAEndpoints.cpp @@ -2,7 +2,10 @@ #include #include #include +#include +#include "FirmwareImageUpdater.h" #include "UpdateFilesystemServiceJob.h" +#include "UpdateFirmwareFromUploadServiceJob.h" using namespace euph; @@ -80,7 +83,6 @@ void euph::registerLocalOTAEndpoints(bell::BellHTTPServer& server, return server.makeEmptyResponse(); } - const struct mg_request_info* req_info = mg_get_request_info(conn); std::string tmpDir = ctx->rootPath + "/tmp"; FilesystemUpdateContext context{.hasValidFile = false, @@ -99,7 +101,6 @@ void euph::registerLocalOTAEndpoints(bell::BellHTTPServer& server, struct mg_form_data_handler fdh = {filesystemUpdateFieldFound, filesystemUpdateFieldGet, NULL, &context}; - (void)req_info; int ret = mg_handle_form_request(conn, &fdh); if (context.file.is_open()) { @@ -128,6 +129,38 @@ void euph::registerLocalOTAEndpoints(bell::BellHTTPServer& server, "application/json\r\nConnection: close\r\n\r\n"); mg_printf(conn, "{}"); + return server.makeEmptyResponse(); + }); + + server.registerPost( + "/api/emergency-mode/firmware-image-update", + [&server, ctxPtr](struct mg_connection* conn) { + auto ctx = ctxPtr.lock(); + if (!ctx) { + EUPH_LOG(error, TAG, "Could not lock context."); + return server.makeEmptyResponse(); + } + + if (!ctx->firmwareImageUpdaterFactory) { + EUPH_LOG(error, TAG, "Firmware image updater factory not set."); + return server.makeJsonResponse( + "{\"status\": \"error\", \"message\":\"Firmware image update " + "is " + "not available on this platform.\"}", + 404); + } + + std::binary_semaphore update_done(0); + + bool job_submitted = ctx->serviceTask->submitJob( + std::make_unique(conn, + update_done)); + EUPH_LOG(info, TAG, "Firmware image update job submitted."); + update_done + .acquire(); // Wait for the job to finish (the response will be sent by it) + + EUPH_LOG(info, TAG, "Firmware image update job finished."); + return server.makeEmptyResponse(); }); } diff --git a/src/core/main/app/ServiceTask.cpp b/src/core/main/app/ServiceTask.cpp index 7b1b3786..4af726a9 100644 --- a/src/core/main/app/ServiceTask.cpp +++ b/src/core/main/app/ServiceTask.cpp @@ -12,7 +12,9 @@ void ServiceJob::reportProgress(std::shared_ptr ctx, } ServiceTask::ServiceTask(std::weak_ptr ctx) - : bell::Task("ServiceTask", 1024 * 16, 1, 0), ctx(ctx), jobSemaphore(0) { + : bell::Task("ServiceTask", 1024 * 16, 1, 0, false), + ctx(ctx), + jobSemaphore(0) { startTask(); } diff --git a/src/core/main/app/UpdateFirmwareFromUploadServiceJob.cpp b/src/core/main/app/UpdateFirmwareFromUploadServiceJob.cpp new file mode 100644 index 00000000..935ca338 --- /dev/null +++ b/src/core/main/app/UpdateFirmwareFromUploadServiceJob.cpp @@ -0,0 +1,111 @@ +#include "UpdateFirmwareFromUploadServiceJob.h" + +using namespace euph; + +const char* TAG = "UpdateFirmwareFromUploadServiceJob"; + +UpdateFirmwareFromUploadServiceJob::UpdateFirmwareFromUploadServiceJob( + mg_connection* conn, std::binary_semaphore& done) + : done(done), conn(conn) {} + +std::string UpdateFirmwareFromUploadServiceJob::jobTypeName() { + return TAG; +} + +struct FirmwareImageUpdateContext { + std::weak_ptr ctx; + std::unique_ptr updater; + bool hasValidField; +}; + +/** + * @brief Function to filter the incoming form + * fields for the firmware image update endpoint. + * @see mg_form_data_handler + */ +static int firmwareImageUpdateFieldFound(const char* key, const char* filename, + char* path, size_t pathlen, + void* userData) { + std::string_view keyView(key); + FirmwareImageUpdateContext* context = + static_cast(userData); + context->hasValidField = false; + if (keyView == "firmware") { + context->hasValidField = true; + return MG_FORM_FIELD_STORAGE_GET; + } + + return MG_FORM_FIELD_STORAGE_SKIP; +} + +/** + * @brief Function to handle the incoming form fields for the firmware image update + * @see mg_form_data_handler + */ +static int firmwareImageUpdateFieldGet(const char* key, const char* value, + size_t valuelen, void* userData) { + std::string_view keyView(key); + FirmwareImageUpdateContext* context = + static_cast(userData); + + if (context->hasValidField) { + try { + if (!context->updater) { + context->updater = context->ctx.lock()->firmwareImageUpdaterFactory(); + } + + context->updater->writeData(reinterpret_cast(value), + valuelen); + } catch (const FirmwareImageUpdaterException& e) { + EUPH_LOG(error, TAG, "Error writing firmware image data: %s", e.what()); + return MG_FORM_FIELD_HANDLE_ABORT; + } + return MG_FORM_FIELD_HANDLE_GET; + } + + return MG_FORM_FIELD_HANDLE_NEXT; +} + +void UpdateFirmwareFromUploadServiceJob::run( + std::shared_ptr ctx) { + FirmwareImageUpdateContext context{.ctx = ctx, .updater = nullptr}; + + struct mg_form_data_handler fdh = {firmwareImageUpdateFieldFound, + firmwareImageUpdateFieldGet, NULL, + &context}; + int ret = mg_handle_form_request(conn, &fdh); + if (ret <= 0 || !context.hasValidField || !context.updater) { + if (context.updater) { + context.updater->abort(); + } + mg_printf(conn, + "HTTP/1.1 400 OK\r\nContent-Type: " + "application/json\r\nConnection: close\r\n\r\n"); + mg_printf(conn, "{\"status\": \"error\"}"); + done.release(); + return; + } + + try { + context.updater->finalize(); + } catch (const FirmwareImageUpdaterException& e) { + EUPH_LOG(error, TAG, "Error finalizing firmware image update: %s", + e.what()); + mg_printf(conn, + "HTTP/1.1 500 OK\r\nContent-Type: " + "application/json\r\nConnection: close\r\n\r\n"); + mg_printf(conn, "{\"status\": \"error\"}"); + done.release(); + return; + } + + EUPH_LOG(info, TAG, + "Firmware image update written and finalized successfully."); + // TODO: reboot + + mg_printf(conn, + "HTTP/1.1 200 OK\r\nContent-Type: " + "application/json\r\nConnection: close\r\n\r\n"); + mg_printf(conn, "{}"); + done.release(); +} diff --git a/src/core/main/app/emergency.html b/src/core/main/app/emergency.html index d9476732..7fa4739b 100644 --- a/src/core/main/app/emergency.html +++ b/src/core/main/app/emergency.html @@ -52,7 +52,6 @@

Emergency mode

-

Activated due to:

Technical information:

@@ -70,6 +69,15 @@ 

Repair options

+ +
+ Upload new firmware image +
+ Select .bin file: + + +
+
diff --git a/src/core/main/app/include/FirmwareImageUpdater.h b/src/core/main/app/include/FirmwareImageUpdater.h index af404ff7..505dc1fe 100644 --- a/src/core/main/app/include/FirmwareImageUpdater.h +++ b/src/core/main/app/include/FirmwareImageUpdater.h @@ -51,7 +51,6 @@ class FirmwareImageUpdater { /** * @brief Abort the OTA transaction. * Do not switch to the updated firmware (the partition can be clobbered if this is called after writeData()). - * @throw FirmwareImageUpdaterException if the OTA transaction cannot be aborted. */ virtual void abort() = 0; diff --git a/src/core/main/app/include/UpdateFirmwareFromUploadServiceJob.h b/src/core/main/app/include/UpdateFirmwareFromUploadServiceJob.h new file mode 100644 index 00000000..3153a56f --- /dev/null +++ b/src/core/main/app/include/UpdateFirmwareFromUploadServiceJob.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include "ServiceTask.h" +#include "civetweb.h" + +namespace euph { + +/** + * @brief Updates the firmware image from an uploaded file using a http POST request. + */ +class UpdateFirmwareFromUploadServiceJob : public euph::ServiceJob { + public: + UpdateFirmwareFromUploadServiceJob(mg_connection* conn, + std::binary_semaphore& done); + + virtual std::string jobTypeName() override; + virtual void run(std::shared_ptr ctx) override; + + std::binary_semaphore& done; + + private: + mg_connection* conn; +}; + +} // namespace euph diff --git a/src/targets/esp32/main/app/ESP32FirmwareImageUpdater.cpp b/src/targets/esp32/main/app/ESP32FirmwareImageUpdater.cpp index 2f29d5c8..3ae96724 100644 --- a/src/targets/esp32/main/app/ESP32FirmwareImageUpdater.cpp +++ b/src/targets/esp32/main/app/ESP32FirmwareImageUpdater.cpp @@ -2,19 +2,20 @@ #include "EuphLogger.h" #include "esp_err.h" #include "fmt/core.h" + using namespace euph; ESP32FirmwareImageUpdater::ESP32FirmwareImageUpdater() : FirmwareImageUpdater() { - const esp_partition_t* partition = esp_ota_get_next_update_partition(NULL); - if (partition == NULL) { + partitionToWrite = esp_ota_get_next_update_partition(NULL); + if (partitionToWrite == NULL) { throw FirmwareImageUpdaterException("No OTA partition found for update"); } EUPH_LOG(info, "ESP32FirmwareImageUpdater", "Starting ota with partition %s", - partition->label); + partitionToWrite->label); esp_err_t err = - esp_ota_begin(partition, OTA_WITH_SEQUENTIAL_WRITES, &otaHandle); + esp_ota_begin(partitionToWrite, OTA_WITH_SEQUENTIAL_WRITES, &otaHandle); if (err != ESP_OK) { throw FirmwareImageUpdaterException( fmt::format("Failed to start OTA: {}", esp_err_to_name(err)).c_str()); @@ -41,6 +42,13 @@ void ESP32FirmwareImageUpdater::finalize() { fmt::format("Failed to finalize OTA: {}", esp_err_to_name(err)) .c_str()); } + + err = esp_ota_set_boot_partition(partitionToWrite); + if (err != ESP_OK) { + throw FirmwareImageUpdaterException( + fmt::format("Failed to set boot partition: {}", esp_err_to_name(err)) + .c_str()); + } } void ESP32FirmwareImageUpdater::abort() { diff --git a/src/targets/esp32/main/app/include/ESP32FirmwareImageUpdater.h b/src/targets/esp32/main/app/include/ESP32FirmwareImageUpdater.h index 72e86e82..86ca18ee 100644 --- a/src/targets/esp32/main/app/include/ESP32FirmwareImageUpdater.h +++ b/src/targets/esp32/main/app/include/ESP32FirmwareImageUpdater.h @@ -23,5 +23,6 @@ class ESP32FirmwareImageUpdater : public FirmwareImageUpdater { private: size_t bytesWritten = 0; esp_ota_handle_t otaHandle; + const esp_partition_t* partitionToWrite; }; } // namespace euph