use thiserror::Error;
-/// Errors originating from the M17 Rust Toolkit core
+/// Errors from the M17 Rust Toolkit
#[derive(Debug, Error)]
pub enum M17Error {
#[error("given callsign contains at least one character invalid in M17: {0}")]
#[error("adapter error for id {0}: {1}")]
Adapter(usize, #[source] AdapterError),
+
+ #[error("soundmodem component error: {0}")]
+ Soundmodem(#[source] SoundmodemError),
}
+/// Arbitrary error type returned from adapters, which may be user-implemented
pub type AdapterError = Box<dyn std::error::Error + Sync + Send + 'static>;
+/// Arbitrary error type returned from soundmodem components, which may be user-implemented
+pub type SoundmodemError = Box<dyn std::error::Error + Sync + Send + 'static>;
+
/// Iterator over potentially multiple errors
#[derive(Debug, Error)]
pub struct M17Errors(pub(crate) Vec<M17Error>);
impl Display for M17Errors {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "{:?}", self.0)
+ let mut displays = vec![];
+ for e in &self.0 {
+ displays.push(e.to_string());
+ }
+ write!(f, "[{}]", displays.join(", "))
}
}
+
+#[derive(Debug, Error)]
+pub enum M17SoundmodemError {}
};
use crate::{
- error::M17Error,
+ error::{M17Error, SoundmodemError},
soundmodem::{InputSource, SoundmodemEvent},
};
}
impl InputSource for RtlSdr {
- fn start(&self, tx: SyncSender<SoundmodemEvent>) {
- // TODO: error handling
+ fn start(&self, tx: SyncSender<SoundmodemEvent>) -> Result<(), SoundmodemError> {
let mut cmd = Command::new("rtl_fm")
.args([
"-E",
"48k",
])
.stdout(Stdio::piped())
- .spawn()
- .unwrap();
+ .spawn()?;
let mut stdout = cmd.stdout.take().unwrap();
let mut buf = [0u8; 1024];
let mut leftover: Option<u8> = None;
}
});
*self.rtlfm.lock().unwrap() = Some(cmd);
+ Ok(())
}
- fn close(&self) {
+ fn close(&self) -> Result<(), SoundmodemError> {
if let Some(mut process) = self.rtlfm.lock().unwrap().take() {
let _ = process.kill();
}
+ Ok(())
}
}
use serialport::SerialPort;
-use crate::soundmodem::Ptt;
+use crate::{error::SoundmodemError, soundmodem::Ptt};
/// The pin on the serial port which is driving PTT
pub enum PttPin {
.map(|i| i.port_name)
}
- pub fn new(port_name: &str, pin: PttPin) -> Self {
+ pub fn new(port_name: &str, pin: PttPin) -> Result<Self, SoundmodemError> {
// TODO: error handling
let port = serialport::new(port_name, 9600).open().unwrap();
let mut s = Self { port, pin };
- s.ptt_off();
- s
+ s.ptt_off()?;
+ Ok(s)
}
}
impl Ptt for SerialPtt {
- fn ptt_on(&mut self) {
- let _ = match self.pin {
+ fn ptt_on(&mut self) -> Result<(), SoundmodemError> {
+ Ok(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 {
+ fn ptt_off(&mut self) -> Result<(), SoundmodemError> {
+ Ok(match self.pin {
PttPin::Rts => self.port.write_request_to_send(false),
PttPin::Dtr => self.port.write_data_terminal_ready(false),
- };
+ }?)
}
}
};
use crate::{
- error::M17Error,
+ error::{M17Error, SoundmodemError},
soundmodem::{InputSource, OutputBuffer, OutputSink, SoundmodemEvent},
};
}
impl InputSource for SoundcardInputSource {
- fn start(&self, samples: SyncSender<SoundmodemEvent>) {
- let _ = self.event_tx.send(SoundcardEvent::StartInput { samples });
+ fn start(&self, samples: SyncSender<SoundmodemEvent>) -> Result<(), SoundmodemError> {
+ Ok(self.event_tx.send(SoundcardEvent::StartInput { samples })?)
}
- fn close(&self) {
- let _ = self.event_tx.send(SoundcardEvent::CloseInput);
+ fn close(&self) -> Result<(), SoundmodemError> {
+ Ok(self.event_tx.send(SoundcardEvent::CloseInput)?)
}
}
}
impl OutputSink for SoundcardOutputSink {
- fn start(&self, event_tx: SyncSender<SoundmodemEvent>, buffer: Arc<RwLock<OutputBuffer>>) {
- let _ = self
+ fn start(
+ &self,
+ event_tx: SyncSender<SoundmodemEvent>,
+ buffer: Arc<RwLock<OutputBuffer>>,
+ ) -> Result<(), SoundmodemError> {
+ Ok(self
.event_tx
- .send(SoundcardEvent::StartOutput { event_tx, buffer });
+ .send(SoundcardEvent::StartOutput { event_tx, buffer })?)
}
- fn close(&self) {
- let _ = self.event_tx.send(SoundcardEvent::CloseOutput);
+ fn close(&self) -> Result<(), SoundmodemError> {
+ Ok(self.event_tx.send(SoundcardEvent::CloseOutput)?)
}
}
-use crate::error::M17Error;
+use crate::error::{M17Error, SoundmodemError};
use crate::tnc::{Tnc, TncError};
use log::debug;
use m17core::kiss::MAX_FRAME_LEN;
tnc.set_data_carrier_detect(demodulator.data_carrier_detect());
}
SoundmodemEvent::Start => {
- input.start(event_tx.clone());
- output.start(event_tx.clone(), out_buffer.clone());
+ // TODO: runtime event handling
+ input.start(event_tx.clone()).unwrap();
+ output.start(event_tx.clone(), out_buffer.clone()).unwrap();
}
SoundmodemEvent::Close => {
- ptt_driver.ptt_off();
+ ptt_driver.ptt_off().unwrap();
break;
}
SoundmodemEvent::DidReadFromOutputBuffer { len, timestamp } => {
let new_ptt = tnc.ptt();
if new_ptt != ptt {
if new_ptt {
- ptt_driver.ptt_on();
+ ptt_driver.ptt_on().unwrap();
} else {
- ptt_driver.ptt_off();
+ ptt_driver.ptt_off().unwrap();
}
}
ptt = new_ptt;
}
pub trait InputSource: Send + Sync + 'static {
- fn start(&self, samples: SyncSender<SoundmodemEvent>);
- fn close(&self);
+ fn start(&self, samples: SyncSender<SoundmodemEvent>) -> Result<(), SoundmodemError>;
+ fn close(&self) -> Result<(), SoundmodemError>;
}
pub struct InputRrcFile {
}
impl InputSource for InputRrcFile {
- fn start(&self, samples: SyncSender<SoundmodemEvent>) {
+ fn start(&self, samples: SyncSender<SoundmodemEvent>) -> Result<(), SoundmodemError> {
let (end_tx, end_rx) = channel();
let baseband = self.baseband.clone();
std::thread::spawn(move || {
}
});
*self.end_tx.lock().unwrap() = Some(end_tx);
+ Ok(())
}
- fn close(&self) {
+ fn close(&self) -> Result<(), SoundmodemError> {
let _ = self.end_tx.lock().unwrap().take();
+ Ok(())
}
}
}
impl InputSource for NullInputSource {
- fn start(&self, samples: SyncSender<SoundmodemEvent>) {
+ fn start(&self, samples: SyncSender<SoundmodemEvent>) -> Result<(), SoundmodemError> {
let (end_tx, end_rx) = channel();
std::thread::spawn(move || {
// assuming 48 kHz for now
}
});
*self.end_tx.lock().unwrap() = Some(end_tx);
+ Ok(())
}
- fn close(&self) {
+ fn close(&self) -> Result<(), SoundmodemError> {
let _ = self.end_tx.lock().unwrap().take();
+ Ok(())
}
}
}
pub trait OutputSink: Send + Sync + 'static {
- fn start(&self, event_tx: SyncSender<SoundmodemEvent>, buffer: Arc<RwLock<OutputBuffer>>);
- fn close(&self);
+ fn start(
+ &self,
+ event_tx: SyncSender<SoundmodemEvent>,
+ buffer: Arc<RwLock<OutputBuffer>>,
+ ) -> Result<(), SoundmodemError>;
+ fn close(&self) -> Result<(), SoundmodemError>;
}
pub struct OutputRrcFile {
}
impl OutputSink for OutputRrcFile {
- fn start(&self, event_tx: SyncSender<SoundmodemEvent>, buffer: Arc<RwLock<OutputBuffer>>) {
+ fn start(
+ &self,
+ event_tx: SyncSender<SoundmodemEvent>,
+ buffer: Arc<RwLock<OutputBuffer>>,
+ ) -> Result<(), SoundmodemError> {
let (end_tx, end_rx) = channel();
- let path = self.path.clone();
+ let mut file = File::create(self.path.clone())?;
std::thread::spawn(move || {
- // TODO: error handling
- let mut file = File::create(path).unwrap();
-
// assuming 48 kHz for now
const TICK: Duration = Duration::from_millis(25);
const SAMPLES_PER_TICK: usize = 1200;
}
});
*self.end_tx.lock().unwrap() = Some(end_tx);
+ Ok(())
}
- fn close(&self) {
+ fn close(&self) -> Result<(), SoundmodemError> {
let _ = self.end_tx.lock().unwrap().take();
+ Ok(())
}
}
}
impl OutputSink for NullOutputSink {
- fn start(&self, event_tx: SyncSender<SoundmodemEvent>, buffer: Arc<RwLock<OutputBuffer>>) {
+ fn start(
+ &self,
+ event_tx: SyncSender<SoundmodemEvent>,
+ buffer: Arc<RwLock<OutputBuffer>>,
+ ) -> Result<(), SoundmodemError> {
let (end_tx, end_rx) = channel();
std::thread::spawn(move || {
// assuming 48 kHz for now
}
});
*self.end_tx.lock().unwrap() = Some(end_tx);
+ Ok(())
}
- fn close(&self) {
+ fn close(&self) -> Result<(), SoundmodemError> {
let _ = self.end_tx.lock().unwrap().take();
+ Ok(())
}
}
pub trait Ptt: Send + 'static {
- fn ptt_on(&mut self);
- fn ptt_off(&mut self);
+ fn ptt_on(&mut self) -> Result<(), SoundmodemError>;
+ fn ptt_off(&mut self) -> Result<(), SoundmodemError>;
}
/// There is no PTT because this TNC will never make transmissions on a real radio.
}
impl Ptt for NullPtt {
- fn ptt_on(&mut self) {}
- fn ptt_off(&mut self) {}
+ fn ptt_on(&mut self) -> Result<(), SoundmodemError> {
+ Ok(())
+ }
+
+ fn ptt_off(&mut self) -> Result<(), SoundmodemError> {
+ Ok(())
+ }
}
std::thread::spawn(move || stream_thread(end_rx, setup_tx, state, output_card));
self.state.lock().unwrap().end_tx = Some(end_tx);
// Propagate any errors arising in the thread
- Ok(setup_rx.recv()??)
+ setup_rx.recv()?
}
fn close(&self) -> Result<(), AdapterError> {
pub fn mod_test() {
let soundcard = Soundcard::new("plughw:CARD=Device,DEV=0").unwrap();
soundcard.set_tx_inverted(true);
- let ptt = SerialPtt::new("/dev/ttyUSB0", PttPin::Rts);
+ let ptt = SerialPtt::new("/dev/ttyUSB0", PttPin::Rts).unwrap();
let soundmodem = Soundmodem::new(soundcard.input(), soundcard.output(), ptt);
let app = M17App::new(soundmodem);
app.start().unwrap();
fn main() {
let soundcard = Soundcard::new("plughw:CARD=Device,DEV=0").unwrap();
soundcard.set_tx_inverted(true);
- let ptt = SerialPtt::new("/dev/ttyUSB0", PttPin::Rts);
+ let ptt = SerialPtt::new("/dev/ttyUSB0", PttPin::Rts).unwrap();
let soundmodem = Soundmodem::new(soundcard.input(), soundcard.output(), ptt);
let app = M17App::new(soundmodem);