]> code.octet-stream.net Git - m17rt/blob - tools/m17rt-netclient/src/main.rs
Make netclient work against mrefd
[m17rt] / tools / m17rt-netclient / src / main.rs
1 use std::{io::stdin, sync::Arc};
2
3 use clap::{Arg, value_parser};
4 use m17app::{
5 adapter::StreamAdapter,
6 app::M17App,
7 link_setup::M17Address,
8 reflector::{ReflectorClientConfig, ReflectorClientTnc, StatusHandler},
9 };
10 use m17codec2::{rx::Codec2RxAdapter, tx::Codec2TxAdapter};
11
12 fn main() {
13 let args = clap::Command::new("m17rt-netclient")
14 .arg(
15 Arg::new("hostname")
16 .long("hostname")
17 .short('s')
18 .required(true)
19 .help("Domain or IP of reflector"),
20 )
21 .arg(
22 Arg::new("port")
23 .long("port")
24 .short('p')
25 .value_parser(value_parser!(u16))
26 .default_value("17000")
27 .help("Reflector listening port"),
28 )
29 .arg(
30 Arg::new("callsign")
31 .long("callsign")
32 .short('c')
33 .value_parser(valid_callsign)
34 .required(true)
35 .help("Your callsign for reflector registration and transmissions"),
36 )
37 .arg(
38 Arg::new("reflector")
39 .long("reflector")
40 .short('r')
41 .value_parser(valid_callsign)
42 .required(true)
43 .help("Reflector designator/callsign, often starting with 'M17-'"),
44 )
45 .arg(
46 Arg::new("module")
47 .long("module")
48 .short('m')
49 .value_parser(valid_module)
50 .required(true)
51 .help("Module to connect to (A-Z)"),
52 )
53 .arg(
54 Arg::new("input")
55 .long("input")
56 .short('i')
57 .help("Soundcard name for microphone, otherwise system default"),
58 )
59 .arg(
60 Arg::new("output")
61 .long("output")
62 .short('o')
63 .help("Soundcard name for speaker, otherwise system default"),
64 )
65 .get_matches();
66
67 let hostname = args.get_one::<String>("hostname").unwrap();
68 let port = args.get_one::<u16>("port").unwrap();
69 let callsign = args.get_one::<M17Address>("callsign").unwrap();
70 let reflector = args.get_one::<M17Address>("reflector").unwrap();
71 let module = args.get_one::<char>("module").unwrap();
72 let input = args.get_one::<String>("input");
73 let output = args.get_one::<String>("output");
74
75 let ref_with_mod = format!("{} {}", reflector, module);
76 let Ok(reflector) = M17Address::from_callsign(&ref_with_mod) else {
77 println!(
78 "Unable to create valid destination address for reflector + callsign '{ref_with_mod}'"
79 );
80 std::process::exit(1);
81 };
82
83 let mut tx = Codec2TxAdapter::new(callsign.clone(), reflector);
84 if let Some(input) = input {
85 tx.set_input_card(input);
86 }
87 let ptt = tx.ptt();
88
89 let mut rx = Codec2RxAdapter::new();
90 if let Some(output) = output {
91 rx.set_output_card(output);
92 }
93
94 let config = ReflectorClientConfig {
95 hostname: hostname.clone(),
96 port: *port,
97 module: *module,
98 local_callsign: callsign.clone(),
99 };
100 let tnc = ReflectorClientTnc::new(config, ConsoleStatusHandler);
101 let app = M17App::new(tnc);
102 app.add_stream_adapter(ConsoleAdapter).unwrap();
103 app.add_stream_adapter(tx).unwrap();
104 app.add_stream_adapter(rx).unwrap();
105 app.start().unwrap();
106
107 println!(">>> PRESS ENTER TO TOGGLE PTT <<<");
108 let mut buf = String::new();
109
110 loop {
111 let _ = stdin().read_line(&mut buf);
112 ptt.set_ptt(true);
113 println!("PTT ON: PRESS ENTER TO END");
114
115 let _ = stdin().read_line(&mut buf);
116 ptt.set_ptt(false);
117 println!("PTT OFF");
118 }
119 }
120
121 fn valid_module(m: &str) -> Result<char, String> {
122 let m = m.to_ascii_uppercase();
123 if m.len() != 1 || !m.chars().next().unwrap().is_alphabetic() {
124 return Err("Module must be a single letter from A to Z".to_owned());
125 }
126 Ok(m.chars().next().unwrap())
127 }
128
129 fn valid_callsign(c: &str) -> Result<M17Address, String> {
130 M17Address::from_callsign(c).map_err(|e| e.to_string())
131 }
132
133 struct ConsoleAdapter;
134 impl StreamAdapter for ConsoleAdapter {
135 fn stream_began(&self, link_setup: m17app::link_setup::LinkSetup) {
136 println!(
137 "Incoming transmission begins. From: {} To: {}",
138 link_setup.source(),
139 link_setup.destination()
140 );
141 }
142
143 fn stream_data(&self, _frame_number: u16, is_final: bool, _data: Arc<[u8; 16]>) {
144 if is_final {
145 println!("Incoming transmission ends.");
146 }
147 }
148 }
149
150 struct ConsoleStatusHandler;
151 impl StatusHandler for ConsoleStatusHandler {
152 fn status_changed(&mut self, status: m17app::reflector::TncStatus) {
153 println!("Client status: {status:?}")
154 }
155 }