X-Git-Url: https://code.octet-stream.net/m17rt/blobdiff_plain/2fb25de49daca6ddff6f5af13bcf7c314aafafb3..16aaa4ac98d3719986e49623483c7f17306a4f95:/m17app/src/soundmodem.rs?ds=sidebyside diff --git a/m17app/src/soundmodem.rs b/m17app/src/soundmodem.rs index 1cb871a..59b56c9 100644 --- a/m17app/src/soundmodem.rs +++ b/m17app/src/soundmodem.rs @@ -1,10 +1,10 @@ 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; use std::collections::VecDeque; +use std::fmt::Display; use std::fs::File; use std::io::{self, ErrorKind, Read, Write}; use std::path::PathBuf; @@ -12,6 +12,7 @@ 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, @@ -20,8 +21,12 @@ pub struct Soundmodem { } 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); spawn_soundmodem_worker( @@ -31,6 +36,7 @@ impl Soundmodem { Box::new(input), Box::new(output), Box::new(ptt), + Box::new(error), ); Self { event_tx, @@ -40,6 +46,114 @@ impl Soundmodem { } } +#[derive(Debug, Clone, Copy)] +pub enum ErrorSource { + Input, + Output, + Ptt, +} + +impl Display for ErrorSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Input => write!(f, "Input"), + Self::Output => write!(f, "Output"), + Self::Ptt => write!(f, "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) + } +} + +/// Soundmodem errors will be ignored. +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; + } +} + +/// Soundmodem errors will be logged at DEBUG level via the `log` crate. +pub struct LogErrorHandler; + +impl LogErrorHandler { + pub fn new() -> Self { + Self {} + } +} + +impl Default for LogErrorHandler { + fn default() -> Self { + Self::new() + } +} + +impl ErrorHandler for LogErrorHandler { + fn soundmodem_error(&mut self, source: ErrorSource, err: SoundmodemError) { + log::debug!("Soundmodem error: {source} - {err}"); + } +} + +/// Soundmodem errors will be logged to stdout. +pub struct StdoutErrorHandler; + +impl StdoutErrorHandler { + pub fn new() -> Self { + Self {} + } +} + +impl Default for StdoutErrorHandler { + fn default() -> Self { + Self::new() + } +} + +impl ErrorHandler for StdoutErrorHandler { + fn soundmodem_error(&mut self, source: ErrorSource, err: SoundmodemError) { + println!("Soundmodem error: {source} - {err}"); + } +} + +#[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, @@ -99,14 +213,12 @@ impl Tnc for Soundmodem { }) } - 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 +229,7 @@ pub enum SoundmodemEvent { Close, DidReadFromOutputBuffer { len: usize, timestamp: Instant }, OutputUnderrun, + RuntimeError(ErrorSource, SoundmodemError), } fn spawn_soundmodem_worker( @@ -126,6 +239,7 @@ fn spawn_soundmodem_worker( input: Box, output: Box, mut ptt_driver: Box, + mut error_handler: Box, ) { std::thread::spawn(move || { // TODO: should be able to provide a custom Demodulator for a soundmodem @@ -170,12 +284,23 @@ 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.soundmodem_error(ErrorSource::Ptt, e); + } break; } SoundmodemEvent::DidReadFromOutputBuffer { len, timestamp } => { @@ -193,17 +318,23 @@ 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.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.soundmodem_error(ErrorSource::Ptt, e); + } + } else if let Err(e) = ptt_driver.ptt_off() { + error_handler.soundmodem_error(ErrorSource::Ptt, e); } } ptt = new_ptt; @@ -237,8 +368,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 +391,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 +410,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 +426,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 +452,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 +466,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 +524,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 +548,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 +584,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 +599,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 +638,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 +659,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 +673,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(()) } }