]> code.octet-stream.net Git - m17rt/commitdiff
Add basic m17rt-netclient tool
authorThomas Karpiniec <tom.karpiniec@outlook.com>
Wed, 4 Jun 2025 11:37:11 +0000 (21:37 +1000)
committerThomas Karpiniec <tom.karpiniec@outlook.com>
Wed, 4 Jun 2025 11:37:11 +0000 (21:37 +1000)
Cargo.lock
Cargo.toml
m17app/src/reflector.rs
tools/m17rt-netclient/Cargo.toml [new file with mode: 0644]
tools/m17rt-netclient/src/main.rs [new file with mode: 0644]

index 754a0cc4b86d2a35922fc470e4b04b7f01c0dec9..f67d132640e198695cc095ad8cab6892de7f1d2a 100644 (file)
@@ -197,6 +197,33 @@ dependencies = [
  "libloading",
 ]
 
  "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"
 [[package]]
 name = "codec2"
 version = "0.3.0"
@@ -512,6 +539,15 @@ dependencies = [
  "m17core",
 ]
 
  "m17core",
 ]
 
+[[package]]
+name = "m17rt-netclient"
+version = "0.1.0"
+dependencies = [
+ "clap",
+ "m17app",
+ "m17codec2",
+]
+
 [[package]]
 name = "m17rt-rxpacket"
 version = "0.1.0"
 [[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"
 
 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"
 [[package]]
 name = "syn"
 version = "2.0.94"
index fab714681ec9003c2f2b5981eb14f494e195bf98..3ea321813861d765fb5af77feadeba19e6270d94 100644 (file)
@@ -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"
 resolver = "2"
 members = [
     "m17app", "m17codec2", "m17core", "tools/m17rt-demod", "tools/m17rt-mod", "tools/m17rt-txpacket", "tools/m17rt-rxpacket", "tools/m17rt-soundcards"
-]
+, "tools/m17rt-netclient"]
index 419093eb7b0dc2b41f77d4385315e4761fc58e3c..9b383f72f5cea62a8a534c42a4baaf823b752f00 100644 (file)
@@ -22,10 +22,10 @@ use m17core::{
 
 #[derive(Debug, PartialEq, Eq, Clone)]
 pub struct ReflectorClientConfig {
 
 #[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<Mutex<dyn StatusHandler + Send + 'static>>;
 }
 
 type WrappedStatusHandler = Arc<Mutex<dyn StatusHandler + Send + 'static>>;
diff --git a/tools/m17rt-netclient/Cargo.toml b/tools/m17rt-netclient/Cargo.toml
new file mode 100644 (file)
index 0000000..e78b89e
--- /dev/null
@@ -0,0 +1,12 @@
+[package]
+name = "m17rt-netclient"
+version = "0.1.0"
+edition = "2024"
+license = "MIT"
+authors = ["Thomas Karpiniec <tom.karpiniec@outlook.com"]
+publish = false
+
+[dependencies]
+clap = "4.5.39"
+m17app = { path = "../../m17app" }
+m17codec2 = { path = "../../m17codec2" }
diff --git a/tools/m17rt-netclient/src/main.rs b/tools/m17rt-netclient/src/main.rs
new file mode 100644 (file)
index 0000000..9b65e1b
--- /dev/null
@@ -0,0 +1,138 @@
+use std::{io::stdin, sync::Arc};
+
+use clap::{Arg, value_parser};
+use m17app::{
+    adapter::StreamAdapter,
+    app::M17App,
+    link_setup::M17Address,
+    reflector::{ReflectorClientConfig, ReflectorClientTnc, StatusHandler},
+};
+use m17codec2::{rx::Codec2RxAdapter, tx::Codec2TxAdapter};
+
+fn main() {
+    let args = clap::Command::new("m17rt-netclient")
+        .arg(
+            Arg::new("hostname")
+                .long("hostname")
+                .short('s')
+                .required(true)
+                .help("Domain or IP of reflector"),
+        )
+        .arg(
+            Arg::new("port")
+                .long("port")
+                .short('p')
+                .value_parser(value_parser!(u16))
+                .default_value("17000")
+                .help("Reflector listening port"),
+        )
+        .arg(
+            Arg::new("callsign")
+                .long("callsign")
+                .short('c')
+                .value_parser(valid_callsign)
+                .required(true)
+                .help("Your callsign for reflector registration and transmissions"),
+        )
+        .arg(
+            Arg::new("module")
+                .long("module")
+                .short('m')
+                .value_parser(valid_module)
+                .required(true)
+                .help("Module to connect to (A-Z)"),
+        )
+        .arg(
+            Arg::new("input")
+                .long("input")
+                .short('i')
+                .help("Soundcard name for microphone, otherwise system default"),
+        )
+        .arg(
+            Arg::new("output")
+                .long("output")
+                .short('o')
+                .help("Soundcard name for speaker, otherwise system default"),
+        )
+        .get_matches();
+
+    let hostname = args.get_one::<String>("hostname").unwrap();
+    let port = args.get_one::<u16>("port").unwrap();
+    let callsign = args.get_one::<M17Address>("callsign").unwrap();
+    let module = args.get_one::<char>("module").unwrap();
+    let input = args.get_one::<String>("input");
+    let output = args.get_one::<String>("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<char, String> {
+    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, String> {
+    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:?}")
+    }
+}