platform/
settings.rs

1//! Stabilizer Settings Management
2//!
3//! # Design
4//! Stabilizer supports two types of settings:
5//! 1. Static Device Configuration
6//! 2. Dynamic Run-time Settings
7//!
8//! Static device configuration settings are loaded and used only at device power-up. These include
9//! things like the MQTT broker address and the MQTT identifier. Conversely, the dynamic run-time
10//! settings can be changed and take effect immediately during device operation.
11//!
12//! This settings management interface is currently targeted at the static device configuration
13//! settings. Settings are persisted into the unused 1MB flash bank of Stabilizer for future
14//! recall. They can be modified via the USB interface to facilitate device configuration.
15//!
16//! Settings are stored in flash using a key-value pair mapping, where the `key` is the name of the
17//! entry in the settings structure. This has a number of benefits:
18//! 1. The `Settings` structure can have new entries added to it in the future without losing old
19//!    settings values, as each entry of the `Settings` struct is stored separately as its own
20//!    key-value pair.
21//! 2. The `Settings` can be used among multiple Stabilizer firmware versions that need the same
22//!    settings values
23//! 3. Unknown/unneeded settings values in flash can be actively ignored, facilitating simple flash
24//!    storage sharing.
25use crate::{dfu, metadata::ApplicationMetadata};
26use embassy_futures::block_on;
27use embedded_io::{Read as EioRead, ReadReady, Write as EioWrite, WriteReady};
28use embedded_storage_async::nor_flash::NorFlash;
29use heapless::{String, Vec};
30use miniconf::{
31    Path, TreeDeserializeOwned, TreeSchema, TreeSerialize, postcard,
32};
33use sequential_storage::{
34    cache::NoCache,
35    map::{SerializationError, fetch_item, store_item},
36};
37use serial_settings::{BestEffortInterface, Platform, Settings};
38
39#[derive(
40    Default, serde::Serialize, serde::Deserialize, Clone, PartialEq, Eq,
41)]
42pub struct SettingsKey(Vec<u8, 128>);
43
44impl sequential_storage::map::Key for SettingsKey {
45    fn serialize_into(
46        &self,
47        buffer: &mut [u8],
48    ) -> Result<usize, SerializationError> {
49        Ok(::postcard::to_slice(self, buffer)
50            .map_err(|_| SerializationError::BufferTooSmall)?
51            .len())
52    }
53
54    fn deserialize_from(
55        buffer: &[u8],
56    ) -> Result<(Self, usize), SerializationError> {
57        let original_length = buffer.len();
58        let (result, remainder) = ::postcard::take_from_bytes(buffer)
59            .map_err(|_| SerializationError::BufferTooSmall)?;
60        Ok((result, original_length - remainder.len()))
61    }
62}
63
64pub struct SerialSettingsPlatform<C, F, S> {
65    /// The interface to read/write data to/from serially (via text) to the user.
66    pub interface: BestEffortInterface<S>,
67
68    pub _settings_marker: core::marker::PhantomData<C>,
69
70    /// The storage mechanism used to persist settings to between boots.
71    pub storage: F,
72
73    /// Metadata associated with the application
74    pub metadata: &'static ApplicationMetadata,
75}
76
77impl<C, F, S> SerialSettingsPlatform<C, F, S>
78where
79    C: TreeDeserializeOwned + TreeSerialize + TreeSchema,
80    F: NorFlash,
81{
82    pub fn load(structure: &mut C, storage: &mut F) {
83        // Loop over flash and read settings
84        let mut buffer = [0u8; 512];
85        for path in C::SCHEMA
86            .nodes::<Path<String<128>, '/'>, { serial_settings::MAX_DEPTH }>()
87        {
88            let path = path.unwrap();
89
90            // Try to fetch the setting from flash.
91            let value: &[u8] = match block_on(fetch_item(
92                storage,
93                0..storage.capacity() as _,
94                &mut NoCache::new(),
95                &mut buffer,
96                &SettingsKey(path.clone().into_inner().into_bytes()),
97            )) {
98                Err(e) => {
99                    log::warn!(
100                        "Failed to fetch `{}` from flash: {e:?}",
101                        path.0.as_str()
102                    );
103                    continue;
104                }
105                Ok(Some(value)) => value,
106                Ok(None) => continue,
107            };
108
109            // An empty vector may be saved to flash to "erase" a setting, since the H7 doesn't support
110            // multi-write NOR flash. If we see an empty vector, ignore this entry.
111            if value.is_empty() {
112                continue;
113            }
114
115            log::info!("Loading initial `{}` from flash", path.0.as_str());
116
117            let flavor = ::postcard::de_flavors::Slice::new(value);
118            if let Err(e) = postcard::set_by_key(structure, &path, flavor) {
119                log::warn!(
120                    "Failed to deserialize `{}` from flash: {e:?}",
121                    path.0.as_str()
122                );
123            }
124        }
125    }
126}
127
128impl<C, F, S> Platform for SerialSettingsPlatform<C, F, S>
129where
130    C: Settings,
131    F: NorFlash,
132    S: EioWrite + WriteReady + ReadReady + EioRead,
133{
134    type Interface = BestEffortInterface<S>;
135    type Settings = C;
136    type Error = sequential_storage::Error<F::Error>;
137
138    fn fetch<'a>(
139        &mut self,
140        buf: &'a mut [u8],
141        key: &[u8],
142    ) -> Result<Option<&'a [u8]>, Self::Error> {
143        let range = 0..self.storage.capacity() as _;
144        block_on(fetch_item(
145            &mut self.storage,
146            range,
147            &mut NoCache::new(),
148            buf,
149            &SettingsKey(Vec::try_from(key).unwrap()),
150        ))
151        .map(|v| v.filter(|v: &&[u8]| !v.is_empty()))
152    }
153
154    fn store(
155        &mut self,
156        buf: &mut [u8],
157        key: &[u8],
158        value: &[u8],
159    ) -> Result<(), Self::Error> {
160        let range = 0..self.storage.capacity() as _;
161        block_on(store_item(
162            &mut self.storage,
163            range,
164            &mut NoCache::new(),
165            buf,
166            &SettingsKey(Vec::try_from(key).unwrap()),
167            &value,
168        ))
169    }
170
171    fn clear(&mut self, buf: &mut [u8], key: &[u8]) -> Result<(), Self::Error> {
172        self.store(buf, key, b"")
173    }
174
175    fn cmd(&mut self, cmd: &str) {
176        match cmd {
177            "reboot" => cortex_m::peripheral::SCB::sys_reset(),
178            "dfu" => dfu::dfu_reboot(),
179            "service" => {
180                write!(&mut self.interface, "{}", &self.metadata).unwrap();
181            }
182            _ => {
183                writeln!(
184                    self.interface_mut(),
185                    "Invalid platform command: `{cmd}` not in [`dfu`, `reboot`, `service`]"
186                )
187                .ok();
188            }
189        }
190    }
191
192    fn interface_mut(&mut self) -> &mut Self::Interface {
193        &mut self.interface
194    }
195}