]> code.octet-stream.net Git - m17rt/commitdiff
Serial PTT support in soundmodem
authorThomas Karpiniec <tom.karpiniec@outlook.com>
Sun, 19 Jan 2025 11:02:49 +0000 (22:02 +1100)
committerThomas Karpiniec <tom.karpiniec@outlook.com>
Sun, 19 Jan 2025 11:02:49 +0000 (22:02 +1100)
Cargo.lock
m17app/Cargo.toml
m17app/src/app.rs
m17app/src/lib.rs
m17app/src/serial.rs [new file with mode: 0644]
m17app/src/soundmodem.rs
m17codec2/src/lib.rs
tools/m17rt-demod/src/main.rs
tools/m17rt-mod/src/main.rs

index 840aad69e086cad42d280f705d772830ae4d2f75..3b493469d3d0ccf47600e8e0caa6d91332d38114 100755 (executable)
@@ -213,6 +213,16 @@ dependencies = [
  "memchr",
 ]
 
+[[package]]
+name = "core-foundation"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
 [[package]]
 name = "core-foundation-sys"
 version = "0.8.7"
@@ -352,6 +362,16 @@ dependencies = [
  "hashbrown",
 ]
 
+[[package]]
+name = "io-kit-sys"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b"
+dependencies = [
+ "core-foundation-sys",
+ "mach2",
+]
+
 [[package]]
 name = "is_terminal_polyfill"
 version = "1.70.1"
@@ -437,6 +457,7 @@ dependencies = [
  "cpal",
  "log",
  "m17core",
+ "serialport",
 ]
 
 [[package]]
@@ -533,6 +554,17 @@ dependencies = [
  "jni-sys",
 ]
 
+[[package]]
+name = "nix"
+version = "0.26.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
+dependencies = [
+ "bitflags 1.3.2",
+ "cfg-if",
+ "libc",
+]
+
 [[package]]
 name = "nom"
 version = "7.1.3"
@@ -690,6 +722,30 @@ dependencies = [
  "winapi-util",
 ]
 
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "serialport"
+version = "4.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ecfc4858c2266c7695d8b8460bbd612fa81bd2e250f5f0dd16195e4b4f8b3d8"
+dependencies = [
+ "bitflags 2.6.0",
+ "cfg-if",
+ "core-foundation",
+ "core-foundation-sys",
+ "io-kit-sys",
+ "mach2",
+ "nix",
+ "scopeguard",
+ "unescaper",
+ "winapi",
+]
+
 [[package]]
 name = "shlex"
 version = "1.3.0"
@@ -744,6 +800,15 @@ dependencies = [
  "winnow",
 ]
 
+[[package]]
+name = "unescaper"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c878a167baa8afd137494101a688ef8c67125089ff2249284bd2b5f9bfedb815"
+dependencies = [
+ "thiserror",
+]
+
 [[package]]
 name = "unicode-ident"
 version = "1.0.14"
@@ -843,6 +908,22 @@ dependencies = [
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
 [[package]]
 name = "winapi-util"
 version = "0.1.9"
@@ -852,6 +933,12 @@ dependencies = [
  "windows-sys 0.59.0",
 ]
 
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
 [[package]]
 name = "windows"
 version = "0.54.0"
index daca51f5b666f4b1a0c833399fcbffe532a11d31..4a083bd73974f8dc6937a5a3b001b14b1f674b1b 100755 (executable)
@@ -13,3 +13,4 @@ repository = "https://code.octet-stream.net/m17rt"
 cpal = "0.15.3"
 m17core = { path = "../m17core" }
 log = "0.4.22"
+serialport = {version = "4.7.0", default-features = false }
index 561a2b7ae569b6266e6dc9d39a589fd18e712d0c..b1426638e28e1dd4a3dcef0d1ec96c5cbd2db5a9 100644 (file)
@@ -72,6 +72,8 @@ impl M17App {
     }
 
     pub fn close(&self) {
+        // TODO: blocking function to indicate TNC has finished closing
+        // then we could call this in a signal handler to ensure PTT is dropped before quit
         let _ = self.event_tx.send(TncControlEvent::Close);
     }
 }
