kopia lustrzana https://github.com/micropython/micropython
Merge 624174aa18
into 5114f2c1ea
commit
3459ede422
|
@ -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
|
||||
~~~~~~~~~
|
||||
|
||||
|
|
|
@ -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) },
|
||||
|
|
|
@ -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()
|
||||
|
|
Ładowanie…
Reference in New Issue