Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ab34d54

Browse files
author
Emil Kondayan
committed
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
1 parent 782d5b2 commit ab34d54

File tree

3 files changed

+423
-34
lines changed

3 files changed

+423
-34
lines changed

docs/library/esp32.rst

Lines changed: 142 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,15 +105,154 @@ methods to enable over-the-air (OTA) updates.
105105

106106
.. classmethod:: Partition.mark_app_valid_cancel_rollback()
107107

108-
Signals that the current boot is considered successful.
109-
Calling ``mark_app_valid_cancel_rollback`` is required on the first boot of a new
110-
partition to avoid an automatic rollback at the next boot.
108+
Signals that the current boot is considered successful by writing to the "otadata"
109+
partition. Calling ``mark_app_valid_cancel_rollback`` is required on the first boot of a
110+
new partition to avoid an automatic rollback at the next boot.
111111
This uses the ESP-IDF "app rollback" feature with "CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE"
112112
and an ``OSError(-261)`` is raised if called on firmware that doesn't have the
113113
feature enabled.
114114
It is OK to call ``mark_app_valid_cancel_rollback`` on every boot and it is not
115115
necessary when booting firmare that was loaded using esptool.
116116

117+
.. classmethod:: Partition.mark_app_invalid_rollback_and_reboot()
118+
119+
Mark the current app partition invalid by writing to the "otadata"
120+
partition, rollback to the previous workable app and then reboots.
121+
If the rollback is sucessfull, the device will reset. If the flash does not have
122+
at least one valid app (except the running app) then rollback is not possible.
123+
If the "CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE" option is set, and a reset occurs without
124+
calling either
125+
``mark_app_valid_cancel_rollback()`` or ``mark_app_invalid_rollback_and_reboot()``
126+
function then the application is rolled back.
127+
This uses the ESP-IDF "app rollback" feature with "CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE"
128+
and an ``OSError(-261)`` is raised if called on firmware that doesn't have the
129+
feature enabled.
130+
131+
.. classmethod:: Partition.check_rollback_is_possible()
132+
133+
Returns True if at least one valid app is found(except the running one)
134+
Returns False otherwise.
135+
136+
Checks if there is a bootable application on the slots which can be booted in case of
137+
rollback. For an application to be considered bootable, the following conditions
138+
must be met: the app must be marked to be valid(marked in otadata as not UNDEFINED,
139+
INVALID or ABORTED and crc is good); must be marked bootable; secure_version of
140+
app >= secure_version of efuse (if anti-rollback is enabled).
141+
142+
.. method:: Partition.app_description()
143+
144+
Returns a 7-tuple ``(secure_version, version, project_name, compile_time, compile_date,
145+
idf_version, elf_sha256)`` which is a description of the app partition pointed by the
146+
object.
147+
148+
If the object does not contain an app partition, OsError exception will be raised:
149+
``ESP_ERR_NOT_FOUND`` no app description structure is found. Magic word is incorrect.
150+
``ESP_ERR_NOT_SUPPORTED`` Partition is not application.
151+
``ESP_ERR_INVALID_ARG`` Partition’s offset exceeds partition size.
152+
``ESP_ERR_INVALID_SIZE`` Read would go out of bounds of the partition.
153+
154+
.. method:: Partition.app_state()
155+
156+
Returns the app state of a valid ota partition. It can be one of the following strings:
157+
``new``: Monitor the first boot. In bootloader this state is changed to "pending verify"
158+
``verify``: First boot for this app. If this state persists during second boot, then it
159+
will be changed to ``aborted``
160+
``valid``: App was confirmed as workable. App can boot and work without limits
161+
``invalid``: App was confirmed as non-workable. This app will not be selected to
162+
boot at all
163+
``aborted``: App could not confirmed as workable or non-workable. In bootloader
164+
"pending verify" state will be changed to ``aborted``. This app will not be selected
165+
to boot at all
166+
``undefined``: App can boot and work without limits
167+
168+
One of the following OsError can be raised:
169+
``ESP_ERR_NOT_SUPPORTED``: Partition is not ota.
170+
``ESP_ERR_NOT_FOUND``: Partition table does not have otadata or state was not found for
171+
given partition.
172+
173+
.. method:: Partition.ota_begin(image_size)
174+
175+
Prepares the partition for an OTA update and start the process of updating.
176+
The target partition is erased to the specified image size. If the size of the
177+
artition is not known in advance, the entire partition is eraesd.
178+
179+
Note: This function is available since ESP-IDF version 4.3
180+
181+
Note: If the rollback option is enabled and the running application has the
182+
"pending verify" state then it will lead to the ESP_ERR_OTA_ROLLBACK_INVALID_STATE error.
183+
Confirm the running app before to run download a new app, use
184+
mark_app_valid_cancel_rollback() function
185+
186+
``image_size``: The size of the image to be written. 0 indicates a partition of unknown
187+
size. If you know the size of the partition in advance, you can pass the size in bytes.
188+
The default value is "0"
189+
190+
Returns an integer handle, associated with the ota update process. The update
191+
process must be ended by calling ``ota_end()`. Since ESP-IDF version 4.3,
192+
an update process can also be ended by ``ota_abort()``.
193+
194+
An OsError can be raised if there is an error with the update process:
195+
``ESP_ERR_INVALID_ARG``: Partition doesn’t point to an OTA app partition
196+
``ESP_ERR_NO_MEM``: Cannot allocate memory for OTA operation
197+
``ESP_ERR_OTA_PARTITION_CONFLICT``: Partition holds the currently running firmware,
198+
cannot update in place
199+
``ESP_ERR_NOT_FOUND``: Partition argument not found in partition table
200+
``ESP_ERR_OTA_SELECT_INFO_INVALID``: The OTA data partition contains invalid data
201+
``ESP_ERR_INVALID_SIZE``: Partition doesn’t fit in configured flash size
202+
``ESP_ERR_FLASH_OP_TIMEOUT`` or ``ESP_ERR_FLASH_OP_FAIL``: Flash write failed
203+
``ESP_ERR_OTA_ROLLBACK_INVALID_STATE``: If the running app has not confirmed state. Before
204+
performing an update, the application must be valid
205+
206+
.. method:: Partition.ota_write(handle, buf)
207+
208+
Write OTA update data to the target partition. This function can be called multiple times
209+
as data is received during the OTA operation. Data is written sequentially to the partition.
210+
211+
``handle``: The handle returned by ``ota_begin()``
212+
``buf``: Data buffer to write
213+
214+
An OsError can be raised if there is an error with the update process:
215+
``ESP_ERR_INVALID_ARG``: Handle is invalid
216+
``ESP_ERR_OTA_VALIDATE_FAILED``: First byte of image contains invalid app image magic byte
217+
``ESP_ERR_FLASH_OP_TIMEOUT`` or ``ESP_ERR_FLASH_OP_FAIL``: Flash write failed
218+
``ESP_ERR_OTA_SELECT_INFO_INVALID``: OTA data partition has invalid contents
219+
220+
.. method:: Partition.ota_write_with_offset(handle, buffer, offset)
221+
222+
Write OTA update data to the target partition. This function writes data in non contiguous
223+
manner. If flash encryption is enabled, data should be 16 byte aligned.
224+
225+
Note: This function is available since ESP-IDF version 4.2
226+
227+
Note: While performing OTA, if the packets arrive out of order, esp_ota_write_with_offset()
228+
can be used to write data in non contiguous manner. Use of esp_ota_write_with_offset() in
229+
combination with esp_ota_write() is not recommended.
230+
231+
An OsError can be raised if there is an error with the update process:
232+
``ESP_ERR_INVALID_ARG``: handle is invalid
233+
``ESP_ERR_OTA_VALIDATE_FAILED``: First byte of image contains invalid app image magic byte
234+
``ESP_ERR_FLASH_OP_TIMEOUT`` or ``ESP_ERR_FLASH_OP_FAIL``: Flash write failed
235+
``ESP_ERR_OTA_SELECT_INFO_INVALID``: OTA data partition has invalid contents
236+
237+
.. method:: Partition.ota_end(handle)
238+
239+
Finish the OTA update process and validate newly written app image.
240+
241+
An OsError can be raised if there is an error with the update process:
242+
``ESP_ERR_NOT_FOUND``: OTA handle was not found
243+
``ESP_ERR_INVALID_ARG``: Handle was never written to
244+
``ESP_ERR_OTA_VALIDATE_FAILED``: OTA image is invalid (either not a valid app image, or
245+
if secure boot is enabled - signature failed to verify)
246+
``ESP_ERR_INVALID_STATE``: If flash encryption is enabled, this result indicates an internal
247+
error writing the final encrypted bytes to flash
248+
249+
.. method:: Partition.ota_abort(handle)
250+
251+
Aborts the OTA process and frees resources
252+
253+
An OsError can be raised if there is an error:
254+
``ESP_ERR_NOT_FOUND``: OTA handle was not found
255+
117256
Constants
118257
~~~~~~~~~
119258

ports/esp32/esp32_partition.c

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,130 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_partition_mark_app_valid_cancel_rollback_
218218
STATIC MP_DEFINE_CONST_CLASSMETHOD_OBJ(esp32_partition_mark_app_valid_cancel_rollback_obj,
219219
MP_ROM_PTR(&esp32_partition_mark_app_valid_cancel_rollback_fun_obj));
220220

221+
STATIC mp_obj_t esp32_partition_mark_app_invalid_rollback_and_reboot(mp_obj_t cls_in) {
222+
check_esp_err(esp_ota_mark_app_invalid_rollback_and_reboot());
223+
return mp_const_none;
224+
}
225+
STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_partition_mark_app_invalid_rollback_and_reboot_fun_obj,
226+
esp32_partition_mark_app_invalid_rollback_and_reboot);
227+
STATIC MP_DEFINE_CONST_CLASSMETHOD_OBJ(esp32_mark_app_invalid_rollback_and_reboot_obj,
228+
MP_ROM_PTR(&esp32_partition_mark_app_invalid_rollback_and_reboot_fun_obj));
229+
230+
STATIC mp_obj_t esp32_check_rollback_is_possible(mp_obj_t cls_in) {
231+
return mp_obj_new_bool(esp_ota_check_rollback_is_possible());
232+
}
233+
STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_check_rollback_is_possible_fun_obj, esp32_check_rollback_is_possible);
234+
STATIC MP_DEFINE_CONST_CLASSMETHOD_OBJ(esp32_check_rollback_is_possible_obj, MP_ROM_PTR(&esp32_check_rollback_is_possible_fun_obj));
235+
236+
STATIC mp_obj_t esp32_app_description(mp_obj_t self_in) {
237+
esp32_partition_obj_t *self = MP_OBJ_TO_PTR(self_in);
238+
esp_app_desc_t app;
239+
240+
check_esp_err(esp_ota_get_partition_description(self->part, &app));
241+
242+
mp_obj_t tuple[] = {
243+
mp_obj_new_int_from_uint(app.secure_version),
244+
mp_obj_new_str(app.version, strlen(app.version)),
245+
mp_obj_new_str(app.project_name, strlen(app.project_name)),
246+
mp_obj_new_str(app.time, strlen(app.time)),
247+
mp_obj_new_str(app.date, strlen(app.date)),
248+
mp_obj_new_str(app.idf_ver, strlen(app.idf_ver)),
249+
mp_obj_new_bytes(app.app_elf_sha256, 32)
250+
};
251+
return mp_obj_new_tuple(7, tuple);
252+
}
253+
STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_app_description_obj, esp32_app_description);
254+
255+
STATIC mp_obj_t esp32_app_get_state(mp_obj_t self_in) {
256+
esp32_partition_obj_t *self = MP_OBJ_TO_PTR(self_in);
257+
char *ret = NULL;
258+
esp_ota_img_states_t state;
259+
260+
check_esp_err(esp_ota_get_state_partition(self->part, &state));
261+
262+
switch (state) {
263+
// Monitor the first boot. In bootloader this state is changed to ESP_OTA_IMG_PENDING_VERIFY.
264+
case ESP_OTA_IMG_NEW:
265+
ret = "new";
266+
break;
267+
// First boot for this app. If this state persists during second boot, then it will be changed to ABORTED.
268+
case ESP_OTA_IMG_PENDING_VERIFY:
269+
ret = "verify";
270+
break;
271+
// App was confirmed as workable. App can boot and work without limits.
272+
case ESP_OTA_IMG_VALID:
273+
ret = "valid";
274+
break;
275+
// App was confirmed as non-workable. This app will not be selected to boot at all.
276+
case ESP_OTA_IMG_INVALID:
277+
ret = "invalid";
278+
break;
279+
// 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.
280+
case ESP_OTA_IMG_ABORTED:
281+
ret = "aborted";
282+
break;
283+
// App can boot and work without limits.
284+
default:
285+
ret = "undefined";
286+
}
287+
return mp_obj_new_str(ret, strlen(ret));
288+
}
289+
STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_app_get_state_obj, esp32_app_get_state);
290+
291+
STATIC mp_obj_t esp32_ota_begin(size_t n_args, const mp_obj_t *args) {
292+
esp32_partition_obj_t *self = MP_OBJ_TO_PTR(args[0]);
293+
esp_ota_handle_t handle;
294+
size_t image_size = 0;
295+
296+
if (n_args == 2) {
297+
image_size = mp_obj_get_int(args[1]);
298+
}
299+
check_esp_err(esp_ota_begin(self->part, image_size, &handle));
300+
return mp_obj_new_int_from_uint(handle);
301+
}
302+
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_ota_begin_obj, 1, 2, esp32_ota_begin);
303+
304+
STATIC mp_obj_t esp32_ota_write(mp_obj_t self_in, const mp_obj_t handle_in, const mp_obj_t data_in) {
305+
const esp_ota_handle_t handle = mp_obj_get_int(handle_in);
306+
mp_buffer_info_t bufinfo;
307+
mp_get_buffer_raise(data_in, &bufinfo, MP_BUFFER_READ);
308+
309+
check_esp_err(esp_ota_write(handle, bufinfo.buf, bufinfo.len));
310+
return mp_const_none;
311+
}
312+
STATIC MP_DEFINE_CONST_FUN_OBJ_3(esp32_ota_write_obj, esp32_ota_write);
313+
314+
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0)
315+
STATIC mp_obj_t esp32_ota_write_with_offset(size_t n_args, const mp_obj_t *args) {
316+
esp_ota_handle_t handle = mp_obj_get_int(args[1]);
317+
mp_buffer_info_t bufinfo;
318+
mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_READ);
319+
const uint32_t offset = mp_obj_get_int(args[3]);
320+
321+
check_esp_err(esp_ota_write_with_offset(handle, bufinfo.buf, bufinfo.len, offset));
322+
return mp_const_none;
323+
}
324+
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_ota_write_with_offset_obj, 4, 4, esp32_ota_write_with_offset);
325+
#endif
326+
327+
STATIC mp_obj_t esp32_ota_end(mp_obj_t self_in, const mp_obj_t handle_in) {
328+
const esp_ota_handle_t handle = mp_obj_get_int(handle_in);
329+
330+
check_esp_err(esp_ota_end(handle));
331+
return mp_const_none;
332+
}
333+
STATIC MP_DEFINE_CONST_FUN_OBJ_2(esp32_ota_end_obj, esp32_ota_end);
334+
335+
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0)
336+
STATIC mp_obj_t esp32_ota_abort(mp_obj_t self_in, const mp_obj_t handle_in) {
337+
esp_ota_handle_t handle = mp_obj_get_int(handle_in);
338+
339+
check_esp_err(esp_ota_abort(handle));
340+
return mp_const_none;
341+
}
342+
STATIC MP_DEFINE_CONST_FUN_OBJ_2(esp32_ota_abort_obj, esp32_ota_abort);
343+
#endif
344+
221345
STATIC const mp_rom_map_elem_t esp32_partition_locals_dict_table[] = {
222346
{ MP_ROM_QSTR(MP_QSTR_find), MP_ROM_PTR(&esp32_partition_find_obj) },
223347

@@ -228,8 +352,22 @@ STATIC const mp_rom_map_elem_t esp32_partition_locals_dict_table[] = {
228352

229353
{ MP_ROM_QSTR(MP_QSTR_set_boot), MP_ROM_PTR(&esp32_partition_set_boot_obj) },
230354
{ MP_ROM_QSTR(MP_QSTR_mark_app_valid_cancel_rollback), MP_ROM_PTR(&esp32_partition_mark_app_valid_cancel_rollback_obj) },
355+
{ MP_ROM_QSTR(MP_QSTR_mark_app_invalid_rollback_and_reboot), MP_ROM_PTR(&esp32_mark_app_invalid_rollback_and_reboot_obj) },
356+
{ MP_ROM_QSTR(MP_QSTR_check_rollback_is_possible), MP_ROM_PTR(&esp32_check_rollback_is_possible_obj) },
231357
{ MP_ROM_QSTR(MP_QSTR_get_next_update), MP_ROM_PTR(&esp32_partition_get_next_update_obj) },
232358

359+
{ MP_ROM_QSTR(MP_QSTR_app_description), MP_ROM_PTR(&esp32_app_description_obj) },
360+
{ MP_ROM_QSTR(MP_QSTR_app_state), MP_ROM_PTR(&esp32_app_get_state_obj) },
361+
{ MP_ROM_QSTR(MP_QSTR_ota_begin), MP_ROM_PTR(&esp32_ota_begin_obj) },
362+
{ MP_ROM_QSTR(MP_QSTR_ota_write), MP_ROM_PTR(&esp32_ota_write_obj) },
363+
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0)
364+
{ MP_ROM_QSTR(MP_QSTR_ota_write_with_offset), MP_ROM_PTR(&esp32_ota_write_with_offset_obj) },
365+
#endif
366+
{ MP_ROM_QSTR(MP_QSTR_ota_end), MP_ROM_PTR(&esp32_ota_end_obj) },
367+
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0)
368+
{ MP_ROM_QSTR(MP_QSTR_ota_abort), MP_ROM_PTR(&esp32_ota_abort_obj) },
369+
#endif
370+
233371
{ MP_ROM_QSTR(MP_QSTR_BOOT), MP_ROM_INT(ESP32_PARTITION_BOOT) },
234372
{ MP_ROM_QSTR(MP_QSTR_RUNNING), MP_ROM_INT(ESP32_PARTITION_RUNNING) },
235373
{ MP_ROM_QSTR(MP_QSTR_TYPE_APP), MP_ROM_INT(ESP_PARTITION_TYPE_APP) },

0 commit comments

Comments
 (0)