index 7530aa26521ed6051b7760b7f290590d0ff5c174..61545858499c8a5b2db18b0fa7febfece37ccc57 100755 (executable)
@@ -1,4 +1,5 @@
 pub mod adapter;
 pub mod app;
+pub mod serial;
 pub mod soundmodem;
 pub mod tnc;
diff --git a/m17app/src/serial.rs b/m17app/src/serial.rs
new file mode 100644 (file)
index 0000000..d3eec19
--- /dev/null
@@ -0,0 +1,47 @@
+use serialport::SerialPort;
+
+use crate::soundmodem::Ptt;
+
+/// The pin on the serial port which is driving PTT
+pub enum PttPin {
+    // Ready To Send (RTS)
+    Rts,
+    // Data Terminal ready (DTR)
+    Dtr,
+}
+
+pub struct SerialPtt {
+    port: Box<dyn SerialPort>,
+    pin: PttPin,
+}
+
+impl SerialPtt {
+    pub fn available_ports() -> impl Iterator<Item = String> {
+        serialport::available_ports()
+            .unwrap_or_else(|_| vec![])
+            .into_iter()
+            .map(|i| i.port_name)
+    }
+
+    pub fn new(port_name: &str, pin: PttPin) -> Self {
+        // TODO: error handling
+        let port = serialport::new(port_name, 9600).open().unwrap();
+        Self { port, pin }
+    }
+}
+
+impl Ptt for SerialPtt {
+    fn ptt_on(&mut self) {
+        let _ = match self.pin {
+            PttPin::Rts => self.port.write_request_to_send(true),
+            PttPin::Dtr => self.port.write_data_terminal_ready(true),
+        };
+    }
+
+    fn ptt_off(&mut self) {
+        let _ = match self.pin {
+            PttPin::Rts => self.port.write_request_to_send(false),
+            PttPin::Dtr => self.port.write_data_terminal_ready(false),
+        };
+    }
+}
index 74c176bba754a087cda4826f80c5348ced43e63e..dced157eeab6cee20fa3607a3c6acfcebbcf15cd 100644 (file)
@@ -23,7 +23,7 @@ pub struct Soundmodem {
 }
 
 impl Soundmodem {
-    pub fn new_with_input_and_output<I: InputSource, O: OutputSink>(input: I, output: O) -> Self {
+    pub fn new<I: InputSource, O: OutputSink, P: Ptt>(input: I, output: O, ptt: P) -> Self {
         // must create TNC here
         let (event_tx, event_rx) = sync_channel(128);
         let (kiss_out_tx, kiss_out_rx) = sync_channel(128);
@@ -33,6 +33,7 @@ impl Soundmodem {
             kiss_out_tx,
             Box::new(input),
             Box::new(output),
+            Box::new(ptt),
         );
         Self {
             event_tx,
@@ -127,6 +128,7 @@ fn spawn_soundmodem_worker(
     kiss_out_tx: SyncSender<Arc<[u8]>>,
     input: Box<dyn InputSource>,
     output: Box<dyn OutputSink>,
+    mut ptt_driver: Box<dyn Ptt>,
 ) {
     std::thread::spawn(move || {
         // TODO: should be able to provide a custom Demodulator for a soundmodem
@@ -170,7 +172,10 @@ fn spawn_soundmodem_worker(
                     input.start(event_tx.clone());
                     output.start(event_tx.clone(), out_buffer.clone());
                 }
-                SoundmodemEvent::Close => break,
+                SoundmodemEvent::Close => {
+                    ptt_driver.ptt_off();
+                    break;
+                }
                 SoundmodemEvent::DidReadFromOutputBuffer { len, timestamp } => {
                     let (occupied, internal_latency) = {
                         let out_buffer = out_buffer.read().unwrap();
@@ -194,9 +199,9 @@ fn spawn_soundmodem_worker(
             let new_ptt = tnc.ptt();
             if new_ptt != ptt {
                 if new_ptt {
-                    // turn it on
+                    ptt_driver.ptt_on();
                 } else {
-                    // turn it off
+                    ptt_driver.ptt_off();
                 }
             }
             ptt = new_ptt;
@@ -637,3 +642,22 @@ impl OutputSink for OutputSoundcard {
         let _ = self.end_tx.lock().unwrap().take();
     }
 }
+
+pub trait Ptt: Send + 'static {
+    fn ptt_on(&mut self);
+    fn ptt_off(&mut self);
+}
+
+/// There is no PTT because this TNC will never make transmissions on a real radio.
+pub struct NullPtt;
+
+impl NullPtt {
+    pub fn new() -> Self {
+        Self
+    }
+}
+
+impl Ptt for NullPtt {
+    fn ptt_on(&mut self) {}
+    fn ptt_off(&mut self) {}
+}
index e33409f81b25f2176fd2bc78d9aa30b4f42f2d4d..5146bcaac5678f7cbfc70b4e5eec5b2ae9e74c49 100755 (executable)
@@ -142,7 +142,7 @@ fn stream_thread(end: Receiver<()>, state: Arc<Mutex<AdapterState>>, output_card
     let stream = device
         .build_output_stream(
             &config.into(),
-            move |data: &mut [i16], info: &cpal::OutputCallbackInfo| {
+            move |data: &mut [i16], _info: &cpal::OutputCallbackInfo| {
                 output_cb(data, &state);
             },
             |e| {
index 40ab3db590673f5f826fbbbd7c3d4a825983e97b..9c229af22dd1d990610d7684e05b11ee334cb5d3 100755 (executable)
@@ -1,5 +1,5 @@
 use m17app::app::M17App;
-use m17app::soundmodem::{InputRrcFile, InputSoundcard, NullOutputSink, Soundmodem};
+use m17app::soundmodem::{InputRrcFile, InputSoundcard, NullOutputSink, NullPtt, Soundmodem};
 use m17codec2::Codec2Adapter;
 use std::path::PathBuf;
 
@@ -9,7 +9,7 @@ pub fn m17app_test() {
     //let path = PathBuf::from("../../../Data/mymod-noisy.raw");
     let source = InputRrcFile::new(path);
     //let source = InputSoundcard::new();
-    let soundmodem = Soundmodem::new_with_input_and_output(source, NullOutputSink::new());
+    let soundmodem = Soundmodem::new(source, NullOutputSink::new(), NullPtt::new());
     let app = M17App::new(soundmodem);
     app.add_stream_adapter(Codec2Adapter::new());
     app.start();
index 968c22a44d0d0a441dc543c7e0b4d0e3fa087ab9..7ba69612f65979534ce59f767e3e3abd46f8b58c 100644 (file)
@@ -1,7 +1,7 @@
 use m17app::app::M17App;
 use m17app::soundmodem::{
-    InputRrcFile, InputSoundcard, NullInputSource, NullOutputSink, OutputRrcFile, OutputSoundcard,
-    Soundmodem,
+    InputRrcFile, InputSoundcard, NullInputSource, NullOutputSink, NullPtt, OutputRrcFile,
+    OutputSoundcard, Soundmodem,
 };
 use m17codec2::{Codec2Adapter, WavePlayer};
 use std::path::PathBuf;
@@ -11,7 +11,7 @@ pub fn mod_test() {
     //let out_path = PathBuf::from("../../../Data/mymod.rrc");
     //let output = OutputRrcFile::new(out_path);
     let output = OutputSoundcard::new();
-    let soundmodem = Soundmodem::new_with_input_and_output(NullInputSource::new(), output);
+    let soundmodem = Soundmodem::new(NullInputSource::new(), output, NullPtt::new());
     let app = M17App::new(soundmodem);
     app.start();
     std::thread::sleep(std::time::Duration::from_secs(1));