Files
asmr/src/main.rs
T
2026-09-11 17:52:17 -05:00

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));
}
}