]> code.octet-stream.net Git - m17rt/blob - tools/m17rt-netclient/src/main.rs
5fee7d6ed5275296b66a4c3f02216cdc7c5b0e7a
[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 // It is current convention that mrefd requires the destination of transmissions to match the reflector.
76 // If you are connected to "M17-XXX" on module B then you must set the dst to "M17-XXX B".
77 // This requirement is likely to change but for the purposes of this test client we'll hard-code the
78 // behaviour for the time being.
79 let ref_with_mod = format!("{} {}", reflector, module);
80 let Ok(reflector) = M17Address::from_callsign(&ref_with_mod) else {
81 println!(
82 "Unable to create valid destination address for reflector + callsign '{ref_with_mod}'"
83 );
84 std::process::exit(1);
85 };
86
87 let mut tx = Codec2TxAdapter::new(callsign.clone(), reflector);
88 if let Some(input) = input {
89 tx.set_input_card(input);
90 }
91 let ptt = tx.ptt();
92
93 let mut rx = Codec2RxAdapter::new();
94 if let Some(output) = output {
95 rx.set_output_card(output);
96 }
97
98 let config = ReflectorClientConfig {
99 hostname: hostname.clone(),
100 port: *port,
101 module: *module,
102 local_callsign: callsign.clone(),
103 };
104 let tnc = ReflectorClientTnc::new(config, ConsoleStatusHandler);
105 let app = M17App::new(tnc);
106 app.add_stream_adapter(ConsoleAdapter).unwrap();
107 app.add_stream_adapter(tx).unwrap();
108 app.add_stream_adapter(rx).unwrap();
109 app.start().unwrap();
110
111 println!(">>> PRESS ENTER TO TOGGLE PTT <<<");
112 let mut buf = String::new();
113
114 loop {
115 let _ = stdin().read_line(&mut buf);
116 ptt.set_ptt(true);
117 println!("PTT ON: PRESS ENTER TO END");
118
119 let _ = stdin().read_line(&mut buf);
120 ptt.set_ptt(false);
121 println!("PTT OFF");
122 }
123 }
124
125 fn valid_module(m: &str) -> Result<char, String> {
126 let m = m.to_ascii_uppercase();
127 if m.len() != 1 || !m.chars().next().unwrap().is_alphabetic() {
128 return Err("Module must be a single letter from A to Z".to_owned());
129 }
130 Ok(m.chars().next().unwrap())
131 }
132
133 fn valid_callsign(c: &str) -> Result<M17Address, String> {
134 M17Address::from_callsign(c).map_err(|e| e.to_string())
135 }
136
137 struct ConsoleAdapter;
138 impl StreamAdapter for ConsoleAdapter {
139 fn stream_began(&self, link_setup: m17app::link_setup::LinkSetup) {
140 println!(
141 "Incoming transmission begins. From: {} To: {}",
142 link_setup.source(),
143 link_setup.destination()
144 );
145 }
146
147 fn stream_data(&self, _frame_number: u16, is_final: bool, _data: Arc<[u8; 16]>) {
148 if is_final {
149 println!("Incoming transmission ends.");
150 }
151 }
152 }
153
154 struct ConsoleStatusHandler;
155 impl StatusHandler for ConsoleStatusHandler {
156 fn status_changed(&mut self, status: m17app::reflector::TncStatus) {
157 println!("Client status: {status:?}")
158 }
159 }