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

Skip to content

Commit 954c57f

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 esp32/ota: Implement ESP-IDF OTA functionality.
1 parent bc424dd commit 954c57f

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
@@ -133,15 +133,154 @@ methods to enable over-the-air (OTA) updates.
133133

134134
.. classmethod:: Partition.mark_app_valid_cancel_rollback()
135135

136-
Signals that the current boot is considered successful.
137-
Calling ``mark_app_valid_cancel_rollback`` is required on the first boot of a new
138-
partition to avoid an automatic rollback at the next boot.
136+
Signals that the current boot is considered successful by writing to the "otadata"
137+
partition. Calling ``mark_app_valid_cancel_rollback`` is required on the first boot of a
138+
new partition to avoid an automatic rollback at the next boot.
139139
This uses the ESP-IDF "app rollback" feature with "CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE"
140140
and an ``OSError(-261)`` is raised if called on firmware that doesn't have the
141141
feature enabled.
142142
It is OK to call ``mark_app_valid_cancel_rollback`` on every boot and it is not
143143
necessary when booting firmware that was loaded using esptool.
144144

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

ports/esp32/esp32_partition.c

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,130 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_partition_mark_app_valid_cancel_rollback_
265265
STATIC MP_DEFINE_CONST_CLASSMETHOD_OBJ(esp32_partition_mark_app_valid_cancel_rollback_obj,
266266
MP_ROM_PTR(&esp32_partition_mark_app_valid_cancel_rollback_fun_obj));
267267

