diff --git a/components/app_update/esp_ota_ops.c b/components/app_update/esp_ota_ops.c index c212c5f8d0..f77d54b545 100644 --- a/components/app_update/esp_ota_ops.c +++ b/components/app_update/esp_ota_ops.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -47,6 +47,7 @@ typedef struct ota_ops_entry_ { bool need_erase; uint32_t wrote_size; uint8_t partial_bytes; + bool ota_resumption; WORD_ALIGNED_ATTR uint8_t partial_data[16]; LIST_ENTRY(ota_ops_entry_) entries; } ota_ops_entry_t; @@ -119,6 +120,24 @@ static esp_ota_img_states_t set_new_state_otadata(void) #endif } +static ota_ops_entry_t* esp_ota_init_entry(const esp_partition_t *partition) +{ + ota_ops_entry_t *new_entry = (ota_ops_entry_t *) calloc(1, sizeof(ota_ops_entry_t)); + if (new_entry == NULL) { + return NULL; + } + + LIST_INSERT_HEAD(&s_ota_ops_entries_head, new_entry, entries); + + new_entry->partition.staging = partition; + new_entry->partition.final = partition; + new_entry->partition.finalize_with_copy = false; + new_entry->handle = ++s_ota_ops_last_handle; + + return new_entry; +} + + esp_err_t esp_ota_begin(const esp_partition_t *partition, size_t image_size, esp_ota_handle_t *out_handle) { ota_ops_entry_t *new_entry; @@ -153,17 +172,10 @@ esp_err_t esp_ota_begin(const esp_partition_t *partition, size_t image_size, esp #endif } - new_entry = (ota_ops_entry_t *) calloc(1, sizeof(ota_ops_entry_t)); + new_entry = esp_ota_init_entry(partition); if (new_entry == NULL) { return ESP_ERR_NO_MEM; } - - LIST_INSERT_HEAD(&s_ota_ops_entries_head, new_entry, entries); - - new_entry->partition.staging = partition; - new_entry->partition.final = partition; - new_entry->partition.finalize_with_copy = false; - new_entry->handle = ++s_ota_ops_last_handle; new_entry->need_erase = (image_size == OTA_WITH_SEQUENTIAL_WRITES); *out_handle = new_entry->handle; @@ -197,6 +209,54 @@ esp_err_t esp_ota_begin(const esp_partition_t *partition, size_t image_size, esp return ESP_OK; } +esp_err_t esp_ota_resume(const esp_partition_t *partition, const size_t erase_size, const size_t image_offset, esp_ota_handle_t *out_handle) +{ + ota_ops_entry_t *new_entry; + + if ((partition == NULL) || (out_handle == NULL)) { + return ESP_ERR_INVALID_ARG; + } + + if (image_offset > partition->size) { + return ESP_ERR_INVALID_ARG; + } + + partition = esp_partition_verify(partition); + if (partition == NULL) { + return ESP_ERR_NOT_FOUND; + } + + if (partition->type == ESP_PARTITION_TYPE_APP) { + // The staging partition cannot be of type Factory, but the final partition can be. + if (!is_ota_partition(partition)) { + return ESP_ERR_INVALID_ARG; + } + } + + const esp_partition_t* running_partition = esp_ota_get_running_partition(); + if (partition == running_partition) { + return ESP_ERR_OTA_PARTITION_CONFLICT; + } + + new_entry = esp_ota_init_entry(partition); + if (new_entry == NULL) { + return ESP_ERR_NO_MEM; + } + + if (partition->type == ESP_PARTITION_TYPE_BOOTLOADER) { + esp_image_bootloader_offset_set(partition->address); + } + if (partition->type == ESP_PARTITION_TYPE_BOOTLOADER || partition->type == ESP_PARTITION_TYPE_PARTITION_TABLE) { + esp_flash_set_dangerous_write_protection(esp_flash_default_chip, false); + } + + new_entry->ota_resumption = true; + new_entry->wrote_size = image_offset; + new_entry->need_erase = (erase_size == OTA_WITH_SEQUENTIAL_WRITES); + *out_handle = new_entry->handle; + return ESP_OK; +} + esp_err_t esp_ota_set_final_partition(esp_ota_handle_t handle, const esp_partition_t *final, bool finalize_with_copy) { ota_ops_entry_t *it = get_ota_ops_entry(handle); @@ -206,9 +266,14 @@ esp_err_t esp_ota_set_final_partition(esp_ota_handle_t handle, const esp_partiti if (it == NULL) { return ESP_ERR_NOT_FOUND; } - if (it->wrote_size != 0) { + + // If OTA resumption is enabled, it->wrote_size may already contain the size of previously written data. + // Ensure that wrote_size is zero only when OTA resumption is disabled, as any non-zero value in this case + // indicates an invalid state. + if (!it->ota_resumption && it->wrote_size != 0) { return ESP_ERR_INVALID_STATE; } + if (it->partition.staging != final) { const esp_partition_t* final_partition = esp_partition_verify(final); if (final_partition == NULL) { diff --git a/components/app_update/include/esp_ota_ops.h b/components/app_update/include/esp_ota_ops.h index fff87f359b..8ae4cdd315 100644 --- a/components/app_update/include/esp_ota_ops.h +++ b/components/app_update/include/esp_ota_ops.h @@ -104,6 +104,32 @@ int esp_ota_get_app_elf_sha256(char* dst, size_t size) __attribute__((deprecated */ esp_err_t esp_ota_begin(const esp_partition_t* partition, size_t image_size, esp_ota_handle_t* out_handle); +/** + * @brief Resume an interrupted OTA update by continuing to write to the specified partition. + * + * This function is used when an OTA update was previously started and needs to be resumed after an interruption. + * It continues the OTA process from the specified offset within the partition. + * + * Unlike esp_ota_begin(), this function does not erase the partition which receives the OTA update, but rather expects that part of the image + * has already been written correctly, and it resumes writing from the given offset. + * + * @param partition Pointer to info for the partition which is receiving the OTA update. Required. + * @param erase_size Specifies how much flash memory to erase before resuming OTA, depending on whether a sequential write or a bulk erase is being used. + * @param image_offset Offset from where to resume the OTA process. Should be set to the number of bytes already written. + * @param out_handle On success, returns a handle that should be used for subsequent esp_ota_write() and esp_ota_end() calls. + * + * @return + * - ESP_OK: OTA operation resumed successfully. + * - ESP_ERR_INVALID_ARG: partition, out_handle were NULL or image_offset arguments is negative, or partition doesn't point to an OTA app partition. + * - ESP_ERR_NO_MEM: Cannot allocate memory for OTA operation. + * - ESP_ERR_OTA_PARTITION_CONFLICT: Partition holds the currently running firmware, cannot update in place. + * - ESP_ERR_NOT_FOUND: Partition argument not found in partition table. + * - ESP_ERR_OTA_SELECT_INFO_INVALID: The OTA data partition contains invalid data. + * - ESP_ERR_INVALID_SIZE: Partition doesn't fit in configured flash size. + * - ESP_ERR_FLASH_OP_TIMEOUT or ESP_ERR_FLASH_OP_FAIL: Flash write failed. + */ +esp_err_t esp_ota_resume(const esp_partition_t *partition, const size_t erase_size, const size_t image_offset, esp_ota_handle_t *out_handle); + /** * @brief Set the final destination partition for OTA update * diff --git a/components/esp_https_ota/include/esp_https_ota.h b/components/esp_https_ota/include/esp_https_ota.h index a8963986c9..6cfef10f8b 100644 --- a/components/esp_https_ota/include/esp_https_ota.h +++ b/components/esp_https_ota/include/esp_https_ota.h @@ -64,6 +64,8 @@ typedef struct { bool partial_http_download; /*!< Enable Firmware image to be downloaded over multiple HTTP requests */ int max_http_request_size; /*!< Maximum request size for partial HTTP download */ uint32_t buffer_caps; /*!< The memory capability to use when allocating the buffer for OTA update. Default capability is MALLOC_CAP_DEFAULT */ + bool ota_resumption; /*!< Enable resumption in downloading of OTA image between reboots */ + size_t ota_image_bytes_written; /*!< Number of OTA image bytes written to flash so far, updated by the application when OTA data is written successfully in the target OTA partition. */ #if CONFIG_ESP_HTTPS_OTA_DECRYPT_CB || __DOXYGEN__ decrypt_cb_t decrypt_cb; /*!< Callback for external decryption layer */ void *decrypt_user_ctx; /*!< User context for external decryption layer */ diff --git a/components/esp_https_ota/src/esp_https_ota.c b/components/esp_https_ota/src/esp_https_ota.c index 197ab8170c..7660ea4b49 100644 --- a/components/esp_https_ota/src/esp_https_ota.c +++ b/components/esp_https_ota/src/esp_https_ota.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -13,6 +13,7 @@ #include #include #include +#include "esp_check.h" ESP_EVENT_DEFINE_BASE(ESP_HTTPS_OTA_EVENT); @@ -35,6 +36,7 @@ typedef enum { ESP_HTTPS_OTA_BEGIN, ESP_HTTPS_OTA_IN_PROGRESS, ESP_HTTPS_OTA_SUCCESS, + ESP_HTTPS_OTA_RESUME, } esp_https_ota_state; struct esp_https_ota_handle { @@ -292,6 +294,16 @@ esp_err_t esp_https_ota_begin(const esp_https_ota_config_t *ota_config, esp_http #endif } +#if CONFIG_ESP_HTTPS_OTA_DECRYPT_CB + if (ota_config->decrypt_cb == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (ota_config->ota_resumption) { + // OTA resumption is not supported for pre-encrypted firmware case + return ESP_ERR_NOT_SUPPORTED; + } +#endif + esp_https_ota_t *https_ota_handle = calloc(1, sizeof(esp_https_ota_t)); if (!https_ota_handle) { ESP_LOGE(TAG, "Couldn't allocate memory to upgrade data buffer"); @@ -309,6 +321,14 @@ esp_err_t esp_https_ota_begin(const esp_https_ota_config_t *ota_config, esp_http https_ota_handle->max_authorization_retries = 0; } + if (ota_config->ota_resumption) { + // We allow resumption only if we have minimum buffer size already written to flash + if (ota_config->ota_image_bytes_written >= DEFAULT_OTA_BUF_SIZE) { + ESP_LOGI(TAG, "Valid OTA resumption case, offset %d", ota_config->ota_image_bytes_written); + https_ota_handle->binary_file_len = ota_config->ota_image_bytes_written; + } + } + /* Initiate HTTP Connection */ https_ota_handle->http_client = esp_http_client_init(ota_config->http_config); if (https_ota_handle->http_client == NULL) { @@ -325,6 +345,19 @@ esp_err_t esp_https_ota_begin(const esp_https_ota_config_t *ota_config, esp_http } } + /* + * If OTA resumption is enabled, set the "Range" header to resume downloading the OTA image + * from the last written byte. For non-partial cases, the range pattern is 'from-'. + * Partial cases ('from-to') are handled separately below based on the remaining data to + * be downloaded and the max_http_request_size. + */ + if (https_ota_handle->binary_file_len > 0 && !https_ota_handle->partial_http_download) { + char *header_val = NULL; + asprintf(&header_val, "bytes=%d-", https_ota_handle->binary_file_len); + esp_http_client_set_header(https_ota_handle->http_client, "Range", header_val); + free(header_val); + } + if (https_ota_handle->partial_http_download) { esp_http_client_set_method(https_ota_handle->http_client, HTTP_METHOD_HEAD); err = esp_http_client_perform(https_ota_handle->http_client); @@ -343,14 +376,20 @@ esp_err_t esp_https_ota_begin(const esp_https_ota_config_t *ota_config, esp_http https_ota_handle->image_length = esp_http_client_get_content_length(https_ota_handle->http_client); #if CONFIG_ESP_HTTPS_OTA_DECRYPT_CB /* In case of pre ecnrypted OTA, actual image size of OTA binary includes header size - which stored in variable enc_img_header_size*/ + * which stored in variable enc_img_header_size */ https_ota_handle->image_length -= ota_config->enc_img_header_size; #endif esp_http_client_close(https_ota_handle->http_client); - if (https_ota_handle->image_length > https_ota_handle->max_http_request_size) { + const int range_start = https_ota_handle->binary_file_len; + const int range_end = (https_ota_handle->image_length > https_ota_handle->max_http_request_size + range_start) ? + (range_start + https_ota_handle->max_http_request_size - 1) : + https_ota_handle->image_length; + + // Additional sanity to not set Range header if it covers whole image range + if ((range_end - range_start) < https_ota_handle->image_length) { char *header_val = NULL; - asprintf(&header_val, "bytes=0-%d", https_ota_handle->max_http_request_size - 1); + asprintf(&header_val, "bytes=%d-%d", range_start, range_end); if (header_val == NULL) { ESP_LOGE(TAG, "Failed to allocate memory for HTTP header"); err = ESP_ERR_NO_MEM; @@ -423,19 +462,14 @@ esp_err_t esp_https_ota_begin(const esp_https_ota_config_t *ota_config, esp_http goto http_cleanup; } #if CONFIG_ESP_HTTPS_OTA_DECRYPT_CB - if (ota_config->decrypt_cb == NULL) { - err = ESP_ERR_INVALID_ARG; - goto http_cleanup; - } https_ota_handle->decrypt_cb = ota_config->decrypt_cb; https_ota_handle->decrypt_user_ctx = ota_config->decrypt_user_ctx; https_ota_handle->enc_img_header_size = ota_config->enc_img_header_size; #endif https_ota_handle->ota_upgrade_buf_size = alloc_size; https_ota_handle->bulk_flash_erase = ota_config->bulk_flash_erase; - https_ota_handle->binary_file_len = 0; *handle = (esp_https_ota_handle_t)https_ota_handle; - https_ota_handle->state = ESP_HTTPS_OTA_BEGIN; + https_ota_handle->state = https_ota_handle->binary_file_len ? ESP_HTTPS_OTA_RESUME : ESP_HTTPS_OTA_BEGIN; return ESP_OK; http_cleanup: @@ -500,29 +534,43 @@ static esp_err_t get_description_from_image(esp_https_ota_handle_t https_ota_han ESP_LOGE(TAG, "esp_https_ota_get_img_desc: Invalid state"); return ESP_ERR_INVALID_STATE; } - if (read_header(handle) != ESP_OK) { + + unsigned img_info_len = 0; + if (handle->partition.final->type == ESP_PARTITION_TYPE_APP) { + img_info_len = sizeof(esp_app_desc_t); + } else if (handle->partition.final->type == ESP_PARTITION_TYPE_BOOTLOADER) { + img_info_len = sizeof(esp_bootloader_desc_t); + } else { + ESP_LOGE(TAG, "This partition type (%d) is not supported", handle->partition.final->type); return ESP_FAIL; } - void *img_info = (void *)&handle->ota_upgrade_buf[sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)]; - unsigned img_info_len; + const int offset = sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t); + void *img_info = NULL; + + if (handle->binary_file_len >= offset + img_info_len) { + esp_err_t ret = esp_partition_read(handle->partition.staging, offset, handle->ota_upgrade_buf, img_info_len); + ESP_RETURN_ON_ERROR(ret, TAG, "partition read failed %d", ret); + img_info = (void *) handle->ota_upgrade_buf; + } else { + if (read_header(handle) != ESP_OK) { + return ESP_FAIL; + } + img_info = (void *)&handle->ota_upgrade_buf[offset]; + } + if (handle->partition.final->type == ESP_PARTITION_TYPE_APP) { - img_info_len = sizeof(esp_app_desc_t); esp_app_desc_t *app_info = (esp_app_desc_t *)img_info; if (app_info->magic_word != ESP_APP_DESC_MAGIC_WORD) { ESP_LOGE(TAG, "Incorrect app descriptor magic"); return ESP_FAIL; } } else if (handle->partition.final->type == ESP_PARTITION_TYPE_BOOTLOADER) { - img_info_len = sizeof(esp_bootloader_desc_t); esp_bootloader_desc_t *bootloader_info = (esp_bootloader_desc_t *)img_info; if (bootloader_info->magic_byte != ESP_BOOTLOADER_DESC_MAGIC_BYTE) { ESP_LOGE(TAG, "Incorrect bootloader descriptor magic"); return ESP_FAIL; } - } else { - ESP_LOGE(TAG, "This partition type (%d) is not supported", handle->partition.final->type); - return ESP_FAIL; } memcpy(new_img_info, img_info, img_info_len); @@ -614,6 +662,16 @@ esp_err_t esp_https_ota_perform(esp_https_ota_handle_t https_ota_handle) } } return _ota_write(handle, data_buf, binary_file_len); + case ESP_HTTPS_OTA_RESUME: + ESP_LOGD(TAG, "OTA resumption case"); + err = esp_ota_resume(handle->partition.staging, erase_size, handle->binary_file_len, &handle->update_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ota_resume failed (%s)", esp_err_to_name(err)); + return err; + } + esp_ota_set_final_partition(handle->update_handle, handle->partition.final, handle->partition.finalize_with_copy); + handle->state = ESP_HTTPS_OTA_IN_PROGRESS; + /* falls through */ case ESP_HTTPS_OTA_IN_PROGRESS: data_read = esp_http_client_read(handle->http_client, handle->ota_upgrade_buf, @@ -721,6 +779,7 @@ esp_err_t esp_https_ota_finish(esp_https_ota_handle_t https_ota_handle) err = esp_ota_end(handle->update_handle); /* falls through */ case ESP_HTTPS_OTA_BEGIN: + case ESP_HTTPS_OTA_RESUME: if (handle->ota_upgrade_buf) { free(handle->ota_upgrade_buf); } @@ -771,6 +830,7 @@ esp_err_t esp_https_ota_abort(esp_https_ota_handle_t https_ota_handle) err = esp_ota_abort(handle->update_handle); /* falls through */ case ESP_HTTPS_OTA_BEGIN: + case ESP_HTTPS_OTA_RESUME: if (handle->ota_upgrade_buf) { free(handle->ota_upgrade_buf); } @@ -827,6 +887,11 @@ esp_err_t esp_https_ota(const esp_https_ota_config_t *ota_config) return ESP_ERR_INVALID_ARG; } + if (ota_config->ota_resumption) { + ESP_LOGE(TAG, "OTA resumption is not supported in esp_https_ota API"); + return ESP_ERR_NOT_SUPPORTED; + } + esp_https_ota_handle_t https_ota_handle = NULL; esp_err_t err = esp_https_ota_begin(ota_config, &https_ota_handle); if (err != ESP_OK) { diff --git a/examples/system/ota/advanced_https_ota/main/Kconfig.projbuild b/examples/system/ota/advanced_https_ota/main/Kconfig.projbuild index 3c05c92944..7bc48c99fc 100644 --- a/examples/system/ota/advanced_https_ota/main/Kconfig.projbuild +++ b/examples/system/ota/advanced_https_ota/main/Kconfig.projbuild @@ -44,4 +44,13 @@ menu "Example Configuration" help This options specifies HTTP request size. Number of bytes specified in this option will be downloaded in single HTTP request. + + config EXAMPLE_ENABLE_OTA_RESUMPTION + bool "Enable OTA resumption" + default n + help + This enables the OTA resumption feature. + This option allows one to configure the OTA process to resume downloading the OTA image + from where it left off in case of an error or reboot. + endmenu diff --git a/examples/system/ota/advanced_https_ota/main/advanced_https_ota_example.c b/examples/system/ota/advanced_https_ota/main/advanced_https_ota_example.c index 631e6ac388..5659a9d950 100644 --- a/examples/system/ota/advanced_https_ota/main/advanced_https_ota_example.c +++ b/examples/system/ota/advanced_https_ota/main/advanced_https_ota_example.c @@ -14,6 +14,7 @@ #include "esp_system.h" #include "esp_event.h" #include "esp_log.h" +#include "esp_check.h" #include "esp_ota_ops.h" #include "esp_http_client.h" #include "esp_https_ota.h" @@ -39,6 +40,96 @@ extern const uint8_t server_cert_pem_end[] asm("_binary_ca_cert_pem_end"); #define OTA_URL_SIZE 256 +#ifdef CONFIG_EXAMPLE_ENABLE_OTA_RESUMPTION + +#define NVS_NAMESPACE_OTA_RESUMPTION "ota_resumption" +#define NVS_KEY_OTA_WR_LENGTH "nvs_ota_wr_len" +#define NVS_KEY_SAVED_URL "nvs_ota_url" + +static esp_err_t example_ota_res_get_ota_written_len_from_nvs(const nvs_handle_t nvs_ota_resumption_handle, const char *client_ota_url, uint32_t *nvs_ota_wr_len) +{ + esp_err_t err; + char saved_url[OTA_URL_SIZE] = {0}; + size_t url_len = sizeof(saved_url); + + *nvs_ota_wr_len = 0; + + // Retrieve the saved URL from NVS + err = nvs_get_str(nvs_ota_resumption_handle, NVS_KEY_SAVED_URL, saved_url, &url_len); + if (err == ESP_ERR_NVS_NOT_FOUND) { + ESP_LOGD(TAG, "Saved URL is not initialized yet!"); + return err; + } else if (err != ESP_OK) { + ESP_LOGE(TAG, "Error reading saved URL (%s)", esp_err_to_name(err)); + return err; + } + + // Compare the current URL with the saved URL + if (strcmp(client_ota_url, saved_url) != 0) { + ESP_LOGD(TAG, "URLs do not match. Restarting OTA from beginning."); + return ESP_ERR_INVALID_STATE; + } + + // Fetch the saved write length only if URLs match + uint16_t saved_wr_len_kb = 0; + err = nvs_get_u16(nvs_ota_resumption_handle, NVS_KEY_OTA_WR_LENGTH, &saved_wr_len_kb); + if (err == ESP_ERR_NVS_NOT_FOUND) { + ESP_LOGD(TAG, "The write length is not initialized yet!"); + *nvs_ota_wr_len = 0; + return err; + } else if (err != ESP_OK) { + ESP_LOGE(TAG, "Error reading OTA write length (%s)", esp_err_to_name(err)); + return err; + } + + // Convert the saved value back to bytes + *nvs_ota_wr_len = saved_wr_len_kb * 1024; + + return ESP_OK; +} + +static esp_err_t example_ota_res_save_ota_cfg_to_nvs(const nvs_handle_t nvs_ota_resumption_handle, int nvs_ota_wr_len, const char *client_ota_url) +{ + // Convert the write length to kilobytes to optimize NVS space utilization + uint16_t wr_len_kb = nvs_ota_wr_len / 1024; + + // Save the current OTA write length to NVS + ESP_RETURN_ON_ERROR(nvs_set_u16(nvs_ota_resumption_handle, NVS_KEY_OTA_WR_LENGTH, wr_len_kb), TAG, "Failed to set OTA write length"); + + // Save the URL only if the OTA write length is non-zero and the URL is not already saved + if (nvs_ota_wr_len) { + char saved_url[OTA_URL_SIZE] = {0}; + size_t url_len = sizeof(saved_url); + + esp_err_t err = nvs_get_str(nvs_ota_resumption_handle, NVS_KEY_SAVED_URL, saved_url, &url_len); + if (err == ESP_ERR_NVS_NOT_FOUND || strcmp(saved_url, client_ota_url) != 0) { + // URL not saved or changed; save it now + ESP_RETURN_ON_ERROR(nvs_set_str(nvs_ota_resumption_handle, NVS_KEY_SAVED_URL, client_ota_url), TAG, "Failed to set URL in NVS"); + } else if (err != ESP_OK) { + ESP_LOGE(TAG, "Error reading OTA URL"); + return err; + } + } + + ESP_RETURN_ON_ERROR(nvs_commit(nvs_ota_resumption_handle), TAG, "Failed to commit NVS"); + ESP_LOGD(TAG, "Saving state in NVS. Total image written so far : %d KB", wr_len_kb); + return ESP_OK; +} + +static esp_err_t example_ota_res_cleanup_ota_cfg_from_nvs(nvs_handle_t handle) { + esp_err_t ret; + + // Erase all keys in the NVS handle and commit changes + ESP_GOTO_ON_ERROR(nvs_erase_all(handle), err, TAG, "Error in erasing NVS"); + ESP_GOTO_ON_ERROR(nvs_commit(handle), err, TAG, "Error in committing NVS"); + ret = ESP_OK; +err: + nvs_close(handle); + return ret; +} + +#endif + /* Event handler for catching system events */ static void event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) @@ -123,6 +214,7 @@ void advanced_ota_example_task(void *pvParameter) { ESP_LOGI(TAG, "Starting Advanced OTA example"); + esp_err_t err; esp_err_t ota_finish_err = ESP_OK; esp_http_client_config_t config = { .url = CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL, @@ -152,23 +244,44 @@ void advanced_ota_example_task(void *pvParameter) config.skip_cert_common_name_check = true; #endif +#ifdef CONFIG_EXAMPLE_ENABLE_OTA_RESUMPTION + nvs_handle_t nvs_ota_resumption_handle; + err = nvs_open(NVS_NAMESPACE_OTA_RESUMPTION, NVS_READWRITE, &nvs_ota_resumption_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Error (%s) opening NVS handle!", esp_err_to_name(err)); + vTaskDelete(NULL); + } + + uint32_t ota_wr_len = 0; // Variable to hold the written length + err = example_ota_res_get_ota_written_len_from_nvs(nvs_ota_resumption_handle, config.url, &ota_wr_len); + if (err != ESP_OK) { + ESP_LOGD(TAG, "Starting OTA from beginning"); + } else { + ESP_LOGD(TAG, "OTA write length fetched successfully"); + } +#endif + esp_https_ota_config_t ota_config = { .http_config = &config, .http_client_init_cb = _http_client_init_cb, // Register a callback to be invoked after esp_http_client is initialized #ifdef CONFIG_EXAMPLE_ENABLE_PARTIAL_HTTP_DOWNLOAD .partial_http_download = true, .max_http_request_size = CONFIG_EXAMPLE_HTTP_REQUEST_SIZE, +#endif +#ifdef CONFIG_EXAMPLE_ENABLE_OTA_RESUMPTION + .ota_resumption = true, + .ota_image_bytes_written = ota_wr_len, #endif }; esp_https_ota_handle_t https_ota_handle = NULL; - esp_err_t err = esp_https_ota_begin(&ota_config, &https_ota_handle); + err = esp_https_ota_begin(&ota_config, &https_ota_handle); if (err != ESP_OK) { ESP_LOGE(TAG, "ESP HTTPS OTA Begin failed"); vTaskDelete(NULL); } - esp_app_desc_t app_desc; + esp_app_desc_t app_desc = {}; err = esp_https_ota_get_img_desc(https_ota_handle, &app_desc); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_https_ota_get_img_desc failed"); @@ -188,13 +301,26 @@ void advanced_ota_example_task(void *pvParameter) // esp_https_ota_perform returns after every read operation which gives user the ability to // monitor the status of OTA upgrade by calling esp_https_ota_get_image_len_read, which gives length of image // data read so far. - ESP_LOGD(TAG, "Image bytes read: %d", esp_https_ota_get_image_len_read(https_ota_handle)); + const size_t len = esp_https_ota_get_image_len_read(https_ota_handle); + ESP_LOGD(TAG, "Image bytes read: %d", len); +#ifdef CONFIG_EXAMPLE_ENABLE_OTA_RESUMPTION + err = example_ota_res_save_ota_cfg_to_nvs(nvs_ota_resumption_handle, len, config.url); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to save OTA config to NVS (%s).", esp_err_to_name(err)); + } +#endif } if (esp_https_ota_is_complete_data_received(https_ota_handle) != true) { // the OTA image was not completely received and user can customise the response to this situation. ESP_LOGE(TAG, "Complete data was not received."); } else { +#ifdef CONFIG_EXAMPLE_ENABLE_OTA_RESUMPTION + err = example_ota_res_cleanup_ota_cfg_from_nvs(nvs_ota_resumption_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to clean up OTA config from NVS (%s).", esp_err_to_name(err)); + } +#endif ota_finish_err = esp_https_ota_finish(https_ota_handle); if ((err == ESP_OK) && (ota_finish_err == ESP_OK)) { ESP_LOGI(TAG, "ESP_HTTPS_OTA upgrade successful. Rebooting ..."); diff --git a/examples/system/ota/advanced_https_ota/sdkconfig.defaults b/examples/system/ota/advanced_https_ota/sdkconfig.defaults index 2289a82300..1a0d099075 100644 --- a/examples/system/ota/advanced_https_ota/sdkconfig.defaults +++ b/examples/system/ota/advanced_https_ota/sdkconfig.defaults @@ -1,4 +1,4 @@ # Default sdkconfig parameters to use the OTA # partition table layout, with a 4MB flash size CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y -CONFIG_PARTITION_TABLE_TWO_OTA=y +CONFIG_PARTITION_TABLE_TWO_OTA_LARGE=y