mirror of
				https://gitea.farpn.net/w1cdn/mwtchahrd.git
				synced 2025-11-03 13:36:36 -06:00 
			
		
		
		
	Clean up APRS location logic and add spotter location.
This commit is contained in:
		
							
								
								
									
										163
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										163
									
								
								src/main.rs
									
									
									
									
									
								
							@@ -65,7 +65,7 @@ struct Cli {
 | 
			
		||||
    
 | 
			
		||||
    /// Spotter callsign (e.g. W1CDN)
 | 
			
		||||
    #[arg(short = 's', long)]
 | 
			
		||||
    spotter: Option<String>,
 | 
			
		||||
    my_call: Option<String>,
 | 
			
		||||
    
 | 
			
		||||
    /// 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<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)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// 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<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