From a55a59d78fea5b8b639ce594d339ef53290d141e Mon Sep 17 00:00:00 2001 From: Thomas Karpiniec Date: Wed, 4 Jun 2025 21:37:11 +1000 Subject: [PATCH] Add basic m17rt-netclient tool --- Cargo.lock | 42 +++++++++ Cargo.toml | 2 +- m17app/src/reflector.rs | 8 +- tools/m17rt-netclient/Cargo.toml | 12 +++ tools/m17rt-netclient/src/main.rs | 138 ++++++++++++++++++++++++++++++ 5 files changed, 197 insertions(+), 5 deletions(-) create mode 100644 tools/m17rt-netclient/Cargo.toml create mode 100644 tools/m17rt-netclient/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 754a0cc..f67d132 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -197,6 +197,33 @@ dependencies = [ "libloading", ] +[[package]] +name = "clap" +version = "4.5.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + [[package]] name = "codec2" version = "0.3.0" @@ -512,6 +539,15 @@ dependencies = [ "m17core", ] +[[package]] +name = "m17rt-netclient" +version = "0.1.0" +dependencies = [ + "clap", + "m17app", + "m17codec2", +] + [[package]] name = "m17rt-rxpacket" version = "0.1.0" @@ -808,6 +844,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.94" diff --git a/Cargo.toml b/Cargo.toml index fab7146..3ea3218 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,4 +2,4 @@ resolver = "2" members = [ "m17app", "m17codec2", "m17core", "tools/m17rt-demod", "tools/m17rt-mod", "tools/m17rt-txpacket", "tools/m17rt-rxpacket", "tools/m17rt-soundcards" -] +, "tools/m17rt-netclient"] diff --git a/m17app/src/reflector.rs b/m17app/src/reflector.rs index 419093e..9b383f7 100644 --- a/m17app/src/reflector.rs +++ b/m17app/src/reflector.rs @@ -22,10 +22,10 @@ use m17core::{ #[derive(Debug, PartialEq, Eq, Clone)] pub struct ReflectorClientConfig { - hostname: String, - port: u16, - module: char, - local_callsign: M17Address, + pub hostname: String, + pub port: u16, + pub module: char, + pub local_callsign: M17Address, } type WrappedStatusHandler = Arc>; diff --git a/tools/m17rt-netclient/Cargo.toml b/tools/m17rt-netclient/Cargo.toml new file mode 100644 index 0000000..e78b89e --- /dev/null +++ b/tools/m17rt-netclient/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "m17rt-netclient" +version = "0.1.0" +edition = "2024" +license = "MIT" +authors = ["Thomas Karpiniec ("hostname").unwrap(); + let port = args.get_one::("port").unwrap(); + let callsign = args.get_one::("callsign").unwrap(); + let module = args.get_one::("module").unwrap(); + let input = args.get_one::("input"); + let output = args.get_one::("output"); + + let mut tx = Codec2TxAdapter::new(callsign.clone(), M17Address::new_broadcast()); + if let Some(input) = input { + tx.set_input_card(input); + } + let ptt = tx.ptt(); + + let mut rx = Codec2RxAdapter::new(); + if let Some(output) = output { + rx.set_output_card(output); + } + + let config = ReflectorClientConfig { + hostname: hostname.clone(), + port: *port, + module: *module, + local_callsign: callsign.clone(), + }; + let tnc = ReflectorClientTnc::new(config, ConsoleStatusHandler); + let app = M17App::new(tnc); + app.add_stream_adapter(ConsoleAdapter).unwrap(); + app.add_stream_adapter(tx).unwrap(); + app.add_stream_adapter(rx).unwrap(); + app.start().unwrap(); + + println!(">>> PRESS ENTER TO TOGGLE PTT <<<"); + let mut buf = String::new(); + + loop { + let _ = stdin().read_line(&mut buf); + ptt.set_ptt(true); + println!("PTT ON: PRESS ENTER TO END"); + + let _ = stdin().read_line(&mut buf); + ptt.set_ptt(false); + println!("PTT OFF"); + } +} + +fn valid_module(m: &str) -> Result { + let m = m.to_ascii_uppercase(); + if m.len() != 1 || !m.chars().next().unwrap().is_alphabetic() { + return Err("Module must be a single letter from A to Z".to_owned()); + } + Ok(m.chars().next().unwrap()) +} + +fn valid_callsign(c: &str) -> Result { + M17Address::from_callsign(c).map_err(|e| e.to_string()) +} + +struct ConsoleAdapter; +impl StreamAdapter for ConsoleAdapter { + fn stream_began(&self, link_setup: m17app::link_setup::LinkSetup) { + println!( + "Transmission begins. From: {} To: {}", + link_setup.source(), + link_setup.destination() + ); + } + + fn stream_data(&self, _frame_number: u16, is_final: bool, _data: Arc<[u8; 16]>) { + if is_final { + println!("Transmission ends."); + } + } +} + +struct ConsoleStatusHandler; +impl StatusHandler for ConsoleStatusHandler { + fn status_changed(&mut self, status: m17app::reflector::TncStatus) { + println!("Client status: {status:?}") + } +} -- 2.39.5