serial_settings/
lib.rs

1#![doc = include_str!("../README.md")]
2#![no_std]
3
4use embedded_io::{ErrorType, Read, ReadReady, Write};
5use heapless::String;
6use miniconf::{
7    NodeIter, Path, SerdeError, TreeDeserializeOwned, TreeSchema,
8    TreeSerialize, ValueError, json_core, postcard,
9};
10
11mod interface;
12pub use interface::BestEffortInterface;
13
14/// Specifies the API required for objects that are used as settings with the serial terminal
15/// interface.
16pub trait Settings:
17    TreeSchema + TreeSerialize + TreeDeserializeOwned + Clone
18{
19    /// Reset the settings to their default values.
20    fn reset(&mut self) {}
21}
22
23/// Platform support for serial settings.
24///
25/// Covers platform-specific commands, persistent key-value storage and the
26/// Read/Write interface for interaction.
27///
28/// Assuming there are no unit fields in the `Settings`, the empty value can be
29/// used to mark the "cleared" state.
30pub trait Platform {
31    /// This type specifies the interface to the user, for example, a USB CDC-ACM serial port.
32    type Interface: embedded_io::Read
33        + embedded_io::ReadReady
34        + embedded_io::Write;
35
36    type Error: core::fmt::Debug;
37
38    type Settings: Settings;
39
40    /// Fetch a value from persisten storage
41    fn fetch<'a>(
42        &mut self,
43        buf: &'a mut [u8],
44        key: &[u8],
45    ) -> Result<Option<&'a [u8]>, Self::Error>;
46
47    /// Store a value to persistent storage
48    fn store(
49        &mut self,
50        buf: &mut [u8],
51        key: &[u8],
52        value: &[u8],
53    ) -> Result<(), Self::Error>;
54
55    /// Remove a key from storage.
56    fn clear(&mut self, buf: &mut [u8], key: &[u8]) -> Result<(), Self::Error>;
57
58    /// Execute a platform specific command.
59    fn cmd(&mut self, cmd: &str);
60
61    /// Return a mutable reference to the `Interface`.
62    fn interface_mut(&mut self) -> &mut Self::Interface;
63}
64
65struct Interface<'a, P> {
66    platform: P,
67    buffer: &'a mut [u8],
68    updated: bool,
69}
70
71impl<'a, P: Platform> Interface<'a, P> {
72    fn handle_platform(
73        _menu: &menu::Menu<Self, P::Settings>,
74        item: &menu::Item<Self, P::Settings>,
75        args: &[&str],
76        interface: &mut Self,
77        _settings: &mut P::Settings,
78    ) {
79        let key = menu::argument_finder(item, args, "cmd").unwrap().unwrap();
80        interface.platform.cmd(key)
81    }
82
83    fn iter_root<F>(
84        key: Option<&str>,
85        interface: &mut Self,
86        settings: &mut P::Settings,
87        mut func: F,
88    ) where
89        F: FnMut(
90            Path<&str, '/'>,
91            &mut Self,
92            &mut P::Settings,
93            &mut P::Settings,
94        ),
95    {
96        let iter = if let Some(key) = key {
97            match NodeIter::with_root(P::Settings::SCHEMA, Path::<_, '/'>(key))
98            {
99                Ok(it) => it,
100                Err(e) => {
101                    writeln!(interface, "Failed to locate `{key}`: {e}")
102                        .unwrap();
103                    return;
104                }
105            }
106        } else {
107            NodeIter::<Path<String<128>, '/'>, MAX_DEPTH>::new(
108                P::Settings::SCHEMA,
109            )
110        };
111
112        let mut defaults = settings.clone();
113        defaults.reset();
114        for key in iter {
115            match key {
116                Ok(key) => func(
117                    Path(key.0.as_str()),
118                    interface,
119                    settings,
120                    &mut defaults,
121                ),
122                Err(depth) => {
123                    writeln!(
124                        interface,
125                        "Failed to build path: no space at depth {depth}"
126                    )
127                    .unwrap();
128                }
129            }
130        }
131    }
132
133    fn handle_get(
134        _menu: &menu::Menu<Self, P::Settings>,
135        item: &menu::Item<Self, P::Settings>,
136        args: &[&str],
137        interface: &mut Self,
138        settings: &mut P::Settings,
139    ) {
140        let key = menu::argument_finder(item, args, "path").unwrap();
141        Self::iter_root(
142            key,
143            interface,
144            settings,
145            |key, interface, settings, defaults| {
146                // Get current
147                let check = match json_core::get_by_key(
148                    settings,
149                    key,
150                    interface.buffer,
151                ) {
152                    Err(SerdeError::Value(ValueError::Absent)) => {
153                        return;
154                    }
155                    Err(e) => {
156                        writeln!(interface, "Failed to get `{}`: {e}", key.0)
157                            .unwrap();
158                        return;
159                    }
160                    Ok(len) => {
161                        write!(
162                            interface.platform.interface_mut(),
163                            "{}: {}",
164                            key.0,
165                            core::str::from_utf8(&interface.buffer[..len])
166                                .unwrap()
167                        )
168                        .unwrap();
169                        yafnv::fnv1a::<u32>(&interface.buffer[..len])
170                    }
171                };
172
173                // Get default and compare
174                match json_core::get_by_key(defaults, key, interface.buffer) {
175                    Err(SerdeError::Value(ValueError::Absent)) => {
176                        write!(interface, " [default: absent]")
177                    }
178                    Err(e) => {
179                        write!(interface, " [default serialization error: {e}]")
180                    }
181                    Ok(len) => {
182                        if yafnv::fnv1a::<u32>(&interface.buffer[..len])
183                            != check
184                        {
185                            write!(
186                                interface.platform.interface_mut(),
187                                " [default: {}]",
188                                core::str::from_utf8(&interface.buffer[..len])
189                                    .unwrap()
190                            )
191                        } else {
192                            write!(interface, " [default]")
193                        }
194                    }
195                }
196                .unwrap();
197
198                // Get stored and compare
199                match interface
200                    .platform
201                    .fetch(interface.buffer, key.0.as_bytes())
202                {
203                    Err(e) => write!(interface, " [fetch error: {e:?}]"),
204                    Ok(None) => write!(interface, " [not stored]"),
205                    Ok(Some(stored)) => {
206                        let slic = ::postcard::de_flavors::Slice::new(stored);
207                        // Use defaults as scratch space for postcard->json conversion
208                        match postcard::set_by_key(defaults, key, slic) {
209                            Err(e) => write!(
210                                interface,
211                                " [stored deserialize error: {e}]"
212                            ),
213                            Ok(_rest) => match json_core::get_by_key(
214                                defaults,
215                                key,
216                                interface.buffer,
217                            ) {
218                                Err(e) => write!(
219                                    interface,
220                                    " [stored serialization error: {e}]"
221                                ),
222                                Ok(len) => {
223                                    if yafnv::fnv1a::<u32>(
224                                        &interface.buffer[..len],
225                                    ) != check
226                                    {
227                                        write!(
228                                            interface.platform.interface_mut(),
229                                            " [stored: {}]",
230                                            core::str::from_utf8(
231                                                &interface.buffer[..len]
232                                            )
233                                            .unwrap()
234                                        )
235                                    } else {
236                                        write!(interface, " [stored]")
237                                    }
238                                }
239                            },
240                        }
241                    }
242                }
243                .unwrap();
244                writeln!(interface).unwrap();
245            },
246        );
247    }
248
249    fn handle_clear(
250        _menu: &menu::Menu<Self, P::Settings>,
251        item: &menu::Item<Self, P::Settings>,
252        args: &[&str],
253        interface: &mut Self,
254        settings: &mut P::Settings,
255    ) {
256        let key = menu::argument_finder(item, args, "path").unwrap();
257        Self::iter_root(
258            key,
259            interface,
260            settings,
261            |key, interface, settings, defaults| {
262                // Get current value checksum
263                let slic =
264                    ::postcard::ser_flavors::Slice::new(interface.buffer);
265                let check = match postcard::get_by_key(settings, key, slic) {
266                    Err(SerdeError::Value(ValueError::Absent)) => {
267                        return;
268                    }
269                    Err(e) => {
270                        writeln!(interface, "Failed to get {}: {e:?}", key.0)
271                            .unwrap();
272                        return;
273                    }
274                    Ok(slic) => yafnv::fnv1a::<u32>(slic),
275                };
276
277                // Get default if different
278                let slic =
279                    ::postcard::ser_flavors::Slice::new(interface.buffer);
280                let slic = match postcard::get_by_key(defaults, key, slic) {
281                    Err(SerdeError::Value(ValueError::Absent)) => {
282                        log::warn!(
283                            "Can't clear. Default is absent: `{}`",
284                            key.0
285                        );
286                        None
287                    }
288                    Err(e) => {
289                        writeln!(
290                            interface,
291                            "Failed to get default `{}`: {e}",
292                            key.0
293                        )
294                        .unwrap();
295                        return;
296                    }
297                    Ok(slic) => {
298                        if yafnv::fnv1a::<u32>(slic) != check {
299                            Some(slic)
300                        } else {
301                            None
302                        }
303                    }
304                };
305
306                // Set default
307                if let Some(slic) = slic {
308                    let slic = ::postcard::de_flavors::Slice::new(slic);
309                    match postcard::set_by_key(settings, key, slic) {
310                        Err(SerdeError::Value(ValueError::Absent)) => {
311                            return;
312                        }
313                        Err(e) => {
314                            writeln!(
315                                interface,
316                                "Failed to set {}: {e:?}",
317                                key.0
318                            )
319                            .unwrap();
320                            return;
321                        }
322                        Ok(_rest) => {
323                            interface.updated = true;
324                            writeln!(interface, "Cleared current `{}`", key.0)
325                                .unwrap()
326                        }
327                    }
328                }
329
330                // Check for stored
331                match interface
332                    .platform
333                    .fetch(interface.buffer, key.0.as_bytes())
334                {
335                    Err(e) => {
336                        writeln!(
337                            interface,
338                            "Failed to fetch `{}`: {e:?}",
339                            key.0
340                        )
341                        .unwrap();
342                    }
343                    Ok(None) => {}
344                    // Clear stored
345                    Ok(Some(_stored)) => match interface
346                        .platform
347                        .clear(interface.buffer, key.0.as_bytes())
348                    {
349                        Ok(()) => {
350                            writeln!(interface, "Clear stored `{}`", key.0)
351                        }
352                        Err(e) => {
353                            writeln!(
354                                interface,
355                                "Failed to clear `{}` from storage: {e:?}",
356                                key.0
357                            )
358                        }
359                    }
360                    .unwrap(),
361                }
362            },
363        );
364        interface.updated = true;
365        writeln!(interface, "Some values may require reboot to become active")
366            .unwrap();
367    }
368
369    fn handle_store(
370        _menu: &menu::Menu<Self, P::Settings>,
371        item: &menu::Item<Self, P::Settings>,
372        args: &[&str],
373        interface: &mut Self,
374        settings: &mut P::Settings,
375    ) {
376        let key = menu::argument_finder(item, args, "path").unwrap();
377        let force = menu::argument_finder(item, args, "force")
378            .unwrap()
379            .is_some();
380        Self::iter_root(
381            key,
382            interface,
383            settings,
384            |key, interface, settings, defaults| {
385                // Get default value checksum
386                let slic =
387                    ::postcard::ser_flavors::Slice::new(interface.buffer);
388                let mut check = match postcard::get_by_key(defaults, key, slic)
389                {
390                    // Could also serialize directly into the hasher for all these checksum calcs
391                    Ok(slic) => yafnv::fnv1a::<u32>(slic),
392                    Err(SerdeError::Value(ValueError::Absent)) => {
393                        log::warn!("Default absent: `{}`", key.0);
394                        return;
395                    }
396                    Err(e) => {
397                        writeln!(
398                            interface,
399                            "Failed to get `{}` default: {e:?}",
400                            key.0
401                        )
402                        .unwrap();
403                        return;
404                    }
405                };
406
407                // Get stored value checksum
408                match interface
409                    .platform
410                    .fetch(interface.buffer, key.0.as_bytes())
411                {
412                    Ok(None) => {}
413                    Ok(Some(stored)) => {
414                        let stored = yafnv::fnv1a::<u32>(stored);
415                        if stored != check {
416                            log::debug!(
417                                "Stored differs from default: `{}`",
418                                key.0
419                            );
420                        } else {
421                            log::debug!("Stored matches default: `{}`", key.0);
422                        }
423                        check = stored;
424                    }
425                    Err(e) => {
426                        writeln!(
427                            interface,
428                            "Failed to fetch `{}`: {e:?}",
429                            key.0
430                        )
431                        .unwrap();
432                    }
433                }
434
435                // Get value
436                let slic =
437                    ::postcard::ser_flavors::Slice::new(interface.buffer);
438                let value = match postcard::get_by_key(settings, key, slic) {
439                    Ok(value) => value,
440                    Err(SerdeError::Value(ValueError::Absent)) => {
441                        return;
442                    }
443                    Err(e) => {
444                        writeln!(interface, "Could not get `{}`: {e}", key.0)
445                            .unwrap();
446                        return;
447                    }
448                };
449
450                // Check for mismatch
451                if yafnv::fnv1a::<u32>(value) == check && !force {
452                    log::debug!(
453                        "Not saving matching default/stored `{}`",
454                        key.0
455                    );
456                    return;
457                }
458                let len = value.len();
459                let (value, rest) = interface.buffer.split_at_mut(len);
460
461                // Store
462                match interface.platform.store(rest, key.0.as_bytes(), value) {
463                    Ok(_) => writeln!(interface, "`{}` stored", key.0),
464                    Err(e) => {
465                        writeln!(
466                            interface,
467                            "Failed to store `{}`: {e:?}",
468                            key.0
469                        )
470                    }
471                }
472                .unwrap();
473            },
474        );
475        writeln!(interface, "Some values may require reboot to become active")
476            .unwrap();
477    }
478
479    fn handle_set(
480        _menu: &menu::Menu<Self, P::Settings>,
481        item: &menu::Item<Self, P::Settings>,
482        args: &[&str],
483        interface: &mut Self,
484        settings: &mut P::Settings,
485    ) {
486        let key = menu::argument_finder(item, args, "path").unwrap().unwrap();
487        let value =
488            menu::argument_finder(item, args, "value").unwrap().unwrap();
489
490        // Now, write the new value into memory.
491        match json_core::set(settings, key, value.as_bytes()) {
492            Ok(_) => {
493                interface.updated = true;
494                writeln!(
495                    interface,
496                    "Set but not stored. May require store and reboot to activate."
497                )
498            }
499            Err(e) => {
500                writeln!(interface, "Failed to set `{key}`: {e:?}")
501            }
502        }
503        .unwrap();
504    }
505
506    fn menu() -> menu::Menu<'a, Self, P::Settings> {
507        menu::Menu {
508            label: "settings",
509            items: &[
510                &menu::Item {
511                    command: "get",
512                    help: Some(
513                        "List paths and read current, default, and stored values",
514                    ),
515                    item_type: menu::ItemType::Callback {
516                        function: Self::handle_get,
517                        parameters: &[menu::Parameter::Optional {
518                            parameter_name: "path",
519                            help: Some(
520                                "The path of the value or subtree to list/read.",
521                            ),
522                        }],
523                    },
524                },
525                &menu::Item {
526                    command: "set",
527                    help: Some("Update a value"),
528                    item_type: menu::ItemType::Callback {
529                        function: Self::handle_set,
530                        parameters: &[
531                            menu::Parameter::Mandatory {
532                                parameter_name: "path",
533                                help: Some("The path to set"),
534                            },
535                            menu::Parameter::Mandatory {
536                                parameter_name: "value",
537                                help: Some(
538                                    "The value to be written, JSON-encoded",
539                                ),
540                            },
541                        ],
542                    },
543                },
544                &menu::Item {
545                    command: "store",
546                    help: Some("Store values that differ from defaults"),
547                    item_type: menu::ItemType::Callback {
548                        function: Self::handle_store,
549                        parameters: &[
550                            menu::Parameter::Named {
551                                parameter_name: "force",
552                                help: Some(
553                                    "Also store values that match defaults",
554                                ),
555                            },
556                            menu::Parameter::Optional {
557                                parameter_name: "path",
558                                help: Some(
559                                    "The path of the value or subtree to store.",
560                                ),
561                            },
562                        ],
563                    },
564                },
565                &menu::Item {
566                    command: "clear",
567                    help: Some(
568                        "Clear active to defaults and remove all stored values",
569                    ),
570                    item_type: menu::ItemType::Callback {
571                        function: Self::handle_clear,
572                        parameters: &[menu::Parameter::Optional {
573                            parameter_name: "path",
574                            help: Some(
575                                "The path of the value or subtree to clear",
576                            ),
577                        }],
578                    },
579                },
580                &menu::Item {
581                    command: "platform",
582                    help: Some("Platform specific commands"),
583                    item_type: menu::ItemType::Callback {
584                        function: Self::handle_platform,
585                        parameters: &[menu::Parameter::Mandatory {
586                            parameter_name: "cmd",
587                            help: Some(
588                                "The name of the command (e.g. `reboot`, `service`, `dfu`).",
589                            ),
590                        }],
591                    },
592                },
593            ],
594            entry: None,
595            exit: None,
596        }
597    }
598}
599
600impl<P: Platform> core::fmt::Write for Interface<'_, P> {
601    fn write_str(&mut self, s: &str) -> core::fmt::Result {
602        self.platform
603            .interface_mut()
604            .write_all(s.as_bytes())
605            .or(Err(core::fmt::Error))
606    }
607}
608
609impl<P: Platform> ErrorType for Interface<'_, P> {
610    type Error = <P::Interface as ErrorType>::Error;
611}
612
613impl<P: Platform> Write for Interface<'_, P> {
614    fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
615        self.platform.interface_mut().write(buf)
616    }
617
618    fn flush(&mut self) -> Result<(), Self::Error> {
619        self.platform.interface_mut().flush()
620    }
621}
622
623/// Max settings depth
624pub const MAX_DEPTH: usize = 16;
625
626// The Menu runner
627pub struct Runner<'a, P: Platform>(
628    menu::Runner<'a, Interface<'a, P>, P::Settings, [u8]>,
629);
630
631impl<'a, P: Platform> Runner<'a, P> {
632    /// Constructor
633    ///
634    /// # Args
635    /// * `platform` - The platform associated with the serial settings, providing the necessary
636    ///   context and API to manage device settings.
637    ///
638    /// * `line_buf` - A buffer used for maintaining the serial menu input line. It should be at
639    ///   least as long as the longest user input.
640    ///
641    /// * `serialize_buf` - A buffer used for serializing and deserializing settings. This buffer
642    ///   needs to be at least as big as twice the biggest serialized setting plus its path.
643    pub fn new(
644        platform: P,
645        line_buf: &'a mut [u8],
646        serialize_buf: &'a mut [u8],
647        settings: &mut P::Settings,
648    ) -> Result<Self, P::Error> {
649        assert!(P::Settings::SCHEMA.shape().max_depth <= MAX_DEPTH);
650        Ok(Self(menu::Runner::new(
651            Interface::menu(),
652            line_buf,
653            Interface {
654                platform,
655                buffer: serialize_buf,
656                updated: false,
657            },
658            settings,
659        )))
660    }
661
662    /// Get the device communication interface
663    pub fn interface_mut(&mut self) -> &mut P::Interface {
664        self.0.interface.platform.interface_mut()
665    }
666
667    pub fn platform_mut(&mut self) -> &mut P {
668        &mut self.0.interface.platform
669    }
670
671    pub fn platform(&mut self) -> &P {
672        &self.0.interface.platform
673    }
674
675    /// Must be called periodically to process user input.
676    ///
677    /// # Returns
678    /// A boolean indicating true if the settings were modified.
679    pub fn poll(
680        &mut self,
681        settings: &mut P::Settings,
682    ) -> Result<bool, <P::Interface as embedded_io::ErrorType>::Error> {
683        self.0.interface.updated = false;
684
685        while self.interface_mut().read_ready()? {
686            let mut buffer = [0u8; 64];
687            let count = self.interface_mut().read(&mut buffer)?;
688            for &value in &buffer[..count] {
689                self.0.input_byte(value, settings);
690            }
691        }
692
693        Ok(self.0.interface.updated)
694    }
695}