diff --git a/embassy/demos/std/Cargo.lock b/embassy/demos/std/Cargo.lock index 8f17a036c15d4ff7a54be84ae14daf81bc3983a0..1dca071627664968c16cbc94fd63f339c2793f51 100644 --- a/embassy/demos/std/Cargo.lock +++ b/embassy/demos/std/Cargo.lock @@ -838,6 +838,12 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "menu" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b03d7f798bfe97329ad6df937951142eec93886b37d87010502dd25e8cc75fd5" + [[package]] name = "nb" version = "0.1.3" @@ -1277,6 +1283,7 @@ dependencies = [ "heapless", "libc", "log", + "menu", "rand", "sha2 0.10.6", "static_cell", diff --git a/embassy/demos/std/Cargo.toml b/embassy/demos/std/Cargo.toml index c1ec25b5dd21dc6a352e671d176c0c6d34e74e9d..c7a133999d0b520d4355c292c43ea976c33d8f6e 100644 --- a/embassy/demos/std/Cargo.toml +++ b/embassy/demos/std/Cargo.toml @@ -28,6 +28,8 @@ heapless = "0.7.15" libc = "0.2.101" async-io = "1.6.0" +menu = "0.3" + sunset-embassy = { path = "../../" } sunset = { path = "../../.." } diff --git a/embassy/demos/std/src/demo_menu.rs b/embassy/demos/std/src/demo_menu.rs new file mode 100644 index 0000000000000000000000000000000000000000..a5443efd8b7795cf48c51e57493d35e1502f50ab --- /dev/null +++ b/embassy/demos/std/src/demo_menu.rs @@ -0,0 +1,180 @@ +use sunset_embassy::SSHServer; +use sunset::Result; + +use menu::*; +use core::fmt::Write; + +#[derive(Default)] +pub struct Output { + s: heapless::String<1024>, +} + +impl Output { + pub async fn flush(&mut self, serv: &SSHServer<'_>, chan: u32) -> Result<()> { + + let mut b = self.s.as_str().as_bytes(); + while b.len() > 0 { + let l = serv.write_channel(chan, None, b).await?; + b = &b[l..]; + } + self.s.clear(); + Ok(()) + } +} + +impl core::fmt::Write for Output { + fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { + for c in s.chars() { + if c == '\n' { + self.s.push('\r').map_err(|_| core::fmt::Error)?; + } + self.s.push(c).map_err(|_| core::fmt::Error)?; + } + Ok(()) + } +} + +// from menu crate examples/simple.rs + +pub const ROOT_MENU: Menu<Output> = Menu { + label: "root", + items: &[ + &Item { + item_type: ItemType::Callback { + function: select_foo, + parameters: &[ + Parameter::Mandatory { + parameter_name: "a", + help: Some("This is the help text for 'a'"), + }, + Parameter::Optional { + parameter_name: "b", + help: None, + }, + Parameter::Named { + parameter_name: "verbose", + help: None, + }, + Parameter::NamedValue { + parameter_name: "level", + argument_name: "INT", + help: Some("Set the level of the dangle"), + }, + ], + }, + command: "foo", + help: Some( + "Makes a foo appear. + +This is some extensive help text. + +It contains multiple paragraphs and should be preceeded by the parameter list. +", + ), + }, + &Item { + item_type: ItemType::Callback { + function: select_bar, + parameters: &[], + }, + command: "bar", + help: Some("fandoggles a bar"), + }, + &Item { + item_type: ItemType::Menu(&Menu { + label: "sub", + items: &[ + &Item { + item_type: ItemType::Callback { + function: select_baz, + parameters: &[], + }, + command: "baz", + help: Some("thingamobob a baz"), + }, + &Item { + item_type: ItemType::Callback { + function: select_quux, + parameters: &[], + }, + command: "quux", + help: Some("maximum quux"), + }, + ], + entry: Some(enter_sub), + exit: Some(exit_sub), + }), + command: "sub", + help: Some("enter sub-menu"), + }, + ], + entry: Some(enter_root), + exit: Some(exit_root), +}; + +fn enter_root(_menu: &Menu<Output>, context: &mut Output) { + writeln!(context, "In enter_root").unwrap(); +} + +fn exit_root(_menu: &Menu<Output>, context: &mut Output) { + writeln!(context, "In exit_root").unwrap(); +} + +fn select_foo<'a>(_menu: &Menu<Output>, item: &Item<Output>, args: &[&str], context: &mut Output) { + writeln!(context, "In select_foo. Args = {:?}", args).unwrap(); + writeln!( + context, + "a = {:?}", + ::menu::argument_finder(item, args, "a") + ) + .unwrap(); + writeln!( + context, + "b = {:?}", + ::menu::argument_finder(item, args, "b") + ) + .unwrap(); + writeln!( + context, + "verbose = {:?}", + ::menu::argument_finder(item, args, "verbose") + ) + .unwrap(); + writeln!( + context, + "level = {:?}", + ::menu::argument_finder(item, args, "level") + ) + .unwrap(); + writeln!( + context, + "no_such_arg = {:?}", + ::menu::argument_finder(item, args, "no_such_arg") + ) + .unwrap(); +} + +fn select_bar<'a>(_menu: &Menu<Output>, _item: &Item<Output>, args: &[&str], context: &mut Output) { + writeln!(context, "In select_bar. Args = {:?}", args).unwrap(); +} + +fn enter_sub(_menu: &Menu<Output>, context: &mut Output) { + writeln!(context, "In enter_sub").unwrap(); +} + +fn exit_sub(_menu: &Menu<Output>, context: &mut Output) { + writeln!(context, "In exit_sub").unwrap(); +} + +fn select_baz<'a>(_menu: &Menu<Output>, _item: &Item<Output>, args: &[&str], context: &mut Output) { + writeln!(context, "In select_baz: Args = {:?}", args).unwrap(); +} + +fn select_quux<'a>( + _menu: &Menu<Output>, + _item: &Item<Output>, + args: &[&str], + context: &mut Output, +) { + writeln!(context, "In select_quux: Args = {:?}", args).unwrap(); +} diff --git a/embassy/demos/std/src/main.rs b/embassy/demos/std/src/main.rs index 83a6ca6242a606e744f975513ed96fe38b7b2746..deac6e983dd2dfb49317bce2e55bc294d3de6f63 100644 --- a/embassy/demos/std/src/main.rs +++ b/embassy/demos/std/src/main.rs @@ -27,6 +27,9 @@ use core::task::{Context,Poll,Waker,RawWaker,RawWakerVTable}; use rand::rngs::OsRng; use rand::RngCore; +use menu::Runner as MenuRunner; +use menu::Menu; + use sunset::*; use sunset::error::TrapBug; use sunset_embassy::SSHServer; @@ -34,6 +37,7 @@ use sunset_embassy::SSHServer; use crate::tuntap::TunTapDevice; mod tuntap; +mod demo_menu; const NUM_LISTENERS: usize = 4; @@ -87,7 +91,6 @@ async fn listener(stack: &'static Stack<TunTapDevice>) -> ! { loop { let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); - socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); info!("Listening on TCP:22..."); if let Err(e) = socket.accept(22).await { @@ -158,6 +161,7 @@ impl<'a> ServBehaviour for DemoServer<'a> { } } + #[derive(Default)] struct DemoShell { notify: Signal<CriticalSectionRawMutex, u32>, @@ -167,28 +171,31 @@ impl DemoShell { async fn run<'f>(&self, serv: &SSHServer<'f>) -> Result<()> { let session = async { + // wait for a shell to start let chan = self.notify.wait().await; + let mut menu_buf = [0u8; 64]; + let mut menu_out = demo_menu::Output::default(); + + let mut menu = MenuRunner::new(&demo_menu::ROOT_MENU, &mut menu_buf, menu_out); + loop { - let mut b = [0u8; 100]; + let mut b = [0u8; 20]; let lr = serv.read_channel_stdin(chan, &mut b).await?; let b = &mut b[..lr]; - for c in b.iter_mut() { - if *c >= b'1' && *c <= b'9' { - *c = b'1' + (b'9' - *c) - } - } - let lw = serv.write_channel(chan, None, b).await?; - if lr != lw { - trace!("read/write mismatch {} {}", lr, lw); + for c in b.iter() { + menu.input_byte(*c); + menu.context.flush(serv, chan).await?; } } Ok(()) }; + session.await } } + async fn session(socket: &mut TcpSocket<'_>) -> sunset::Result<()> { let shell = DemoShell::default(); let app = DemoServer::new(&shell)?;