Skip to main content

esp_idf_svc/
ota.rs

1//! Over The Air Updates (OTA)
2//!
3//! The OTA update mechanism allows a device to update itself based on data
4//! received while the normal firmware is running (for example, over Wi-Fi or
5//! Bluetooth.)
6//!
7//! # Requirements
8//!
9//! OTA updates needs a different partition table than the default one. For being able to update
10//! the firmware while running, we need to have at least 2 OTA partitions. Learn more about
11//! partition tables on [esp-idf documentation](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/kconfig.html#config-partition-table-type).
12//!
13//! To use a different partition than the default, you should create a CSV file (or download one
14//! from [the esp-idf repository](https://github.com/espressif/esp-idf/tree/master/components/partition_table)).
15//! For example, you can use this partition table that defines 2 OTA partitions of 1,7Mb:
16//!
17//! ```
18//! nvs,      data, nvs,     ,        0x6000,
19//! otadata,  data, ota,     ,        0x2000,
20//! phy_init, data, phy,     ,        0x1000,
21//! ota_0,    app,  ota_0,   ,        1700K,
22//! ota_1,    app,  ota_1,   ,        1700K,
23//! ```
24//!
25//! Then, configure `espflash` to use this partition table by creating an `espflash.toml`:
26//!
27//! ```
28//! partition_table = "./partition-table.csv"
29//! ```
30//!
31//! Once an OTA update have been done, the ESP will continue to boot on the second OTA partition.
32//! You can reset the booting partition by using the `--erase-parts otadata` option of `espflash`.
33//! Add it to the `runner` command in your project `.cargo/config.yml` file.
34//!
35//! # Rollback
36//!
37//! Once an OTA update happened and the ESP reboots, you have the opportunity to mark the new
38//! firmware has valid, or rollback to a previously working firmware.
39//!
40//! By default, a new firmware will continue to be selected by the bootloader until it is explicitly
41//! marked as invalid. You can change this behavior by setting the
42//! `CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE` option. When enabled, if a reset happen before the
43//! firmware is marked as valid, the bootloader will automatically rollback to the previous valid
44//! firmware.
45//!
46//! To enable this option, add this line to your `sdkconfig.defaults` file:
47//! ```
48//! CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y
49//! ```
50//! Then add `--bootloader ./target/<your arch>/debug/bootloader.bin` option to the `runner` command
51//! of your `.cargo/config.yml` file. For example:
52//!
53//! ```
54//! [target.xtensa-esp32-espidf]
55//! runner = "espflash flash --monitor --erase-parts otadata --bootloader ./target/xtensa-esp32-espidf/debug/bootloader.bin"
56//! ```
57//!
58//! # Examples
59//!
60//! The following example shows approximate steps for performing an OTA update.
61//!
62//! ```
63//! // 1. Obtain an instance of OTA:
64//! let mut ota = EspOta::new().expect("obtain OTA instance");
65//!
66//! // 2. Initiate update and obtain an instance of `EspOtaUpdate`:
67//! let mut update = ota.initiate_update().expect("initiate OTA");
68//!
69//! // 3. Write the program data:
70//! while let Some(data) = my_wireless.get_ota_data() {
71//!     update.write(&data).expect("write OTA data");
72//! }
73//!
74//! // 4. Finalize update:
75//! update.complete().expect("complete OTA");
76//!
77//! // 5. Reboot:
78//! esp_idf_svc::hal::reset::restart();
79//! ```
80//! After rebooting and confirming that the new firmware works, mark it as valid.
81//! If this is not done, firmware will be rolled back.
82//!
83//! ```
84//! // Note: starting a new scope here to ensure that ota instance is dropped at the end.
85//! {
86//!     let mut ota = EspOta::new().expect("obtain OTA instance");
87//!     ota.mark_running_slot_valid().expect("mark app as valid");
88//! }
89//! ```
90
91use core::cmp::min;
92use core::fmt::Write;
93use core::marker::PhantomData;
94use core::mem;
95use core::ptr;
96
97use ::log::*;
98use embedded_svc::ota::OtaUpdateFinished;
99
100use embedded_svc::io;
101use embedded_svc::ota::{FirmwareInfoLoader, Ota, OtaUpdate};
102
103pub use embedded_svc::ota::{FirmwareInfo, LoadResult, Slot, SlotState, UpdateProgress};
104
105use crate::sys::*;
106
107use crate::io::EspIOError;
108use crate::private::{cstr::*, mutex};
109
110static TAKEN: mutex::Mutex<bool> = mutex::Mutex::new(false);
111
112#[deprecated(note = "Use `EspFirmwareInfoLoad` instead")]
113pub struct EspFirmwareInfoLoader(heapless::Vec<u8, 512>);
114
115#[allow(deprecated)]
116impl EspFirmwareInfoLoader {
117    pub const fn new() -> Self {
118        Self(heapless::Vec::new())
119    }
120
121    pub fn load(&mut self, buf: &[u8]) -> Result<LoadResult, EspError> {
122        if !self.is_loaded() {
123            let remaining = self.0.capacity() - self.0.len();
124            if remaining > 0 {
125                self.0
126                    .extend_from_slice(&buf[..min(buf.len(), remaining)])
127                    .unwrap();
128            }
129        }
130
131        Ok(if self.is_loaded() {
132            LoadResult::Loaded
133        } else {
134            LoadResult::LoadMore
135        })
136    }
137
138    pub fn is_loaded(&self) -> bool {
139        self.0.len()
140            >= mem::size_of::<esp_image_header_t>()
141                + mem::size_of::<esp_image_segment_header_t>()
142                + mem::size_of::<esp_app_desc_t>()
143    }
144
145    pub fn get_info(&self) -> Result<FirmwareInfo, EspError> {
146        if self.is_loaded() {
147            let app_desc_slice = &self.0[mem::size_of::<esp_image_header_t>()
148                + mem::size_of::<esp_image_segment_header_t>()
149                ..mem::size_of::<esp_image_header_t>()
150                    + mem::size_of::<esp_image_segment_header_t>()
151                    + mem::size_of::<esp_app_desc_t>()];
152
153            let app_desc = unsafe {
154                (app_desc_slice.as_ptr() as *const esp_app_desc_t)
155                    .as_ref()
156                    .unwrap()
157            };
158
159            let mut info = FirmwareInfo {
160                version: heapless::String::new(),
161                released: heapless::String::new(),
162                description: None,
163                signature: None,
164                download_id: None,
165            };
166
167            EspFirmwareInfoLoad::load_firmware_info(&mut info, app_desc)?;
168
169            Ok(info)
170        } else {
171            Err(EspError::from_infallible::<ESP_ERR_INVALID_SIZE>())
172        }
173    }
174}
175
176#[allow(deprecated)]
177impl Default for EspFirmwareInfoLoader {
178    fn default() -> Self {
179        Self::new()
180    }
181}
182
183#[allow(deprecated)]
184impl io::ErrorType for EspFirmwareInfoLoader {
185    type Error = EspIOError;
186}
187
188#[allow(deprecated)]
189impl FirmwareInfoLoader for EspFirmwareInfoLoader {
190    fn load(&mut self, buf: &[u8]) -> Result<LoadResult, Self::Error> {
191        Ok(EspFirmwareInfoLoader::load(self, buf)?)
192    }
193
194    fn is_loaded(&self) -> bool {
195        EspFirmwareInfoLoader::is_loaded(self)
196    }
197
198    fn get_info(&self) -> Result<FirmwareInfo, Self::Error> {
199        Ok(EspFirmwareInfoLoader::get_info(self)?)
200    }
201}
202
203/// Native ESP-IDF firmware information
204#[derive(Debug, Clone)]
205pub struct EspNativeFirmwareInfo<'a> {
206    /// Image header
207    pub image_header: &'a esp_image_header_t,
208    /// Segment header
209    pub segment_header: &'a esp_image_segment_header_t,
210    /// Application description
211    pub app_desc: &'a esp_app_desc_t,
212}
213
214/// A firmware info loader that tries to read the firmware info directly
215/// from a user-supplied buffer which can be re-used for other purposes afterwards.
216///
217/// This is a more efficient version of the now-deprecated `EspFirmwareInfoLoader`.
218pub struct EspFirmwareInfoLoad;
219
220impl EspFirmwareInfoLoad {
221    /// Fetches the native ESP-IDF firmware information from the firmware binary data chunk loaded so far.
222    ///
223    /// Returns `Some(EspNativeFirmwareInfo)` if the information was successfully fetched.
224    /// Returns `None` if the firmware data has not been loaded completely yet.
225    pub fn fetch_native<'a>(&self, data: &'a [u8]) -> Option<EspNativeFirmwareInfo<'a>> {
226        let loaded = data.len()
227            >= mem::size_of::<esp_image_header_t>()
228                + mem::size_of::<esp_image_segment_header_t>()
229                + mem::size_of::<esp_app_desc_t>();
230
231        if loaded {
232            let image_header_slice = &data[..mem::size_of::<esp_image_header_t>()];
233            let image_segment_header_slice = &data[mem::size_of::<esp_image_header_t>()
234                ..mem::size_of::<esp_image_header_t>()
235                    + mem::size_of::<esp_image_segment_header_t>()];
236            let app_desc_slice = &data[mem::size_of::<esp_image_header_t>()
237                + mem::size_of::<esp_image_segment_header_t>()
238                ..mem::size_of::<esp_image_header_t>()
239                    + mem::size_of::<esp_image_segment_header_t>()
240                    + mem::size_of::<esp_app_desc_t>()];
241
242            let image_header = unsafe {
243                (image_header_slice.as_ptr() as *const esp_image_header_t)
244                    .as_ref()
245                    .unwrap()
246            };
247
248            let segment_header = unsafe {
249                (image_segment_header_slice.as_ptr() as *const esp_image_segment_header_t)
250                    .as_ref()
251                    .unwrap()
252            };
253
254            let app_desc = unsafe {
255                (app_desc_slice.as_ptr() as *const esp_app_desc_t)
256                    .as_ref()
257                    .unwrap()
258            };
259
260            Some(EspNativeFirmwareInfo {
261                image_header,
262                segment_header,
263                app_desc,
264            })
265        } else {
266            None
267        }
268    }
269
270    /// Fetches firmware information from the firmware binary data chunk loaded so far.
271    ///
272    /// Returns `true` if the information was successfully fetched.
273    /// Returns `false` if the firmware data has not been loaded completely yet.
274    pub fn fetch(&self, data: &[u8], info: &mut FirmwareInfo) -> Result<bool, EspIOError> {
275        if let Some(native_info) = self.fetch_native(data) {
276            Self::load_firmware_info(info, native_info.app_desc)?;
277
278            Ok(true)
279        } else {
280            Ok(false)
281        }
282    }
283
284    pub fn load_firmware_info(
285        info: &mut FirmwareInfo,
286        app_desc: &esp_app_desc_t,
287    ) -> Result<(), EspError> {
288        info.version.clear();
289        info.version
290            .push_str(unsafe { from_cstr_ptr(&app_desc.version as *const _) })
291            .map_err(|_| EspError::from_infallible::<ESP_ERR_INVALID_SIZE>())?;
292
293        info.released.clear();
294        write!(
295            &mut info.released,
296            "{} {}",
297            unsafe { from_cstr_ptr(&app_desc.date as *const _) },
298            unsafe { from_cstr_ptr(&app_desc.time as *const _) }
299        )
300        .map_err(|_| EspError::from_infallible::<ESP_ERR_INVALID_SIZE>())?;
301
302        if let Some(description) = info.description.as_mut() {
303            description.clear();
304            description
305                .push_str(unsafe { from_cstr_ptr(&app_desc.project_name as *const _) })
306                .map_err(|_| EspError::from_infallible::<ESP_ERR_INVALID_SIZE>())?;
307        }
308
309        if let Some(signature) = info.signature.as_mut() {
310            signature.clear();
311            signature
312                .extend_from_slice(&app_desc.app_elf_sha256)
313                .map_err(|_| EspError::from_infallible::<ESP_ERR_INVALID_SIZE>())?;
314        }
315
316        if let Some(download_id) = info.download_id.as_mut() {
317            download_id.clear();
318        }
319
320        Ok(())
321    }
322}
323
324impl io::ErrorType for EspFirmwareInfoLoad {
325    type Error = EspIOError;
326}
327
328#[derive(Debug)]
329pub struct EspOtaUpdate<'a> {
330    update_partition: *const esp_partition_t,
331    update_handle: esp_ota_handle_t,
332    _data: PhantomData<&'a mut ()>,
333}
334
335impl<'a> EspOtaUpdate<'a> {
336    /// Writes OTA update data to partition.
337    /// This function can be called multiple times as data is received during the OTA operation.
338    /// Data is written sequentially to the partition.
339    ///
340    /// # Errors
341    ///
342    /// Returns an error if data could not be written to flash.
343    pub fn write(&mut self, buf: &[u8]) -> Result<(), EspError> {
344        self.check_write()?;
345
346        if !buf.is_empty() {
347            esp!(unsafe { esp_ota_write(self.update_handle, buf.as_ptr() as _, buf.len() as _) })?;
348        }
349
350        Ok(())
351    }
352
353    /// This function does not perform any flash operations, as flash writes are not cached and,
354    /// therefore, do not need to be flushed.
355    ///
356    /// # Errors
357    ///
358    /// Returns an error update partition is not valid.
359    pub fn flush(&mut self) -> Result<(), EspError> {
360        self.check_write()?;
361
362        Ok(())
363    }
364
365    /// Finishes the OTA update and validates the new app image. Returns an instance of `EspOtaUpdateFinished`.
366    ///
367    /// <div class="warning">
368    /// This function does not update the boot partition. The user must call activate()
369    /// on the returned instance of EspOtaUpdateFinished.
370    /// </div>
371    ///
372    /// See also: [`complete`](Self::complete)
373    pub fn finish(self) -> Result<EspOtaUpdateFinished<'a>, EspError> {
374        self.check_write()?;
375
376        esp!(unsafe { esp_ota_end(self.update_handle) })?;
377        let update_partition = self.update_partition;
378
379        // `Drop::drop` must not be called on `EspOtaUpdate` after the OTA handle has been
380        // invalidated.
381        mem::forget(self);
382
383        Ok(EspOtaUpdateFinished {
384            update_partition,
385            _data: PhantomData,
386        })
387    }
388
389    /// Completes the OTA process by validating the new app image and updating the boot partition.
390    pub fn complete(self) -> Result<(), EspError> {
391        self.check_write()?;
392
393        esp!(unsafe { esp_ota_end(self.update_handle) })?;
394        esp!(unsafe { esp_ota_set_boot_partition(self.update_partition) })?;
395
396        // `Drop::drop` must not be called on `EspOtaUpdate` after the OTA handle has been
397        // invalidated.
398        mem::forget(self);
399
400        Ok(())
401    }
402
403    /// Cancels the update.
404    pub fn abort(self) -> Result<(), EspError> {
405        // The OTA update is aborted when `EspOtaUpdate` is dropped.
406        Ok(())
407    }
408
409    fn check_write(&self) -> Result<(), EspError> {
410        if !self.update_partition.is_null() {
411            Ok(())
412        } else {
413            Err(EspError::from_infallible::<ESP_FAIL>())
414        }
415    }
416}
417
418impl Drop for EspOtaUpdate<'_> {
419    fn drop(&mut self) {
420        // SAFETY: `esp_ota_abort` can only fail if the provided OTA handle is invalid.
421        //
422        // 1) The only safe way to acquire an `EspOtaUpdate` is through `EspOta::initiate_update`
423        //    which constructs the new instance using an OTA handle returned by `esp_ota_begin`.
424        // 2) The methods which invalidate the OTA handle all call `mem::forget(self)`.
425        //
426        // This means that our API guarantees that the OTA handle contained in this struct is valid
427        // and so calling this function will always be safe.
428        unsafe { esp_ota_abort(self.update_handle) };
429    }
430}
431
432#[derive(Debug)]
433pub struct EspOtaUpdateFinished<'a> {
434    update_partition: *const esp_partition_t,
435    _data: PhantomData<&'a mut ()>,
436}
437
438impl EspOtaUpdateFinished<'_> {
439    /// Sets the boot partition to the newly updated app partition.
440    /// The app will be run on the next boot.
441    pub fn activate(self) -> Result<(), EspError> {
442        esp!(unsafe { esp_ota_set_boot_partition(self.update_partition) })
443    }
444}
445
446#[derive(Debug)]
447pub struct EspOta(());
448
449impl EspOta {
450    /// Obtains an instance of `EspOta`. Only one instance can exist at a time.
451    ///
452    /// # Errors
453    ///
454    /// Returns an error if `EspOta` already exists.
455    pub fn new() -> Result<Self, EspError> {
456        let mut taken = TAKEN.lock();
457
458        if *taken {
459            return Err(EspError::from_infallible::<ESP_ERR_INVALID_STATE>());
460        }
461
462        *taken = true;
463
464        Ok(Self(()))
465    }
466
467    /// Returns the currently configured boot slot.
468    ///
469    /// # Errors
470    ///
471    /// Returns an error if partition table is invalid or a flash read operation failed.
472    pub fn get_boot_slot(&self) -> Result<Slot, EspError> {
473        if let Some(partition) = unsafe { esp_ota_get_boot_partition().as_ref() } {
474            self.get_slot(partition)
475        } else {
476            Err(EspError::from_infallible::<ESP_ERR_NOT_FOUND>())
477        }
478    }
479
480    /// Returns the currently running app slot.
481    ///
482    /// # Errors
483    ///
484    /// Returns an error if no partition is found or flash read operation failed.
485    pub fn get_running_slot(&self) -> Result<Slot, EspError> {
486        if let Some(partition) = unsafe { esp_ota_get_running_partition().as_ref() } {
487            self.get_slot(partition)
488        } else {
489            Err(EspError::from_infallible::<ESP_ERR_NOT_FOUND>())
490        }
491    }
492
493    /// Returns the slot of the next OTA app partition to be used for the new firmware.
494    ///
495    /// # Errors
496    ///
497    /// Returns an error if OTA data partition is invalid, or no eligible OTA app slot partition was found.
498    pub fn get_update_slot(&self) -> Result<Slot, EspError> {
499        if let Some(partition) = unsafe { esp_ota_get_next_update_partition(ptr::null()).as_ref() }
500        {
501            self.get_slot(partition)
502        } else {
503            Err(EspError::from_infallible::<ESP_ERR_NOT_FOUND>())
504        }
505    }
506
507    /// Returns the last slot with invalid state (invalid or aborted image).
508    pub fn get_last_invalid_slot(&self) -> Result<Option<Slot>, EspError> {
509        if let Some(partition) = unsafe { esp_ota_get_last_invalid_partition().as_ref() } {
510            Ok(Some(self.get_slot(partition)?))
511        } else {
512            Ok(None)
513        }
514    }
515
516    /// Returns true if a factory partition is present.
517    pub fn is_factory_reset_supported(&self) -> Result<bool, EspError> {
518        self.get_factory_partition()
519            .map(|factory| !factory.is_null())
520    }
521
522    /// Sets the boot partition to factory partition.
523    ///
524    /// # Errors
525    ///
526    /// Returns an error if factory partition is not present or boot partition could not be set.
527    pub fn factory_reset(&mut self) -> Result<(), EspError> {
528        let factory = self.get_factory_partition()?;
529
530        esp!(unsafe { esp_ota_set_boot_partition(factory) })?;
531
532        Ok(())
533    }
534
535    /// Initiates the OTA process and returns an instance of `EspOtaUpdate`
536    /// to be used for performing the OTA operations.
537    ///
538    /// # Errors
539    ///
540    /// Returns an error if OTA could not be initiated (OTA partition not found, flash error).
541    pub fn initiate_update(&mut self) -> Result<EspOtaUpdate<'_>, EspError> {
542        self.initiate_update_with_known_size(OTA_SIZE_UNKNOWN as usize)
543    }
544
545    /// We need to erase enough space in flash for the new image. By default `initiate_update`
546    /// passes `OTA_SIZE_UNKNOWN` which causes the entire partition to be erased, but this is slow
547    /// if the flash size is large and the image is relatively small. By setting the known size of
548    /// the image, we erase only what needs to be erased. If `file_size` is smaller than the size
549    /// of the actual image written, this will result in a corrupted image.
550    pub fn initiate_update_with_known_size(
551        &mut self,
552        file_size: usize,
553    ) -> Result<EspOtaUpdate<'_>, EspError> {
554        // This might return a null pointer in case no valid partition can be found.
555        // We don't have to handle this error in here, as this will implicitly trigger an error
556        // as soon as the null pointer is provided to `esp_ota_begin`.
557        let partition = unsafe { esp_ota_get_next_update_partition(ptr::null()) };
558
559        let mut handle: esp_ota_handle_t = Default::default();
560
561        esp!(unsafe { esp_ota_begin(partition, file_size, &mut handle) })?;
562
563        Ok(EspOtaUpdate {
564            update_partition: partition,
565            update_handle: handle,
566            _data: PhantomData,
567        })
568    }
569
570    /// Marks the current application as valid.
571    ///
572    /// If rollback is enabled, the application must confirm its operability by calling
573    /// `mark_running_slot_valid()` function, otherwise the application will be rolled back upon reboot.
574    pub fn mark_running_slot_valid(&mut self) -> Result<(), EspError> {
575        Ok(esp!(unsafe { esp_ota_mark_app_valid_cancel_rollback() })?)
576    }
577
578    /// Rolls back to the previously workable app with reboot.
579    ///
580    /// If rollback is successful then device will reset, otherwise the function will return `Err`.
581    /// If the flash does not have at least one app (except the running app) then rollback is not possible.
582    ///
583    /// # Errors
584    ///
585    /// Returns an error if the rollback was not possible.
586    pub fn mark_running_slot_invalid_and_reboot(&mut self) -> EspError {
587        if let Err(err) = esp!(unsafe { esp_ota_mark_app_invalid_rollback_and_reboot() }) {
588            err
589        } else {
590            unreachable!()
591        }
592    }
593
594    fn get_factory_partition(&self) -> Result<*const esp_partition_t, EspError> {
595        let partition_iterator = unsafe {
596            esp_partition_find(
597                esp_partition_type_t_ESP_PARTITION_TYPE_APP,
598                esp_partition_subtype_t_ESP_PARTITION_SUBTYPE_APP_FACTORY,
599                b"factory\0" as *const _ as *const _,
600            )
601        };
602
603        if partition_iterator.is_null() {
604            return Err(EspError::from_infallible::<ESP_ERR_NOT_SUPPORTED>());
605        }
606
607        let partition = unsafe { esp_partition_get(partition_iterator) };
608
609        unsafe { esp_partition_iterator_release(partition_iterator) };
610
611        Ok(partition)
612    }
613
614    fn get_slot(&self, partition: &esp_partition_t) -> Result<Slot, EspError> {
615        Ok(Slot {
616            label: unsafe { from_cstr_ptr(&partition.label as *const _ as *const _) }
617                .try_into()
618                .unwrap(),
619            state: self.get_state(partition)?,
620            firmware: self.get_firmware_info(partition)?,
621        })
622    }
623
624    fn get_state(&self, partition: &esp_partition_t) -> Result<SlotState, EspError> {
625        let mut state: esp_ota_img_states_t = Default::default();
626
627        let err =
628            unsafe { esp_ota_get_state_partition(partition as *const _, &mut state as *mut _) };
629
630        Ok(if err == ESP_ERR_NOT_FOUND {
631            SlotState::Unknown
632        } else if err == ESP_ERR_NOT_SUPPORTED {
633            SlotState::Factory
634        } else {
635            esp!(err)?;
636
637            #[allow(non_upper_case_globals)]
638            match state {
639                esp_ota_img_states_t_ESP_OTA_IMG_NEW
640                | esp_ota_img_states_t_ESP_OTA_IMG_PENDING_VERIFY => SlotState::Unverified,
641                esp_ota_img_states_t_ESP_OTA_IMG_VALID => SlotState::Valid,
642                esp_ota_img_states_t_ESP_OTA_IMG_INVALID
643                | esp_ota_img_states_t_ESP_OTA_IMG_ABORTED => SlotState::Invalid,
644                esp_ota_img_states_t_ESP_OTA_IMG_UNDEFINED => SlotState::Unknown,
645                _ => SlotState::Unknown,
646            }
647        })
648    }
649
650    fn get_firmware_info(
651        &self,
652        partition: &esp_partition_t,
653    ) -> Result<Option<FirmwareInfo>, EspError> {
654        let mut app_desc: esp_app_desc_t = Default::default();
655
656        let err =
657            unsafe { esp_ota_get_partition_description(partition as *const _, &mut app_desc) };
658
659        Ok(if err == ESP_ERR_NOT_FOUND {
660            None
661        } else {
662            esp!(err)?;
663
664            let mut info = FirmwareInfo {
665                version: heapless::String::new(),
666                released: heapless::String::new(),
667                description: Some(heapless::String::new()),
668                signature: Some(heapless::Vec::new()),
669                download_id: None,
670            };
671
672            EspFirmwareInfoLoad::load_firmware_info(&mut info, &app_desc)?;
673
674            Some(info)
675        })
676    }
677}
678
679impl Drop for EspOta {
680    fn drop(&mut self) {
681        *TAKEN.lock() = false;
682
683        info!("Dropped");
684    }
685}
686
687impl io::ErrorType for EspOta {
688    type Error = EspIOError;
689}
690
691impl Ota for EspOta {
692    type Update<'a>
693        = EspOtaUpdate<'a>
694    where
695        Self: 'a;
696
697    fn get_boot_slot(&self) -> Result<Slot, Self::Error> {
698        EspOta::get_boot_slot(self).map_err(EspIOError)
699    }
700
701    fn get_running_slot(&self) -> Result<Slot, Self::Error> {
702        EspOta::get_running_slot(self).map_err(EspIOError)
703    }
704
705    fn get_update_slot(&self) -> Result<Slot, Self::Error> {
706        EspOta::get_update_slot(self).map_err(EspIOError)
707    }
708
709    fn is_factory_reset_supported(&self) -> Result<bool, Self::Error> {
710        EspOta::is_factory_reset_supported(self).map_err(EspIOError)
711    }
712
713    fn factory_reset(&mut self) -> Result<(), Self::Error> {
714        EspOta::factory_reset(self).map_err(EspIOError)
715    }
716
717    fn initiate_update(&mut self) -> Result<Self::Update<'_>, Self::Error> {
718        EspOta::initiate_update(self).map_err(EspIOError)
719    }
720
721    fn mark_running_slot_valid(&mut self) -> Result<(), Self::Error> {
722        EspOta::mark_running_slot_valid(self).map_err(EspIOError)
723    }
724
725    fn mark_running_slot_invalid_and_reboot(&mut self) -> Self::Error {
726        EspIOError(EspOta::mark_running_slot_invalid_and_reboot(self))
727    }
728}
729
730unsafe impl Send for EspOtaUpdate<'_> {}
731
732impl io::ErrorType for EspOtaUpdate<'_> {
733    type Error = EspIOError;
734}
735
736impl<'a> OtaUpdate for EspOtaUpdate<'a> {
737    type OtaUpdateFinished = EspOtaUpdateFinished<'a>;
738
739    fn finish(self) -> Result<Self::OtaUpdateFinished, Self::Error> {
740        let finish = EspOtaUpdate::finish(self)?;
741
742        Ok(finish)
743    }
744
745    fn complete(self) -> Result<(), Self::Error> {
746        EspOtaUpdate::complete(self)?;
747
748        Ok(())
749    }
750
751    fn abort(self) -> Result<(), Self::Error> {
752        EspOtaUpdate::abort(self)?;
753
754        Ok(())
755    }
756}
757
758impl io::Write for EspOtaUpdate<'_> {
759    fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
760        EspOtaUpdate::write(self, buf)?;
761
762        Ok(buf.len())
763    }
764
765    fn flush(&mut self) -> Result<(), Self::Error> {
766        EspOtaUpdate::flush(self)?;
767
768        Ok(())
769    }
770}
771
772unsafe impl Send for EspOtaUpdateFinished<'_> {}
773
774impl io::ErrorType for EspOtaUpdateFinished<'_> {
775    type Error = EspIOError;
776}
777
778impl OtaUpdateFinished for EspOtaUpdateFinished<'_> {
779    fn activate(self) -> Result<(), Self::Error> {
780        EspOtaUpdateFinished::activate(self)?;
781
782        Ok(())
783    }
784}