306 lines
10 KiB
Rust
306 lines
10 KiB
Rust
// === 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::{Button, 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 {
|
|
/// 0 for not pressed, 1 for pressed
|
|
data: u8,
|
|
},
|
|
|
|
/// Simulate the PIC controller sending us a Down character
|
|
PicRecvDown {
|
|
/// 0 for not pressed, 1 for pressed
|
|
data: u8,
|
|
},
|
|
|
|
/// Simulate the PIC controller sending us a Stop character
|
|
PicRecvStop {
|
|
/// 0 for not pressed, 1 for pressed
|
|
data: u8,
|
|
},
|
|
|
|
/// Simulate the PIC controller sending a "pair" button press
|
|
PicRecvPair,
|
|
|
|
/// 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 },
|
|
|
|
/// Change log level (None: 0, .. Tracing: 5)
|
|
Log { level: u8 },
|
|
|
|
/// Abort (resets microcontroller)
|
|
Abort,
|
|
|
|
/// Erase BLE Bonding information
|
|
ClearBleBonds,
|
|
|
|
/// Whatever misc. output I need
|
|
Misc,
|
|
|
|
}
|
|
|
|
fn input_to_button(i: u8) -> Option<Button> {
|
|
match i {
|
|
0 => {Some(Button::Released)}
|
|
1 => {Some(Button::Pressed)}
|
|
_ => {None}
|
|
}
|
|
}
|
|
|
|
pub fn process_menu(
|
|
cli: &mut CliHandle<'_, SimpleWriter, Infallible>,
|
|
command: Menu,//<'_>,
|
|
dispatch: &Sender<Commands>,
|
|
) -> Result<(), Infallible> {
|
|
match command {
|
|
// We ignore sending errors throughout because the Cli interface is only for
|
|
// testing and debugging.
|
|
Menu::PicRecvUp {data} => {
|
|
let but = input_to_button(data);
|
|
match but {
|
|
Some(d) => {
|
|
println!("Sending PicRecvUp command");
|
|
let _ = dispatch.send_blocking(Commands::PicRecvUp{data: d});
|
|
}
|
|
None => {println!("Incorrect value; enter 0 or 1")}
|
|
}
|
|
}
|
|
Menu::PicRecvDown{data} => {
|
|
let but = input_to_button(data);
|
|
match but {
|
|
Some(d) => {
|
|
println!("Sending PicRecvUp command");
|
|
let _ = dispatch.send_blocking(Commands::PicRecvDown{data: d});
|
|
}
|
|
None => {println!("Incorrect value; enter 0 or 1")}
|
|
}
|
|
}
|
|
Menu::PicRecvStop{data} => {
|
|
let but = input_to_button(data);
|
|
match but {
|
|
Some(d) => {
|
|
println!("Sending PicRecvUp command");
|
|
let _ = dispatch.send_blocking(Commands::PicRecvStop{data: d});
|
|
}
|
|
None => {println!("Incorrect value; enter 0 or 1")}
|
|
}
|
|
}
|
|
Menu::PicRecvPair => {
|
|
cli.writer().write_str("Sending PIC command and timer reset (the job of the pair button driver, once PIC interface exists)")?; //TODO: PIC get this.
|
|
let _ = dispatch.send_blocking(Commands::AllowPairing);
|
|
}
|
|
Menu::BluetoothUp { data } => {
|
|
let but = input_to_button(data);
|
|
match but {
|
|
Some(d) => {
|
|
println!("Sending PicRecvUp command");
|
|
let _ = dispatch.send_blocking(Commands::BluetoothUp{data: d});
|
|
}
|
|
None => {println!("Incorrect value; enter 0 or 1")}
|
|
}
|
|
}
|
|
Menu::BluetoothDown { data } => {
|
|
let but = input_to_button(data);
|
|
match but {
|
|
Some(d) => {
|
|
println!("Sending PicRecvUp command");
|
|
let _ = dispatch.send_blocking(Commands::BluetoothDown{data: d});
|
|
}
|
|
None => {println!("Incorrect value; enter 0 or 1")}
|
|
}
|
|
}
|
|
Menu::BluetoothStop { data } => {
|
|
let but = input_to_button(data);
|
|
match but {
|
|
Some(d) => {
|
|
println!("Sending PicRecvUp command");
|
|
let _ = dispatch.send_blocking(Commands::BluetoothStop{data: d});
|
|
}
|
|
None => {println!("Incorrect value; enter 0 or 1")}
|
|
}
|
|
}
|
|
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");
|
|
}
|
|
Menu::Abort => {panic!("CLI user requested abort");}
|
|
Menu::ClearBleBonds => {
|
|
let _ = dispatch.send_blocking(Commands::EraseBleBonds);
|
|
}
|
|
Menu::Misc => {
|
|
println!("string {}, arc string {}, enum value {}, discriminant {}", std::mem::size_of::<String>(), std::mem::size_of::<std::sync::Arc<String>>(), std::mem::size_of::<Menu>(), std::mem::size_of::<std::mem::Discriminant<Menu>>());
|
|
}
|
|
}
|
|
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<usize, Self::Error> {
|
|
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<Commands>) -> 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?; // no need to panic on test console driver timer failure
|
|
match reader.read_exact(&mut buf) {
|
|
Ok(_) => {
|
|
//cli.process_byte::<Menu, _>(buf[0], &mut Menu::processor(process_menu))?;
|
|
cli.process_byte::<Menu, _>(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 CLI input: {}", e)}
|
|
}
|
|
}
|
|
}
|
|
}
|