Skip to content

Commit 4db74d3

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 13b13d1 commit 4db74d3

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)
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