]> code.octet-stream.net Git - m17rt/blob - tools/m17rt-netclient/src/main.rs
9b65e1bfbe8c5a62a93d8e5f445fb173bd90c1c6
[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("module")
39 .long("module")
40 .short('m')
41 .value_parser(valid_module)
42 .required(true)
43 .help("Module to connect to (A-Z)"),
44 )
45 .arg(
46 Arg::new("input")
47 .long("input")
48 .short('i')
49 .help("Soundcard name for microphone, otherwise system default"),
50 )
51 .arg(
52 Arg::new("output")
53 .long("output")
54 .short('o')
55 .help("Soundcard name for speaker, otherwise system default"),
56 )
57 .get_matches();
58
59 let hostname = args.get_one::<String>("hostname").unwrap();
60 let port = args.get_one::<u16>("port").unwrap();
61 let callsign = args.get_one::<M17Address>("callsign").unwrap();
62 let module = args.get_one::<char>("module").unwrap();
63 let input = args.get_one::<String>("input");
64 let output = args.get_one::<String>("output");
65
66 let mut tx = Codec2TxAdapter::new(callsign.clone(), M17Address::new_broadcast());
67 if let Some(input) = input {
68 tx.set_input_card(input);
69 }
70 let ptt = tx.ptt();
71
72 let mut rx = Codec2RxAdapter::new();
73 if let Some(output) = output {
74 rx.set_output_card(output);
75 }
76
77 let config = ReflectorClientConfig {
78 hostname: hostname.clone(),
79 port: *port,
80 module: *module,
81 local_callsign: callsign.clone(),
82 };
83 let tnc = ReflectorClientTnc::new(config, ConsoleStatusHandler);
84 let app = M17App::new(tnc);
85 app.add_stream_adapter(ConsoleAdapter).unwrap();
86 app.add_stream_adapter(tx).unwrap();
87 app.add_stream_adapter(rx).unwrap();
88 app.start().unwrap();
89
90 println!(">>> PRESS ENTER TO TOGGLE PTT <<<");
91 let mut buf = String::new();
92
93 loop {
94 let _ = stdin().read_line(&mut buf);
95 ptt.set_ptt(true);
96 println!("PTT ON: PRESS ENTER TO END");
97
98 let _ = stdin().read_line(&mut buf);
99 ptt.set_ptt(false);
100 println!("PTT OFF");
101 }
102 }
103
104 fn valid_module(m: &str) -> Result<char, String> {
105 let m = m.to_ascii_uppercase();
106 if m.len() != 1 || !m.chars().next().unwrap().is_alphabetic() {
107 return Err("Module must be a single letter from A to Z".to_owned());
108 }
109 Ok(m.chars().next().unwrap())
110 }
111
112 fn valid_callsign(c: &str) -> Result<M17Address, String> {
113 M17Address::from_callsign(c).map_err(|e| e.to_string())
114 }
115
116 struct ConsoleAdapter;
117 impl StreamAdapter for ConsoleAdapter {
118 fn stream_began(&self, link_setup: m17app::link_setup::LinkSetup) {
119 println!(
120 "Transmission begins. From: {} To: {}",
121 link_setup.source(),
122 link_setup.destination()
123 );
124 }
125
126 fn stream_data(&self, _frame_number: u16, is_final: bool, _data: Arc<[u8; 16]>) {
127 if is_final {
128 println!("Transmission ends.");
129 }
130 }
131 }
132
133 struct ConsoleStatusHandler;
134 impl StatusHandler for ConsoleStatusHandler {
135 fn status_changed(&mut self, status: m17app::reflector::TncStatus) {
136 println!("Client status: {status:?}")
137 }
138 }