use chrono::Local; use clap::Parser; use nmea::Nmea; use nmea::SentenceType; 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; use riglib::{Rig as Rig_riglib, ReceiverId}; use riglib::kenwood::KenwoodBuilder; use riglib::kenwood::models::ts_890s; use riglib::s_units_from_dbm; /// Command-line arguments using Clap. #[derive(Parser, Debug)] #[command(author, version, about = "ASMR: Automatic S-Meter Reader")] struct Cli { /// Output file path #[arg(short = 'r', long, default_value = "qmx")] rig: Option, /// Rig COM port #[arg(short = 'c', long)] com: Option, /// GPS COM port #[arg(short = 'g', long)] gps: Option, /// Output file path #[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, } // Get location // Mofified from https://amiok.net/gitea/W1CDN/gpsll fn get_loc(port: String) -> [f64; 2] { let mut nmea = Nmea::default(); // Harcode port for dev //let port_name = "/dev/tty.usbmodem1301"; let port_name = port; //fn read_loc(com: &str) -> &str { //println!("Reading from {port_name}"); let port = serialport::new(port_name, 9600) .timeout(Duration::from_millis(1000)) .open(); if let Ok(mut serial) = port { let mut buf = [0u8; 1024]; loop { match serial.read(&mut buf) { Ok(n) => { let data = String::from_utf8_lossy(&buf[..n]); for line in data.lines() { // Check sentence type let sentence_type = match nmea.parse(line) { Ok(sentence_type) => sentence_type, Err(_) => { break; } }; // Only keep the GLL sentences if sentence_type == SentenceType::GLL { let lat = match nmea.latitude { Some(lat) => lat, None => -9999.25, }; let lon = match nmea.longitude { Some(lon) => lon, None => -9999.25, }; //let date = Local::now(); //println!("{}, {lat}, {lon}", date.format("%Y-%m-%d %H:%M:%S")); return [lat, lon]; //"{},{:.8},{:.8}", //date.format("%Y-%m-%d %H:%M:%S"), //"{:.8},{:.8}", } } } Err(_) => {} // lol that I can just not put a return in? } } } else { // 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); } return [-9999.25, -9999.25]; //format!("\nCould not connect to GPS port. Is it listed above?"); } } // Rig struct struct Rig { name: String, baud_rate: u32, data_bits: DataBits, stop_bits: StopBits, parity: Parity, s_meter_cat: String, freq_cat: String, } // Create a rig struct fn build_rig( name: &str, baud_rate: u32, data_bits: u8, stop_bits: u8, parity: &str, s_meter_cat: &str, freq_cat: &str, ) -> Rig { Rig { name: name.to_string(), baud_rate, data_bits: match data_bits { 5 => DataBits::Five, 6 => DataBits::Six, 7 => DataBits::Seven, 8 => DataBits::Eight, _ => DataBits::Eight, }, stop_bits: match stop_bits { 1 => StopBits::One, 2 => StopBits::Two, _ => StopBits::One, }, parity: match parity { "none" => Parity::None, "odd" => Parity::Odd, "even" => Parity::Even, _ => Parity::None, }, s_meter_cat: s_meter_cat.to_string(), freq_cat: freq_cat.to_string(), } } // Get rig connection info // https://kevinlynagh.com/notes/match-vs-lookup/ fn get_rig(x: &str) -> Rig { match x { "qmx" => build_rig("qmx", 9600, 8, 2, "none", "SM", "FA"), "ic-7300" => build_rig("ic-7300", 115200, 8, 1, "none", "1502", "FA"), _ => { // This prints a message every loop but that's OK. println!("Rig '{x}' in rig list. Using default connection values."); build_rig("qmx", 9600, 8, 2, "none", "SM", "FA") } } } // Read S-meter fn get_smeter(comport: &str, rig_name: &str, frequency: u32) -> String { // Get the serial connection info for the rig name let rig = get_rig(rig_name); // Set up the serial port let mut port = serialport::new(comport, rig.baud_rate) .data_bits(rig.data_bits) .stop_bits(rig.stop_bits) .parity(rig.parity) .timeout(Duration::from_millis(100)) .open() .expect("Failed to open port"); // Set the requested frequency // let output = "FA00014075000;"; // set VFO A frequency let output = format!("{}{:011};", rig.freq_cat, frequency); // dbg!(&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), } // Duh, you need the ";" on the end! let output = format!("{};", rig.s_meter_cat); //"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? } } } // 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 #[tokio::main] async fn main() -> anyhow::Result<()> { // Arguments let cli = Cli::parse(); let rig_name = match cli.rig { Some(rig) => { println!("Connecting to rig {}", rig); rig } None => { println!("No rig `-r` provided, using default (qmx)."); "qmx".to_string() } //todo!() }; 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` rig port argument, use one listed above."); return Ok(()); } //todo!() }; let gps = match cli.gps { Some(gps) => gps, 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 `-g` GPS port argument, use one listed above."); return Ok(()); } //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 Ok(()); } //todo!() }; let wait: u16 = match cli.wait { Some(wait) => wait, None => { return Ok(()); } //todo!() }; println!("Press Ctrl + C to quit"); let rig1 = KenwoodBuilder::new(ts_890s()) .serial_port(&com) .build() .await?; let info = rig1.info(); println!("Connected: {} {}\n", info.manufacturer, info.model_name); let rx = ReceiverId::VFO_A; dbg!(rx); let freq = rig1.get_frequency(ReceiverId::VFO_A).await?; println!("VFO-A: {} Hz", freq); //rig1.set_frequency(ReceiverId::VFO_A,).await?; // let s_val = rig1.get_s_meter(rx).await?; // println!("{s_val}"); // Simple test loop to make sure we are getting values each time //for _i in 1..11 { loop { // Get location let ll = get_loc(gps.clone()); // Get datestamp let date = Local::now(); // Get S-meter //let s_val = get_smeter(&com, &rig_name, frequency); let s_val = rig1.get_s_meter(rx).await?; // this is failing on the QMX using TS-890 unless I mess with source let s_units = s_units_from_dbm(s_val); //println!("{a}"); let s_val_i = s_val as i32; // dbg!(n); println!( "{} {:04} {} {}", date.format("%Y-%m-%d %H:%M:%S").to_string(), s_val_i, if s_val_i > 0 { "#".repeat(s_val_i.try_into().unwrap()) } else { "".to_string() }, s_units ); // match s_val { // // 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(), format!("{:09}", frequency), format!("{:.8}", ll[0]), format!("{:.8}", ll[1]), s_val_i.to_string(), ], &file_out, "csv", ) { println!("{}", err); } } sleep(Duration::from_millis((wait as u64 * 1000) - 1000)); } Ok(()) }