"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"
"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"
"cpal",
"log",
"m17core",
+ "serialport",
]
[[package]]
"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"
"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"
"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"
"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"
"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"
cpal = "0.15.3"
m17core = { path = "../m17core" }
log = "0.4.22"
+serialport = {version = "4.7.0", default-features = false }
}
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);
}
}
pub mod adapter;
pub mod app;
+pub mod serial;
pub mod soundmodem;
pub mod tnc;
--- /dev/null
+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),
+ };
+ }
+}
}
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);
kiss_out_tx,
Box::new(input),
Box::new(output),
+ Box::new(ptt),
);
Self {
event_tx,
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
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();
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;
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) {}
+}
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| {
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;
//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();
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;
//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));