This crate allows easy [OTA updates] for ESP32 chips using only safe Rust. The crate is completely transport agnostic, meaning it does not deal with how you transfer the new app image to the ESP.
esp-idf-svc
After writing this library I learned that the esp-idf-svc
crate [has experimental support for
OTA updates]. It's "hidden" in a module that is not visible in the generated documentation,
due to being behind a feature that is not enabled by default, nor when generating the docs.
This section will explain how to use esp-ota
in an application to write a downloaded app image
to the flash and boot from it.
The chip must have at least two app, ota_X
partitions, so it can boot from one
and write the OTA update to the other one. And a data, ota
partition to store
information about which partition the bootloader should boot from.
Create a file called partitions.csv
. You can read more about [ESP partition tables]
on espressifs website. But here is a fairly default example that works on a 4M flash:
```csv
nvs, data, nvs, 0x9000, 0x4000, otadata, data, ota, 0xd000, 0x2000, phyinit, data, phy, 0xf000, 0x1000, ota0, app, ota0, 0x10000, 0x180000, ota1, app, ota_1, 0x190000, 0x180000, ```
And tell espflash to use it in Cargo.toml
. Read more in the [cargo espflash documentation]:
toml
[package.metadata.espflash]
partition_table = "partitions.csv"
The app that you want to flash must have the correct format. It should not be the ELF executable
produced by a regular cargo build
in an ESP project, but rather the ESP32 specific
[app image format].
Both [esptool.py elf2image
] and espflash save-image
can be used to convert the binary:
$ esptool.py --chip ESP32-C3 elf2image --output my-app.bin target/release/my-app
$ espflash save-image ESP32-C3 target/release/my-app my-app.bin
To flash a new app to the next partition on the flash, include code similar to this:
```rust // This is a very unrealistic example. You usually don't store the new app in the // old app. Instead you obtain it by downloading it from somewhere or similar. const NEWAPP: &[u8] = includebytes!("../my-app.bin");
// Finds the next suitable OTA partition and erases it let mut ota = esp_ota::OtaUpdate::begin()?;
// Write the app to flash. Normally you would download
// the app and call ota.write
every time you have obtained
// a part of the app image. This example is not realistic,
// since it has the entire new app bundled.
for appchunk in NEWAPP.chunks(4096) {
ota.write(app_chunk)?;
}
// Performs validation of the newly written app image and completes the OTA update. let mut completed_ota = ota.finalize()?;
// Sets the newly written to partition as the next partition to boot from. completedota.setasbootpartition()?; // Restarts the CPU, booting into the newly written app. completed_ota.restart(); ```
And if [the rollback feature] is enabled, you need to validate that the new app works as intended, or perform a rollback. Read more in the espressif documentation on [app rollback].
rust
fn main() {
if is_working_as_intended() {
esp_ota::mark_app_valid();
} else {
esp_ota::rollback_and_reboot().expect("Failed to roll back to working app");
}
}