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
14pub trait Settings:
17 TreeKey + TreeSerialize + TreeDeserializeOwned + Clone
18{
19 fn reset(&mut self) {}
21}
22
23pub trait Platform {
31 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 fn fetch<'a>(
42 &mut self,
43 buf: &'a mut [u8],
44 key: &[u8],
45 ) -> Result<Option<&'a [u8]>, Self::Error>;
46
47 fn store(
49 &mut self,
50 buf: &mut [u8],
51 key: &[u8],
52 value: &[u8],
53 ) -> Result<(), Self::Error>;
54
55 fn clear(&mut self, buf: &mut [u8], key: &[u8]) -> Result<(), Self::Error>;
57
58 fn cmd(&mut self, cmd: &str);
60
61 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 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 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 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 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 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 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 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 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 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 let slic =
383 ::postcard::ser_flavors::Slice::new(interface.buffer);
384 let mut check = match postcard::get_by_key(defaults, key, slic)
385 {
386 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 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 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 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 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 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
604pub 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 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 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 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}