From 5078f40e3c86c2d1fb97e408e6a7785ddea443b8 Mon Sep 17 00:00:00 2001 From: Jan Umbach Date: Mon, 14 Aug 2023 22:05:27 +0200 Subject: [PATCH] added rollback and fixed memory leaks --- include/jannex/ota.h | 1 + include/jannex/otaUpdate.h | 2 +- src/api/console.c | 204 ++++++++++++++++++++++++++++++++----- src/httpsRequest.c | 5 + src/jannex.c | 43 ++++---- src/ota.c | 152 +++++++++++++++++++++++++-- src/otaUpdate.c | 6 +- src/storage.c | 7 +- 8 files changed, 364 insertions(+), 56 deletions(-) diff --git a/include/jannex/ota.h b/include/jannex/ota.h index 84a7eb7..221092f 100644 --- a/include/jannex/ota.h +++ b/include/jannex/ota.h @@ -1,5 +1,6 @@ #pragma once void checkForUpdatesAndInstall(void *parameter); +void checkIfOTAwasSuccessful(void *parameter); extern bool updateAvailable; diff --git a/include/jannex/otaUpdate.h b/include/jannex/otaUpdate.h index dda1ea9..03a7946 100644 --- a/include/jannex/otaUpdate.h +++ b/include/jannex/otaUpdate.h @@ -1,3 +1,3 @@ #pragma once -void startOTAupdate(); \ No newline at end of file +void startOTAupdate(bool forceUpdate); diff --git a/src/api/console.c b/src/api/console.c index f32d9de..ad5929d 100644 --- a/src/api/console.c +++ b/src/api/console.c @@ -11,22 +11,93 @@ #include "esp_websocket_client.h" #include "esp_event.h" +#include +#include +#include +#include + +#include "mbedtls/base64.h" + +#define CALC_BASE64_ENCODED_LENGTH(input_length) ((4 * ((input_length + 2) / 3)) + 1) + char cacheMessage[CONFIG_HTTP_SERVER_DEBUG_CACHE_LENGTH]; +char cacheMessageClone[CONFIG_HTTP_SERVER_DEBUG_CACHE_LENGTH]; int cacheMessageIndex = 0; +SemaphoreHandle_t cacheMutex; + const char *TAG = "Console"; +const char *LOG_TAG = "[LOG]"; esp_websocket_client_handle_t client = NULL; +int skippedMessages = 0; + void sendOlderMessages() { if (esp_websocket_client_is_connected(client)) { - if (cacheMessageIndex > 0) + // printf("cacheMessageIndex:%i\n", cacheMessageIndex); + + int newDataLength = 0; + + // mutex lock + if (xSemaphoreTake(cacheMutex, (TickType_t)10) == pdTRUE) { - esp_websocket_client_send_text(client, cacheMessage, cacheMessageIndex, 1000 / portTICK_PERIOD_MS); - cacheMessageIndex = 0; + if (cacheMessageIndex > 0) + { + // clone cacheMessage to cacheMessageClone + memcpy(cacheMessageClone, cacheMessage, cacheMessageIndex); + + newDataLength = cacheMessageIndex; + + // clear cacheMessage + cacheMessageIndex = 0; + } + + // mutex unlock + xSemaphoreGive(cacheMutex); } + + if (newDataLength > 0) + { + + cacheMessageClone[newDataLength - 1] = '\0'; + esp_websocket_client_send_text(client, cacheMessageClone, newDataLength, 1000 / portTICK_PERIOD_MS); + newDataLength = 0; + + /*// count \n in cacheMessage + int count = 0; + for (int i = 0; i < cacheMessageIndex; i++) + { + if (cacheMessage[i] == '\n') + { + count++; + } + } + + printf("count:%i\n", count); + + // split cacheMessage into multiple messages + char *pch = strtok(cacheMessage, "\n"); + while (pch != NULL) + { + if (strlen(pch) > 0) + { + printf("pf-'%s'\n", pch); + + esp_websocket_client_send_text(client, pch, strlen(pch), 1000 / portTICK_PERIOD_MS); + } + pch = strtok(NULL, "\n"); + } + cacheMessageIndex = 0;*/ + } + else + { + } + } + else + { } } @@ -35,48 +106,104 @@ int jannex_LOG(const char *format, va_list args) int status = 0; // sprintf to jannex_LOG func and dynamically allocate memory - char *message; - status = vasprintf(&message, format, args); + char *esp_message; + status = vasprintf(&esp_message, format, args); - // check if socket is online - if (client != NULL && esp_websocket_client_is_connected(client)) + size_t encoded_length = 0; + size_t input_length = strlen(esp_message); + size_t message_length = CALC_BASE64_ENCODED_LENGTH(input_length) + 1; + + char *message = (char *)malloc(message_length + 1 + 1); // + \n + \0 + + if (message != NULL) { - sendOlderMessages(); - esp_websocket_client_send_text(client, message, strlen(message), 1000 / portTICK_PERIOD_MS); - } - else - { - if (cacheMessageIndex + strlen(message) + 1 > CONFIG_HTTP_SERVER_DEBUG_CACHE_LENGTH) + int ret = mbedtls_base64_encode((unsigned char *)message, message_length - 1, &encoded_length, (unsigned char *)esp_message, input_length); + + if (ret != 0) { - // Cache is full - // replace at the end of the cache - - strcpy(cacheMessage + CONFIG_HTTP_SERVER_DEBUG_CACHE_LENGTH - 30, " ! CONSOLE CACHE OVERFLOW ! \n"); - cacheMessageIndex = CONFIG_HTTP_SERVER_DEBUG_CACHE_LENGTH; + printf("Base64 encoding failed!"); + skippedMessages++; goto END; } - // cache message - if (cacheMessageIndex == 0) + // set last char to \n + message[encoded_length] = '\n'; + message[encoded_length + 1] = '\0'; + } + else + { + printf("Memory allocation failed!"); + skippedMessages++; + + goto END; + } + + message_length = encoded_length + 1; + + // check if socket is online + /*if (client != NULL && esp_websocket_client_is_connected(client)) + { + // print last char of message in hex + // printf("last char: %02x\n", message[encoded_length - 1]); + + sendOlderMessages(); + + printf("message_length:%i\n", message_length); + esp_websocket_client_send_text(client, message, message_length, 1000 / portTICK_PERIOD_MS); + } + else*/ + { + // Take the mutex before writing to the variable + if (xSemaphoreTake(cacheMutex, portMAX_DELAY) == pdTRUE) { - strcpy(cacheMessage + cacheMessageIndex, message); - cacheMessageIndex += strlen(message); + + if (cacheMessageIndex + message_length + 1 > CONFIG_HTTP_SERVER_DEBUG_CACHE_LENGTH) + { + // Cache is full + // replace at the end of the cache + + strcpy(cacheMessage + CONFIG_HTTP_SERVER_DEBUG_CACHE_LENGTH - 7, "!CCO!\n"); + cacheMessageIndex = CONFIG_HTTP_SERVER_DEBUG_CACHE_LENGTH; + + xSemaphoreGive(cacheMutex); + goto END; + } + + // cache message thread safe + // xSemaphoreTake(cacheMessageSemaphore, portMAX_DELAY); + + // cache message + if (cacheMessageIndex == 0) + { + strcpy(cacheMessage + cacheMessageIndex, message); + cacheMessageIndex += message_length; + } + else + { + // cacheMessage[cacheMessageIndex] = '\n'; + strcpy(cacheMessage + cacheMessageIndex, message); + cacheMessageIndex += message_length; + } + + // Release the mutex + xSemaphoreGive(cacheMutex); } else { - // cacheMessage[cacheMessageIndex] = '\n'; - strcpy(cacheMessage + cacheMessageIndex, message); - cacheMessageIndex += strlen(message); + // Unable to take the mutex + // Handle the error } } END: + free(esp_message); free(message); if (status >= 0) { + return vprintf(format, args); // print to console } return status; @@ -153,7 +280,10 @@ static void console_task(void *pvParameters) esp_websocket_client_config_t websocket_cfg = { .uri = CONFIG_HTTP_SERVER_DEBUG_URL, .port = CONFIG_HTTP_SERVER_DEBUG_PORT, - + .task_stack = 1024 * 6, + .reconnect_timeout_ms = 5000, + .network_timeout_ms = 500, + .ping_interval_sec = 10000, }; ESP_LOGI(TAG, "Connecting to %s... on port %d", websocket_cfg.uri, websocket_cfg.port); @@ -176,12 +306,32 @@ static void console_task(void *pvParameters) esp_websocket_client_destroy(client);*/ - vTaskDelete(NULL); + while (true) + { + sendOlderMessages(); + vTaskDelay(500 / portTICK_PERIOD_MS); + } } void initConsole() { + cacheMutex = xSemaphoreCreateMutex(); + if (cacheMutex == NULL) + { + // Mutex creation failed + // Handle the error + printf("initConsole Mutex creation failed!!!!!!!!!!!"); + } + esp_log_set_vprintf(&jannex_LOG); + + if (cacheMutex == NULL) + { + // Mutex creation failed + // Handle the error + printf("initConsole Mutex creation failed!!!!!!!!!!!"); + ESP_LOGE(TAG, "initConsole Mutex creation failed!!!!!!!!!!!"); + } } void connectConsole() diff --git a/src/httpsRequest.c b/src/httpsRequest.c index a6c2231..8ee08fc 100644 --- a/src/httpsRequest.c +++ b/src/httpsRequest.c @@ -22,6 +22,11 @@ esp_err_t https_get_request_SYNC(const char *url, char *out_buffer, int *out_buf }; esp_http_client_handle_t client = esp_http_client_init(&config); + if (client == NULL) + { + ESP_LOGE(TAG, "Failed to initialize HTTP client"); + return ESP_FAIL; + } // GET Request esp_http_client_set_method(client, HTTP_METHOD_GET); diff --git a/src/jannex.c b/src/jannex.c index d72ff04..89b72bd 100644 --- a/src/jannex.c +++ b/src/jannex.c @@ -62,6 +62,28 @@ void initialize_sntp() }*/ } +void startJannex(void *parameter) +{ + defaultValuesToStorage(); + +#ifdef WIFI_ENABLED + esp_log_level_set("esp-x509-crt-bundle", ESP_LOG_WARN); + esp_log_level_set("wifi", ESP_LOG_WARN); + init_wifi(); +#endif + +#ifdef WEB_SERVER_ENABLED + initServer(); +#endif + +#ifdef CONFIG_HTTP_SERVER_ENABLE_DEBUG_MODE + connectConsole(); +#endif // CONFIG_HTTP_SERVER_ENABLE_DEBUG_MODE + + initialize_sntp(); + vTaskDelete(NULL); +} + void initJannex() { initStorage(); @@ -80,26 +102,11 @@ void initJannex() ESP_LOGI(jannexTAG, "Current Build date: %s", app_desc->date); ESP_LOGI(jannexTAG, "Current Build time: %s", app_desc->time); - defaultValuesToStorage(); - -#ifdef WIFI_ENABLED - esp_log_level_set("esp-x509-crt-bundle", ESP_LOG_WARN); - esp_log_level_set("wifi", ESP_LOG_WARN); - init_wifi(); -#endif - -#ifdef WEB_SERVER_ENABLED - initServer(); -#endif - -#ifdef CONFIG_HTTP_SERVER_ENABLE_DEBUG_MODE - connectConsole(); -#endif // CONFIG_HTTP_SERVER_ENABLE_DEBUG_MODE - - initialize_sntp(); + xTaskCreate(startJannex, "startJannex", 4096, NULL, 5, NULL); + xTaskCreate(checkIfOTAwasSuccessful, "checkIfOTAwasSuccessful", 4096, NULL, 5, NULL); } void updateJannex() { - xTaskCreate(&checkForUpdatesAndInstall, "checkForUpdatesAndInstall", 8192, NULL, 5, NULL); + xTaskCreate(checkForUpdatesAndInstall, "checkForUpdatesAndInstall", 8192, NULL, 5, NULL); } \ No newline at end of file diff --git a/src/ota.c b/src/ota.c index 3a7cab8..d28e015 100644 --- a/src/ota.c +++ b/src/ota.c @@ -13,11 +13,38 @@ #include "esp_app_desc.h" #include "esp_ota_ops.h" +#include "string.h" + +// #include "esp_heap_trace.h" + +// #define NUM_RECORDS 100 +// static heap_trace_record_t trace_record[NUM_RECORDS]; // This buffer must be in internal RAM + bool updateAvailable = false; bool isCheckingForUpdates = false; -void getNewestVersion() +void printFreeHeap(int start) { + + if (start == 0) + { + ESP_LOGI(jannexTAG, " \\/ \\/ \\/ \\/ \\/ Free heap: "); + ESP_LOGI(jannexTAG, " %i", heap_caps_get_free_size(MALLOC_CAP_8BIT)); + } + else if (start == -1) + { + ESP_LOGI(jannexTAG, " %i", heap_caps_get_free_size(MALLOC_CAP_8BIT)); + ESP_LOGI(jannexTAG, " /\\ /\\ /\\ /\\ /\\ Free heap: "); + } + else + { + ESP_LOGI(jannexTAG, " %i", heap_caps_get_free_size(MALLOC_CAP_8BIT)); + } +} + +void getNewestVersion(bool forceUpdate) +{ + isCheckingForUpdates = true; // buffer for the response @@ -53,8 +80,10 @@ void getNewestVersion() ESP_LOGE(jannexTAG, "HTTP GET Response: %i %i %s", status_code, responseLength, response); ESP_LOGE(jannexTAG, "Parser failed"); isCheckingForUpdates = false; - return; + + goto FREE_MEM; } + char newestVersion[32]; if (json_obj_get_string(&jctx, "error", newestVersion, sizeof(newestVersion)) == OS_SUCCESS) @@ -62,7 +91,8 @@ void getNewestVersion() ESP_LOGW(jannexTAG, "Error: %s", newestVersion); // ERROR isCheckingForUpdates = false; - return; + + goto FREE_MEM; } if (json_obj_get_string(&jctx, "version", newestVersion, sizeof(newestVersion)) == OS_SUCCESS) @@ -73,7 +103,8 @@ void getNewestVersion() { ESP_LOGI(jannexTAG, "No version"); isCheckingForUpdates = false; - return; + + goto FREE_MEM; } // print current version @@ -140,22 +171,95 @@ void getNewestVersion() updateAvailable = true; } + esp_partition_t *last_invalid_app = esp_ota_get_last_invalid_partition(); + if (last_invalid_app != NULL) + { + // get partition description + + esp_app_desc_t last_app_desc; + esp_err_t ret = esp_ota_get_partition_description(last_invalid_app, &last_app_desc); + if (ret == ESP_OK) + { + ESP_LOGI(jannexTAG, "Last invalid app partition: %s", last_app_desc.version); + // check if the last invalid partition is the same as the newest version + if (strcmp(last_app_desc.version, newestVersion) == 0) + { + // the last invalid partition is the same as the newest version -> DONT update + ESP_LOGE(jannexTAG, "BAD UPDATE DETECTED -> KEEP CURRENT VERSION AND DO NOT UPDATE"); + vTaskDelay(10000 / portTICK_PERIOD_MS); + updateAvailable = false; + } + } + else + ESP_LOGE(jannexTAG, "Could not get last invalid app partition description: %s", esp_err_to_name(ret)); + } /* + else + { + ESP_LOGI(jannexTAG, "No invalid partition"); + }*/ + isCheckingForUpdates = false; // esp ota do update if (updateAvailable) { - startOTAupdate(); + startOTAupdate(forceUpdate); + } + +FREE_MEM: + + json_parse_end(&jctx); + // free(response); // crashes ESP +} + +void printFreeHeapTask(void *parameter) +{ + while (1) + { + size_t heap = esp_get_free_heap_size(); + ESP_LOGI(jannexTAG, "Biggest free heap Block: %i", heap); + ESP_LOGI(jannexTAG, "Total Free heap: %i", heap_caps_get_free_size(MALLOC_CAP_8BIT)); + + vTaskDelay(500 / portTICK_PERIOD_MS); } } void checkForUpdatesAndInstall(void *parameter) { + // ESP_ERROR_CHECK(heap_trace_init_standalone(trace_record, NUM_RECORDS)); + + xTaskCreate(printFreeHeapTask, "printFreeHeapTask", 4096, NULL, 5, NULL); + + vTaskDelay(pdMS_TO_TICKS(5000)); // Delay for 5000 milliseconds while (1) { + + // get current ota state + esp_partition_t *running = esp_ota_get_running_partition(); + esp_ota_img_states_t state; + esp_err_t ret = esp_ota_get_state_partition(running, &state); + + if (ret == ESP_OK) + { + if (state == ESP_OTA_IMG_PENDING_VERIFY) + { + ESP_LOGI(jannexTAG, "OTA update pending verify..."); + // The firmware is half-way through an OTA update, but it's waiting to verify. + // We'll just reboot. + vTaskDelay(pdMS_TO_TICKS(5000)); // Delay for 5000 milliseconds + continue; + } + } + if (isCheckingForUpdates == false) - getNewestVersion(); + { + // ESP_ERROR_CHECK(heap_trace_start(HEAP_TRACE_LEAKS)); + getNewestVersion(false); + // ESP_ERROR_CHECK(heap_trace_stop()); + // heap_trace_dump(); + // vTaskDelay(pdMS_TO_TICKS(50000)); // Delay for 50000 milliseconds + } #ifdef CONFIG_FIRMWARE_UPDATE_SCAN_SECONDS if (CONFIG_FIRMWARE_UPDATE_SCAN_SECONDS >= 1) @@ -169,3 +273,39 @@ void checkForUpdatesAndInstall(void *parameter) } } } + +void checkIfOTAwasSuccessful(void *parameter) +{ + esp_partition_t *running = esp_ota_get_running_partition(); + esp_ota_img_states_t state; + esp_err_t ret = esp_ota_get_state_partition(running, &state); + + if (ret != ESP_OK) + { + ESP_LOGE(jannexTAG, "esp_ota_get_state_partition failed! %i", ret); + vTaskDelete(NULL); + return; + } + + if (state == ESP_OTA_IMG_PENDING_VERIFY) + { + ESP_LOGI(jannexTAG, "ESP_OTA_IMG_PENDING_VERIFY"); + vTaskDelay(1000 * 30 / portTICK_PERIOD_MS); // wait 30sec minute + // run diagnostic function ... + bool diagnostic_is_ok = true; // diagnostic(); + if (diagnostic_is_ok) + { + ESP_LOGI(jannexTAG, "Diagnostics completed successfully! Continuing execution ..."); + esp_ota_mark_app_valid_cancel_rollback(); + } + else + { + ESP_LOGE(jannexTAG, "Diagnostics failed! Start rollback to the previous version ..."); + esp_ota_mark_app_invalid_rollback_and_reboot(); + } + } + + ESP_LOGI(jannexTAG, "State: %i", state); + + vTaskDelete(NULL); +} \ No newline at end of file diff --git a/src/otaUpdate.c b/src/otaUpdate.c index 2c4ea38..f3880e7 100644 --- a/src/otaUpdate.c +++ b/src/otaUpdate.c @@ -47,7 +47,7 @@ esp_err_t _http_event_handler(esp_http_client_event_t *evt) return ESP_OK; } -void startOTAupdate() +void startOTAupdate(bool forceUpdate) { // get mac address uint8_t mac[6]; @@ -65,7 +65,7 @@ void startOTAupdate() esp_http_client_config_t config = { .url = url, .crt_bundle_attach = esp_crt_bundle_attach, - .buffer_size = 4096, + .buffer_size = 4096 * 4, .event_handler = _http_event_handler, .keep_alive_enable = true, @@ -90,4 +90,4 @@ void startOTAupdate() { ESP_LOGE(TAG, "Firmware upgrade failed!"); } -} \ No newline at end of file +} diff --git a/src/storage.c b/src/storage.c index bd6b7e9..20f4df9 100644 --- a/src/storage.c +++ b/src/storage.c @@ -27,7 +27,7 @@ esp_err_t readStorage(const char *key, char *out_value, size_t *length, char *de case ESP_OK: // ESP_LOGI(TAG, "Current %s: %s", key, out_value); - return ret; + break; case ESP_ERR_NVS_NOT_FOUND: ESP_LOGI(TAG, "The value is not initialized yet!\n"); @@ -35,7 +35,12 @@ esp_err_t readStorage(const char *key, char *out_value, size_t *length, char *de default: ESP_LOGE(TAG, "Error (%s) reading!\n", esp_err_to_name(ret)); } + nvs_close(my_handle); + if (ret == ESP_OK) + { + return ret; + } } // if not found, set to default value