268+
STATIC mp_obj_t esp32_partition_mark_app_invalid_rollback_and_reboot(mp_obj_t cls_in) {
269+
check_esp_err(esp_ota_mark_app_invalid_rollback_and_reboot());
270+
return mp_const_none;
271+
}
272+
STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_partition_mark_app_invalid_rollback_and_reboot_fun_obj,
273+
esp32_partition_mark_app_invalid_rollback_and_reboot);
274+
STATIC MP_DEFINE_CONST_CLASSMETHOD_OBJ(esp32_mark_app_invalid_rollback_and_reboot_obj,
275+
MP_ROM_PTR(&esp32_partition_mark_app_invalid_rollback_and_reboot_fun_obj));
276+
277+
STATIC mp_obj_t esp32_check_rollback_is_possible(mp_obj_t cls_in) {
278+
return mp_obj_new_bool(esp_ota_check_rollback_is_possible());
279+
}
280+
STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_check_rollback_is_possible_fun_obj, esp32_check_rollback_is_possible);
281+
STATIC MP_DEFINE_CONST_CLASSMETHOD_OBJ(esp32_check_rollback_is_possible_obj, MP_ROM_PTR(&esp32_check_rollback_is_possible_fun_obj));
282+
283+
STATIC mp_obj_t esp32_app_description(mp_obj_t self_in) {
284+
esp32_partition_obj_t *self = MP_OBJ_TO_PTR(self_in);
285+
esp_app_desc_t app;
286+
287+
check_esp_err(esp_ota_get_partition_description(self->part, &app));
288+
289+
mp_obj_t tuple[] = {
290+
mp_obj_new_int_from_uint(app.secure_version),
291+
mp_obj_new_str(app.version, strlen(app.version)),
292+
mp_obj_new_str(app.project_name, strlen(app.project_name)),
293+
mp_obj_new_str(app.time, strlen(app.time)),
294+
mp_obj_new_str(app.date, strlen(app.date)),
295+
mp_obj_new_str(app.idf_ver, strlen(app.idf_ver)),
296+
mp_obj_new_bytes(app.app_elf_sha256, 32)
297+
};
298+
return mp_obj_new_tuple(7, tuple);
299+
}
300+
STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_app_description_obj, esp32_app_description);
301+
302+
STATIC mp_obj_t esp32_app_get_state(mp_obj_t self_in) {
303+
esp32_partition_obj_t *self = MP_OBJ_TO_PTR(self_in);
304+
char *ret = NULL;
305+
esp_ota_img_states_t state;
306+
307+
check_esp_err(esp_ota_get_state_partition(self->part, &state));
308+
309+
switch (state) {
310+
// Monitor the first boot. In bootloader this state is changed to ESP_OTA_IMG_PENDING_VERIFY.
311+
case ESP_OTA_IMG_NEW:
312+
ret = "new";
313+
break;
314+
// First boot for this app. If this state persists during second boot, then it will be changed to ABORTED.
315+
case ESP_OTA_IMG_PENDING_VERIFY:
316+
ret = "verify";
317+
break;
318+
// App was confirmed as workable. App can boot and work without limits.
319+
case ESP_OTA_IMG_VALID:
320+
ret = "valid";
321+
break;
322+
// App was confirmed as non-workable. This app will not be selected to boot at all.
323+
case ESP_OTA_IMG_INVALID:
324+
ret = "invalid";
325+
break;
326+
// 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.
327+
case ESP_OTA_IMG_ABORTED:
328+
ret = "aborted";
329+
break;
330+
// App can boot and work without limits.
331+
default:
332+
ret = "undefined";
333+
}
334+
return mp_obj_new_str(ret, strlen(ret));
335+
}
336+
STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_app_get_state_obj, esp32_app_get_state);
337+
338+
STATIC mp_obj_t esp32_ota_begin(size_t n_args, const mp_obj_t *args) {
339+
esp32_partition_obj_t *self = MP_OBJ_TO_PTR(args[0]);
340+
esp_ota_handle_t handle;
341+
size_t image_size = 0;
342+
343+
if (n_args == 2) {
344+
image_size = mp_obj_get_int(args[1]);
345+
}
346+
check_esp_err(esp_ota_begin(self->part, image_size, &handle));
347+
return mp_obj_new_int_from_uint(handle);
348+
}
349+
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_ota_begin_obj, 1, 2, esp32_ota_begin);
350+
351+
STATIC mp_obj_t esp32_ota_write(mp_obj_t self_in, const mp_obj_t handle_in, const mp_obj_t data_in) {
352+
const esp_ota_handle_t handle = mp_obj_get_int(handle_in);
353+
mp_buffer_info_t bufinfo;
354+
mp_get_buffer_raise(data_in, &bufinfo, MP_BUFFER_READ);
355+
356+
check_esp_err(esp_ota_write(handle, bufinfo.buf, bufinfo.len));
357+
return mp_const_none;
358+
}
359+
STATIC MP_DEFINE_CONST_FUN_OBJ_3(esp32_ota_write_obj, esp32_ota_write);
360+
361+
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0)
362+
STATIC mp_obj_t esp32_ota_write_with_offset(size_t n_args, const mp_obj_t *args) {
363+
esp_ota_handle_t handle = mp_obj_get_int(args[1]);
364+
mp_buffer_info_t bufinfo;
365+
mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_READ);
366+
const uint32_t offset = mp_obj_get_int(args[3]);
367+
368+
check_esp_err(esp_ota_write_with_offset(handle, bufinfo.buf, bufinfo.len, offset));
369+
return mp_const_none;
370+
}
371+
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_ota_write_with_offset_obj, 4, 4, esp32_ota_write_with_offset);
372+
#endif
373+
374+
STATIC mp_obj_t esp32_ota_end(mp_obj_t self_in, const mp_obj_t handle_in) {
375+
const esp_ota_handle_t handle = mp_obj_get_int(handle_in);
376+
377+
check_esp_err(esp_ota_end(handle));
378+
return mp_const_none;
379+
}
380+
STATIC MP_DEFINE_CONST_FUN_OBJ_2(esp32_ota_end_obj, esp32_ota_end);
381+
382+
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0)
383+
STATIC mp_obj_t esp32_ota_abort(mp_obj_t self_in, const mp_obj_t handle_in) {
384+
esp_ota_handle_t handle = mp_obj_get_int(handle_in);
385+
386+
check_esp_err(esp_ota_abort(handle));
387+
return mp_const_none;
388+
}
389+
STATIC MP_DEFINE_CONST_FUN_OBJ_2(esp32_ota_abort_obj, esp32_ota_abort);
390+
#endif
391+
268392
STATIC const mp_rom_map_elem_t esp32_partition_locals_dict_table[] = {
269393
{ MP_ROM_QSTR(MP_QSTR_find), MP_ROM_PTR(&esp32_partition_find_obj) },
270394

@@ -275,8 +399,22 @@ STATIC const mp_rom_map_elem_t esp32_partition_locals_dict_table[] = {
275399

276400
{ MP_ROM_QSTR(MP_QSTR_set_boot), MP_ROM_PTR(&esp32_partition_set_boot_obj) },
277401
{ MP_ROM_QSTR(MP_QSTR_mark_app_valid_cancel_rollback), MP_ROM_PTR(&esp32_partition_mark_app_valid_cancel_rollback_obj) },
402+
{ MP_ROM_QSTR(MP_QSTR_mark_app_invalid_rollback_and_reboot), MP_ROM_PTR(&esp32_mark_app_invalid_rollback_and_reboot_obj) },
403+
{ MP_ROM_QSTR(MP_QSTR_check_rollback_is_possible), MP_ROM_PTR(&esp32_check_rollback_is_possible_obj) },
278404
{ MP_ROM_QSTR(MP_QSTR_get_next_update), MP_ROM_PTR(&esp32_partition_get_next_update_obj) },
279405

406+
{ MP_ROM_QSTR(MP_QSTR_app_description), MP_ROM_PTR(&esp32_app_description_obj) },
407+
{ MP_ROM_QSTR(MP_QSTR_app_state), MP_ROM_PTR(&esp32_app_get_state_obj) },
408+
{ MP_ROM_QSTR(MP_QSTR_ota_begin), MP_ROM_PTR(&esp32_ota_begin_obj) },
409+
{ MP_ROM_QSTR(MP_QSTR_ota_write), MP_ROM_PTR(&esp32_ota_write_obj) },
410+
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0)
411+
{ MP_ROM_QSTR(MP_QSTR_ota_write_with_offset), MP_ROM_PTR(&esp32_ota_write_with_offset_obj) },
412+
#endif
413+
{ MP_ROM_QSTR(MP_QSTR_ota_end), MP_ROM_PTR(&esp32_ota_end_obj) },
414+
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0)
415+
{ MP_ROM_QSTR(MP_QSTR_ota_abort), MP_ROM_PTR(&esp32_ota_abort_obj) },
416+
#endif
417+
280418
{ MP_ROM_QSTR(MP_QSTR_BOOT), MP_ROM_INT(ESP32_PARTITION_BOOT) },
281419
{ MP_ROM_QSTR(MP_QSTR_RUNNING), MP_ROM_INT(ESP32_PARTITION_RUNNING) },
282420
{ MP_ROM_QSTR(MP_QSTR_TYPE_APP), MP_ROM_INT(ESP_PARTITION_TYPE_APP) },

0 commit comments

Comments
 (0)