use anyhow::{ Context, Result, }; use clap::{ Parser, Subcommand, }; use env_logger; use hex; use improv_setup::improv::{ IMPROV_HEADER, IMPROV_VERSION, RPCCommand, CurrentState, calculate_checksum, ImprovDataToPacket, ImprovDataFromPacket, RawPacket, ImprovPacket, RequestCurrentStateCommand, RequestDeviceInformationPacket, }; use improv_setup::serial; use log::{ debug, log_enabled, info, Level, }; use tokio_serial; #[derive(Subcommand, Clone)] enum DeviceCommands { /// Request current state State, /// Set wifi credentials SetWifi { /// SSID of the network ssid: String, /// Password for the SSID password: String, }, /// Request device info Info, } impl Default for DeviceCommands { fn default() -> Self { return Self::State; } } #[derive(Subcommand, Clone)] enum Commands { /// List available serial devices ListDevices, /// Device Device { /// Path to the serial device path: String, /// Baud rate used for connecting to the serial device #[arg(long, default_value_t = 115200)] baud_rate: u32, #[command(subcommand)] device_command: Option, }, } #[derive(Parser)] #[command(version, about, long_about = None)] struct Cli { #[command(subcommand)] command: Option, } fn to_ascii_debug(bytes: &Vec) -> String { let mut out = String::new(); for b in bytes { if b.is_ascii_graphic() { out += &b.escape_ascii().to_string(); } else { out.push_str("."); } } return out; } fn to_bytewise_debug(bytes: &Vec) -> String { let mut out = String::new(); for b in bytes { out += &hex::encode(&[b.clone()]); out += " "; if b.is_ascii_graphic() { out += &b.escape_ascii().to_string(); } out += "\n"; } return out; } #[tokio::main] async fn main() -> Result<()>{ env_logger::init(); let cli = Cli::parse(); let command: Commands = match &cli.command { Some(command) => command.clone(), None => Commands::ListDevices, }; match &command { Commands::ListDevices => { println!("{}", tokio_serial::available_ports() .context("Failed to list available ports")? .iter() .map(|serialport| serialport.port_name.clone()) .fold(String::new(), |a, b| a + &b + &String::from("\n"))); }, Commands::Device {path, baud_rate, device_command} => { match &device_command.clone().unwrap_or_default() { DeviceCommands::State => { let request_current_state_packet = RequestCurrentStateCommand {}; let mut serial_interface = serial::SerialInterface::new(path, *baud_rate).context("Couldn't init serial interface")?; serial_interface.send(&request_current_state_packet).context("Failed to send improv packet")?; let result_bytes = serial_interface.recv_bytes().context("Couldn't receive any improv packet")?; let raw_packet = RawPacket::try_from_bytes(&result_bytes).context("Failed to deserialize packet")?; if let ImprovPacket::CurrentStateResponse(current_state_response) = ImprovPacket::try_from_raw_packet(&raw_packet).context("Failed to read packet")? { println!("Current state: {}", ¤t_state_response.current_state); } if let ImprovPacket::ErrorState(error_state) = ImprovPacket::try_from_raw_packet(&raw_packet).context("Failed to read packet")? { println!("Error state: {}", &error_state.error_state); } }, DeviceCommands::SetWifi {ssid, password} => { println!("Not implemented"); }, DeviceCommands::Info => { let request_device_information_packet = RequestDeviceInformationPacket {}; let mut serial_interface = serial::SerialInterface::new(path, *baud_rate).context("Couldn't init serial interface")?; serial_interface.send(&request_device_information_packet).context("Failed to send improv packet")?; let result_bytes = serial_interface.recv_bytes().context("Couldn't receive any improv packet")?; let raw_packet = RawPacket::try_from_bytes(&result_bytes).context("Failed to deserialize packet")?; if let ImprovPacket::RPCResult(rpc_result) = ImprovPacket::try_from_raw_packet(&raw_packet).context("Failed to read packet")? { for r in rpc_result.results { println!("{}", &r); } } }, }; }, }; Ok(()) }