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