From 9dcd79736bc87f5fd17bbbe1439bfeeaac22bdc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=B3r=C3=A1nt=20Pint=C3=A9r?= Date: Sun, 1 Dec 2024 16:25:58 +0100 Subject: [PATCH] Use ESP-IDF's own HTTPS OTA --- main/kernel/Kernel.hpp | 99 +++++++++++++++++++++++++++++------------- 1 file changed, 69 insertions(+), 30 deletions(-) diff --git a/main/kernel/Kernel.hpp b/main/kernel/Kernel.hpp index 499f8748..28be256c 100644 --- a/main/kernel/Kernel.hpp +++ b/main/kernel/Kernel.hpp @@ -4,12 +4,11 @@ #include #include +#include +#include +#include #include -#include -#include -#include - #include #include @@ -216,7 +215,6 @@ class Kernel { return ""; } - Log.info("Starting update..."); auto fUpdate = fs.open(UPDATE_FILE, FILE_READ); JsonDocument doc; auto error = deserializeJson(doc, fUpdate); @@ -231,39 +229,80 @@ class Kernel { return "Command contains empty url"; } + Log.info("Updating from version %s via URL %s", + FARMHUB_VERSION, url.c_str()); + Log.debug("Waiting for network..."); WiFiConnection connection(wifi, WiFiConnection::Mode::NoAwait); if (!connection.await(15s)) { return "Network not ready, aborting update"; } - httpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); - Log.info("Updating from version %s via URL %s", - FARMHUB_VERSION, url.c_str()); - - HTTPUpdateResult result = HTTP_UPDATE_NO_UPDATES; - // Run in separate task to allocate enough stack - SemaphoreHandle_t completionSemaphore = xSemaphoreCreateBinary(); - Task::run("http-update", 16 * 1024, [&](Task& task) { - // Allocate on heap to avoid wasting stack - std::unique_ptr client = std::make_unique(); - // Allow insecure connections for testing - client->setInsecure(); - result = httpUpdate.update(*client, url, FARMHUB_VERSION); - xSemaphoreGive(completionSemaphore); - }); - xSemaphoreTake(completionSemaphore, portMAX_DELAY); - - switch (result) { - case HTTP_UPDATE_FAILED: - return httpUpdate.getLastErrorString() + " (" + String(httpUpdate.getLastError()) + ")"; - case HTTP_UPDATE_NO_UPDATES: - return "No updates available"; - case HTTP_UPDATE_OK: - return "Update OK"; + Log.info("WiFi connected: %s", WiFi.localIP().toString().c_str()); + + // TODO Disable power save mode for WiFi + + esp_http_client_config_t httpConfig = { + .url = url.c_str(), + .timeout_ms = duration_cast(2min).count(), + .max_redirection_count = 5, + .event_handler = httpEventHandler, + .buffer_size = 8192, + .buffer_size_tx = 8192, + .user_data = this, + // TODO Do we need this? + .skip_cert_common_name_check = true, + .crt_bundle_attach = esp_crt_bundle_attach, + .keep_alive_enable = true, + }; + esp_https_ota_config_t otaConfig = { + .http_config = &httpConfig, + // TODO Make it work without partial downloads + // With this we seem to be able to install a new firmware, even if very slowly; + // without it we get the error upon download completion: 'no more processes'. + .partial_http_download = true, + }; + esp_err_t ret = esp_https_ota(&otaConfig); + if (ret == ESP_OK) { + Log.info("Update succeeded, rebooting in 5 seconds..."); + Task::delay(5s); + esp_restart(); + } else { + Log.error("Update failed (err = %d), continuing with regular boot", + ret); + return "Firmware upgrade failed: " + String(ret); + } + } // namespace farmhub::kernel + + static esp_err_t httpEventHandler(esp_http_client_event_t* event) { + switch (event->event_id) { + case HTTP_EVENT_ERROR: + Log.error("HTTP error, status code: %d", + esp_http_client_get_status_code(event->client)); + break; + case HTTP_EVENT_ON_CONNECTED: + Log.debug("HTTP connected"); + break; + case HTTP_EVENT_HEADERS_SENT: + Log.trace("HTTP headers sent"); + break; + case HTTP_EVENT_ON_HEADER: + Log.trace("HTTP header: %s: %s", event->header_key, event->header_value); + break; + case HTTP_EVENT_ON_DATA: + Log.debug("HTTP data: %d bytes", event->data_len); + break; + case HTTP_EVENT_ON_FINISH: + Log.debug("HTTP finished"); + break; + case HTTP_EVENT_DISCONNECTED: + Log.debug("HTTP disconnected"); + break; default: - return "Unknown response"; + Log.warn("Unknown HTTP event %d", event->event_id); + break; } + return ESP_OK; } TDeviceConfiguration& deviceConfig;