Skip to content

Commit

Permalink
Add firmware image updates from emegency mode
Browse files Browse the repository at this point in the history
Needs a reboot
  • Loading branch information
alufers committed Jun 11, 2024
1 parent b2df914 commit 8b290d5
Show file tree
Hide file tree
Showing 8 changed files with 213 additions and 10 deletions.
37 changes: 35 additions & 2 deletions src/core/main/app/LocalOTAEndpoints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
#include <filesystem>
#include <fstream>
#include <memory>
#include <semaphore>
#include "FirmwareImageUpdater.h"
#include "UpdateFilesystemServiceJob.h"
#include "UpdateFirmwareFromUploadServiceJob.h"

using namespace euph;

Expand Down Expand Up @@ -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,
Expand All @@ -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()) {
Expand Down Expand Up @@ -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<UpdateFirmwareFromUploadServiceJob>(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();
});
}
4 changes: 3 additions & 1 deletion src/core/main/app/ServiceTask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ void ServiceJob::reportProgress(std::shared_ptr<euph::Context> ctx,
}

ServiceTask::ServiceTask(std::weak_ptr<euph::Context> 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();
}

Expand Down
111 changes: 111 additions & 0 deletions src/core/main/app/UpdateFirmwareFromUploadServiceJob.cpp
Original file line number Diff line number Diff line change
@@ -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<euph::Context> ctx;
std::unique_ptr<FirmwareImageUpdater> 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<FirmwareImageUpdateContext*>(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<FirmwareImageUpdateContext*>(userData);

if (context->hasValidField) {
try {
if (!context->updater) {
context->updater = context->ctx.lock()->firmwareImageUpdaterFactory();
}

context->updater->writeData(reinterpret_cast<const uint8_t*>(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<euph::Context> 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();
}
26 changes: 24 additions & 2 deletions src/core/main/app/emergency.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@
<body>
<div class="container">
<h1>Emergency mode</h1>

<p>Activated due to: <strong id="emergencyModeReason"></strong></p>
<p>Technical information:</p>
<pre>
Expand All @@ -70,6 +69,15 @@ <h2>Repair options</h2>
<button type="button" id="upload-fs">Upload</button>
</div>
</details>

<details>
<summary>Upload new firmware image</summary>
<div>
Select .bin file:
<input type="file" id="firmware-file" accept=".bin" />
<button type="button" id="upload-firmware">Upload</button>
</div>
</details>
</div>
<script>
(() => {
Expand All @@ -89,7 +97,6 @@ <h2>Repair options</h2>
alert("Please select a file");
return;
}

const formData = new FormData();
formData.append("fs", file);

Expand All @@ -98,6 +105,21 @@ <h2>Repair options</h2>
body: formData,
}).then((res) => res.json());
});

$("#upload-firmware").addEventListener("click", () => {
const file = $("#firmware-file").files[0];
if (!file) {
alert("Please select a file");
return;
}
const formData = new FormData();
formData.append("firmware", file);

fetch("/api/emergency-mode/firmware-image-update", {
method: "POST",
body: formData,
}).then((res) => res.json());
});
})();
</script>
</body>
Expand Down
1 change: 0 additions & 1 deletion src/core/main/app/include/FirmwareImageUpdater.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
27 changes: 27 additions & 0 deletions src/core/main/app/include/UpdateFirmwareFromUploadServiceJob.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#pragma once

#include <semaphore>
#include <string>
#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<euph::Context> ctx) override;

std::binary_semaphore& done;

private:
mg_connection* conn;
};

} // namespace euph
16 changes: 12 additions & 4 deletions src/targets/esp32/main/app/ESP32FirmwareImageUpdater.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand All @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 8b290d5

Please sign in to comment.