]> code.octet-stream.net Git - m17rt/commitdiff
Error handler for soundmodem components
authorThomas Karpiniec <tom.karpiniec@outlook.com>
Wed, 5 Feb 2025 08:25:15 +0000 (19:25 +1100)
committerThomas Karpiniec <tom.karpiniec@outlook.com>
Wed, 5 Feb 2025 08:25:15 +0000 (19:25 +1100)
13 files changed:
m17app/src/adapter.rs
m17app/src/app.rs
m17app/src/error.rs
m17app/src/rtlsdr.rs
m17app/src/serial.rs
m17app/src/soundcard.rs
m17app/src/soundmodem.rs
m17app/src/test_util.rs
m17app/src/tnc.rs
tools/m17rt-demod/src/main.rs
tools/m17rt-mod/src/main.rs
tools/m17rt-rxpacket/src/main.rs
tools/m17rt-txpacket/src/main.rs

index 0e23924690fe695d74808ef7f724e748adb36efd..df7388ca1e4e3b068d5b2e9f657342f7cfa4423e 100644 (file)
@@ -70,8 +70,7 @@ pub trait StreamAdapter: Send + Sync + 'static {
         let _ = data;
     }
 
-    // TODO
-    // fn stream_lost(&self);
+    // TODO: callbacks for LICH metadata received
     // fn stream_assembled_text_block()
     // fn stream_gnss_data()
     // fn stream_extended_callsign_data()
