From d353b1b055970448cb69ee4ec95c7c9f2b97343b Mon Sep 17 00:00:00 2001 From: mattbk Date: Fri, 24 Oct 2025 23:16:19 -0500 Subject: [PATCH 01/10] Parse APRS location from packets that have that information. --- Cargo.toml | 2 ++ src/main.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4516ff0..bdc2fac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,8 @@ authors = ["Chris, N6CTA "] anyhow = "1.0" chrono = "0.4" clap = { version = "4", features = ["derive"] } +rand = "0.9.2" +regex = "1.12.2" reqwest = { version = "0.12.24", features = ["json", "blocking"] } serde_json = "1.0.145" socket2 = "0.5" diff --git a/src/main.rs b/src/main.rs index a4e3fa8..c9ecdd9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,9 @@ use std::net::UdpSocket; use std::net::Ipv4Addr; use serde_json::json; use reqwest; +use regex::Regex; +use rand::Rng; + /// Validate that the provided port string can be parsed into a u16 and is nonzero. fn validate_port(port: &str) -> Result { @@ -396,6 +399,9 @@ fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager) { } else { basic_destination.clone() }; + // Extract location from APRS format + let (lat, lon) = aprs_loc(&text); + //println!("{}, {}", lat, lon); // Ignore frames where the basic destination contains "NODES" (case‑insensitive). if basic_destination.to_uppercase().contains("NODES") { @@ -437,6 +443,8 @@ fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager) { "de_call": &spotter, "freq": &freq, "comment": &text, + "dx_latitude": &lat, + "dx_longitude": &lon, "mode": "PKT", "mode_type": "DATA", "mode_source": "SPOT", @@ -446,6 +454,7 @@ fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager) { let res = client.post(spothole_url) .json(&packet) .send(); + println!("sent = {}", packet); println!("res = {res:?}"); @@ -461,7 +470,9 @@ fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager) { "source": &source, "spotter": &spotter, "summary": &summary, - "text": &text, + "text": &text, + "dx_latitude": &lat, + "dx_longitude": &lon, "freq": &freq, "timestamp": SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(), "type": "data" @@ -569,4 +580,49 @@ fn main() -> Result<()> { println!("Disconnected. Reconnecting in {} ms...", reconnect_delay_ms); sleep(Duration::from_millis(reconnect_delay_ms)); } +} + +fn aprs_loc(packet: &str) -> (f64, f64) { + + // Capture different pieces of the location stringf + let re_loc = Regex::new(r"(?P\d{2})(?P[\d ]{2}\.[\d ]{2})(?P[nsNS])/(?P\d{3})(?P[\d ]{2}\.[\d ]{2})(?P[ewEW])").unwrap(); + + // Only proceed if there were captures + match re_loc.captures(&packet) { + Some(_caps) => { + + // Break captures into named values + let loc = re_loc.captures(&packet).unwrap(); + + // Initiate randomness for ambiguity.. + let mut rng = rand::rng(); + + // Convert to decimal degrees. If ambiguity spaces are included (see APRS spec), replace them with random digits. + let mut lat_dec: f64 = &loc["latd"].trim().parse().expect("Expects a number!") + (&loc["latm"].replace(" ", &rng.random_range(0..9).to_string()).trim().parse().expect("Expects a number!") / 60.0); + // If south, make negative + if &loc["ns"] == "S" { + lat_dec = lat_dec * -1.0 + } + + // TODO if there are spaces in loc["latm"], the same spaces need to be in loc["lonm"] for proper ambiguity according to APRS spec + + let mut lon_dec: f64 = &loc["lond"].trim().parse().expect("Expects a number!") + (&loc["lonm"].replace(" ", &rng.random_range(0..9).to_string()).trim().parse().expect("Expects a number!") / 60.0); + // If west, make negative + if &loc["ew"] == "W" { + lon_dec = lon_dec * -1.0 + } + + // String to paste into map for testing + //println!("{}, {}", lat_dec, lon_dec); + + // Return + (lat_dec, lon_dec) + + } + // Otherwise if there were no captures, return bad data + None => { + // The regex did not match. Deal with it here! + (-9999.0_f64, -9999.0_f64) + } +} } \ No newline at end of file From f1d0f6dc31d710fe51194c0ca54f16d22c80ef51 Mon Sep 17 00:00:00 2001 From: mattbk Date: Sat, 25 Oct 2025 22:46:43 -0500 Subject: [PATCH 02/10] Clean up APRS location logic and add spotter location. --- src/main.rs | 163 +++++++++++++++++++++++++++------------------------- 1 file changed, 85 insertions(+), 78 deletions(-) diff --git a/src/main.rs b/src/main.rs index c9ecdd9..1c4911b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -65,7 +65,7 @@ struct Cli { /// Spotter callsign (e.g. W1CDN) #[arg(short = 's', long)] - spotter: Option, + my_call: Option, /// Spot UI frames to a Spothole server #[arg(short = 'o', long, default_value_t = false)] @@ -79,6 +79,14 @@ struct Cli { #[arg(short = 'f', long, default_value_t = 14105000)] freq: u32, + /// Spotter latitude DD.dddd + #[arg(short = 'y', long, default_value_t = -9999.0_f64)] + my_lat: f64, + + /// Spotter longitude DD.dddd; to send negaitve (W or S), use = e.g. --my-lon=-97.1 + #[arg(short = 'x', long, default_value_t = -9999.0_f64)] + my_lon: f64, + } /// Convert a byte slice into a hex-dump string for debugging purposes. @@ -379,9 +387,10 @@ fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager) { let source = hdr.callfrom_str(); let basic_destination = hdr.callto_str(); let timestamp = Local::now().format("%H:%M:%S").to_string(); - let spotter = &cli.spotter; + let my_call = &cli.my_call; let freq = &cli.freq; - //let spothole_alt = &cli.spothole_alt; + let my_lat = &cli.my_lat; + let my_lon = &cli.my_lon; // If user provides an alternate Spothole URL, use that one let spothole_url = match &cli.spothole_alt { @@ -399,9 +408,12 @@ fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager) { } else { basic_destination.clone() }; + // Extract location from APRS format let (lat, lon) = aprs_loc(&text); - //println!("{}, {}", lat, lon); + // Only send good locations on + let json_lat = if lat > -9999.0_f64 && lon > -9999.0_f64 { lat.to_string() } else { "".to_string() }; // send nothing + let json_lon = if lat > -9999.0_f64 && lon > -9999.0_f64 { lon.to_string() } else { "".to_string() }; // send nothing // Ignore frames where the basic destination contains "NODES" (case‑insensitive). if basic_destination.to_uppercase().contains("NODES") { @@ -430,34 +442,27 @@ fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager) { // If Spothole is enabled if summary == "UI" && cli.spothole { - // curl --request POST --header "Content-Type: application/json" - // --data '{"dx_call":"M0TRT","time":1760019539, "freq":14200000, - // "comment":"Test spot please ignore", "de_call":"M0TRT"}' https://spothole.app/api/v1/spot - - println!("spothole_url: {}", spothole_url); - - - // POST JSON - let packet = json!({ - "dx_call": &source, - "de_call": &spotter, - "freq": &freq, - "comment": &text, - "dx_latitude": &lat, - "dx_longitude": &lon, - "mode": "PKT", - "mode_type": "DATA", - "mode_source": "SPOT", - "time": SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(), - }); - let client = reqwest::blocking::Client::new(); - let res = client.post(spothole_url) + // POST JSON + let packet = json!({ + "dx_call": &source, + "de_call": &my_call, + "de_latitude": &my_lat, + "de_longitude": &my_lon, + "freq": &freq, + "comment": &text, + "dx_latitude": &json_lat, + "dx_longitude": &json_lon, + "mode": "PKT", + "mode_type": "DATA", + "mode_source": "SPOT", + "time": SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(), + }); + let client = reqwest::blocking::Client::new(); + let res = client.post(spothole_url) .json(&packet) .send(); - println!("sent = {}", packet); - println!("res = {res:?}"); - - + println!("sent to {} = {}", spothole_url, packet); + println!("res = {res:?}"); } // Send UDP @@ -468,11 +473,13 @@ fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager) { let packet = json!({ "final_destination": &final_destination, "source": &source, - "spotter": &spotter, + "spotter": &my_call, + "spotter_latitude": &my_lat, + "spotter_longitude": &my_lon, "summary": &summary, "text": &text, - "dx_latitude": &lat, - "dx_longitude": &lon, + "dx_latitude": &json_lat, + "dx_longitude": &json_lon, "freq": &freq, "timestamp": SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(), "type": "data" @@ -491,6 +498,51 @@ fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager) { } } + +fn aprs_loc(packet: &str) -> (f64, f64) { + // Capture different pieces of the location stringf + let re_loc = Regex::new(r"(?P\d{2})(?P[\d ]{2}\.[\d ]{2})(?P[nsNS])/(?P\d{3})(?P[\d ]{2}\.[\d ]{2})(?P[ewEW])").unwrap(); + + // Only proceed if there were captures + match re_loc.captures(&packet) { + Some(_caps) => { + + // Break captures into named values + let loc = re_loc.captures(&packet).unwrap(); + + // Initiate randomness for ambiguity.. + let mut rng = rand::rng(); + + // Convert to decimal degrees. If ambiguity spaces are included (see APRS spec), replace them with random digits. + let mut lat_dec: f64 = &loc["latd"].trim().parse().expect("Expects a number!") + (&loc["latm"].replace(" ", &rng.random_range(0..9).to_string()).trim().parse().expect("Expects a number!") / 60.0); + // If south, make negative + if &loc["ns"] == "S" { + lat_dec = lat_dec * -1.0 + } + + // TODO if there are spaces in loc["latm"], the same spaces need to be in loc["lonm"] for proper ambiguity according to APRS spec + + let mut lon_dec: f64 = &loc["lond"].trim().parse().expect("Expects a number!") + (&loc["lonm"].replace(" ", &rng.random_range(0..9).to_string()).trim().parse().expect("Expects a number!") / 60.0); + // If west, make negative + if &loc["ew"] == "W" { + lon_dec = lon_dec * -1.0 + } + + // String to paste into map for testing + //println!("{}, {}", lat_dec, lon_dec); + + // Return + (lat_dec, lon_dec) + + } + // Otherwise if there were no captures, return bad data + None => { + // The regex did not match. Deal with it here! + (-9999.0_f64, -9999.0_f64) + } +} +} + /// Main entry point: /// - Parses CLI options. /// - Connects to the AGWPE server and sends the monitor command. @@ -580,49 +632,4 @@ fn main() -> Result<()> { println!("Disconnected. Reconnecting in {} ms...", reconnect_delay_ms); sleep(Duration::from_millis(reconnect_delay_ms)); } -} - -fn aprs_loc(packet: &str) -> (f64, f64) { - - // Capture different pieces of the location stringf - let re_loc = Regex::new(r"(?P\d{2})(?P[\d ]{2}\.[\d ]{2})(?P[nsNS])/(?P\d{3})(?P[\d ]{2}\.[\d ]{2})(?P[ewEW])").unwrap(); - - // Only proceed if there were captures - match re_loc.captures(&packet) { - Some(_caps) => { - - // Break captures into named values - let loc = re_loc.captures(&packet).unwrap(); - - // Initiate randomness for ambiguity.. - let mut rng = rand::rng(); - - // Convert to decimal degrees. If ambiguity spaces are included (see APRS spec), replace them with random digits. - let mut lat_dec: f64 = &loc["latd"].trim().parse().expect("Expects a number!") + (&loc["latm"].replace(" ", &rng.random_range(0..9).to_string()).trim().parse().expect("Expects a number!") / 60.0); - // If south, make negative - if &loc["ns"] == "S" { - lat_dec = lat_dec * -1.0 - } - - // TODO if there are spaces in loc["latm"], the same spaces need to be in loc["lonm"] for proper ambiguity according to APRS spec - - let mut lon_dec: f64 = &loc["lond"].trim().parse().expect("Expects a number!") + (&loc["lonm"].replace(" ", &rng.random_range(0..9).to_string()).trim().parse().expect("Expects a number!") / 60.0); - // If west, make negative - if &loc["ew"] == "W" { - lon_dec = lon_dec * -1.0 - } - - // String to paste into map for testing - //println!("{}, {}", lat_dec, lon_dec); - - // Return - (lat_dec, lon_dec) - - } - // Otherwise if there were no captures, return bad data - None => { - // The regex did not match. Deal with it here! - (-9999.0_f64, -9999.0_f64) - } -} } \ No newline at end of file From 863657f61f70de5da8a09eb90f1af297cbc0f80c Mon Sep 17 00:00:00 2001 From: mattbk Date: Mon, 27 Oct 2025 20:59:58 -0500 Subject: [PATCH 03/10] Cache locations shared in packets. --- src/main.rs | 51 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/src/main.rs b/src/main.rs index 1c4911b..507eaeb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -378,7 +378,7 @@ impl BufferManager { /// - Filters out frames destined for "NODES" and frames with an XID payload. /// - Optionally filters to only UI frames if requested. /// - Buffers multi-line frames and prints a formatted session line. -fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager) { +fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager, loc_store: &mut HashMap>) { let hdr = &frame.header; // Process only frames on the specified channel. if hdr.port != cli.channel as i32 { @@ -411,9 +411,26 @@ fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager) { // Extract location from APRS format let (lat, lon) = aprs_loc(&text); + + // Store the location + // Only update location store if there is a real location + if lat > -9999.0_f64 && lon > -9999.0_f64 { + let loc = vec![lat.to_string(), lon.to_string()]; + loc_store.insert(source.clone(), loc); + } + + // Look up a stored location + // If it doesn't exist, set to empty + let stored_loc = match loc_store.get(&source) { + Some(loc_value) => loc_value, + None => &vec!["".to_string(), "".to_string()], + }; + // Only send good locations on - let json_lat = if lat > -9999.0_f64 && lon > -9999.0_f64 { lat.to_string() } else { "".to_string() }; // send nothing - let json_lon = if lat > -9999.0_f64 && lon > -9999.0_f64 { lon.to_string() } else { "".to_string() }; // send nothing + let json_lat = stored_loc[0].clone() ; + let json_lon = stored_loc[1].clone() ; + //let json_lat = if lat > -9999.0_f64 && lon > -9999.0_f64 { lat.to_string() } else { old_loc[0].clone() }; + //let json_lon = if lat > -9999.0_f64 && lon > -9999.0_f64 { lon.to_string() } else { old_loc[1].clone() }; // Ignore frames where the basic destination contains "NODES" (case‑insensitive). if basic_destination.to_uppercase().contains("NODES") { @@ -440,6 +457,18 @@ fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager) { return; } + // In non-debug mode, print the session line and any additional lines. + if !cli.debug { + print_session_line(×tamp, &source, &final_destination, &summary); + if lines.len() > 1 { + for line in &lines[1..] { + println!("{}", line); + } + } + } + + println!("Stored location: {} {}", stored_loc[0].clone(), stored_loc[1].clone()); + // If Spothole is enabled if summary == "UI" && cli.spothole { // POST JSON @@ -486,16 +515,6 @@ fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager) { }); let _ = socket.expect("REASON").send_to(packet.to_string().as_bytes(), uaddr); } - - // In non-debug mode, print the session line and any additional lines. - if !cli.debug { - print_session_line(×tamp, &source, &final_destination, &summary); - if lines.len() > 1 { - for line in &lines[1..] { - println!("{}", line); - } - } - } } @@ -554,6 +573,10 @@ fn main() -> Result<()> { let uaddr = format!("{}:{}", cli.uip, cli.uport); let reconnect_delay_ms = 5000; + // Set up the location store + //let mut loc_store = HashMap:: new(); + let mut loc_store: HashMap> = HashMap::new(); + if cli.uport != 55555 { // Bind the client socket to any available address and port let socket = UdpSocket::bind("0.0.0.0:0")?; @@ -601,7 +624,7 @@ fn main() -> Result<()> { while buffer.len() >= 36 { match parse_frame(&buffer, cli.debug) { Ok((consumed, frame)) => { - handle_frame(&frame, &cli, &mut buffers); + handle_frame(&frame, &cli, &mut buffers, &mut loc_store); // Remove the processed frame from the buffer. buffer.drain(0..consumed); } From b6b627ef656981effbb3462380c905a728c03f42 Mon Sep 17 00:00:00 2001 From: mattbk Date: Tue, 28 Oct 2025 12:42:33 -0500 Subject: [PATCH 04/10] Rearrange. --- src/main.rs | 47 ++++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/main.rs b/src/main.rs index 507eaeb..596fc8c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -408,29 +408,6 @@ fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager, loc_st } else { basic_destination.clone() }; - - // Extract location from APRS format - let (lat, lon) = aprs_loc(&text); - - // Store the location - // Only update location store if there is a real location - if lat > -9999.0_f64 && lon > -9999.0_f64 { - let loc = vec![lat.to_string(), lon.to_string()]; - loc_store.insert(source.clone(), loc); - } - - // Look up a stored location - // If it doesn't exist, set to empty - let stored_loc = match loc_store.get(&source) { - Some(loc_value) => loc_value, - None => &vec!["".to_string(), "".to_string()], - }; - - // Only send good locations on - let json_lat = stored_loc[0].clone() ; - let json_lon = stored_loc[1].clone() ; - //let json_lat = if lat > -9999.0_f64 && lon > -9999.0_f64 { lat.to_string() } else { old_loc[0].clone() }; - //let json_lon = if lat > -9999.0_f64 && lon > -9999.0_f64 { lon.to_string() } else { old_loc[1].clone() }; // Ignore frames where the basic destination contains "NODES" (case‑insensitive). if basic_destination.to_uppercase().contains("NODES") { @@ -467,6 +444,29 @@ fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager, loc_st } } + // Extract location from APRS format + let (lat, lon) = aprs_loc(&text); + + // Store the location + // Only update location store if there is a real location + if lat > -9999.0_f64 && lon > -9999.0_f64 { + let loc = vec![lat.to_string(), lon.to_string()]; + loc_store.insert(source.clone(), loc); + } + + // Look up a stored location + // If it doesn't exist, set to empty + let stored_loc = match loc_store.get(&source) { + Some(loc_value) => loc_value, + None => &vec!["".to_string(), "".to_string()], + }; + + // Only send good locations on + let json_lat = stored_loc[0].clone() ; + let json_lon = stored_loc[1].clone() ; + //let json_lat = if lat > -9999.0_f64 && lon > -9999.0_f64 { lat.to_string() } else { old_loc[0].clone() }; + //let json_lon = if lat > -9999.0_f64 && lon > -9999.0_f64 { lon.to_string() } else { old_loc[1].clone() }; + println!("Stored location: {} {}", stored_loc[0].clone(), stored_loc[1].clone()); // If Spothole is enabled @@ -474,6 +474,7 @@ fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager, loc_st // POST JSON let packet = json!({ "dx_call": &source, + //"dx_aprs_ssid": "7", "de_call": &my_call, "de_latitude": &my_lat, "de_longitude": &my_lon, From d20188e152d530ae36c341e932e9a507ef0527e5 Mon Sep 17 00:00:00 2001 From: mattbk Date: Tue, 28 Oct 2025 21:37:44 -0500 Subject: [PATCH 05/10] Split call and ssid. --- src/main.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index 596fc8c..6aff823 100644 --- a/src/main.rs +++ b/src/main.rs @@ -392,6 +392,19 @@ fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager, loc_st let my_lat = &cli.my_lat; let my_lon = &cli.my_lon; + // Split callsign and SSID if there are both + let (source_call, source_ssid) = if source.contains("-") { + let re_source = Regex::new(r"(?P.*)-(?P[\d])").unwrap(); + // Break captures into named values + let callssid = re_source.captures(&source).unwrap(); + + (&callssid["call"].to_string(), &callssid["ssid"].to_string()) + } else { + (&source, &"".to_string()) + }; + + //println!("{} - {}", source_call, source_ssid); + // If user provides an alternate Spothole URL, use that one let spothole_url = match &cli.spothole_alt { Some(spothole_alt) => spothole_alt, @@ -464,8 +477,6 @@ fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager, loc_st // Only send good locations on let json_lat = stored_loc[0].clone() ; let json_lon = stored_loc[1].clone() ; - //let json_lat = if lat > -9999.0_f64 && lon > -9999.0_f64 { lat.to_string() } else { old_loc[0].clone() }; - //let json_lon = if lat > -9999.0_f64 && lon > -9999.0_f64 { lon.to_string() } else { old_loc[1].clone() }; println!("Stored location: {} {}", stored_loc[0].clone(), stored_loc[1].clone()); @@ -473,8 +484,8 @@ fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager, loc_st if summary == "UI" && cli.spothole { // POST JSON let packet = json!({ - "dx_call": &source, - //"dx_aprs_ssid": "7", + "dx_call": &source_call, + "dx_aprs_ssid": &source_ssid, "de_call": &my_call, "de_latitude": &my_lat, "de_longitude": &my_lon, @@ -520,7 +531,7 @@ fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager, loc_st fn aprs_loc(packet: &str) -> (f64, f64) { - // Capture different pieces of the location stringf + // Capture different pieces of the location string let re_loc = Regex::new(r"(?P\d{2})(?P[\d ]{2}\.[\d ]{2})(?P[nsNS])/(?P\d{3})(?P[\d ]{2}\.[\d ]{2})(?P[ewEW])").unwrap(); // Only proceed if there were captures From 8af43a54bbe25c1cab4676ab77f3ef963b3a1388 Mon Sep 17 00:00:00 2001 From: mattbk Date: Thu, 30 Oct 2025 08:30:20 -0500 Subject: [PATCH 06/10] Change SSID field to match API update. --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 6aff823..84bafcf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -485,7 +485,7 @@ fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager, loc_st // POST JSON let packet = json!({ "dx_call": &source_call, - "dx_aprs_ssid": &source_ssid, + "dx_ssid": &source_ssid, "de_call": &my_call, "de_latitude": &my_lat, "de_longitude": &my_lon, From ad5f2ccd0dc00f2aac40f77cf9f6dfd4ae218df7 Mon Sep 17 00:00:00 2001 From: mattbk Date: Sat, 8 Nov 2025 17:03:06 -0600 Subject: [PATCH 07/10] Send actual null values instead of empty text strings to spothole if there is no SSID. --- src/main.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index 84bafcf..87dc15c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,7 @@ use std::thread::sleep; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::net::UdpSocket; use std::net::Ipv4Addr; -use serde_json::json; +use serde_json::{json}; use reqwest; use regex::Regex; use rand::Rng; @@ -393,15 +393,19 @@ fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager, loc_st let my_lon = &cli.my_lon; // Split callsign and SSID if there are both - let (source_call, source_ssid) = if source.contains("-") { + let (source_call, source_ssid): (&String, Option<&String>) = if source.contains("-") { let re_source = Regex::new(r"(?P.*)-(?P[\d])").unwrap(); // Break captures into named values let callssid = re_source.captures(&source).unwrap(); - (&callssid["call"].to_string(), &callssid["ssid"].to_string()) + (&callssid["call"].to_string(), Some(&callssid["ssid"].to_string())) + // Otherwise there is just the call and no SSID } else { - (&source, &"".to_string()) + (&source, None) + //(&source, &String::new()) }; + println!("{:?}",(source_call, source_ssid)); + //println!("{} - {}", source_call, source_ssid); @@ -478,7 +482,7 @@ fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager, loc_st let json_lat = stored_loc[0].clone() ; let json_lon = stored_loc[1].clone() ; - println!("Stored location: {} {}", stored_loc[0].clone(), stored_loc[1].clone()); + //println!("Stored location: {} {}", stored_loc[0].clone(), stored_loc[1].clone()); // If Spothole is enabled if summary == "UI" && cli.spothole { From 268a79acda32b15a53fb7438c998bdd577da5d60 Mon Sep 17 00:00:00 2001 From: mattbk Date: Sat, 8 Nov 2025 17:07:40 -0600 Subject: [PATCH 08/10] Remove comment. --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 87dc15c..8b78f6b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -404,7 +404,7 @@ fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager, loc_st (&source, None) //(&source, &String::new()) }; - println!("{:?}",(source_call, source_ssid)); + //println!("{:?}",(source_call, source_ssid)); //println!("{} - {}", source_call, source_ssid); From 21499a93d9d1e6c13c1a9fd91e273dabb8f67dfb Mon Sep 17 00:00:00 2001 From: mattbk Date: Sat, 8 Nov 2025 19:14:43 -0600 Subject: [PATCH 09/10] Allow negative numbers in lat/lon via CLI without equal sign. --- src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 8b78f6b..7b3933b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -80,11 +80,11 @@ struct Cli { freq: u32, /// Spotter latitude DD.dddd - #[arg(short = 'y', long, default_value_t = -9999.0_f64)] + #[arg(short = 'y', long, default_value_t = -9999.0_f64, allow_negative_numbers = true)] my_lat: f64, /// Spotter longitude DD.dddd; to send negaitve (W or S), use = e.g. --my-lon=-97.1 - #[arg(short = 'x', long, default_value_t = -9999.0_f64)] + #[arg(short = 'x', long, default_value_t = -9999.0_f64, allow_negative_numbers = true)] my_lon: f64, } From 7ce55f020b1be7b0bca9174b69f41890de3753d1 Mon Sep 17 00:00:00 2001 From: mattbk Date: Sat, 8 Nov 2025 20:58:36 -0600 Subject: [PATCH 10/10] Allow none values for lat and lon of spotter so empty values get sent to spothole. --- src/main.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7b3933b..77f1e27 100644 --- a/src/main.rs +++ b/src/main.rs @@ -80,12 +80,12 @@ struct Cli { freq: u32, /// Spotter latitude DD.dddd - #[arg(short = 'y', long, default_value_t = -9999.0_f64, allow_negative_numbers = true)] - my_lat: f64, + #[arg(short = 'y', long, allow_negative_numbers = true)] + my_lat: Option, /// Spotter longitude DD.dddd; to send negaitve (W or S), use = e.g. --my-lon=-97.1 - #[arg(short = 'x', long, default_value_t = -9999.0_f64, allow_negative_numbers = true)] - my_lon: f64, + #[arg(short = 'x', long, allow_negative_numbers = true)] + my_lon: Option, }