228 lines
6.5 KiB
Rust
228 lines
6.5 KiB
Rust
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<String>,
|
|
|
|
/// Format
|
|
#[arg(short = 'o', long,)]
|
|
file: Option<String>,
|
|
|
|
/// Frequency
|
|
#[arg(short = 'f', long)]
|
|
freq: Option<u32>,
|
|
|
|
/// Seconds to wait between measurements
|
|
#[arg(short = 'w', long, default_value = "5")]
|
|
wait: Option<u16>,
|
|
}
|
|
|
|
// 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<dyn Error>> {
|
|
// 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::<i32>() {
|
|
// 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));
|
|
}
|
|
}
|