esp_bootloader_esp_idf/
ota_updater.rs

1//! # A more convenient way to access Over The Air Updates (OTA) functionality.
2
3use crate::{
4    ota::OtaImageState,
5    partitions::{AppPartitionSubType, Error, FlashRegion, PartitionTable},
6};
7
8/// This can be used as more convenient - yet less flexible, way to do OTA updates.
9///
10/// If you need lower level access see [crate::ota::Ota]
11#[derive(Debug)]
12#[cfg_attr(feature = "defmt", derive(defmt::Format))]
13pub struct OtaUpdater<'a, F>
14where
15    F: embedded_storage::Storage,
16{
17    flash: &'a mut F,
18    pt: PartitionTable<'a>,
19    ota_count: usize,
20}
21
22impl<'a, F> OtaUpdater<'a, F>
23where
24    F: embedded_storage::Storage,
25{
26    /// Create a new instance of [OtaUpdater].
27    ///
28    /// # Errors
29    /// [Error::Invalid] if no OTA data partition or less than two OTA app partition were found.
30    pub fn new(
31        flash: &'a mut F,
32        buffer: &'a mut [u8; crate::partitions::PARTITION_TABLE_MAX_LEN],
33    ) -> Result<Self, Error> {
34        let pt = crate::partitions::read_partition_table(flash, buffer)?;
35
36        let mut ota_count = 0;
37        let mut has_ota_data = false;
38        for part in pt.iter() {
39            match part.partition_type() {
40                crate::partitions::PartitionType::App(subtype) => {
41                    if subtype != crate::partitions::AppPartitionSubType::Factory
42                        && subtype != crate::partitions::AppPartitionSubType::Test
43                    {
44                        ota_count += 1;
45                    }
46                }
47                crate::partitions::PartitionType::Data(
48                    crate::partitions::DataPartitionSubType::Ota,
49                ) => {
50                    has_ota_data = true;
51                }
52                _ => (),
53            }
54        }
55
56        if !has_ota_data {
57            return Err(Error::Invalid);
58        }
59
60        if ota_count < 2 {
61            return Err(Error::Invalid);
62        }
63
64        Ok(Self {
65            flash,
66            pt,
67            ota_count,
68        })
69    }
70
71    fn with_ota<R>(
72        &mut self,
73        f: impl FnOnce(crate::ota::Ota<'_, F>) -> Result<R, Error>,
74    ) -> Result<R, Error> {
75        let ota_part = self
76            .pt
77            .find_partition(crate::partitions::PartitionType::Data(
78                crate::partitions::DataPartitionSubType::Ota,
79            ))?;
80        if let Some(ota_part) = ota_part {
81            let mut ota_part = ota_part.as_embedded_storage(self.flash);
82            let ota = crate::ota::Ota::new(&mut ota_part, self.ota_count)?;
83            f(ota)
84        } else {
85            Err(Error::Invalid)
86        }
87    }
88
89    fn next_ota_part(&mut self) -> Result<crate::partitions::AppPartitionSubType, Error> {
90        let current = self.selected_partition()?;
91        let next = match current {
92            AppPartitionSubType::Factory => AppPartitionSubType::Ota0,
93            _ => AppPartitionSubType::from_ota_app_number(
94                (current.ota_app_number() + 1) % self.ota_count as u8,
95            )?,
96        };
97
98        // make sure we don't select the currently booted partition
99        let booted = self.pt.booted_partition()?;
100        let next = if let Some(booted) = booted {
101            if booted.partition_type() == crate::partitions::PartitionType::App(next) {
102                AppPartitionSubType::from_ota_app_number(
103                    (current.ota_app_number() + 2) % self.ota_count as u8,
104                )?
105            } else {
106                next
107            }
108        } else {
109            next
110        };
111
112        Ok(next)
113    }
114
115    /// Returns the currently selected app partition.
116    pub fn selected_partition(&mut self) -> Result<crate::partitions::AppPartitionSubType, Error> {
117        self.with_ota(|mut ota| ota.current_app_partition())
118    }
119
120    /// Get the [OtaImageState] of the currently selected partition.
121    ///
122    /// # Errors
123    /// A [Error::InvalidState] if no partition is currently selected.
124    pub fn current_ota_state(&mut self) -> Result<OtaImageState, Error> {
125        self.with_ota(|mut ota| ota.current_ota_state())
126    }
127
128    /// Set the [OtaImageState] of the currently selected slot.
129    ///
130    /// # Errors
131    /// A [Error::InvalidState] if no partition is currently selected.
132    pub fn set_current_ota_state(&mut self, state: OtaImageState) -> Result<(), Error> {
133        self.with_ota(|mut ota| ota.set_current_ota_state(state))
134    }
135
136    /// Selects the next active OTA-slot as current.
137    ///
138    /// After calling this other functions referencing the current partition will use the newly
139    /// activated partition.
140    ///
141    /// Passing [AppPartitionSubType::Factory] will reset the OTA-data
142    pub fn activate_next_partition(&mut self) -> Result<(), Error> {
143        let next_slot = self.next_ota_part()?;
144        self.with_ota(|mut ota| ota.set_current_app_partition(next_slot))
145    }
146
147    /// Executes the given closure with the partition which would be selected by
148    /// [Self::activate_next_partition].
149    ///
150    /// [FlashRegion] and the [AppPartitionSubType] is passed into the closure.
151    pub fn with_next_partition<R>(
152        &mut self,
153        f: impl FnOnce(FlashRegion<'_, F>, AppPartitionSubType) -> R,
154    ) -> Result<R, Error> {
155        let next_slot = self.next_ota_part()?;
156
157        if let Some(flash_region) = self
158            .pt
159            .find_partition(crate::partitions::PartitionType::App(next_slot))?
160        {
161            Ok(f(flash_region.as_embedded_storage(self.flash), next_slot))
162        } else {
163            Err(Error::Invalid)
164        }
165    }
166}