From: Thomas Karpiniec Date: Wed, 5 Feb 2025 08:25:15 +0000 (+1100) Subject: Error handler for soundmodem components X-Git-Url: https://code.octet-stream.net/m17rt/commitdiff_plain/64599440f241f7bb897a95b72ed7130231966518?ds=sidebyside Error handler for soundmodem components --- diff --git a/m17app/src/adapter.rs b/m17app/src/adapter.rs index 0e23924..df7388c 100644 --- a/m17app/src/adapter.rs +++ b/m17app/src/adapter.rs @@ -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() diff --git a/m17app/src/app.rs b/m17app/src/app.rs index dc2138d..b938a2c 100644 --- a/m17app/src/app.rs +++ b/m17app/src/app.rs @@ -354,22 +354,15 @@ fn spawn_writer(mut tnc: T, event_rx: mpsc::Receiver) { 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(); } } } diff --git a/m17app/src/error.rs b/m17app/src/error.rs index 634a531..e820eae 100644 --- a/m17app/src/error.rs +++ b/m17app/src/error.rs @@ -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}" )] diff --git a/m17app/src/rtlsdr.rs b/m17app/src/rtlsdr.rs index 7829f96..269769b 100644 --- a/m17app/src/rtlsdr.rs +++ b/m17app/src/rtlsdr.rs @@ -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) -> Result<(), SoundmodemError> { - let mut cmd = Command::new("rtl_fm") + fn start(&self, tx: SyncSender, 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 = 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(()) } } diff --git a/m17app/src/serial.rs b/m17app/src/serial.rs index 667d10a..7747c3d 100644 --- a/m17app/src/serial.rs +++ b/m17app/src/serial.rs @@ -24,8 +24,7 @@ impl SerialPtt { } pub fn new(port_name: &str, pin: PttPin) -> Result { - // 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) diff --git a/m17app/src/soundcard.rs b/m17app/src/soundcard.rs index f89d9f4..dc08036 100644 --- a/m17app/src/soundcard.rs +++ b/m17app/src/soundcard.rs @@ -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>(card_name: S) -> Result { + pub fn new>(card_name: S) -> Result { 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, + errors: SoundmodemErrorSender, }, CloseInput, StartOutput { event_tx: SyncSender, buffer: Arc>, + errors: SoundmodemErrorSender, }, CloseOutput, } @@ -118,12 +121,14 @@ pub struct SoundcardInputSource { } impl InputSource for SoundcardInputSource { - fn start(&self, samples: SyncSender) -> Result<(), SoundmodemError> { - Ok(self.event_tx.send(SoundcardEvent::StartInput { samples })?) + fn start(&self, samples: SyncSender, 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, buffer: Arc>, - ) -> 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, - setup_tx: SyncSender>, + setup_tx: SyncSender>, 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 = 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 = 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), +} diff --git a/m17app/src/soundmodem.rs b/m17app/src/soundmodem.rs index 1cb871a..e7c0022 100644 --- a/m17app/src/soundmodem.rs +++ b/m17app/src/soundmodem.rs @@ -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, kiss_out_rx: Arc>>>, partial_kiss_out: Arc>>, + error_handler: ErrorHandlerInternal, } impl Soundmodem { - pub fn new(input: I, output: O, ptt: P) -> Self { - // must create TNC here + pub fn new( + 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 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>>; + +#[derive(Clone)] +pub struct SoundmodemErrorSender { + source: ErrorSource, + event_tx: SyncSender, +} + +impl SoundmodemErrorSender { + pub fn send_error>(&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, output: Box, mut ptt_driver: Box, + 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) -> Result<(), SoundmodemError>; - fn close(&self) -> Result<(), SoundmodemError>; + fn start(&self, samples: SyncSender, errors: SoundmodemErrorSender); + fn close(&self); } pub struct InputRrcFile { @@ -260,7 +352,7 @@ impl InputRrcFile { } impl InputSource for InputRrcFile { - fn start(&self, samples: SyncSender) -> Result<(), SoundmodemError> { + fn start(&self, samples: SyncSender, 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>>, } @@ -314,7 +413,7 @@ impl NullInputSource { } impl InputSource for NullInputSource { - fn start(&self, samples: SyncSender) -> Result<(), SoundmodemError> { + fn start(&self, samples: SyncSender, 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, buffer: Arc>, - ) -> 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, buffer: Arc>, - ) -> 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>>, } @@ -479,7 +599,8 @@ impl OutputSink for NullOutputSink { &self, event_tx: SyncSender, buffer: Arc>, - ) -> 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(()) } } diff --git a/m17app/src/test_util.rs b/m17app/src/test_util.rs index ea654f1..3cdc8a8 100644 --- a/m17app/src/test_util.rs +++ b/m17app/src/test_util.rs @@ -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 { diff --git a/m17app/src/tnc.rs b/m17app/src/tnc.rs index e7799b4..ef30e38 100644 --- a/m17app/src/tnc.rs +++ b/m17app/src/tnc.rs @@ -17,10 +17,10 @@ pub trait Tnc: Read + Write + Sized + Send + 'static { fn try_clone(&mut self) -> Result; /// 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); } } diff --git a/tools/m17rt-demod/src/main.rs b/tools/m17rt-demod/src/main.rs index bcc5468..0da1501 100755 --- a/tools/m17rt-demod/src/main.rs +++ b/tools/m17rt-demod/src/main.rs @@ -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(); diff --git a/tools/m17rt-mod/src/main.rs b/tools/m17rt-mod/src/main.rs index 6a3c527..104d83e 100644 --- a/tools/m17rt-mod/src/main.rs +++ b/tools/m17rt-mod/src/main.rs @@ -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)); diff --git a/tools/m17rt-rxpacket/src/main.rs b/tools/m17rt-rxpacket/src/main.rs index 11ee69e..a18537e 100755 --- a/tools/m17rt-rxpacket/src/main.rs +++ b/tools/m17rt-rxpacket/src/main.rs @@ -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(); diff --git a/tools/m17rt-txpacket/src/main.rs b/tools/m17rt-txpacket/src/main.rs index fcac6f5..ca5f373 100644 --- a/tools/m17rt-txpacket/src/main.rs +++ b/tools/m17rt-txpacket/src/main.rs @@ -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();