From 5c5df89950ffc4660b799747685edf05caf26f0e Mon Sep 17 00:00:00 2001 From: "nilesh.kale" Date: Thu, 23 Jan 2025 10:24:03 +0530 Subject: [PATCH 1/4] feat(esp_https_ota): added ota resumption feature This commit enabled configurable OTA resumption feature in advanced ota example. This resumes downloading OTA image from where it left off in case of an error or reboot. Closes https://github.com/espressif/esp-idf/issues/13127 --- components/app_update/esp_ota_ops.c | 85 +++++++++-- components/app_update/include/esp_ota_ops.h | 26 ++++ .../esp_https_ota/include/esp_https_ota.h | 2 + components/esp_https_ota/src/esp_https_ota.c | 101 +++++++++++--- .../advanced_https_ota/main/Kconfig.projbuild | 9 ++ .../main/advanced_https_ota_example.c | 132 +++++++++++++++++- .../ota/advanced_https_ota/sdkconfig.defaults | 2 +- 7 files changed, 325 insertions(+), 32 deletions(-) diff --git a/components/app_update/esp_ota_ops.c b/components/app_update/esp_ota_ops.c index c212c5f8d05..f77d54b5459 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 fff87f359b2..8ae4cdd3150 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 a8963986c91..6cfef10f8ba 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 197ab8170c4..7660ea4b49a 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 3c05c92944c..7bc48c99fcb 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 631e6ac388f..5659a9d950f 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 2289a82300f..1a0d099075e 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 From 89a96905c44dd4c792fd1b6b5a199a3c35713559 Mon Sep 17 00:00:00 2001 From: Mahavir Jain Date: Thu, 23 Jan 2025 10:25:53 +0530 Subject: [PATCH 2/4] fix(esp_https_ota): handle invalid range condition for OTA resumption case --- components/esp_common/src/esp_err_to_name.c | 4 +++ .../esp_http_client/include/esp_http_client.h | 2 ++ components/esp_https_ota/src/esp_https_ota.c | 25 +++++++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/components/esp_common/src/esp_err_to_name.c b/components/esp_common/src/esp_err_to_name.c index 0c517cb7256..bf4c3b7d7ec 100644 --- a/components/esp_common/src/esp_err_to_name.c +++ b/components/esp_common/src/esp_err_to_name.c @@ -659,6 +659,10 @@ static const esp_err_msg_t esp_err_msg_table[] = { # endif # ifdef ESP_ERR_HTTP_NOT_MODIFIED ERR_TBL_IT(ESP_ERR_HTTP_NOT_MODIFIED), /* 28681 0x7009 HTTP 304 Not Modified, no update available */ +# endif +# ifdef ESP_ERR_HTTP_RANGE_NOT_SATISFIABLE + ERR_TBL_IT(ESP_ERR_HTTP_RANGE_NOT_SATISFIABLE), /* 28682 0x700a HTTP 416 Range Not Satisfiable, + requested range in header is incorrect */ # endif // components/esp-tls/esp_tls_errors.h # ifdef ESP_ERR_ESP_TLS_BASE diff --git a/components/esp_http_client/include/esp_http_client.h b/components/esp_http_client/include/esp_http_client.h index 0d0137af860..fb2bd5b6ee5 100644 --- a/components/esp_http_client/include/esp_http_client.h +++ b/components/esp_http_client/include/esp_http_client.h @@ -227,6 +227,7 @@ typedef enum { HttpStatus_Unauthorized = 401, HttpStatus_Forbidden = 403, HttpStatus_NotFound = 404, + HttpStatus_RangeNotSatisfiable = 416, /* 5xx - Server Error */ HttpStatus_InternalError = 500 @@ -242,6 +243,7 @@ typedef enum { #define ESP_ERR_HTTP_EAGAIN (ESP_ERR_HTTP_BASE + 7) /*!< Mapping of errno EAGAIN to esp_err_t */ #define ESP_ERR_HTTP_CONNECTION_CLOSED (ESP_ERR_HTTP_BASE + 8) /*!< Read FIN from peer and the connection closed */ #define ESP_ERR_HTTP_NOT_MODIFIED (ESP_ERR_HTTP_BASE + 9) /*!< HTTP 304 Not Modified, no update available */ +#define ESP_ERR_HTTP_RANGE_NOT_SATISFIABLE (ESP_ERR_HTTP_BASE + 10) /*!< HTTP 416 Range Not Satisfiable, requested range in header is incorrect */ /** * @brief Start a HTTP session diff --git a/components/esp_https_ota/src/esp_https_ota.c b/components/esp_https_ota/src/esp_https_ota.c index 7660ea4b49a..f771f3c54c2 100644 --- a/components/esp_https_ota/src/esp_https_ota.c +++ b/components/esp_https_ota/src/esp_https_ota.c @@ -108,6 +108,9 @@ static esp_err_t _http_handle_response_code(esp_https_ota_t *https_ota_handle, i } else if (status_code == HttpStatus_NotModified) { ESP_LOGI(TAG, "OTA image not modified since last request (status code: %d)", status_code); return ESP_ERR_HTTP_NOT_MODIFIED; + } else if (status_code == HttpStatus_RangeNotSatisfiable) { + ESP_LOGI(TAG, "Requested range is incorrect (status code: %d)", status_code); + return ESP_ERR_HTTP_RANGE_NOT_SATISFIABLE; } else if (status_code == HttpStatus_Unauthorized) { if (https_ota_handle->max_authorization_retries == 0) { ESP_LOGE(TAG, "Reached max_authorization_retries (%d)", status_code); @@ -402,6 +405,28 @@ esp_err_t esp_https_ota_begin(const esp_https_ota_config_t *ota_config, esp_http } err = _http_connect(https_ota_handle); + if (err == ESP_ERR_HTTP_RANGE_NOT_SATISFIABLE && https_ota_handle->binary_file_len > 0) { + ESP_LOGE(TAG, "OTA resumption failed with err: %d", err); + ESP_LOGI(TAG, "Restarting download from beginning"); + https_ota_handle->binary_file_len = 0; + + // If range in request header is not satisfiable, restart download from beginning + esp_http_client_delete_header(https_ota_handle->http_client, "Range"); + + if (https_ota_handle->partial_http_download && https_ota_handle->image_length > https_ota_handle->max_http_request_size) { + char *header_val = NULL; + asprintf(&header_val, "bytes=0-%d", https_ota_handle->max_http_request_size - 1); + if (header_val == NULL) { + ESP_LOGE(TAG, "Failed to allocate memory for HTTP header"); + err = ESP_ERR_NO_MEM; + goto http_cleanup; + } + esp_http_client_set_header(https_ota_handle->http_client, "Range", header_val); + free(header_val); + } + err = _http_connect(https_ota_handle); + } + if (err != ESP_OK) { if (err != ESP_ERR_HTTP_NOT_MODIFIED) { ESP_LOGE(TAG, "Failed to establish HTTP connection"); From 7bce723d829969b3a9bfa5817e77745fc33b3a4b Mon Sep 17 00:00:00 2001 From: Mahavir Jain Date: Thu, 23 Jan 2025 10:27:38 +0530 Subject: [PATCH 3/4] ci(examples): add tests for OTA resumption feature --- .../advanced_https_ota/pytest_advanced_ota.py | 190 +++++++++++++++++- .../sdkconfig.ci.ota_resumption | 13 ++ ...kconfig.ci.ota_resumption_partial_download | 14 ++ 3 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 examples/system/ota/advanced_https_ota/sdkconfig.ci.ota_resumption create mode 100644 examples/system/ota/advanced_https_ota/sdkconfig.ci.ota_resumption_partial_download diff --git a/examples/system/ota/advanced_https_ota/pytest_advanced_ota.py b/examples/system/ota/advanced_https_ota/pytest_advanced_ota.py index 3f4c9f3843f..d3a823c1cc9 100644 --- a/examples/system/ota/advanced_https_ota/pytest_advanced_ota.py +++ b/examples/system/ota/advanced_https_ota/pytest_advanced_ota.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import http.server import multiprocessing @@ -18,10 +18,28 @@ from pytest_embedded import Dut from RangeHTTPServer import RangeRequestHandler +NVS_PARTITION = 'nvs' + server_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test_certs/server_cert.pem') key_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test_certs/server_key.pem') +def restart_device_with_random_delay(dut: Dut, min_delay: int = 10, max_delay: int = 30) -> None: + """ + Restarts the device after a random delay. + + Parameters: + - dut: The device under test (DUT) instance. + - min_delay: Minimum delay in seconds before restarting. + - max_delay: Maximum delay in seconds before restarting. + """ + delay = random.randint(min_delay, max_delay) + print(f'Waiting for {delay} seconds before restarting the device...') + time.sleep(delay) + dut.serial.hard_reset() # Restart the ESP32 device + print('Device restarted after random delay.') + + def https_request_handler() -> Callable[...,http.server.BaseHTTPRequestHandler]: """ Returns a request handler class that handles broken pipe exception @@ -134,6 +152,89 @@ def test_examples_protocol_advanced_https_ota_example(dut: Dut) -> None: thread1.terminate() +@pytest.mark.esp32 +@pytest.mark.wifi_router +@pytest.mark.parametrize('config', ['ota_resumption'], indirect=True) +def test_examples_protocol_advanced_https_ota_example_ota_resumption(dut: Dut) -> None: + """ + This is a positive test case, which stops the download midway and resumes downloading again. + steps: | + 1. join AP/Ethernet + 2. Fetch OTA image over HTTPS + 3. Reboot with the new OTA image + """ + # Number of iterations to validate OTA + server_port = 8001 + bin_name = 'advanced_https_ota.bin' + + # Erase NVS partition + dut.serial.erase_partition(NVS_PARTITION) + + # Start server + thread1 = multiprocessing.Process(target=start_https_server, args=(dut.app.binary_path, '0.0.0.0', server_port)) + thread1.daemon = True + thread1.start() + try: + # start test + dut.expect('Loaded app from partition at offset', timeout=30) + + if dut.app.sdkconfig.get('EXAMPLE_WIFI_SSID_PWD_FROM_STDIN') is True: + dut.expect('Please input ssid password:') + env_name = 'wifi_router' + ap_ssid = get_env_config_variable(env_name, 'ap_ssid') + ap_password = get_env_config_variable(env_name, 'ap_password') + dut.write(f'{ap_ssid} {ap_password}') + + try: + ip_address = dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', timeout=30)[1].decode() + print('Connected to AP/Ethernet with IP: {}'.format(ip_address)) + except pexpect.exceptions.TIMEOUT: + raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP/Ethernet') + + dut.expect('Starting Advanced OTA example', timeout=30) + host_ip = get_host_ip4_by_dest_ip(ip_address) + + print('writing to device: {}'.format('https://' + host_ip + ':' + str(server_port) + '/' + bin_name)) + dut.write('https://' + host_ip + ':' + str(server_port) + '/' + bin_name) + dut.expect('Starting OTA...', timeout=60) + + restart_device_with_random_delay(dut, 10, 30) + thread1.terminate() + + # Start server + thread1 = multiprocessing.Process(target=start_https_server, args=(dut.app.binary_path, '0.0.0.0', server_port)) + thread1.daemon = True + thread1.start() + + # Validate that the device restarts correctly + dut.expect('Loaded app from partition at offset', timeout=180) + + if dut.app.sdkconfig.get('EXAMPLE_WIFI_SSID_PWD_FROM_STDIN') is True: + dut.expect('Please input ssid password:') + env_name = 'wifi_router' + ap_ssid = get_env_config_variable(env_name, 'ap_ssid') + ap_password = get_env_config_variable(env_name, 'ap_password') + dut.write(f'{ap_ssid} {ap_password}') + + try: + ip_address = dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', timeout=30)[1].decode() + print('Connected to AP/Ethernet with IP: {}'.format(ip_address)) + except pexpect.exceptions.TIMEOUT: + raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP/Ethernet') + + dut.expect('Starting Advanced OTA example', timeout=30) + host_ip = get_host_ip4_by_dest_ip(ip_address) + + print('writing to device: {}'.format('https://' + host_ip + ':' + str(server_port) + '/' + bin_name)) + dut.write('https://' + host_ip + ':' + str(server_port) + '/' + bin_name) + dut.expect('Starting OTA...', timeout=60) + + dut.expect('upgrade successful. Rebooting ...', timeout=150) + + finally: + thread1.terminate() + + @pytest.mark.esp32 @pytest.mark.ethernet_ota def test_examples_protocol_advanced_https_ota_example_truncated_bin(dut: Dut) -> None: @@ -544,6 +645,93 @@ def test_examples_protocol_advanced_https_ota_example_partial_request(dut: Dut) thread1.terminate() +@pytest.mark.esp32 +@pytest.mark.wifi_router +@pytest.mark.parametrize('config', ['ota_resumption_partial_download',], indirect=True) +def test_examples_protocol_advanced_https_ota_example_ota_resumption_partial_download_request(dut: Dut) -> None: + """ + This is a positive test case, to test OTA workflow with Range HTTP header. + steps: | + 1. join AP/Ethernet + 2. Fetch OTA image over HTTPS + 3. Reboot with the new OTA image + """ + server_port = 8001 + # Size of partial HTTP request + request_size = int(dut.app.sdkconfig.get('EXAMPLE_HTTP_REQUEST_SIZE')) + # File to be downloaded. This file is generated after compilation + bin_name = 'advanced_https_ota.bin' + binary_file = os.path.join(dut.app.binary_path, bin_name) + bin_size = os.path.getsize(binary_file) + http_requests = int((bin_size / request_size) - 1) + assert http_requests > 1 + + # Erase NVS partition + dut.serial.erase_partition(NVS_PARTITION) + + # Start server + thread1 = multiprocessing.Process(target=start_https_server, args=(dut.app.binary_path, '0.0.0.0', server_port)) + thread1.daemon = True + thread1.start() + try: + # start test + dut.expect('Loaded app from partition at offset', timeout=30) + + if dut.app.sdkconfig.get('EXAMPLE_WIFI_SSID_PWD_FROM_STDIN') is True: + dut.expect('Please input ssid password:') + env_name = 'wifi_router' + ap_ssid = get_env_config_variable(env_name, 'ap_ssid') + ap_password = get_env_config_variable(env_name, 'ap_password') + dut.write(f'{ap_ssid} {ap_password}') + + try: + ip_address = dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', timeout=30)[1].decode() + print('Connected to AP/Ethernet with IP: {}'.format(ip_address)) + except pexpect.exceptions.TIMEOUT: + raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP') + host_ip = get_host_ip4_by_dest_ip(ip_address) + + dut.expect('Starting Advanced OTA example', timeout=30) + print('writing to device: {}'.format('https://' + host_ip + ':' + str(server_port) + '/' + bin_name)) + dut.write('https://' + host_ip + ':' + str(server_port) + '/' + bin_name) + + restart_device_with_random_delay(dut, 10, 30) + thread1.terminate() + + # Start server + thread1 = multiprocessing.Process(target=start_https_server, args=(dut.app.binary_path, '0.0.0.0', server_port)) + thread1.daemon = True + thread1.start() + + # Validate that the device restarts correctly + dut.expect('Loaded app from partition at offset', timeout=180) + + if dut.app.sdkconfig.get('EXAMPLE_WIFI_SSID_PWD_FROM_STDIN') is True: + dut.expect('Please input ssid password:') + env_name = 'wifi_router' + ap_ssid = get_env_config_variable(env_name, 'ap_ssid') + ap_password = get_env_config_variable(env_name, 'ap_password') + dut.write(f'{ap_ssid} {ap_password}') + + try: + ip_address = dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', timeout=30)[1].decode() + print('Connected to AP/Ethernet with IP: {}'.format(ip_address)) + except pexpect.exceptions.TIMEOUT: + raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP/Ethernet') + + dut.expect('Starting Advanced OTA example', timeout=30) + host_ip = get_host_ip4_by_dest_ip(ip_address) + + print('writing to device: {}'.format('https://' + host_ip + ':' + str(server_port) + '/' + bin_name)) + dut.write('https://' + host_ip + ':' + str(server_port) + '/' + bin_name) + dut.expect('Starting OTA...', timeout=60) + + dut.expect('upgrade successful. Rebooting ...', timeout=150) + + finally: + thread1.terminate() + + @pytest.mark.esp32 @pytest.mark.esp32c3 @pytest.mark.esp32s3 diff --git a/examples/system/ota/advanced_https_ota/sdkconfig.ci.ota_resumption b/examples/system/ota/advanced_https_ota/sdkconfig.ci.ota_resumption new file mode 100644 index 00000000000..f70c92b16fd --- /dev/null +++ b/examples/system/ota/advanced_https_ota/sdkconfig.ci.ota_resumption @@ -0,0 +1,13 @@ +CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL="FROM_STDIN" +CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK=y +CONFIG_EXAMPLE_SKIP_VERSION_CHECK=y +CONFIG_EXAMPLE_OTA_RECV_TIMEOUT=3000 +CONFIG_EXAMPLE_ENABLE_OTA_RESUMPTION=y +CONFIG_EXAMPLE_CONNECT_ETHERNET=n +CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=n +CONFIG_EXAMPLE_CONNECT_WIFI=y +CONFIG_EXAMPLE_WIFI_SSID_PWD_FROM_STDIN=y + +CONFIG_MBEDTLS_TLS_CLIENT_ONLY=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_EXAMPLE_CONNECT_IPV6=n diff --git a/examples/system/ota/advanced_https_ota/sdkconfig.ci.ota_resumption_partial_download b/examples/system/ota/advanced_https_ota/sdkconfig.ci.ota_resumption_partial_download new file mode 100644 index 00000000000..f79b0274be0 --- /dev/null +++ b/examples/system/ota/advanced_https_ota/sdkconfig.ci.ota_resumption_partial_download @@ -0,0 +1,14 @@ +CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL="FROM_STDIN" +CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK=y +CONFIG_EXAMPLE_SKIP_VERSION_CHECK=y +CONFIG_EXAMPLE_OTA_RECV_TIMEOUT=3000 +CONFIG_EXAMPLE_ENABLE_PARTIAL_HTTP_DOWNLOAD=y +CONFIG_EXAMPLE_ENABLE_OTA_RESUMPTION=y +CONFIG_EXAMPLE_CONNECT_ETHERNET=n +CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=n +CONFIG_EXAMPLE_CONNECT_WIFI=y +CONFIG_EXAMPLE_WIFI_SSID_PWD_FROM_STDIN=y + +CONFIG_MBEDTLS_TLS_CLIENT_ONLY=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_EXAMPLE_CONNECT_IPV6=n From 50adc8bcb1f6f6e8d0359572f217f876ee7741bf Mon Sep 17 00:00:00 2001 From: Mahavir Jain Date: Thu, 23 Jan 2025 10:28:59 +0530 Subject: [PATCH 4/4] docs: add documentation for HTTPS OTA resumption feature --- docs/en/api-reference/system/esp_https_ota.rst | 8 ++++++++ docs/zh_CN/api-reference/system/esp_https_ota.rst | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/docs/en/api-reference/system/esp_https_ota.rst b/docs/en/api-reference/system/esp_https_ota.rst index bf59744d49d..8f02cc1e7cc 100644 --- a/docs/en/api-reference/system/esp_https_ota.rst +++ b/docs/en/api-reference/system/esp_https_ota.rst @@ -47,6 +47,14 @@ This option is useful while fetching image from a service like AWS S3, where mbe Default value of mbedTLS Rx buffer size is set to 16 KB. By using ``partial_http_download`` with ``max_http_request_size`` of 4 KB, size of mbedTLS Rx buffer can be reduced to 4 KB. With this configuration, memory saving of around 12 KB is expected. +OTA Resumption +-------------- + +To use the OTA resumption feature, enable the ``ota_resumption`` configuration in the :cpp:struct:`esp_https_ota_config_t`. When OTA resumption is enabled, an OTA image download which has failed previously can be resumed from its intermediate state instead of restarting the whole OTA process from the beginning. This is implemented using the HTTP partial range request feature. + +To specify the point from where the image download should resume, you need to set the ``ota_image_bytes_written`` field in :cpp:struct:`esp_https_ota_config_t`. This value indicates the number of bytes already written to the OTA partition in the previous OTA attempt. + +For reference, you can check the :example:`system/ota/advanced_https_ota`, which demonstrates OTA resumption. In this example, the intermediate OTA state is saved in NVS, allowing the OTA process to resume seamlessly from the last saved state and continue the download. Signature Verification ---------------------- diff --git a/docs/zh_CN/api-reference/system/esp_https_ota.rst b/docs/zh_CN/api-reference/system/esp_https_ota.rst index 8391a22683b..6139c3377f9 100644 --- a/docs/zh_CN/api-reference/system/esp_https_ota.rst +++ b/docs/zh_CN/api-reference/system/esp_https_ota.rst @@ -47,6 +47,14 @@ ESP HTTPS OTA 升级 mbedTLS Rx buffer 的默认大小为 16 KB,但如果将 ``partial_http_download`` 的 ``max_http_request_size`` 设置为 4 KB,便能将 mbedTLS Rx 的 buffer 减小到 4 KB。使用这一配置方式预计可以节省约 12 KB 内存。 +OTA 恢复 +-------- + +在 :cpp:struct:`esp_https_ota_config_t` 中启用 ``ota_resumption`` 配置,即可使用 OTA 恢复功能。启用此功能后,先前失败的 OTA 镜像下载便可以从中断处继续,无需重新开始整个 OTA 过程。此功能是基于 HTTP 的部分范围请求功能实现的。 + +要指定镜像下载的续传位置,需要在 :cpp:struct:`esp_https_ota_config_t` 中设置 ``ota_image_bytes_written`` 字段。此字段的值表示在上一次尝试过程中已写入到 OTA 分区的字节数。 + +如需了解更多,请参阅示例::example:`system/ota/advanced_https_ota`,该示例演示了 OTA 恢复功能。在此示例中, OTA 的中断状态保存在 NVS 中,从而使 OTA 过程能够从上次保存的状态中无缝恢复,并继续下载。 签名验证 -----------------