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 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; } fn find_begin_of_improv_packet(buffer: &Vec) -> Result { let mut improv_header_char: usize = 0; for (i, b) in buffer.iter().enumerate() { if b == &IMPROV_HEADER[improv_header_char] { improv_header_char += 1; if improv_header_char >= IMPROV_HEADER.len() { return Ok(i - (IMPROV_HEADER.len() - 1)); } } else { improv_header_char = 0; if b == &IMPROV_HEADER[improv_header_char] { improv_header_char += 1; } } } return Err(String::from("Improv header not found")); } #[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() .unwrap() .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 {}).to_raw_packet(); println!("{}", hex::encode(&request_current_state_packet.to_bytes())); println!("{}", to_ascii_debug(&request_current_state_packet.to_bytes())); let mut serial_interface = tokio_serial::new(path, *baud_rate).open().unwrap(); serial_interface.write(&request_current_state_packet.to_bytes()).unwrap(); let mut buffer: Vec = Vec::new(); serial_interface.read_to_end(&mut buffer); println!("{}", hex::encode(&buffer)); println!("{}", to_ascii_debug(&buffer)); println!("{}", std::str::from_utf8(&buffer).unwrap_or("")); let improv_packet_offset = find_begin_of_improv_packet(&buffer).unwrap(); println!("{}", improv_packet_offset); let improv_packet_end = improv_packet_offset + 10 + >::into(buffer[improv_packet_offset+8]); let raw_packet = RawPacket::try_from_bytes(&buffer[improv_packet_offset..improv_packet_end].to_vec()).unwrap(); // version println!("Version: {}", &raw_packet.version); // type println!("Type: {}", &raw_packet.r#type); if let ImprovPacket::CurrentStateResponse(current_state_response) = ImprovPacket::try_from_raw_packet(&raw_packet).unwrap() { println!("Current state: {}", ¤t_state_response.current_state); } if let ImprovPacket::ErrorState(error_state) = ImprovPacket::try_from_raw_packet(&raw_packet).unwrap() { println!("Error state: {}", &error_state.error_state); } }, DeviceCommands::SetWifi {ssid, password} => { println!("Not implemented"); }, DeviceCommands::Info => { let request_device_information_packet = (RequestDeviceInformationPacket {}).to_raw_packet(); let mut serial_interface = tokio_serial::new(path, *baud_rate).open().unwrap(); serial_interface.write(&request_device_information_packet.to_bytes()).unwrap(); let mut buffer: Vec = Vec::new(); serial_interface.read_to_end(&mut buffer); let improv_packet_offset = find_begin_of_improv_packet(&buffer).unwrap(); let improv_packet_end = improv_packet_offset + 10 + >::into(buffer[improv_packet_offset+8]); let raw_packet = RawPacket::try_from_bytes(&buffer[improv_packet_offset..improv_packet_end].to_vec()).unwrap(); if let ImprovPacket::RPCResult(rpc_result) = ImprovPacket::try_from_raw_packet(&raw_packet).unwrap() { for r in rpc_result.results { println!("{}", &r); } } }, }; }, }; Ok(()) }