diff --git a/config/esp32/components/chip/CMakeLists.txt b/config/esp32/components/chip/CMakeLists.txt index a3ad2d9c438619..ec4e2682a48229 100644 --- a/config/esp32/components/chip/CMakeLists.txt +++ b/config/esp32/components/chip/CMakeLists.txt @@ -433,6 +433,11 @@ if (CONFIG_ENABLE_ENCRYPTED_OTA) list(APPEND chip_libraries $) endif() +if (CONFIG_ENABLE_DELTA_OTA) + idf_component_get_property(esp_delta_ota_lib espressif__esp_delta_ota COMPONENT_LIB) + list(APPEND chip_libraries $) +endif() + idf_component_get_property(main_lib main COMPONENT_LIB) list(APPEND chip_libraries $) diff --git a/config/esp32/components/chip/Kconfig b/config/esp32/components/chip/Kconfig index 8ba2c1b3986e15..72e97226d37cd9 100644 --- a/config/esp32/components/chip/Kconfig +++ b/config/esp32/components/chip/Kconfig @@ -236,6 +236,15 @@ menu "CHIP Core" help Enable this option to use the pre encrypted OTA image + config ENABLE_DELTA_OTA + bool "Enable delta OTA" + depends on ENABLE_OTA_REQUESTOR + default n + help + Enable this option for delta OTA image updates. + Delta OTA updates allow for smaller, more efficient updates by only + sending the changes between the current and new firmware versions. + config OTA_AUTO_REBOOT_ON_APPLY bool "Reboot the device after applying the OTA image" depends on ENABLE_OTA_REQUESTOR diff --git a/config/esp32/components/chip/idf_component.yml b/config/esp32/components/chip/idf_component.yml index f4b130c7402af5..2c5df57daa7632 100644 --- a/config/esp32/components/chip/idf_component.yml +++ b/config/esp32/components/chip/idf_component.yml @@ -25,3 +25,9 @@ dependencies: rules: - if: "idf_version >=5.0" - if: "target != esp32h2" + + espressif/esp_delta_ota: + version: "1.1.0" + require: public + rules: + - if: "idf_version >=4.3" diff --git a/docs/guides/esp32/ota.md b/docs/guides/esp32/ota.md index 7c9c388b422cb9..d0830530e7f1dc 100644 --- a/docs/guides/esp32/ota.md +++ b/docs/guides/esp32/ota.md @@ -121,3 +121,34 @@ Please follow the steps below to generate an application image for OTA upgrades: ``` 3. Use the `lighting-app-encrypted-ota.bin` file with the OTA Provider app. + +## Delta OTA + +Delta OTA Updates is a feature that enables Over-the-Air (OTA) firmware update with compressed delta binaries. Patch files have smaller size than the original firmware file, which reduces the time and network usage to download the file from the server. Also, no additional storage partition is required for the "patch". + +### Firmware Changes + +- Enable configuration options for OTA requestor and Delta OTA: + + ``` + CONFIG_ENABLE_OTA_REQUESTOR=y + CONFIG_ENABLE_DELTA_OTA=y + ``` + +- Delta binary needs to be generated using binary delta encoding in Python 3.6+. You can install detools using the following command. + + ``` + pip install detools>=0.49.0 + ``` + +- Generate delta binary and compress it using Heatshrink algorithm. + ``` + detools create_patch -c heatshrink delta-ota.bin + ``` + +- Append the Matter OTA header: + ``` + src/app/ota_image_tool.py create --vendor-id 0xFFF1 --product-id 0x8000 --version 2 --version-str "v2.0" -da sha256 delta-ota.bin lighting-app-delta-ota.bin + ``` + +- Use the `lighting-app-delta-ota.bin` file with the OTA Provider app. diff --git a/src/platform/ESP32/OTAImageProcessorImpl.cpp b/src/platform/ESP32/OTAImageProcessorImpl.cpp index 51e9fe50468d22..75fd563e86702a 100644 --- a/src/platform/ESP32/OTAImageProcessorImpl.cpp +++ b/src/platform/ESP32/OTAImageProcessorImpl.cpp @@ -21,6 +21,7 @@ #include #include "OTAImageProcessorImpl.h" +#include "esp_app_format.h" #include "esp_err.h" #include "esp_log.h" #include "esp_ota_ops.h" @@ -31,7 +32,12 @@ #include #endif // CONFIG_ENABLE_ENCRYPTED_OTA +#ifdef CONFIG_ENABLE_DELTA_OTA +#include +#endif // CONFIG_ENABLE_DELTA_OTA + #define TAG "OTAImageProcessor" + using namespace chip::System; using namespace ::chip::DeviceLayer::Internal; @@ -123,6 +129,96 @@ CHIP_ERROR OTAImageProcessorImpl::ProcessBlock(ByteSpan & block) return CHIP_NO_ERROR; } +#ifdef CONFIG_ENABLE_DELTA_OTA +esp_err_t OTAImageProcessorImpl::DeltaOTAReadCallback(uint8_t * buf_p, size_t size, int src_offset) +{ + if (size <= 0 || buf_p == NULL) + { + return ESP_ERR_INVALID_ARG; + } + + const esp_partition_t * current_partition = esp_ota_get_running_partition(); + if (current_partition == NULL) + { + return ESP_FAIL; + } + + esp_err_t err = esp_partition_read(current_partition, src_offset, buf_p, size); + + if (err != ESP_OK) + { + ESP_LOGE(TAG, "esp_partition_read failed (%s)!", esp_err_to_name(err)); + } + + return err; +} + +esp_err_t OTAImageProcessorImpl::DeltaOTAWriteHeader(OTAImageProcessorImpl * imageProcessor, const uint8_t * buf_p, size_t size, + int index) +{ + if (size <= 0 || buf_p == NULL) + { + return ESP_ERR_INVALID_ARG; + } + + static char header_data[IMG_HEADER_LEN]; + static bool chip_id_verified = false; + static int header_data_read = 0; + + if (!chip_id_verified) + { + if (header_data_read + size <= IMG_HEADER_LEN) + { + memcpy(header_data + header_data_read, buf_p, size); + header_data_read += size; + return ESP_OK; + } + else + { + index = IMG_HEADER_LEN - header_data_read; + memcpy(header_data + header_data_read, buf_p, index); + + chip_id_verified = true; + + // Write data in header_data buffer. + esp_err_t err = esp_ota_write(imageProcessor->mOTAUpdateHandle, header_data, IMG_HEADER_LEN); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "esp_ota_write failed (%s)!", esp_err_to_name(err)); + return err; + } + } + } + return ESP_OK; +} + +esp_err_t OTAImageProcessorImpl::DeltaOTAWriteCallback(const uint8_t * buf_p, size_t size, void * arg) +{ + static int index = 0; + auto * imageProcessor = reinterpret_cast(arg); + if (size <= 0 || buf_p == NULL) + { + return ESP_ERR_INVALID_ARG; + } + + esp_err_t err = imageProcessor->DeltaOTAWriteHeader(imageProcessor, buf_p, size, index); + + if (err != ESP_OK) + { + ESP_LOGE(TAG, "DeltaOTAWriteHeader failed (%s)!", esp_err_to_name(err)); + return err; + } + + err = esp_ota_write(imageProcessor->mOTAUpdateHandle, buf_p + index, size - index); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "esp_ota_write failed (%s)!", esp_err_to_name(err)); + } + + return err; +} +#endif // CONFIG_ENABLE_DELTA_OTA + void OTAImageProcessorImpl::HandlePrepareDownload(intptr_t context) { auto * imageProcessor = reinterpret_cast(context); @@ -142,13 +238,31 @@ void OTAImageProcessorImpl::HandlePrepareDownload(intptr_t context) ChipLogError(SoftwareUpdate, "OTA partition not found"); return; } +#ifdef CONFIG_ENABLE_DELTA_OTA + // New image size is unknown for delta OTA, so we use OTA_SIZE_UNKNOWN flag. + esp_err_t err = esp_ota_begin(imageProcessor->mOTAUpdatePartition, OTA_SIZE_UNKNOWN, &(imageProcessor->mOTAUpdateHandle)); +#else esp_err_t err = esp_ota_begin(imageProcessor->mOTAUpdatePartition, OTA_WITH_SEQUENTIAL_WRITES, &(imageProcessor->mOTAUpdateHandle)); +#endif // CONFIG_ENABLE_DELTA_OTA + if (err != ESP_OK) { imageProcessor->mDownloader->OnPreparedForDownload(ESP32Utils::MapError(err)); return; } +#ifdef CONFIG_ENABLE_DELTA_OTA + imageProcessor->delta_ota_cfg.user_data = imageProcessor, + imageProcessor->delta_ota_cfg.read_cb = &(imageProcessor->DeltaOTAReadCallback), + imageProcessor->delta_ota_cfg.write_cb_with_user_data = &(imageProcessor->DeltaOTAWriteCallback), + + imageProcessor->mDeltaOTAUpdateHandle = esp_delta_ota_init(&imageProcessor->delta_ota_cfg); + if (imageProcessor->mDeltaOTAUpdateHandle == NULL) + { + ChipLogError(SoftwareUpdate, "esp_delta_ota_init failed"); + return; + } +#endif // CONFIG_ENABLE_DELTA_OTA #if CONFIG_ENABLE_ENCRYPTED_OTA CHIP_ERROR chipError = imageProcessor->DecryptStart(); @@ -182,7 +296,29 @@ void OTAImageProcessorImpl::HandleFinalize(intptr_t context) } #endif // CONFIG_ENABLE_ENCRYPTED_OTA +#ifdef CONFIG_ENABLE_DELTA_OTA + esp_err_t err = esp_delta_ota_finalize(imageProcessor->mDeltaOTAUpdateHandle); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "esp_delta_ota_finalize() failed (%s)!", esp_err_to_name(err)); + esp_ota_abort(imageProcessor->mOTAUpdateHandle); + imageProcessor->ReleaseBlock(); + PostOTAStateChangeEvent(DeviceLayer::kOtaDownloadFailed); + } + + err = esp_delta_ota_deinit(imageProcessor->mDeltaOTAUpdateHandle); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "esp_delta_ota_deinit() failed (%s)!", esp_err_to_name(err)); + esp_ota_abort(imageProcessor->mOTAUpdateHandle); + imageProcessor->ReleaseBlock(); + PostOTAStateChangeEvent(DeviceLayer::kOtaDownloadFailed); + } + + err = esp_ota_end(imageProcessor->mOTAUpdateHandle); +#else esp_err_t err = esp_ota_end(imageProcessor->mOTAUpdateHandle); +#endif // CONFIG_ENABLE_DELTA_OTA if (err != ESP_OK) { if (err == ESP_ERR_OTA_VALIDATE_FAILED) @@ -264,7 +400,11 @@ void OTAImageProcessorImpl::HandleProcessBlock(intptr_t context) } #endif // CONFIG_ENABLE_ENCRYPTED_OTA - err = esp_ota_write(imageProcessor->mOTAUpdateHandle, blockToWrite.data(), blockToWrite.size()); +#ifdef CONFIG_ENABLE_DELTA_OTA + err = esp_delta_ota_feed_patch(imageProcessor->mDeltaOTAUpdateHandle, blockToWrite.data(), blockToWrite.size()); +#else + err = esp_ota_write(imageProcessor->mOTAUpdateHandle, blockToWrite.data(), blockToWrite.size()); +#endif // CONFIG_ENABLE_DELTA_OTA #if CONFIG_ENABLE_ENCRYPTED_OTA free((void *) (blockToWrite.data())); diff --git a/src/platform/ESP32/OTAImageProcessorImpl.h b/src/platform/ESP32/OTAImageProcessorImpl.h index 4162711eef847a..94b06841f13e32 100644 --- a/src/platform/ESP32/OTAImageProcessorImpl.h +++ b/src/platform/ESP32/OTAImageProcessorImpl.h @@ -27,6 +27,12 @@ #include #endif // CONFIG_ENABLE_ENCRYPTED_OTA +#ifdef CONFIG_ENABLE_DELTA_OTA +#include "esp_app_format.h" +#include +#define IMG_HEADER_LEN sizeof(esp_image_header_t) +#endif // CONFIG_ENABLE_DELTA_OTA + namespace chip { class OTAImageProcessorImpl : public OTAImageProcessorInterface @@ -48,6 +54,11 @@ class OTAImageProcessorImpl : public OTAImageProcessorInterface // @return CHIP_NO_ERROR on success, appropriate error code otherwise CHIP_ERROR InitEncryptedOTA(const CharSpan & key); #endif // CONFIG_ENABLE_ENCRYPTED_OTA +#ifdef CONFIG_ENABLE_DELTA_OTA + esp_err_t DeltaOTAWriteHeader(OTAImageProcessorImpl * imageProcessor, const uint8_t * buf_p, size_t size, int index); + static esp_err_t DeltaOTAReadCallback(uint8_t * buf_p, size_t size, int src_offset); + static esp_err_t DeltaOTAWriteCallback(const uint8_t * buf_p, size_t size, void * arg); +#endif // CONFIG_ENABLE_DELTA_OTA private: static void HandlePrepareDownload(intptr_t context); @@ -64,6 +75,10 @@ class OTAImageProcessorImpl : public OTAImageProcessorInterface MutableByteSpan mBlock; const esp_partition_t * mOTAUpdatePartition = nullptr; esp_ota_handle_t mOTAUpdateHandle; +#ifdef CONFIG_ENABLE_DELTA_OTA + esp_delta_ota_handle_t mDeltaOTAUpdateHandle; + esp_delta_ota_cfg_t delta_ota_cfg; +#endif // CONFIG_ENABLE_DELTA_OTA OTAImageHeaderParser mHeaderParser; #if CONFIG_ENABLE_ENCRYPTED_OTA