Skip to content

Commit 2c8b347

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 5f058e9 commit 2c8b347

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

157157
.. classmethod:: Partition.mark_app_valid_cancel_rollback()
158158

159-
Signals that the current boot is considered successful.
160-
Calling ``mark_app_valid_cancel_rollback`` is required on the first boot of a new
161-
partition to avoid an automatic rollback at the next boot.
159+
Signals that the current boot is considered successful by writing to the "otadata"
160+
partition. Calling ``mark_app_valid_cancel_rollback`` is required on the first boot of a
161+
new partition to avoid an automatic rollback at the next boot.
162162
This uses the ESP-IDF "app rollback" feature with "CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE"
163163
and an ``OSError(-261)`` is raised if called on firmware that doesn't have the
164164
feature enabled.
165165
It is OK to call ``mark_app_valid_cancel_rollback`` on every boot and it is not
166166
necessary when booting firmware that was loaded using esptool.
167167

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

ports/esp32/esp32_partition.c

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,130 @@ static MP_DEFINE_CONST_FUN_OBJ_1(esp32_partition_mark_app_valid_cancel_rollback_
297297
static MP_DEFINE_CONST_CLASSMETHOD_OBJ(esp32_partition_mark_app_valid_cancel_rollback_obj,
298298
MP_ROM_PTR(&esp32_partition_mark_app_valid_cancel_rollback_fun_obj));
299299

300+
static mp_obj_t esp32_partition_mark_app_invalid_rollback_and_reboot(mp_obj_t cls_in) {
301+
check_esp_err(esp_ota_mark_app_invalid_rollback_and_reboot());
302+
return mp_const_none;
303+
}
304+
static MP_DEFINE_CONST_FUN_OBJ_1(esp32_partition_mark_app_invalid_rollback_and_reboot_fun_obj,
305+
esp32_partition_mark_app_invalid_rollback_and_reboot);
306+
static MP_DEFINE_CONST_CLASSMETHOD_OBJ(esp32_mark_app_invalid_rollback_and_reboot_obj,
307+
MP_ROM_PTR(&esp32_partition_mark_app_invalid_rollback_and_reboot_fun_obj));
308+
309+
static mp_obj_t esp32_check_rollback_is_possible(mp_obj_t cls_in) {
310+
return mp_obj_new_bool(esp_ota_check_rollback_is_possible());
311+
}
312+
static MP_DEFINE_CONST_FUN_OBJ_1(esp32_check_rollback_is_possible_fun_obj, esp32_check_rollback_is_possible);
313+
static MP_DEFINE_CONST_CLASSMETHOD_OBJ(esp32_check_rollback_is_possible_obj, MP_ROM_PTR(&esp32_check_rollback_is_possible_fun_obj));
314+
315+
static mp_obj_t esp32_app_description(mp_obj_t self_in) {
316+
esp32_partition_obj_t *self = MP_OBJ_TO_PTR(self_in);
317+
esp_app_desc_t app;
318+
319+
check_esp_err(esp_ota_get_partition_description(self->part, &app));
320+
321+
mp_obj_t tuple[] = {
322+
mp_obj_new_int_from_uint(app.secure_version),
323+
mp_obj_new_str(app.version, strlen(app.version)),
324+
mp_obj_new_str(app.project_name, strlen(app.project_name)),
325+
mp_obj_new_str(app.time, strlen(app.time)),
326+
mp_obj_new_str(app.date, strlen(app.date)),
327+
mp_obj_new_str(app.idf_ver, strlen(app.idf_ver)),
328+
mp_obj_new_bytes(app.app_elf_sha256, 32)
329+
};
330+
return mp_obj_new_tuple(7, tuple);
331+
}
332+
static MP_DEFINE_CONST_FUN_OBJ_1(esp32_app_description_obj, esp32_app_description);
333+
334+
static mp_obj_t esp32_app_get_state(mp_obj_t self_in) {
335+
esp32_partition_obj_t *self = MP_OBJ_TO_PTR(self_in);
336+
char *ret = NULL;
337+
esp_ota_img_states_t state;
338+
339+
check_esp_err(esp_ota_get_state_partition(self->part, &state));
340+
341+
switch (state) {
342+
// Monitor the first boot. In bootloader this state is changed to ESP_OTA_IMG_PENDING_VERIFY.
343+
case ESP_OTA_IMG_NEW:
344+
ret = "new";
345+
break;
346+
// First boot for this app. If this state persists during second boot, then it will be changed to ABORTED.
347+
case ESP_OTA_IMG_PENDING_VERIFY:
348+
ret = "verify";
349+
break;
350+
// App was confirmed as workable. App can boot and work without limits.
351+
case ESP_OTA_IMG_VALID:
352+
ret = "valid";
353+
break;
354+
// App was confirmed as non-workable. This app will not be selected to boot at all.
355+
case ESP_OTA_IMG_INVALID:
356+
ret = "invalid";
357+
break;
358+
// 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.
359+
case ESP_OTA_IMG_ABORTED:
360+
ret = "aborted";
361+
break;
362+
// App can boot and work without limits.
363+
default:
364+
ret = "undefined";
365+
}
366+
return mp_obj_new_str(ret, strlen(ret));
367+
}
368+
static MP_DEFINE_CONST_FUN_OBJ_1(esp32_app_get_state_obj, esp32_app_get_state);
369+
370+
static mp_obj_t esp32_ota_begin(size_t n_args, const mp_obj_t *args) {
371+
esp32_partition_obj_t *self = MP_OBJ_TO_PTR(args[0]);
372+
esp_ota_handle_t handle;
373+
size_t image_size = 0;
374+
375+
if (n_args == 2) {
376+
image_size = mp_obj_get_int(args[1]);
377+
}
378+
check_esp_err(esp_ota_begin(self->part, image_size, &handle));
379+
return mp_obj_new_int_from_uint(handle);
380+
}
381+
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_ota_begin_obj, 1, 2, esp32_ota_begin);
382+
383+
static mp_obj_t esp32_ota_write(mp_obj_t self_in, const mp_obj_t handle_in, const mp_obj_t data_in) {
384+
const esp_ota_handle_t handle = mp_obj_get_int(handle_in);
385+
mp_buffer_info_t bufinfo;
386+
mp_get_buffer_raise(data_in, &bufinfo, MP_BUFFER_READ);
387+
388+
check_esp_err(esp_ota_write(handle, bufinfo.buf, bufinfo.len));
389+
return mp_const_none;
390+
}
391+
static MP_DEFINE_CONST_FUN_OBJ_3(esp32_ota_write_obj, esp32_ota_write);
392+
393+
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0)
394+
static mp_obj_t esp32_ota_write_with_offset(size_t n_args, const mp_obj_t *args) {
395+
esp_ota_handle_t handle = mp_obj_get_int(args[1]);
396+
mp_buffer_info_t bufinfo;
397+
mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_READ);
398+
const uint32_t offset = mp_obj_get_int(args[3]);
399+
400+
check_esp_err(esp_ota_write_with_offset(handle, bufinfo.buf, bufinfo.len, offset));
401+
return mp_const_none;
402+
}
403+
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_ota_write_with_offset_obj, 4, 4, esp32_ota_write_with_offset);
404+
#endif
405+
406+
static mp_obj_t esp32_ota_end(mp_obj_t self_in, const mp_obj_t handle_in) {
407+
const esp_ota_handle_t handle = mp_obj_get_int(handle_in);
408+
409+
check_esp_err(esp_ota_end(handle));
410+
return mp_const_none;
411+
}
412+
static MP_DEFINE_CONST_FUN_OBJ_2(esp32_ota_end_obj, esp32_ota_end);
413+
414+
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0)
415+
static mp_obj_t esp32_ota_abort(mp_obj_t self_in, const mp_obj_t handle_in) {
416+
esp_ota_handle_t handle = mp_obj_get_int(handle_in);
417+
418+
check_esp_err(esp_ota_abort(handle));
419+
return mp_const_none;
420+
}
421+
static MP_DEFINE_CONST_FUN_OBJ_2(esp32_ota_abort_obj, esp32_ota_abort);
422+
#endif
423+
300424
static const mp_rom_map_elem_t esp32_partition_locals_dict_table[] = {
301425
{ MP_ROM_QSTR(MP_QSTR_find), MP_ROM_PTR(&esp32_partition_find_obj) },
302426

@@ -307,8 +431,22 @@ static const mp_rom_map_elem_t esp32_partition_locals_dict_table[] = {
307431

308432
{ MP_ROM_QSTR(MP_QSTR_set_boot), MP_ROM_PTR(&esp32_partition_set_boot_obj) },
309433
{ MP_ROM_QSTR(MP_QSTR_mark_app_valid_cancel_rollback), MP_ROM_PTR(&esp32_partition_mark_app_valid_cancel_rollback_obj) },
434+
{ MP_ROM_QSTR(MP_QSTR_mark_app_invalid_rollback_and_reboot), MP_ROM_PTR(&esp32_mark_app_invalid_rollback_and_reboot_obj) },
435+
{ MP_ROM_QSTR(MP_QSTR_check_rollback_is_possible), MP_ROM_PTR(&esp32_check_rollback_is_possible_obj) },
310436
{ MP_ROM_QSTR(MP_QSTR_get_next_update), MP_ROM_PTR(&esp32_partition_get_next_update_obj) },
311437

438+
{ MP_ROM_QSTR(MP_QSTR_app_description), MP_ROM_PTR(&esp32_app_description_obj) },
439+
{ MP_ROM_QSTR(MP_QSTR_app_state), MP_ROM_PTR(&esp32_app_get_state_obj) },
440+
{ MP_ROM_QSTR(MP_QSTR_ota_begin), MP_ROM_PTR(&esp32_ota_begin_obj) },
441+
{ MP_ROM_QSTR(MP_QSTR_ota_write), MP_ROM_PTR(&esp32_ota_write_obj) },
442+
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0)
443+
{ MP_ROM_QSTR(MP_QSTR_ota_write_with_offset), MP_ROM_PTR(&esp32_ota_write_with_offset_obj) },
444+
#endif
445+
{ MP_ROM_QSTR(MP_QSTR_ota_end), MP_ROM_PTR(&esp32_ota_end_obj) },
446+
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0)
447+
{ MP_ROM_QSTR(MP_QSTR_ota_abort), MP_ROM_PTR(&esp32_ota_abort_obj) },
448+
#endif
449+
312450
{ MP_ROM_QSTR(MP_QSTR_BOOT), MP_ROM_INT(ESP32_PARTITION_BOOT) },
313451
{ MP_ROM_QSTR(MP_QSTR_RUNNING), MP_ROM_INT(ESP32_PARTITION_RUNNING) },
314452
{ MP_ROM_QSTR(MP_QSTR_TYPE_APP), MP_ROM_INT(ESP_PARTITION_TYPE_APP) },

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy