forked from n6cta/mwtchahrd
		
	Spot UI frames to spothole.app #13
| @@ -8,6 +8,7 @@ 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"] } | ||||||
|  | reqwest = { version = "0.12.24", features = ["json", "blocking"] } | ||||||
| serde_json = "1.0.145" | serde_json = "1.0.145" | ||||||
| socket2 = "0.5" | socket2 = "0.5" | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										32
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								README.md
									
									
									
									
									
								
							| @@ -57,6 +57,30 @@ mwtchahrd -i <IP> -p <PORT> [-c <CHANNEL>] [-d] [-u] | |||||||
| - `-u, --ui_only`   | - `-u, --ui_only`   | ||||||
|   Only display frames with a UI payload. When this flag is set, frames that are not UI are skipped. |   Only display frames with a UI payload. When this flag is set, frames that are not UI are skipped. | ||||||
|    |    | ||||||
|  | - `-b, --uip <UIP>` | ||||||
|  |   Send UDP to what IP address (e.g. 127.0.0.1) [default: 127.0.0.1]. | ||||||
|  |    | ||||||
|  | - `-k, --uport <UPORT>` | ||||||
|  |   Send UDP to what port (e.g. 8000; 55555 disables UDP) [default: 55555]. | ||||||
|  |    | ||||||
|  | - `-s, --spotter <SPOTTER>`             | ||||||
|  |   Spotter callsign (e.g. W1CDN). | ||||||
|  |    | ||||||
|  | - `-o, --spothole`                      | ||||||
|  |   Spot UI frames to a Spothole server. | ||||||
|  |    | ||||||
|  | - `-O, --spothole-alt <SPOTHOLE_ALT>`  | ||||||
|  |   Provide a different URL than https://spothole.app/api/v1/spot. | ||||||
|  |    | ||||||
|  | - `-f, --freq <FREQ>`                  | ||||||
|  |   Spotting frequency [default: 14105000]. | ||||||
|  |  | ||||||
|  | - `-h, --help`                          | ||||||
|  |   Print help. | ||||||
|  |    | ||||||
|  | - `-V, --version`                       | ||||||
|  |   Print version. | ||||||
|  |  | ||||||
| ## Examples | ## Examples | ||||||
|  |  | ||||||
| ### Monitoring All Frames | ### Monitoring All Frames | ||||||
| @@ -83,6 +107,14 @@ Monitor only UI frames: | |||||||
| mwtchahrd -i 127.0.0.1 -p 8000 -u | mwtchahrd -i 127.0.0.1 -p 8000 -u | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|  | ### Spotting to [Spothole.app](https://spothole.app) API | ||||||
|  |  | ||||||
|  | Sends only UI frames and mode is hardcoded as `PKT`. Spot from callsign `W1CDN` and report fixed frequency 14.105 MHz: | ||||||
|  |  | ||||||
|  | ```bash | ||||||
|  | mwtchahrd --ip 192.168.0.6 --port 8000 --spotter W1CDN --spothole --freq 14105000 | ||||||
|  | ``` | ||||||
|  |  | ||||||
| ## Code Overview | ## Code Overview | ||||||
|  |  | ||||||
| - **Validation Functions:**   | - **Validation Functions:**   | ||||||
|   | |||||||
							
								
								
									
										52
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										52
									
								
								src/main.rs
									
									
									
									
									
								
							| @@ -11,6 +11,7 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; | |||||||
| use std::net::UdpSocket; | use std::net::UdpSocket; | ||||||
| use std::net::Ipv4Addr; | use std::net::Ipv4Addr; | ||||||
| use serde_json::json; | use serde_json::json; | ||||||
|  | use reqwest; | ||||||
|  |  | ||||||
| /// 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> { | ||||||
| @@ -62,6 +63,19 @@ struct Cli { | |||||||
|     /// Spotter callsign (e.g. W1CDN) |     /// Spotter callsign (e.g. W1CDN) | ||||||
|     #[arg(short = 's', long)] |     #[arg(short = 's', long)] | ||||||
|     spotter: Option<String>, |     spotter: Option<String>, | ||||||
|  |      | ||||||
|  |     /// Spot UI frames to a Spothole server | ||||||
|  |     #[arg(short = 'o', long, default_value_t = false)] | ||||||
|  |     spothole: bool, | ||||||
|  |      | ||||||
|  |     /// Provide a different URL than https://spothole.app/api/v1/spot | ||||||
|  |     #[arg(short = 'O', long)] | ||||||
|  |     spothole_alt: Option<String>, | ||||||
|  |      | ||||||
|  |     /// Spotting frequency | ||||||
|  |     #[arg(short = 'f', long, default_value_t = 14105000)] | ||||||
|  |     freq: u32, | ||||||
|  |      | ||||||
| } | } | ||||||
|  |  | ||||||
| /// Convert a byte slice into a hex-dump string for debugging purposes. | /// Convert a byte slice into a hex-dump string for debugging purposes. | ||||||
| @@ -363,6 +377,14 @@ fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager) { | |||||||
|     let basic_destination = hdr.callto_str(); |     let basic_destination = hdr.callto_str(); | ||||||
|     let timestamp = Local::now().format("%H:%M:%S").to_string(); |     let timestamp = Local::now().format("%H:%M:%S").to_string(); | ||||||
|     let spotter = &cli.spotter; |     let spotter = &cli.spotter; | ||||||
|  |     let freq = &cli.freq; | ||||||
|  |     //let spothole_alt = &cli.spothole_alt; | ||||||
|  |      | ||||||
|  |     // If user provides an alternate Spothole URL, use that one | ||||||
|  |     let spothole_url = match &cli.spothole_alt { | ||||||
|  |         Some(spothole_alt) => spothole_alt, | ||||||
|  |         None => &"https://spothole.app/api/v1/spot".to_string(), | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     // Filter and compute the text from the payload only once. |     // Filter and compute the text from the payload only once. | ||||||
|     let text = filter_text(&frame.payload); |     let text = filter_text(&frame.payload); | ||||||
| @@ -400,6 +422,35 @@ fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager) { | |||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|      |      | ||||||
|  |     // 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, | ||||||
|  |             "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!("res = {res:?}"); | ||||||
|  |          | ||||||
|  |          | ||||||
|  |     } | ||||||
|  |  | ||||||
|     // Send UDP |     // Send UDP | ||||||
|     if cli.uport != 55555 { |     if cli.uport != 55555 { | ||||||
|         let uaddr = format!("{}:{}", cli.uip, cli.uport); |         let uaddr = format!("{}:{}", cli.uip, cli.uport); | ||||||
| @@ -411,6 +462,7 @@ fn handle_frame(frame: &AgwFrame, cli: &Cli, buffers: &mut BufferManager) { | |||||||
|             "spotter": &spotter, |             "spotter": &spotter, | ||||||
|             "summary": &summary, |             "summary": &summary, | ||||||
|             "text": &text, |             "text": &text, | ||||||
|  |             "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" | ||||||
|         }); |         }); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user