Compare commits

..

7 Commits

Author SHA1 Message Date
mattbk 90ba8420c5 Convert QMX s-meter measurements to s-units. 2026-09-12 21:56:54 -05:00
mattbk 0d932efc83 Working snapshot. 2026-09-12 21:22:01 -05:00
mattbk 7c9e033015 Change frequency inside get_smeter(). 2026-09-12 12:06:48 -05:00
mattbk 66eeceb728 Snapshot. 2026-09-12 11:27:01 -05:00
mattbk 8337ed478d Add readme. 2026-09-12 11:12:05 -05:00
mattbk 00a90d56dc Set up framework for different rigs to be used. See #8. 2026-09-12 10:59:27 -05:00
mattbk 15aa249f3c Snapshot. 2026-09-12 10:32:03 -05:00
3 changed files with 154 additions and 61 deletions
+1
View File
@@ -1,2 +1,3 @@
/target /target
/out /out
/riglib
+37
View File
@@ -0,0 +1,37 @@
# ASMR
ASMR: Automatic S-Meter Reader
This program reads location from a GPS dongle and the S-Meter from a
transceiver via a serial port and stores the results in a CSV file
so you can run analysis in another program.
Inspired by RFI Mapper (RFIM) by [W4DD](https://www.qrz.com/db/W4DD)
and written in Rust.
# Use Cases
- Drive a route and measure variation in noise level, to determine local noise sources
- Record noise level at a fixed location over time, to determine temporal noise patterns
- Some combination of the two
# Help
```
asmr --help
ASMR: Automatic S-Meter Reader
Usage: asmr [OPTIONS]
Options:
-r, --rig <RIG> Output file path [default: qmx]
-c, --com <COM> Rig COM port
-g, --gps <GPS> GPS COM port
-o, --file <FILE> Output file path
-f, --freq <FREQ> Frequency
-w, --wait <WAIT> Seconds to wait between measurements [default: 5]
-h, --help Print help
-V, --version Print version
```
# Contact
- [@W1CDN@mastodon.radio](https://mastodon.radio/@W1CDN)
- admin@w1cdn.net
+116 -61
View File
@@ -16,6 +16,10 @@ use std::time::Duration;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(author, version, about = "ASMR: Automatic S-Meter Reader")] #[command(author, version, about = "ASMR: Automatic S-Meter Reader")]
struct Cli { struct Cli {
/// Output file path
#[arg(short = 'r', long, default_value = "qmx")]
rig: Option<String>,
/// Rig COM port /// Rig COM port
#[arg(short = 'c', long)] #[arg(short = 'c', long)]
com: Option<String>, com: Option<String>,
@@ -24,7 +28,7 @@ struct Cli {
#[arg(short = 'g', long)] #[arg(short = 'g', long)]
gps: Option<String>, gps: Option<String>,
/// Format /// Output file path
#[arg(short = 'o', long)] #[arg(short = 'o', long)]
file: Option<String>, file: Option<String>,
@@ -104,20 +108,96 @@ fn get_loc(port: String) -> [f64; 2] {
} }
} }
// 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"),
_ => {
// 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 // Read S-meter
fn get_smeter(comport: &str) -> String { fn get_smeter(comport: &str, rig_name: &str, frequency: u32) -> i32 {
let mut port = serialport::new(comport, 4800) // Get the serial connection info for the rig name
.data_bits(DataBits::Eight) let rig = get_rig(rig_name);
.stop_bits(StopBits::Two)
.parity(Parity::None) // 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)) .timeout(Duration::from_millis(100))
.open() .open()
.expect("Failed to open port"); .expect("Failed to open port");
// Set the requested frequency
let output = format!("{}{};", rig.freq_cat, frequency);
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! // Duh, you need the ";" on the end!
// let output = "FA14075000;"; // set VFO A frequency // let output = "FA14075000;"; // set VFO A frequency
// let output = "FA;"; // ask for VFO A frequency // let output = "FA;"; // ask for VFO A frequency
let output = "SM;"; // ask for S-meter reading let output = format!("{};", rig.s_meter_cat); //"SM;"; // ask for S-meter reading
// What are we actually sending? // What are we actually sending?
//dbg!(output.as_bytes()); //dbg!(output.as_bytes());
@@ -142,7 +222,13 @@ fn get_smeter(comport: &str) -> String {
//let value = data; //let value = data;
// For S-meter, just take the numbers // For S-meter, just take the numbers
let value = &data[2..5]; let value = &data[2..5];
return format!("{}", value); let value_i = value.parse::<i32>().unwrap();
// return format!("{}", value);
if rig_name == "qmx" {
return (value_i as f32 / 6.0) as i32;
} else {
return value_i;
}
// return; // return;
} }
Err(_) => {} // lol that I can just not put a return in? Err(_) => {} // lol that I can just not put a return in?
@@ -150,40 +236,6 @@ fn get_smeter(comport: &str) -> String {
} }
} }
// 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 // Write row to a file
fn write_row(str_arr: [String; 5], file: &String, fmt: &str) -> Result<(), Box<dyn Error>> { 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 // From https://docs.rs/csv/latest/csv/tutorial/index.html#writing-csv
@@ -217,6 +269,17 @@ fn main() {
// Arguments // Arguments
let cli = Cli::parse(); let cli = Cli::parse();
let rig = 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 { let com = match cli.com {
Some(com) => com, Some(com) => com,
None => { None => {
@@ -270,31 +333,23 @@ fn main() {
} //todo!() } //todo!()
}; };
// Set the requested frequency println!("Press Ctrl + C to quit");
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 { loop {
// Get location // Get location
let ll = get_loc(gps.clone()); let ll = get_loc(gps.clone());
// Get datestamp // Get datestamp
let date = Local::now(); let date = Local::now();
// Get S-meter // Get S-meter
let s_val = get_smeter(&com); let s_val = get_smeter(&com, &rig, frequency);
//println!("{a}"); // Print measurements and simple graph
match s_val.parse::<i32>() { println!(
// Simple graph as we go. Eventually this will be replaced. "{} {:#03} {}",
Ok(n) => println!( date.format("%Y-%m-%d %H:%M:%S").to_string(),
"{} {} {}", s_val,
date.format("%Y-%m-%d %H:%M:%S").to_string(), "#".repeat(s_val.try_into().unwrap())
s_val, );
"#".repeat(n.try_into().unwrap())
),
Err(_e) => todo!(),
}
// If they provided a file name // If they provided a file name
if file_out != "no file" { if file_out != "no file" {
if let Err(err) = write_row( if let Err(err) = write_row(
@@ -304,7 +359,7 @@ fn main() {
format!("{:09}", frequency), format!("{:09}", frequency),
format!("{:.8}", ll[0]), format!("{:.8}", ll[0]),
format!("{:.8}", ll[1]), format!("{:.8}", ll[1]),
s_val.to_string(), format!("{}", s_val),
], ],
&file_out, &file_out,
"csv", "csv",