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