use chrono::Local; use clap::Parser; use serialport::DataBits; use serialport::Parity; use serialport::StopBits; use std::error::Error; use std::fs::OpenOptions; use std::io::ErrorKind; use std::io::Write; use std::thread::sleep; use std::time::Duration; /// Command-line arguments using Clap. #[derive(Parser, Debug)] #[command(author, version, about = "ASMR: Automatic S-Meter Reader")] struct Cli { /// COM port #[arg(short = 'c', long)] com: Option, /// Format #[arg(short = 'o', long,)] file: Option, /// Frequency #[arg(short = 'f', long)] freq: Option, /// Seconds to wait between measurements #[arg(short = 'w', long, default_value = "5")] wait: Option, } // Read S-meter fn get_smeter(comport: &str) -> String { let mut port = serialport::new(comport, 4800) .data_bits(DataBits::Eight) .stop_bits(StopBits::Two) .parity(Parity::None) .timeout(Duration::from_millis(100)) .open() .expect("Failed to open port"); // Duh, you need the ";" on the end! // let output = "FA14075000;"; // set VFO A frequency // let output = "FA;"; // ask for VFO A frequency let output = "SM;"; // ask for S-meter reading // What are we actually sending? //dbg!(output.as_bytes()); match port.write(output.as_bytes()) { Ok(_) => { std::io::stdout().flush().unwrap(); } Err(ref e) if e.kind() == ErrorKind::TimedOut => (), Err(e) => eprintln!("{:?}", e), } // Sleep to allow the device to switch from read to write. // Otherwise we can lose some bytes on read. sleep(Duration::from_millis(50)); let mut buf = [0u8; 1024]; loop { match port.read(&mut buf) { Ok(n) => { let data = String::from_utf8_lossy(&buf[..n]).to_string(); //dbg!(data.as_bytes()); //let value = data; // For S-meter, just take the numbers let value = &data[2..5]; return format!("{}", value); // return; } Err(_) => {} // lol that I can just not put a return in? } } } // Set frequency fn set_freq(comport: &str, freq: u32) -> String { let mut port = serialport::new(comport, 4800) .data_bits(DataBits::Eight) .stop_bits(StopBits::Two) .parity(Parity::None) .timeout(Duration::from_millis(100)) .open() .expect("Failed to open port"); // Duh, you need the ";" on the end! //let output = "FA14075000;"; // set VFO A frequency let output = format!("FA{};", freq); //println!("{output}"); match port.write(output.as_bytes()) { Ok(_) => { std::io::stdout().flush().unwrap(); } Err(ref e) if e.kind() == ErrorKind::TimedOut => (), Err(e) => eprintln!("{:?}", e), } let mut buf = [0u8; 1024]; match port.read(&mut buf) { Ok(_) => { return format!("Frequency set."); } Err(_) => { return format!("Failed to set frequency."); } } } // Write row to a file fn write_row(str_arr: [String; 5], file: &String, fmt: &str) -> Result<(), Box> { // From https://docs.rs/csv/latest/csv/tutorial/index.html#writing-csv if fmt == "csv" { let file = OpenOptions::new() .write(true) .create(true) .append(true) .open(file) .unwrap(); let mut wtr = csv::Writer::from_writer(file); // Since we're writing records manually, we must explicitly write our // header record. A header record is written the same way that other // records are written. // wtr.write_record(["City", "State", "Population", "Latitude", "Longitude"])?; let _ = wtr.write_record(str_arr); // A CSV writer maintains an internal buffer, so it's important // to flush the buffer when you're done. wtr.flush()?; Ok(()) } else { println!("Only accepts csv format for now, not recording to file."); Ok(()) } } // Main funtion fn main() { // Arguments let cli = Cli::parse(); let com = match cli.com { Some(com) => com, None => { // https://github.com/serialport/serialport-rs#listing-available-ports let ports = serialport::available_ports().expect("No ports found!"); println!("Available Ports:"); for p in ports { println!("{}", p.port_name); } println!("\nMissing `-c` port argument, use one listed above."); return; } //todo!() }; let file_out = match cli.file { Some(file_out) => { println!("Saving results to {}", file_out); file_out }, None => { println!("Not saving results. Use `-o file.csv` to save to a file."); "no file".to_string() } //todo!() }; let frequency = match cli.freq { Some(frequency) => frequency, None => { return; } //todo!() }; let wait: u16 = match cli.wait { Some(wait) => wait, None => { return; } //todo!() }; // Set the requested frequency set_freq(&com, frequency); println!("Press Ctrl + C to quit."); // Simple test loop to make sure we are getting values each time //for _i in 1..11 { loop { // Get datestamp let date = Local::now(); // Get S-meter let s_val = get_smeter(&com); //println!("{a}"); match s_val.parse::() { // Simple graph as we go. Eventually this will be replaced. Ok(n) => println!( "{} {} {}", date.format("%Y-%m-%d %H:%M:%S").to_string(), s_val, "#".repeat(n.try_into().unwrap()) ), Err(_e) => todo!(), } // If they provided a file name if file_out != "no file" { if let Err(err) = write_row( [ date.format("%Y-%m-%d %H:%M:%S").to_string(), frequency.to_string(), "lat".to_string(), "lon".to_string(), s_val.to_string(), ], &file_out, "csv", ) { println!("{}", err); } } sleep(Duration::from_millis(wait as u64 * 1000)); } }