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