From 624174aa181b23b9118d80775cf8f4e7b92b9058 Mon Sep 17 00:00:00 2001 From: Emil Kondayan Date: Mon, 14 Mar 2022 10:11:11 +0200 Subject: [PATCH] esp32/ota: Implement ESP-IDF OTA functionality. Implemented new functions: * mark_app_invalid_rollback_and_reboot() * check_rollback_is_possible() * app_description() * app_state() * ota_begin() * ota_write() * ota_write_with_offset() for ESP-IDF version >= 4.2 * ota_end() * ota_abort() for ESP-IDF version >= 4.3 * create tests * update documentation esp32/ota: Implement ESP-IDF OTA functionality. --- docs/library/esp32.rst | 145 +++++++++++++++++++++++- ports/esp32/esp32_partition.c | 138 +++++++++++++++++++++++ tests/ports/esp32/partition_ota.py | 174 ++++++++++++++++++++++++----- 3 files changed, 423 insertions(+), 34 deletions(-) diff --git a/docs/library/esp32.rst b/docs/library/esp32.rst index dc35e7905e..370dfab7d3 100644 --- a/docs/library/esp32.rst +++ b/docs/library/esp32.rst @@ -133,15 +133,154 @@ methods to enable over-the-air (OTA) updates. .. classmethod:: Partition.mark_app_valid_cancel_rollback() - Signals that the current boot is considered successful. - Calling ``mark_app_valid_cancel_rollback`` is required on the first boot of a new - partition to avoid an automatic rollback at the next boot. + Signals that the current boot is considered successful by writing to the "otadata" + partition. Calling ``mark_app_valid_cancel_rollback`` is required on the first boot of a + new partition to avoid an automatic rollback at the next boot. This uses the ESP-IDF "app rollback" feature with "CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE" and an ``OSError(-261)`` is raised if called on firmware that doesn't have the feature enabled. It is OK to call ``mark_app_valid_cancel_rollback`` on every boot and it is not necessary when booting firmware that was loaded using esptool. +.. classmethod:: Partition.mark_app_invalid_rollback_and_reboot() + + Mark the current app partition invalid by writing to the "otadata" + partition, rollback to the previous workable app and then reboots. + If the rollback is sucessfull, the device will reset. If the flash does not have + at least one valid app (except the running app) then rollback is not possible. + If the "CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE" option is set, and a reset occurs without + calling either + ``mark_app_valid_cancel_rollback()`` or ``mark_app_invalid_rollback_and_reboot()`` + function then the application is rolled back. + This uses the ESP-IDF "app rollback" feature with "CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE" + and an ``OSError(-261)`` is raised if called on firmware that doesn't have the + feature enabled. + +.. classmethod:: Partition.check_rollback_is_possible() + + Returns True if at least one valid app is found(except the running one) + Returns False otherwise. + + Checks if there is a bootable application on the slots which can be booted in case of + rollback. For an application to be considered bootable, the following conditions + must be met: the app must be marked to be valid(marked in otadata as not UNDEFINED, + INVALID or ABORTED and crc is good); must be marked bootable; secure_version of + app >= secure_version of efuse (if anti-rollback is enabled). + +.. method:: Partition.app_description() + + Returns a 7-tuple ``(secure_version, version, project_name, compile_time, compile_date, + idf_version, elf_sha256)`` which is a description of the app partition pointed by the + object. + + If the object does not contain an app partition, OsError exception will be raised: + ``ESP_ERR_NOT_FOUND`` no app description structure is found. Magic word is incorrect. + ``ESP_ERR_NOT_SUPPORTED`` Partition is not application. + ``ESP_ERR_INVALID_ARG`` Partition’s offset exceeds partition size. + ``ESP_ERR_INVALID_SIZE`` Read would go out of bounds of the partition. + +.. method:: Partition.app_state() + + Returns the app state of a valid ota partition. It can be one of the following strings: + ``new``: Monitor the first boot. In bootloader this state is changed to "pending verify" + ``verify``: First boot for this app. If this state persists during second boot, then it + will be changed to ``aborted`` + ``valid``: App was confirmed as workable. App can boot and work without limits + ``invalid``: App was confirmed as non-workable. This app will not be selected to + boot at all + ``aborted``: App could not confirmed as workable or non-workable. In bootloader + "pending verify" state will be changed to ``aborted``. This app will not be selected + to boot at all + ``undefined``: App can boot and work without limits + + One of the following OsError can be raised: + ``ESP_ERR_NOT_SUPPORTED``: Partition is not ota. + ``ESP_ERR_NOT_FOUND``: Partition table does not have otadata or state was not found for + given partition. + +.. method:: Partition.ota_begin(image_size) + + Prepares the partition for an OTA update and start the process of updating. + The target partition is erased to the specified image size. If the size of the + artition is not known in advance, the entire partition is eraesd. + + Note: This function is available since ESP-IDF version 4.3 + + Note: If the rollback option is enabled and the running application has the + "pending verify" state then it will lead to the ESP_ERR_OTA_ROLLBACK_INVALID_STATE error. + Confirm the running app before to run download a new app, use + mark_app_valid_cancel_rollback() function + + ``image_size``: The size of the image to be written. 0 indicates a partition of unknown + size. If you know the size of the partition in advance, you can pass the size in bytes. + The default value is "0" + + Returns an integer handle, associated with the ota update process. The update + process must be ended by calling ``ota_end()`. Since ESP-IDF version 4.3, + an update process can also be ended by ``ota_abort()``. + + An OsError can be raised if there is an error with the update process: + ``ESP_ERR_INVALID_ARG``: 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_OTA_ROLLBACK_INVALID_STATE``: If the running app has not confirmed state. Before + performing an update, the application must be valid + +.. method:: Partition.ota_write(handle, buf) + + Write OTA update data to the target partition. This function can be called multiple times + as data is received during the OTA operation. Data is written sequentially to the partition. + + ``handle``: The handle returned by ``ota_begin()`` + ``buf``: Data buffer to write + + An OsError can be raised if there is an error with the update process: + ``ESP_ERR_INVALID_ARG``: Handle is invalid + ``ESP_ERR_OTA_VALIDATE_FAILED``: First byte of image contains invalid app image magic byte + ``ESP_ERR_FLASH_OP_TIMEOUT`` or ``ESP_ERR_FLASH_OP_FAIL``: Flash write failed + ``ESP_ERR_OTA_SELECT_INFO_INVALID``: OTA data partition has invalid contents + +.. method:: Partition.ota_write_with_offset(handle, buffer, offset) + + Write OTA update data to the target partition. This function writes data in non contiguous + manner. If flash encryption is enabled, data should be 16 byte aligned. + + Note: This function is available since ESP-IDF version 4.2 + + Note: While performing OTA, if the packets arrive out of order, esp_ota_write_with_offset() + can be used to write data in non contiguous manner. Use of esp_ota_write_with_offset() in + combination with esp_ota_write() is not recommended. + + An OsError can be raised if there is an error with the update process: + ``ESP_ERR_INVALID_ARG``: handle is invalid + ``ESP_ERR_OTA_VALIDATE_FAILED``: First byte of image contains invalid app image magic byte + ``ESP_ERR_FLASH_OP_TIMEOUT`` or ``ESP_ERR_FLASH_OP_FAIL``: Flash write failed + ``ESP_ERR_OTA_SELECT_INFO_INVALID``: OTA data partition has invalid contents + +.. method:: Partition.ota_end(handle) + + Finish the OTA update process and validate newly written app image. + + An OsError can be raised if there is an error with the update process: + ``ESP_ERR_NOT_FOUND``: OTA handle was not found + ``ESP_ERR_INVALID_ARG``: Handle was never written to + ``ESP_ERR_OTA_VALIDATE_FAILED``: OTA image is invalid (either not a valid app image, or + if secure boot is enabled - signature failed to verify) + ``ESP_ERR_INVALID_STATE``: If flash encryption is enabled, this result indicates an internal + error writing the final encrypted bytes to flash + +.. method:: Partition.ota_abort(handle) + + Aborts the OTA process and frees resources + + An OsError can be raised if there is an error: + ``ESP_ERR_NOT_FOUND``: OTA handle was not found + Constants ~~~~~~~~~ diff --git a/ports/esp32/esp32_partition.c b/ports/esp32/esp32_partition.c index e5d4809620..39df1e9dfa 100644 --- a/ports/esp32/esp32_partition.c +++ b/ports/esp32/esp32_partition.c @@ -265,6 +265,130 @@ static MP_DEFINE_CONST_FUN_OBJ_1(esp32_partition_mark_app_valid_cancel_rollback_ static MP_DEFINE_CONST_CLASSMETHOD_OBJ(esp32_partition_mark_app_valid_cancel_rollback_obj, MP_ROM_PTR(&esp32_partition_mark_app_valid_cancel_rollback_fun_obj)); +static mp_obj_t esp32_partition_mark_app_invalid_rollback_and_reboot(mp_obj_t cls_in) { + check_esp_err(esp_ota_mark_app_invalid_rollback_and_reboot()); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(esp32_partition_mark_app_invalid_rollback_and_reboot_fun_obj, + esp32_partition_mark_app_invalid_rollback_and_reboot); +static MP_DEFINE_CONST_CLASSMETHOD_OBJ(esp32_mark_app_invalid_rollback_and_reboot_obj, + MP_ROM_PTR(&esp32_partition_mark_app_invalid_rollback_and_reboot_fun_obj)); + +static mp_obj_t esp32_check_rollback_is_possible(mp_obj_t cls_in) { + return mp_obj_new_bool(esp_ota_check_rollback_is_possible()); +} +static MP_DEFINE_CONST_FUN_OBJ_1(esp32_check_rollback_is_possible_fun_obj, esp32_check_rollback_is_possible); +static MP_DEFINE_CONST_CLASSMETHOD_OBJ(esp32_check_rollback_is_possible_obj, MP_ROM_PTR(&esp32_check_rollback_is_possible_fun_obj)); + +static mp_obj_t esp32_app_description(mp_obj_t self_in) { + esp32_partition_obj_t *self = MP_OBJ_TO_PTR(self_in); + esp_app_desc_t app; + + check_esp_err(esp_ota_get_partition_description(self->part, &app)); + + mp_obj_t tuple[] = { + mp_obj_new_int_from_uint(app.secure_version), + mp_obj_new_str(app.version, strlen(app.version)), + mp_obj_new_str(app.project_name, strlen(app.project_name)), + mp_obj_new_str(app.time, strlen(app.time)), + mp_obj_new_str(app.date, strlen(app.date)), + mp_obj_new_str(app.idf_ver, strlen(app.idf_ver)), + mp_obj_new_bytes(app.app_elf_sha256, 32) + }; + return mp_obj_new_tuple(7, tuple); +} +static MP_DEFINE_CONST_FUN_OBJ_1(esp32_app_description_obj, esp32_app_description); + +static mp_obj_t esp32_app_get_state(mp_obj_t self_in) { + esp32_partition_obj_t *self = MP_OBJ_TO_PTR(self_in); + char *ret = NULL; + esp_ota_img_states_t state; + + check_esp_err(esp_ota_get_state_partition(self->part, &state)); + + switch (state) { + // Monitor the first boot. In bootloader this state is changed to ESP_OTA_IMG_PENDING_VERIFY. + case ESP_OTA_IMG_NEW: + ret = "new"; + break; + // First boot for this app. If this state persists during second boot, then it will be changed to ABORTED. + case ESP_OTA_IMG_PENDING_VERIFY: + ret = "verify"; + break; + // App was confirmed as workable. App can boot and work without limits. + case ESP_OTA_IMG_VALID: + ret = "valid"; + break; + // App was confirmed as non-workable. This app will not be selected to boot at all. + case ESP_OTA_IMG_INVALID: + ret = "invalid"; + break; + // App could not confirmed as workable or non-workable. In bootloader IMG_PENDING_VERIFY state will be changed to IMG_ABORTED. This app will not be selected to boot at all. + case ESP_OTA_IMG_ABORTED: + ret = "aborted"; + break; + // App can boot and work without limits. + default: + ret = "undefined"; + } + return mp_obj_new_str(ret, strlen(ret)); +} +static MP_DEFINE_CONST_FUN_OBJ_1(esp32_app_get_state_obj, esp32_app_get_state); + +static mp_obj_t esp32_ota_begin(size_t n_args, const mp_obj_t *args) { + esp32_partition_obj_t *self = MP_OBJ_TO_PTR(args[0]); + esp_ota_handle_t handle; + size_t image_size = 0; + + if (n_args == 2) { + image_size = mp_obj_get_int(args[1]); + } + check_esp_err(esp_ota_begin(self->part, image_size, &handle)); + return mp_obj_new_int_from_uint(handle); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_ota_begin_obj, 1, 2, esp32_ota_begin); + +static mp_obj_t esp32_ota_write(mp_obj_t self_in, const mp_obj_t handle_in, const mp_obj_t data_in) { + const esp_ota_handle_t handle = mp_obj_get_int(handle_in); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(data_in, &bufinfo, MP_BUFFER_READ); + + check_esp_err(esp_ota_write(handle, bufinfo.buf, bufinfo.len)); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_3(esp32_ota_write_obj, esp32_ota_write); + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0) +static mp_obj_t esp32_ota_write_with_offset(size_t n_args, const mp_obj_t *args) { + esp_ota_handle_t handle = mp_obj_get_int(args[1]); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_READ); + const uint32_t offset = mp_obj_get_int(args[3]); + + check_esp_err(esp_ota_write_with_offset(handle, bufinfo.buf, bufinfo.len, offset)); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_ota_write_with_offset_obj, 4, 4, esp32_ota_write_with_offset); +#endif + +static mp_obj_t esp32_ota_end(mp_obj_t self_in, const mp_obj_t handle_in) { + const esp_ota_handle_t handle = mp_obj_get_int(handle_in); + + check_esp_err(esp_ota_end(handle)); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(esp32_ota_end_obj, esp32_ota_end); + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0) +static mp_obj_t esp32_ota_abort(mp_obj_t self_in, const mp_obj_t handle_in) { + esp_ota_handle_t handle = mp_obj_get_int(handle_in); + + check_esp_err(esp_ota_abort(handle)); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(esp32_ota_abort_obj, esp32_ota_abort); +#endif + static const mp_rom_map_elem_t esp32_partition_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_find), MP_ROM_PTR(&esp32_partition_find_obj) }, @@ -275,8 +399,22 @@ static const mp_rom_map_elem_t esp32_partition_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_set_boot), MP_ROM_PTR(&esp32_partition_set_boot_obj) }, { MP_ROM_QSTR(MP_QSTR_mark_app_valid_cancel_rollback), MP_ROM_PTR(&esp32_partition_mark_app_valid_cancel_rollback_obj) }, + { MP_ROM_QSTR(MP_QSTR_mark_app_invalid_rollback_and_reboot), MP_ROM_PTR(&esp32_mark_app_invalid_rollback_and_reboot_obj) }, + { MP_ROM_QSTR(MP_QSTR_check_rollback_is_possible), MP_ROM_PTR(&esp32_check_rollback_is_possible_obj) }, { MP_ROM_QSTR(MP_QSTR_get_next_update), MP_ROM_PTR(&esp32_partition_get_next_update_obj) }, + { MP_ROM_QSTR(MP_QSTR_app_description), MP_ROM_PTR(&esp32_app_description_obj) }, + { MP_ROM_QSTR(MP_QSTR_app_state), MP_ROM_PTR(&esp32_app_get_state_obj) }, + { MP_ROM_QSTR(MP_QSTR_ota_begin), MP_ROM_PTR(&esp32_ota_begin_obj) }, + { MP_ROM_QSTR(MP_QSTR_ota_write), MP_ROM_PTR(&esp32_ota_write_obj) }, + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0) + { MP_ROM_QSTR(MP_QSTR_ota_write_with_offset), MP_ROM_PTR(&esp32_ota_write_with_offset_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_ota_end), MP_ROM_PTR(&esp32_ota_end_obj) }, + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0) + { MP_ROM_QSTR(MP_QSTR_ota_abort), MP_ROM_PTR(&esp32_ota_abort_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_BOOT), MP_ROM_INT(ESP32_PARTITION_BOOT) }, { MP_ROM_QSTR(MP_QSTR_RUNNING), MP_ROM_INT(ESP32_PARTITION_RUNNING) }, { MP_ROM_QSTR(MP_QSTR_TYPE_APP), MP_ROM_INT(ESP_PARTITION_TYPE_APP) }, diff --git a/tests/ports/esp32/partition_ota.py b/tests/ports/esp32/partition_ota.py index 65e2742ebb..0e6465c49c 100644 --- a/tests/ports/esp32/partition_ota.py +++ b/tests/ports/esp32/partition_ota.py @@ -1,6 +1,7 @@ # Test ESP32 OTA updates, including automatic roll-back. # Running this test requires firmware with an OTA Partition, such as the GENERIC_OTA "board". # This test also requires patience as it copies the boot partition into the other OTA slot. +# The test runs two times to test both copy functions: ota_write() and ota_write_with_offset() import machine from esp32 import Partition @@ -21,13 +22,20 @@ def log(*args): print(*args) -# replace boot.py with the test code that will run on each reboot -import os - +# If step does not exists, this means that this is the first pass try: - os.rename("boot.py", "boot-orig.py") + from step import PASS except: - pass + PASS = 1 + + # replace boot.py with the test code that will run on each reboot + import os + + try: + os.rename("boot.py", "boot-orig.py") + except: + pass + with open("boot.py", "w") as f: f.write("DEBUG=" + str(DEBUG)) f.write( @@ -40,57 +48,66 @@ cur_name = cur.info()[4] def log(*args): if DEBUG: print(*args) -from step import STEP, EXPECT +from step import STEP, EXPECT, PASS log("Running partition: " + cur_name + " STEP=" + str(STEP) + " EXPECT=" + EXPECT) if cur_name != EXPECT: - print("\\x04FAILED: step " + str(STEP) + " expected " + EXPECT + " got " + cur_name + "\\x04") + print("FAILED: step " + str(STEP) + " expected " + EXPECT + " got " + cur_name + "\\n") if STEP == 0: log("Not confirming boot ok and resetting back into first") nxt = cur.get_next_update() with open("step.py", "w") as f: - f.write("STEP=1\\nEXPECT=\\"" + nxt.info()[4] + "\\"\\n") + f.write("STEP=1\\nEXPECT=\\"" + nxt.info()[4] + "\\"\\nPASS=" + str(PASS) + "\\n") machine.reset() elif STEP == 1: log("Booting into second partition again") nxt = cur.get_next_update() nxt.set_boot() with open("step.py", "w") as f: - f.write("STEP=2\\nEXPECT=\\"" + nxt.info()[4] + "\\"\\n") + f.write("STEP=2\\nEXPECT=\\"" + nxt.info()[4] + "\\"\\nPASS=" + str(PASS) + "\\n") machine.reset() elif STEP == 2: log("Confirming boot ok and rebooting into same partition") Partition.mark_app_valid_cancel_rollback() with open("step.py", "w") as f: - f.write("STEP=3\\nEXPECT=\\"" + cur_name + "\\"\\n") + f.write("STEP=3\\nEXPECT=\\"" + cur_name + "\\"\\nPASS=" + str(PASS) + "\\n") machine.reset() elif STEP == 3: - log("Booting into original partition") + log("Mark new app invalid and revert to the original partition!") nxt = cur.get_next_update() - nxt.set_boot() with open("step.py", "w") as f: - f.write("STEP=4\\nEXPECT=\\"" + nxt.info()[4] + "\\"\\n") - machine.reset() + f.write("STEP=4\\nEXPECT=\\"" + nxt.info()[4] + "\\"\\nPASS=" + str(PASS) + "\\n") + if Partition.check_rollback_is_possible(): + Partition.mark_app_invalid_rollback_and_reboot() elif STEP == 4: - log("Confirming boot ok and DONE!") - Partition.mark_app_valid_cancel_rollback() - import os - os.remove("step.py") - os.remove("boot.py") - os.rename("boot-orig.py", "boot.py") - print("\\nSUCCESS!\\n\\x04\\x04") + if PASS == 1: + with open("step.py", "w") as f: + f.write("STEP=0\\nEXPECT=\\"" + cur_name + "\\"\\nPASS=2\\n") + + with open("boot.py", "w") as f: + f.write("import partition_ota") + + print("\\nGoing for pass 2\\n") + elif PASS == 2: + log("Confirming boot ok and DONE!") + Partition.mark_app_valid_cancel_rollback() + import os + os.remove("step.py") + os.remove("boot.py") + os.rename("boot-orig.py", "boot.py") + print("\\nSUCCESS!\\n") + machine.reset() """ ) -def copy_partition(src, dest): +def copy_partition_incr(src, dest): log("Partition copy: {} --> {}".format(src.info(), dest.info())) sz = src.info()[3] - if dest.info()[3] != sz: - raise ValueError("Sizes don't match: {} vs {}".format(sz, dest.info()[3])) - addr = 0 blk = bytearray(4096) + addr = 0 + handle = dest.ota_begin() while addr < sz: if sz - addr < 4096: blk = blk[: sz - addr] @@ -98,20 +115,115 @@ def copy_partition(src, dest): # need to show progress to run-tests.py else it times out print(" ... 0x{:06x}".format(addr)) src.readblocks(addr >> 12, blk) - dest.writeblocks(addr >> 12, blk) + dest.ota_write(handle, blk) addr += len(blk) + dest.ota_end(handle) -# get things started by copying the current partition into the next slot and rebooting -print("Copying current to next partition") +def copy_partition_offset(src, dest): + log("Partition copy: {} --> {}".format(src.info(), dest.info())) + sz = src.info()[3] + blk = bytearray(4096) + addr = 0 + handle = dest.ota_begin() + while addr < sz: + if sz - addr < 4096: + blk = blk[: sz - addr] + if addr & 0xFFFF == 0: + # need to show progress to run-tests else it times out + print(" ... 0x{:06x}".format(addr)) + src.readblocks(addr >> 12, blk) + dest.ota_write_with_offset(handle, blk, addr) + addr += len(blk) + dest.ota_end(handle) + + +# Test current partition if it holds a valid app description. +try: + if cur.app_state() == "valid": + print("TEST [SUCESSFUL]: Current partition contains a valid application") + else: + print( + 'TEST [FAILED]: Current partition state is "{}" expected "valid"'.format( + cur.app_state() + ) + ) + raise SystemExit +except OSError as e: + print("TEST [FAILED]: Current partition state error is {}".format(e)) + +# Test to overwrite the running partition +try: + copy_partition_incr(cur, cur) +except OSError as e: + print("TEST [SUCESSFUL]: Prevent overwriting the running partition. Message: {}".format(e)) +else: + print("TEST [FAILED]: Current partition overwritten") + raise SystemExit + nxt = cur.get_next_update() -copy_partition(cur, nxt) + +# Test if the next partition already contains an app. This test is better to be conducted on an empty partition +try: + nxt.app_state() +except OSError as e: + print( + "TEST [SUCESSFUL]: The next ota partition does not contain an app. Message: {}".format(e) + ) +else: + print( + 'TEST [WARNING]: The state of the next ota partition is "{}". You should run the test on a freshly erased device'.format( + nxt.app_state() + ) + ) + +# Get things started by copying the current partition into the next slot and rebooting +# On the first pass use the incremental copy method +# On the second pass use the offset copy method +print("Copying current to next partition") +if PASS == 1: + # Get ESP-IDF version as integer + cur_desc = cur.app_description()[5] + ver = int(cur_desc[1] + cur_desc[3]) + if ver >= 42: + copy_partition_offset(cur, nxt) + else: + print( + "TEST [SKIP]: Function 'ota_write_with_offset' is available since ESP-IDF version 4.2. Current ESP-IDF version {}.{}".format( + cur_desc[1], cur_desc[3] + ) + ) + PASS = 2 + copy_partition_incr(cur, nxt) +else: + copy_partition_incr(cur, nxt) print("Partition copied, booting into it") -nxt.set_boot() + +# Test next partition state +try: + nxt_sha = nxt.app_description()[6] + cur_sha = cur.app_description()[6] + if cur_sha == nxt_sha: + print("TEST [SUCESSFUL]: The SHA256 of current and next partition are the same") + else: + print( + "TEST [FAILED]: The SHA256 of current and next partition are different. Must be the same" + ) + raise SystemExit +except OSError as e: + print("TEST [FAILED]: Next partition is not valid. Error: {}".format(e)) + raise SystemExit + +try: + nxt.set_boot() +except OSError as e: + print("TEST [FAILED]: Can not set the new partition for the next boot. Error: {}".format(e)) +else: + print("TEST [SUCESSFUL]: The next partition is set for boot") # the step.py file is used to keep track of state across reboots # EXPECT is the name of the partition we expect to reboot into with open("step.py", "w") as f: - f.write('STEP=0\nEXPECT="' + nxt.info()[4] + '"\n') + f.write('STEP=0\nEXPECT="' + nxt.info()[4] + '"\nPASS=' + str(PASS) + "\n") machine.reset()