Skip to content

Commit 3ce6443

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 d3f6ce7 commit 3ce6443

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