// === Constants ============================================================== const MAX_COMMAND_SIZE: usize = 64; // in bytes; we want to test wifi ssid which // can be 32 bytes. const MAX_HISTORY_SIZE: usize = 128; // in bytes const SLEEP_TIME_MS: u64 = 10; // should be > 1/CONFIG_FREERTOS_HZ in sdkconfig // currently this is 1ms, which is also about as // long as it would take uart buffers to overflow // at maximum rate. But this is intended for // human input only. It's noticeably laggy at 50. // ============================================================================ use ::{ anyhow::Result, embedded_cli::{ cli::{CliBuilder, CliHandle}, Command, }, esp_idf_svc::timer::EspTaskTimerService, std::{ convert::Infallible, io::{stdin, stdout, Read, Write}, //time::Duration, // could also use core::time::Duration? }, core::time::Duration, }; use crate::commands::Commands; use async_channel::Sender; use log::*; //{trace, debug, info, warn, error} #[derive(Command)] pub enum Menu<'a> { /// Simulate the PIC controller sending aus n Up character PicRecvUp, /// Simulate the PIC controller sending us a Down character PicRecvDown, /// Simulate the PIC controller sending us a Stop character PicRecvStop, /// Send a bluetooth characteristic: Up BluetoothUp { /// 0 for not pressed, 1 for pressed data: u8, }, /// Send a bluetooth characteristic: Down BluetoothDown { /// 0 for not pressed, 1 for pressed data: u8, }, /// Send a bluetooth characteristic: Stop BluetoothStop { /// 0 for not pressed, 1 for pressed data: u8, }, /// Send a bluetooth characteristic: Learn BluetoothLearn { /// 0 for not pressed, 1 for pressed data: u8, }, /// Send a bluetooth characteristic: Auto BluetoothAuto { /// 0 for not pressed, 1 for pressed data: u8, }, /// Send a bluetooth characteristic: Limits BluetoothTopLimit { data: u8 }, /// Send a bluetooth characteristic: Limits BluetoothBottomLimit { data: u8 }, /// Send a bluetooth characteristic: Wifi SSID BluetoothWifiSsid { ssid: &'a str }, /// Send a bluetooth characteristic: Wifi Password BluetoothWifiPassword { wifipass: &'a str }, Log { level: u8 }, } pub fn process_menu( cli: &mut CliHandle<'_, SimpleWriter, Infallible>, command: Menu<'_>, dispatch: &Sender, ) -> Result<(), Infallible> { match command { // We ignore sending errors throughout because the Cli interface is only for // testing and debugging. Menu::PicRecvUp => { cli.writer().write_str("Sending PicButtonUp Received command")?; let _ = dispatch.send_blocking(Commands::PicRecvUp); } Menu::PicRecvDown => { cli.writer().write_str("Sending PicButtonDown command")?; let _ = dispatch.send_blocking(Commands::PicRecvDown); } Menu::PicRecvStop => { cli.writer().write_str("Sending PicButtonStop command")?; let _ = dispatch.send_blocking(Commands::PicRecvStop); } Menu::BluetoothUp { data } => { cli.writer() .write_str("Sending BluetoothUp")?; let _ = dispatch.send_blocking(Commands::BluetoothUp { data: data }); } Menu::BluetoothDown { data } => { cli.writer() .write_str("Sending BluetoothDown")?; let _ = dispatch.send_blocking(Commands::BluetoothDown { data: data }); } Menu::BluetoothStop { data } => { cli.writer() .write_str("SendingBluetoothStop")?; let _ = dispatch.send_blocking(Commands::BluetoothStop { data: data }); } Menu::BluetoothLearn { data } => { cli.writer() .write_str("TODO: simulate bluetooth characteristic change")?; let _ = data; } Menu::BluetoothAuto { data } => { cli.writer() .write_str("TODO: simulate bluetooth characteristic change")?; let _ = data; } Menu::BluetoothTopLimit { data } => { cli.writer() .write_str("TODO: simulate bluetooth characteristic change")?; let _ = data; } Menu::BluetoothBottomLimit { data } => { cli.writer() .write_str("TODO: simulate bluetooth characteristic change")?; let _ = data; } Menu::BluetoothWifiSsid { ssid } => { cli.writer() .write_str("TODO: simulate bluetooth characteristic change")?; let _ = ssid; } Menu::BluetoothWifiPassword { wifipass } => { cli.writer() .write_str("TODO: simulate bluetooth characteristic change {}")?; let _ = wifipass; } Menu::Log { level } => { match level { 0 => { cli.writer().write_str("Log set to Off")?; log::set_max_level(log::LevelFilter::Off); } 1 => { cli.writer().write_str("Log set to Error")?; log::set_max_level(log::LevelFilter::Error); } 2 => { cli.writer().write_str("Log set to Warn")?; log::set_max_level(log::LevelFilter::Warn); } 3 => { cli.writer().write_str("Log set to Info")?; log::set_max_level(log::LevelFilter::Info); } 4 => { cli.writer().write_str("Log set to Debug")?; log::set_max_level(log::LevelFilter::Debug); } _ => { cli.writer().write_str("Log set to Trace")?; log::set_max_level(log::LevelFilter::Trace); } } error!("error test"); warn!("warn test"); info!("info test"); debug!("debug test"); trace!("trace test"); } } Ok(()) } // === SimpleWriter =========================================================== pub struct SimpleWriter {} impl embedded_io::ErrorType for SimpleWriter { type Error = Infallible; } impl embedded_io::Write for SimpleWriter { fn write(&mut self, buf: &[u8]) -> Result { stdout().write(&buf).unwrap(); Ok(buf.len()) } fn flush(&mut self) -> Result<(), Self::Error> { let _ = stdout().flush(); Ok(()) } } pub async fn start_cli(dispatch: Sender) -> anyhow::Result<()> { let timer_service = EspTaskTimerService::new()?; info!("Setting up command line listener and processor"); let writer = SimpleWriter {}; let mut cli = CliBuilder::default() .writer(writer) .command_buffer([0_u8; MAX_COMMAND_SIZE]) .history_buffer([0_u8; MAX_HISTORY_SIZE]) .prompt("$> ") .build()?; let mut reader = stdin(); let mut buf = [0_u8; 1]; let mut async_timer = timer_service.timer_async()?; loop { async_timer.after(Duration::from_millis(SLEEP_TIME_MS)).await?; match reader.read_exact(&mut buf) { Ok(_) => { //cli.process_byte::(buf[0], &mut Menu::processor(process_menu))?; cli.process_byte::(buf[0], &mut Menu::processor( |cli, command| { process_menu(cli, command, &dispatch) }))?; } Err(e) => match e.kind() { std::io::ErrorKind::WouldBlock => {} // This is expected if there is no input _ => {warn!("Error waiting for input: {}", e)} } }; } }