forked from n6cta/mwtchahrd
Parse APRS location from packets that have that information. #26
@@ -8,6 +8,8 @@ authors = ["Chris, N6CTA <mail@n6cta.com>"]
|
|||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
|
rand = "0.9.2"
|
||||||
|
regex = "1.12.2"
|
||||||
reqwest = { version = "0.12.24", features = ["json", "blocking"] }
|
reqwest = { version = "0.12.24", features = ["json", "blocking"] }
|
||||||
serde_json = "1.0.145"
|
serde_json = "1.0.145"
|
||||||
socket2 = "0.5"
|
socket2 = "0.5"
|
||||||
|
|||||||
58
src/main.rs
58
src/main.rs
@@ -12,6 +12,9 @@ use std::net::UdpSocket;
|
|||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use reqwest;
|
use reqwest;
|
||||||
|
use regex::Regex;
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
|
||||||
/// Validate that the provided port string can be parsed into a u16 and is nonzero.
|
/// Validate that the provided port string can be parsed into a u16 and is nonzero.
|
||||||
fn validate_port(port: &str) -> Result<u16, String> {
|
fn validate_port(port: &str) -> Result<u16, String> {
|
||||||
@@ -396,6 +399,9 @@ fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager) {
|
|||||||
} else {
|
} else {
|
||||||
basic_destination.clone()
|
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).
|
// Ignore frames where the basic destination contains "NODES" (case‑insensitive).
|
||||||
if basic_destination.to_uppercase().contains("NODES") {
|
if basic_destination.to_uppercase().contains("NODES") {
|
||||||
@@ -437,6 +443,8 @@ fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager) {
|
|||||||
"de_call": &spotter,
|
"de_call": &spotter,
|
||||||
"freq": &freq,
|
"freq": &freq,
|
||||||
"comment": &text,
|
"comment": &text,
|
||||||
|
"dx_latitude": &lat,
|
||||||
|
"dx_longitude": &lon,
|
||||||
"mode": "PKT",
|
"mode": "PKT",
|
||||||
"mode_type": "DATA",
|
"mode_type": "DATA",
|
||||||
"mode_source": "SPOT",
|
"mode_source": "SPOT",
|
||||||
@@ -446,6 +454,7 @@ fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager) {
|
|||||||
let res = client.post(spothole_url)
|
let res = client.post(spothole_url)
|
||||||
.json(&packet)
|
.json(&packet)
|
||||||
.send();
|
.send();
|
||||||
|
println!("sent = {}", packet);
|
||||||
println!("res = {res:?}");
|
println!("res = {res:?}");
|
||||||
|
|
||||||
|
|
||||||
@@ -461,7 +470,9 @@ fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager) {
|
|||||||
"source": &source,
|
"source": &source,
|
||||||
"spotter": &spotter,
|
"spotter": &spotter,
|
||||||
"summary": &summary,
|
"summary": &summary,
|
||||||
"text": &text,
|
"text": &text,
|
||||||
|
"dx_latitude": &lat,
|
||||||
|
"dx_longitude": &lon,
|
||||||
"freq": &freq,
|
"freq": &freq,
|
||||||
"timestamp": SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(),
|
"timestamp": SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(),
|
||||||
"type": "data"
|
"type": "data"
|
||||||
@@ -569,4 +580,49 @@ fn main() -> Result<()> {
|
|||||||
println!("Disconnected. Reconnecting in {} ms...", reconnect_delay_ms);
|
println!("Disconnected. Reconnecting in {} ms...", reconnect_delay_ms);
|
||||||
sleep(Duration::from_millis(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<latd>\d{2})(?P<latm>[\d ]{2}\.[\d ]{2})(?P<ns>[nsNS])/(?P<lond>\d{3})(?P<lonm>[\d ]{2}\.[\d ]{2})(?P<ew>[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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user