index dc2138da0605879ef6b34878af5e477b35075143..b938a2cddb27e6593f1f21c326b04642f68e78bd 100644 (file)
@@ -354,22 +354,15 @@ fn spawn_writer<T: Tnc>(mut tnc: T, event_rx: mpsc::Receiver<TncControlEvent>) {
         while let Ok(ev) = event_rx.recv() {
             match ev {
                 TncControlEvent::Kiss(k) => {
-                    if let Err(e) = tnc.write_all(k.as_bytes()) {
-                        debug!("kiss send err: {:?}", e);
+                    if tnc.write_all(k.as_bytes()).is_err() {
                         return;
                     }
                 }
                 TncControlEvent::Start => {
-                    if let Err(e) = tnc.start() {
-                        debug!("tnc start err: {:?}", e);
-                        return;
-                    }
+                    tnc.start();
                 }
                 TncControlEvent::Close => {
-                    if let Err(e) = tnc.close() {
-                        debug!("tnc close err: {:?}", e);
-                        return;
-                    }
+                    tnc.close();
                 }
             }
         }
index 634a531e55a36e0b343c50295c35307899eb9d86..e820eae5101f8a7d7d57e324e108d3691125dd3d 100644 (file)
@@ -11,15 +11,6 @@ pub enum M17Error {
     #[error("given callsign is {0} characters long; maximum is 9")]
     CallsignTooLong(usize),
 
-    #[error("error during soundcard initialisation")]
-    SoundcardInit,
-
-    #[error("unable to locate sound card '{0}' - is it in use?")]
-    SoundcardNotFound(String),
-
-    #[error("unable to set up RTL-SDR receiver")]
-    RtlSdrInit,
-
     #[error(
         "provided packet payload is too large: provided {provided} bytes, capacity {capacity}"
     )]
index 7829f96220357a644fb6934ec4cd00104753047c..269769b4bd2559452a635c5abb2b43e1234b3ec2 100644 (file)
@@ -5,8 +5,8 @@ use std::{
 };
 
 use crate::{
-    error::{M17Error, SoundmodemError},
-    soundmodem::{InputSource, SoundmodemEvent},
+    error::M17Error,
+    soundmodem::{InputSource, SoundmodemErrorSender, SoundmodemEvent},
 };
 
 pub struct RtlSdr {
@@ -26,8 +26,8 @@ impl RtlSdr {
 }
 
 impl InputSource for RtlSdr {
-    fn start(&self, tx: SyncSender<SoundmodemEvent>) -> Result<(), SoundmodemError> {
-        let mut cmd = Command::new("rtl_fm")
+    fn start(&self, tx: SyncSender<SoundmodemEvent>, errors: SoundmodemErrorSender) {
+        let mut cmd = match Command::new("rtl_fm")
             .args([
                 "-E",
                 "offset",
@@ -39,7 +39,14 @@ impl InputSource for RtlSdr {
                 "48k",
             ])
             .stdout(Stdio::piped())
-            .spawn()?;
+            .spawn()
+        {
+            Ok(c) => c,
+            Err(e) => {
+                errors.send_error(e);
+                return;
+            }
+        };
         let mut stdout = cmd.stdout.take().unwrap();
         let mut buf = [0u8; 1024];
         let mut leftover: Option<u8> = None;
@@ -70,13 +77,11 @@ impl InputSource for RtlSdr {
             }
         });
         *self.rtlfm.lock().unwrap() = Some(cmd);
-        Ok(())
     }
 
-    fn close(&self) -> Result<(), SoundmodemError> {
+    fn close(&self) {
         if let Some(mut process) = self.rtlfm.lock().unwrap().take() {
             let _ = process.kill();
         }
-        Ok(())
     }
 }
index 667d10a38f131adcddfe772b88b6ceac2fb790a9..7747c3de73a03bdea38d37898e910bdf184cd621 100644 (file)
@@ -24,8 +24,7 @@ impl SerialPtt {
     }
 
     pub fn new(port_name: &str, pin: PttPin) -> Result<Self, SoundmodemError> {
-        // TODO: error handling
-        let port = serialport::new(port_name, 9600).open().unwrap();
+        let port = serialport::new(port_name, 9600).open()?;
         let mut s = Self { port, pin };
         s.ptt_off()?;
         Ok(s)
index f89d9f4e43a9ee6a1d34ee667a90b98a7c440b8e..dc08036effa7dbe99fc462b07a4d9e13283f7f2b 100644 (file)
@@ -8,12 +8,13 @@ use std::{
 
 use cpal::{
     traits::{DeviceTrait, HostTrait, StreamTrait},
-    SampleFormat, SampleRate, Stream,
+    BuildStreamError, DevicesError, PlayStreamError, SampleFormat, SampleRate, Stream, StreamError,
+    SupportedStreamConfigsError,
 };
+use thiserror::Error;
 
-use crate::{
-    error::{M17Error, SoundmodemError},
-    soundmodem::{InputSource, OutputBuffer, OutputSink, SoundmodemEvent},
+use crate::soundmodem::{
+    InputSource, OutputBuffer, OutputSink, SoundmodemErrorSender, SoundmodemEvent,
 };
 
 pub struct Soundcard {
@@ -21,14 +22,14 @@ pub struct Soundcard {
 }
 
 impl Soundcard {
-    pub fn new<S: Into<String>>(card_name: S) -> Result<Self, M17Error> {
+    pub fn new<S: Into<String>>(card_name: S) -> Result<Self, SoundcardError> {
         let (card_tx, card_rx) = sync_channel(128);
         let (setup_tx, setup_rx) = sync_channel(1);
         spawn_soundcard_worker(card_rx, setup_tx, card_name.into());
         match setup_rx.recv() {
             Ok(Ok(())) => Ok(Self { event_tx: card_tx }),
             Ok(Err(e)) => Err(e),
-            Err(_) => Err(M17Error::SoundcardInit),
+            Err(_) => Err(SoundcardError::SoundcardInit),
         }
     }
 
@@ -104,11 +105,13 @@ enum SoundcardEvent {
     SetTxInverted(bool),
     StartInput {
         samples: SyncSender<SoundmodemEvent>,
+        errors: SoundmodemErrorSender,
     },
     CloseInput,
     StartOutput {
         event_tx: SyncSender<SoundmodemEvent>,
         buffer: Arc<RwLock<OutputBuffer>>,
+        errors: SoundmodemErrorSender,
     },
     CloseOutput,
 }
@@ -118,12 +121,14 @@ pub struct SoundcardInputSource {
 }
 
 impl InputSource for SoundcardInputSource {
-    fn start(&self, samples: SyncSender<SoundmodemEvent>) -> Result<(), SoundmodemError> {
-        Ok(self.event_tx.send(SoundcardEvent::StartInput { samples })?)
+    fn start(&self, samples: SyncSender<SoundmodemEvent>, errors: SoundmodemErrorSender) {
+        let _ = self
+            .event_tx
+            .send(SoundcardEvent::StartInput { samples, errors });
     }
 
-    fn close(&self) -> Result<(), SoundmodemError> {
-        Ok(self.event_tx.send(SoundcardEvent::CloseInput)?)
+    fn close(&self) {
+        let _ = self.event_tx.send(SoundcardEvent::CloseInput);
     }
 }
 
@@ -136,20 +141,23 @@ impl OutputSink for SoundcardOutputSink {
         &self,
         event_tx: SyncSender<SoundmodemEvent>,
         buffer: Arc<RwLock<OutputBuffer>>,
-    ) -> Result<(), SoundmodemError> {
-        Ok(self
-            .event_tx
-            .send(SoundcardEvent::StartOutput { event_tx, buffer })?)
+        errors: SoundmodemErrorSender,
+    ) {
+        let _ = self.event_tx.send(SoundcardEvent::StartOutput {
+            event_tx,
+            buffer,
+            errors,
+        });
     }
 
-    fn close(&self) -> Result<(), SoundmodemError> {
-        Ok(self.event_tx.send(SoundcardEvent::CloseOutput)?)
+    fn close(&self) {
+        let _ = self.event_tx.send(SoundcardEvent::CloseOutput);
     }
 }
 
 fn spawn_soundcard_worker(
     event_rx: Receiver<SoundcardEvent>,
-    setup_tx: SyncSender<Result<(), M17Error>>,
+    setup_tx: SyncSender<Result<(), SoundcardError>>,
     card_name: String,
 ) {
     std::thread::spawn(move || {
@@ -159,7 +167,7 @@ fn spawn_soundcard_worker(
             .unwrap()
             .find(|d| d.name().unwrap() == card_name)
         else {
-            let _ = setup_tx.send(Err(M17Error::SoundcardNotFound(card_name)));
+            let _ = setup_tx.send(Err(SoundcardError::CardNotFound(card_name)));
             return;
         };
 
@@ -173,81 +181,119 @@ fn spawn_soundcard_worker(
             match ev {
                 SoundcardEvent::SetRxInverted(inv) => rx_inverted = inv,
                 SoundcardEvent::SetTxInverted(inv) => tx_inverted = inv,
-                SoundcardEvent::StartInput { samples } => {
-                    let mut input_configs = device.supported_input_configs().unwrap();
-                    let input_config = input_configs
+                SoundcardEvent::StartInput { samples, errors } => {
+                    let mut input_configs = match device.supported_input_configs() {
+                        Ok(c) => c,
+                        Err(e) => {
+                            errors.send_error(SoundcardError::SupportedConfigs(e));
+                            continue;
+                        }
+                    };
+                    let input_config = match input_configs
                         .find(|c| c.channels() == 1 && c.sample_format() == SampleFormat::I16)
-                        .unwrap()
-                        .with_sample_rate(SampleRate(48000));
-                    let stream = device
-                        .build_input_stream(
-                            &input_config.into(),
-                            move |data: &[i16], _info: &cpal::InputCallbackInfo| {
-                                let out: Vec<i16> = data
-                                    .iter()
-                                    .map(|s| if rx_inverted { s.saturating_neg() } else { *s })
-                                    .collect();
-                                let _ =
-                                    samples.try_send(SoundmodemEvent::BasebandInput(out.into()));
-                            },
-                            |e| {
-                                // TODO: abort?
-                                log::debug!("error occurred in soundcard input: {e:?}");
-                            },
-                            None,
-                        )
-                        .unwrap();
-                    stream.play().unwrap();
+                    {
+                        Some(c) => c,
+                        None => {
+                            errors.send_error(SoundcardError::NoValidConfigAvailable);
+                            continue;
+                        }
+                    };
+                    let input_config = input_config.with_sample_rate(SampleRate(48000));
+                    let errors_1 = errors.clone();
+                    let stream = match device.build_input_stream(
+                        &input_config.into(),
+                        move |data: &[i16], _info: &cpal::InputCallbackInfo| {
+                            let out: Vec<i16> = data
+                                .iter()
+                                .map(|s| if rx_inverted { s.saturating_neg() } else { *s })
+                                .collect();
+                            let _ = samples.try_send(SoundmodemEvent::BasebandInput(out.into()));
+                        },
+                        move |e| {
+                            errors_1.send_error(SoundcardError::Stream(e));
+                        },
+                        None,
+                    ) {
+                        Ok(s) => s,
+                        Err(e) => {
+                            errors.send_error(SoundcardError::StreamBuild(e));
+                            continue;
+                        }
+                    };
+                    if let Err(e) = stream.play() {
+                        errors.send_error(SoundcardError::StreamPlay(e));
+                        continue;
+                    }
                     input_stream = Some(stream);
                 }
                 SoundcardEvent::CloseInput => {
                     let _ = input_stream.take();
                 }
-                SoundcardEvent::StartOutput { event_tx, buffer } => {
-                    let mut output_configs = device.supported_output_configs().unwrap();
-                    // TODO: more error handling
-                    let output_config = output_configs
+                SoundcardEvent::StartOutput {
+                    event_tx,
+                    buffer,
+                    errors,
+                } => {
+                    let mut output_configs = match device.supported_output_configs() {
+                        Ok(c) => c,
+                        Err(e) => {
+                            errors.send_error(SoundcardError::SupportedConfigs(e));
+                            continue;
+                        }
+                    };
+                    let output_config = match output_configs
                         .find(|c| c.channels() == 1 && c.sample_format() == SampleFormat::I16)
-                        .unwrap()
-                        .with_sample_rate(SampleRate(48000));
-                    let stream = device
-                        .build_output_stream(
-                            &output_config.into(),
-                            move |data: &mut [i16], info: &cpal::OutputCallbackInfo| {
-                                let mut taken = 0;
-                                let ts = info.timestamp();
-                                let latency = ts
-                                    .playback
-                                    .duration_since(&ts.callback)
-                                    .unwrap_or(Duration::ZERO);
-                                let mut buffer = buffer.write().unwrap();
-                                buffer.latency = latency;
-                                for out in data.iter_mut() {
-                                    if let Some(s) = buffer.samples.pop_front() {
-                                        *out = if tx_inverted { s.saturating_neg() } else { s };
-                                        taken += 1;
-                                    } else if buffer.idling {
-                                        *out = 0;
-                                    } else {
-                                        log::debug!("output soundcard had underrun");
-                                        let _ = event_tx.send(SoundmodemEvent::OutputUnderrun);
-                                        break;
-                                    }
+                    {
+                        Some(c) => c,
+                        None => {
+                            errors.send_error(SoundcardError::NoValidConfigAvailable);
+                            continue;
+                        }
+                    };
+                    let output_config = output_config.with_sample_rate(SampleRate(48000));
+                    let errors_1 = errors.clone();
+                    let stream = match device.build_output_stream(
+                        &output_config.into(),
+                        move |data: &mut [i16], info: &cpal::OutputCallbackInfo| {
+                            let mut taken = 0;
+                            let ts = info.timestamp();
+                            let latency = ts
+                                .playback
+                                .duration_since(&ts.callback)
+                                .unwrap_or(Duration::ZERO);
+                            let mut buffer = buffer.write().unwrap();
+                            buffer.latency = latency;
+                            for out in data.iter_mut() {
+                                if let Some(s) = buffer.samples.pop_front() {
+                                    *out = if tx_inverted { s.saturating_neg() } else { s };
+                                    taken += 1;
+                                } else if buffer.idling {
+                                    *out = 0;
+                                } else {
+                                    let _ = event_tx.send(SoundmodemEvent::OutputUnderrun);
+                                    break;
                                 }
-                                //debug!("latency is {} ms, taken {taken}", latency.as_millis());
-                                let _ = event_tx.send(SoundmodemEvent::DidReadFromOutputBuffer {
-                                    len: taken,
-                                    timestamp: Instant::now(),
-                                });
-                            },
-                            |e| {
-                                // TODO: abort?
-                                log::debug!("error occurred in soundcard output: {e:?}");
-                            },
-                            None,
-                        )
-                        .unwrap();
-                    stream.play().unwrap();
+                            }
+                            let _ = event_tx.send(SoundmodemEvent::DidReadFromOutputBuffer {
+                                len: taken,
+                                timestamp: Instant::now(),
+                            });
+                        },
+                        move |e| {
+                            errors_1.send_error(SoundcardError::Stream(e));
+                        },
+                        None,
+                    ) {
+                        Ok(s) => s,
+                        Err(e) => {
+                            errors.send_error(SoundcardError::StreamBuild(e));
+                            continue;
+                        }
+                    };
+                    if let Err(e) = stream.play() {
+                        errors.send_error(SoundcardError::StreamPlay(e));
+                        continue;
+                    }
                     output_stream = Some(stream);
                 }
                 SoundcardEvent::CloseOutput => {
@@ -257,3 +303,30 @@ fn spawn_soundcard_worker(
         }
     });
 }
+
+#[derive(Debug, Error)]
+pub enum SoundcardError {
+    #[error("sound card init aborted unexpectedly")]
+    SoundcardInit,
+
+    #[error("unable to enumerate devices: {0}")]
+    Host(DevicesError),
+
+    #[error("unable to locate sound card '{0}' - is it in use?")]
+    CardNotFound(String),
+
+    #[error("error occurred in soundcard i/o: {0}")]
+    Stream(#[source] StreamError),
+
+    #[error("unable to retrieve supported configs for soundcard: {0}")]
+    SupportedConfigs(#[source] SupportedStreamConfigsError),
+
+    #[error("could not find a suitable soundcard config")]
+    NoValidConfigAvailable,
+
+    #[error("unable to build soundcard stream: {0}")]
+    StreamBuild(#[source] BuildStreamError),
+
+    #[error("unable to play stream")]
+    StreamPlay(#[source] PlayStreamError),
+}
index 1cb871aaffd93c021c65a8ec5f15373eabbe09c2..e7c0022643e52047c703943e9d1597eea787cb79 100644 (file)
@@ -1,6 +1,5 @@
 use crate::error::{M17Error, SoundmodemError};
 use crate::tnc::{Tnc, TncError};
-use log::debug;
 use m17core::kiss::MAX_FRAME_LEN;
 use m17core::modem::{Demodulator, Modulator, ModulatorAction, SoftDemodulator, SoftModulator};
 use m17core::tnc::SoftTnc;
@@ -12,18 +11,25 @@ use std::sync::mpsc::{channel, sync_channel, Receiver, Sender, SyncSender, TryRe
 use std::sync::RwLock;
 use std::sync::{Arc, Mutex};
 use std::time::{Duration, Instant};
+use thiserror::Error;
 
 pub struct Soundmodem {
     event_tx: SyncSender<SoundmodemEvent>,
     kiss_out_rx: Arc<Mutex<Receiver<Arc<[u8]>>>>,
     partial_kiss_out: Arc<Mutex<Option<PartialKissOut>>>,
+    error_handler: ErrorHandlerInternal,
 }
 
 impl Soundmodem {
-    pub fn new<I: InputSource, O: OutputSink, P: Ptt>(input: I, output: O, ptt: P) -> Self {
-        // must create TNC here
+    pub fn new<I: InputSource, O: OutputSink, P: Ptt, E: ErrorHandler>(
+        input: I,
+        output: O,
+        ptt: P,
+        error: E,
+    ) -> Self {
         let (event_tx, event_rx) = sync_channel(128);
         let (kiss_out_tx, kiss_out_rx) = sync_channel(128);
+        let runtime_error_handler: ErrorHandlerInternal = Arc::new(Mutex::new(Box::new(error)));
         spawn_soundmodem_worker(
             event_tx.clone(),
             event_rx,
@@ -31,15 +37,74 @@ impl Soundmodem {
             Box::new(input),
             Box::new(output),
             Box::new(ptt),
+            runtime_error_handler.clone(),
         );
         Self {
             event_tx,
             kiss_out_rx: Arc::new(Mutex::new(kiss_out_rx)),
             partial_kiss_out: Arc::new(Mutex::new(None)),
+            error_handler: runtime_error_handler,
         }
     }
 }
 
+#[derive(Debug, Clone, Copy)]
+pub enum ErrorSource {
+    Input,
+    Output,
+    Ptt,
+}
+
+pub trait ErrorHandler: Send + Sync + 'static {
+    fn soundmodem_error(&mut self, source: ErrorSource, err: SoundmodemError);
+}
+
+impl<F> ErrorHandler for F
+where
+    F: FnMut(ErrorSource, SoundmodemError) + Send + Sync + 'static,
+{
+    fn soundmodem_error(&mut self, source: ErrorSource, err: SoundmodemError) {
+        self(source, err)
+    }
+}
+
+pub struct NullErrorHandler;
+
+impl NullErrorHandler {
+    pub fn new() -> Self {
+        Self {}
+    }
+}
+
+impl Default for NullErrorHandler {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl ErrorHandler for NullErrorHandler {
+    fn soundmodem_error(&mut self, source: ErrorSource, err: SoundmodemError) {
+        let _ = source;
+        let _ = err;
+    }
+}
+
+type ErrorHandlerInternal = Arc<Mutex<Box<dyn ErrorHandler>>>;
+
+#[derive(Clone)]
+pub struct SoundmodemErrorSender {
+    source: ErrorSource,
+    event_tx: SyncSender<SoundmodemEvent>,
+}
+
+impl SoundmodemErrorSender {
+    pub fn send_error<E: Into<SoundmodemError>>(&self, err: E) {
+        let _ = self
+            .event_tx
+            .send(SoundmodemEvent::RuntimeError(self.source, err.into()));
+    }
+}
+
 struct PartialKissOut {
     output: Arc<[u8]>,
     idx: usize,
@@ -96,17 +161,16 @@ impl Tnc for Soundmodem {
             event_tx: self.event_tx.clone(),
             kiss_out_rx: self.kiss_out_rx.clone(),
             partial_kiss_out: self.partial_kiss_out.clone(),
+            error_handler: self.error_handler.clone(),
         })
     }
 
-    fn start(&mut self) -> Result<(), TncError> {
+    fn start(&mut self) {
         let _ = self.event_tx.send(SoundmodemEvent::Start);
-        Ok(())
     }
 
-    fn close(&mut self) -> Result<(), TncError> {
+    fn close(&mut self) {
         let _ = self.event_tx.send(SoundmodemEvent::Close);
-        Ok(())
     }
 }
 
@@ -117,6 +181,7 @@ pub enum SoundmodemEvent {
     Close,
     DidReadFromOutputBuffer { len: usize, timestamp: Instant },
     OutputUnderrun,
+    RuntimeError(ErrorSource, SoundmodemError),
 }
 
 fn spawn_soundmodem_worker(
@@ -126,6 +191,7 @@ fn spawn_soundmodem_worker(
     input: Box<dyn InputSource>,
     output: Box<dyn OutputSink>,
     mut ptt_driver: Box<dyn Ptt>,
+    error_handler: ErrorHandlerInternal,
 ) {
     std::thread::spawn(move || {
         // TODO: should be able to provide a custom Demodulator for a soundmodem
@@ -170,12 +236,26 @@ fn spawn_soundmodem_worker(
                     tnc.set_data_carrier_detect(demodulator.data_carrier_detect());
                 }
                 SoundmodemEvent::Start => {
-                    // TODO: runtime event handling
-                    input.start(event_tx.clone()).unwrap();
-                    output.start(event_tx.clone(), out_buffer.clone()).unwrap();
+                    let input_errors = SoundmodemErrorSender {
+                        source: ErrorSource::Input,
+                        event_tx: event_tx.clone(),
+                    };
+                    input.start(event_tx.clone(), input_errors);
+                    let output_errors = SoundmodemErrorSender {
+                        source: ErrorSource::Output,
+                        event_tx: event_tx.clone(),
+                    };
+                    output.start(event_tx.clone(), out_buffer.clone(), output_errors);
                 }
                 SoundmodemEvent::Close => {
-                    ptt_driver.ptt_off().unwrap();
+                    input.close();
+                    output.close();
+                    if let Err(e) = ptt_driver.ptt_off() {
+                        error_handler
+                            .lock()
+                            .unwrap()
+                            .soundmodem_error(ErrorSource::Ptt, e);
+                    }
                     break;
                 }
                 SoundmodemEvent::DidReadFromOutputBuffer { len, timestamp } => {
@@ -193,17 +273,29 @@ fn spawn_soundmodem_worker(
                     );
                 }
                 SoundmodemEvent::OutputUnderrun => {
+                    log::debug!("output underrun");
                     // TODO: cancel transmission, send empty data frame to host
                 }
+                SoundmodemEvent::RuntimeError(source, err) => {
+                    error_handler.lock().unwrap().soundmodem_error(source, err);
+                }
             }
 
             // Update PTT state
             let new_ptt = tnc.ptt();
             if new_ptt != ptt {
                 if new_ptt {
-                    ptt_driver.ptt_on().unwrap();
-                } else {
-                    ptt_driver.ptt_off().unwrap();
+                    if let Err(e) = ptt_driver.ptt_on() {
+                        error_handler
+                            .lock()
+                            .unwrap()
+                            .soundmodem_error(ErrorSource::Ptt, e);
+                    }
+                } else if let Err(e) = ptt_driver.ptt_off() {
+                    error_handler
+                        .lock()
+                        .unwrap()
+                        .soundmodem_error(ErrorSource::Ptt, e);
                 }
             }
             ptt = new_ptt;
@@ -237,8 +329,8 @@ fn spawn_soundmodem_worker(
 }
 
 pub trait InputSource: Send + Sync + 'static {
-    fn start(&self, samples: SyncSender<SoundmodemEvent>) -> Result<(), SoundmodemError>;
-    fn close(&self) -> Result<(), SoundmodemError>;
+    fn start(&self, samples: SyncSender<SoundmodemEvent>, errors: SoundmodemErrorSender);
+    fn close(&self);
 }
 
 pub struct InputRrcFile {
@@ -260,7 +352,7 @@ impl InputRrcFile {
 }
 
 impl InputSource for InputRrcFile {
-    fn start(&self, samples: SyncSender<SoundmodemEvent>) -> Result<(), SoundmodemError> {
+    fn start(&self, samples: SyncSender<SoundmodemEvent>, errors: SoundmodemErrorSender) {
         let (end_tx, end_rx) = channel();
         let baseband = self.baseband.clone();
         std::thread::spawn(move || {
@@ -279,8 +371,11 @@ impl InputSource for InputRrcFile {
                 buf[idx] = sample;
                 idx += 1;
                 if idx == SAMPLES_PER_TICK {
-                    if let Err(e) = samples.try_send(SoundmodemEvent::BasebandInput(buf.into())) {
-                        debug!("overflow feeding soundmodem: {e:?}");
+                    if samples
+                        .try_send(SoundmodemEvent::BasebandInput(buf.into()))
+                        .is_err()
+                    {
+                        errors.send_error(InputRrcError::Overflow);
                     }
                     next_tick += TICK;
                     idx = 0;
@@ -292,15 +387,19 @@ impl InputSource for InputRrcFile {
             }
         });
         *self.end_tx.lock().unwrap() = Some(end_tx);
-        Ok(())
     }
 
-    fn close(&self) -> Result<(), SoundmodemError> {
+    fn close(&self) {
         let _ = self.end_tx.lock().unwrap().take();
-        Ok(())
     }
 }
 
+#[derive(Debug, Error)]
+pub enum InputRrcError {
+    #[error("overflow occurred feeding sample to soundmodem")]
+    Overflow,
+}
+
 pub struct NullInputSource {
     end_tx: Mutex<Option<Sender<()>>>,
 }
@@ -314,7 +413,7 @@ impl NullInputSource {
 }
 
 impl InputSource for NullInputSource {
-    fn start(&self, samples: SyncSender<SoundmodemEvent>) -> Result<(), SoundmodemError> {
+    fn start(&self, samples: SyncSender<SoundmodemEvent>, errors: SoundmodemErrorSender) {
         let (end_tx, end_rx) = channel();
         std::thread::spawn(move || {
             // assuming 48 kHz for now
@@ -328,23 +427,30 @@ impl InputSource for NullInputSource {
                 if end_rx.try_recv() != Err(TryRecvError::Empty) {
                     break;
                 }
-                if let Err(e) = samples.try_send(SoundmodemEvent::BasebandInput(
-                    [0i16; SAMPLES_PER_TICK].into(),
-                )) {
-                    debug!("overflow feeding soundmodem: {e:?}");
+                if samples
+                    .try_send(SoundmodemEvent::BasebandInput(
+                        [0i16; SAMPLES_PER_TICK].into(),
+                    ))
+                    .is_err()
+                {
+                    errors.send_error(NullInputError::Overflow);
                 }
             }
         });
         *self.end_tx.lock().unwrap() = Some(end_tx);
-        Ok(())
     }
 
-    fn close(&self) -> Result<(), SoundmodemError> {
+    fn close(&self) {
         let _ = self.end_tx.lock().unwrap().take();
-        Ok(())
     }
 }
 
+#[derive(Debug, Error)]
+pub enum NullInputError {
+    #[error("overflow occurred feeding sample to soundmodem")]
+    Overflow,
+}
+
 impl Default for NullInputSource {
     fn default() -> Self {
         Self::new()
@@ -379,8 +485,9 @@ pub trait OutputSink: Send + Sync + 'static {
         &self,
         event_tx: SyncSender<SoundmodemEvent>,
         buffer: Arc<RwLock<OutputBuffer>>,
-    ) -> Result<(), SoundmodemError>;
-    fn close(&self) -> Result<(), SoundmodemError>;
+        errors: SoundmodemErrorSender,
+    );
+    fn close(&self);
 }
 
 pub struct OutputRrcFile {
@@ -402,9 +509,16 @@ impl OutputSink for OutputRrcFile {
         &self,
         event_tx: SyncSender<SoundmodemEvent>,
         buffer: Arc<RwLock<OutputBuffer>>,
-    ) -> Result<(), SoundmodemError> {
+        errors: SoundmodemErrorSender,
+    ) {
         let (end_tx, end_rx) = channel();
-        let mut file = File::create(self.path.clone())?;
+        let mut file = match File::create(self.path.clone()) {
+            Ok(f) => f,
+            Err(e) => {
+                errors.send_error(OutputRrcError::Open(e));
+                return;
+            }
+        };
         std::thread::spawn(move || {
             // assuming 48 kHz for now
             const TICK: Duration = Duration::from_millis(25);
@@ -431,13 +545,12 @@ impl OutputSink for OutputRrcFile {
                         out.copy_from_slice(&[be[0], be[1]]);
                         buf_used += 2;
                     } else if !buffer.idling {
-                        debug!("output rrc file had underrun");
                         let _ = event_tx.send(SoundmodemEvent::OutputUnderrun);
                         break;
                     }
                 }
                 if let Err(e) = file.write_all(&buf[0..buf_used]) {
-                    debug!("failed to write to rrc file: {e:?}");
+                    errors.send_error(OutputRrcError::WriteError(e));
                     break;
                 }
                 let _ = event_tx.send(SoundmodemEvent::DidReadFromOutputBuffer {
@@ -447,15 +560,22 @@ impl OutputSink for OutputRrcFile {
             }
         });
         *self.end_tx.lock().unwrap() = Some(end_tx);
-        Ok(())
     }
 
-    fn close(&self) -> Result<(), SoundmodemError> {
+    fn close(&self) {
         let _ = self.end_tx.lock().unwrap().take();
-        Ok(())
     }
 }
 
+#[derive(Debug, Error)]
+pub enum OutputRrcError {
+    #[error("unable to open rrc file for writing: {0}")]
+    Open(#[source] std::io::Error),
+
+    #[error("error writing to output file: {0}")]
+    WriteError(#[source] std::io::Error),
+}
+
 pub struct NullOutputSink {
     end_tx: Mutex<Option<Sender<()>>>,
 }
@@ -479,7 +599,8 @@ impl OutputSink for NullOutputSink {
         &self,
         event_tx: SyncSender<SoundmodemEvent>,
         buffer: Arc<RwLock<OutputBuffer>>,
-    ) -> Result<(), SoundmodemError> {
+        _errors: SoundmodemErrorSender,
+    ) {
         let (end_tx, end_rx) = channel();
         std::thread::spawn(move || {
             // assuming 48 kHz for now
@@ -499,7 +620,6 @@ impl OutputSink for NullOutputSink {
                 for _ in 0..SAMPLES_PER_TICK {
                     if buffer.samples.pop_front().is_none() {
                         if !buffer.idling {
-                            debug!("null output had underrun");
                             let _ = event_tx.send(SoundmodemEvent::OutputUnderrun);
                             break;
                         }
@@ -514,12 +634,10 @@ impl OutputSink for NullOutputSink {
             }
         });
         *self.end_tx.lock().unwrap() = Some(end_tx);
-        Ok(())
     }
 
-    fn close(&self) -> Result<(), SoundmodemError> {
+    fn close(&self) {
         let _ = self.end_tx.lock().unwrap().take();
-        Ok(())
     }
 }
 
index ea654f1b49de7dcc69b3bd94a61cbdc6d04ecb52..3cdc8a87c035edd2de049cec7352e742397e7fac 100644 (file)
@@ -10,13 +10,9 @@ impl Tnc for NullTnc {
         Ok(self.clone())
     }
 
-    fn start(&mut self) -> Result<(), crate::tnc::TncError> {
-        Ok(())
-    }
+    fn start(&mut self) {}
 
-    fn close(&mut self) -> Result<(), crate::tnc::TncError> {
-        Ok(())
-    }
+    fn close(&mut self) {}
 }
 
 impl Write for NullTnc {
index e7799b4843cb713fd819e1390110dc8a9030b81c..ef30e38f0d325bd360cc2ddd301226e08f53b65e 100644 (file)
@@ -17,10 +17,10 @@ pub trait Tnc: Read + Write + Sized + Send + 'static {
     fn try_clone(&mut self) -> Result<Self, TncError>;
 
     /// Start I/O.
-    fn start(&mut self) -> Result<(), TncError>;
+    fn start(&mut self);
 
     /// Shut down I/O - it is assumed we cannot restart.
-    fn close(&mut self) -> Result<(), TncError>;
+    fn close(&mut self);
 }
 
 #[derive(Debug, PartialEq, Eq, Clone)]
@@ -34,13 +34,11 @@ impl Tnc for std::net::TcpStream {
         std::net::TcpStream::try_clone(self).map_err(|_| TncError::Unknown)
     }
 
-    fn start(&mut self) -> Result<(), TncError> {
+    fn start(&mut self) {
         // already started, hopefully we get onto reading the socket quickly
-        Ok(())
     }
 
-    fn close(&mut self) -> Result<(), TncError> {
-        self.shutdown(std::net::Shutdown::Both)
-            .map_err(|_| TncError::Unknown)
+    fn close(&mut self) {
+        let _ = self.shutdown(std::net::Shutdown::Both);
     }
 }
index bcc5468a852c962ab31623b36c0f9bca353f57ae..0da1501c4c69a13511d12b58ce1c4569eeaa451f 100755 (executable)
@@ -1,11 +1,16 @@
 use m17app::app::M17App;
 use m17app::soundcard::Soundcard;
-use m17app::soundmodem::{NullOutputSink, NullPtt, Soundmodem};
+use m17app::soundmodem::{NullErrorHandler, NullOutputSink, NullPtt, Soundmodem};
 use m17codec2::Codec2Adapter;
 
 pub fn demod_test() {
     let soundcard = Soundcard::new("plughw:CARD=Device,DEV=0").unwrap();
-    let soundmodem = Soundmodem::new(soundcard.input(), NullOutputSink::new(), NullPtt::new());
+    let soundmodem = Soundmodem::new(
+        soundcard.input(),
+        NullOutputSink::new(),
+        NullPtt::new(),
+        NullErrorHandler::new(),
+    );
     let app = M17App::new(soundmodem);
     app.add_stream_adapter(Codec2Adapter::new()).unwrap();
     app.start().unwrap();
index 6a3c527b9e854c207edc4da8a3ef222d33dceedc..104d83e45ac8893354ef9051259c133dcad7d490 100644 (file)
@@ -2,7 +2,7 @@ use m17app::app::M17App;
 use m17app::link_setup::M17Address;
 use m17app::serial::{PttPin, SerialPtt};
 use m17app::soundcard::Soundcard;
-use m17app::soundmodem::Soundmodem;
+use m17app::soundmodem::{NullErrorHandler, Soundmodem};
 use m17codec2::WavePlayer;
 use std::path::PathBuf;
 
@@ -10,7 +10,12 @@ 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).unwrap();
-    let soundmodem = Soundmodem::new(soundcard.input(), soundcard.output(), ptt);
+    let soundmodem = Soundmodem::new(
+        soundcard.input(),
+        soundcard.output(),
+        ptt,
+        NullErrorHandler::new(),
+    );
     let app = M17App::new(soundmodem);
     app.start().unwrap();
     std::thread::sleep(std::time::Duration::from_secs(1));
index 11ee69e9710e5a35d92e125afd8cc4729da3cd3d..a18537e4e87fe02f7fd8290e156fcd144b2ba4f4 100755 (executable)
@@ -2,13 +2,18 @@ use m17app::adapter::PacketAdapter;
 use m17app::app::M17App;
 use m17app::link_setup::LinkSetup;
 use m17app::soundcard::Soundcard;
-use m17app::soundmodem::{NullOutputSink, NullPtt, Soundmodem};
+use m17app::soundmodem::{NullErrorHandler, NullOutputSink, NullPtt, Soundmodem};
 use m17app::PacketType;
 use std::sync::Arc;
 
 fn main() {
     let soundcard = Soundcard::new("plughw:CARD=Device,DEV=0").unwrap();
-    let soundmodem = Soundmodem::new(soundcard.input(), NullOutputSink::new(), NullPtt::new());
+    let soundmodem = Soundmodem::new(
+        soundcard.input(),
+        NullOutputSink::new(),
+        NullPtt::new(),
+        NullErrorHandler::new(),
+    );
     let app = M17App::new(soundmodem);
     app.add_packet_adapter(PacketPrinter).unwrap();
     app.start().unwrap();
index fcac6f5da4cb96e68d0abe6c5fec6172ac9b42b3..ca5f373923a15207c9f2e3a55338e98f1fe29953 100644 (file)
@@ -2,14 +2,19 @@ use m17app::app::M17App;
 use m17app::link_setup::{LinkSetup, M17Address};
 use m17app::serial::{PttPin, SerialPtt};
 use m17app::soundcard::Soundcard;
-use m17app::soundmodem::Soundmodem;
+use m17app::soundmodem::{NullErrorHandler, Soundmodem};
 use m17core::protocol::PacketType;
 
 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).unwrap();
-    let soundmodem = Soundmodem::new(soundcard.input(), soundcard.output(), ptt);
+    let soundmodem = Soundmodem::new(
+        soundcard.input(),
+        soundcard.output(),
+        ptt,
+        NullErrorHandler::new(),
+    );
     let app = M17App::new(soundmodem);
 
     app.start().unwrap();