diff --git a/src/error.rs b/src/error.rs index bb3b7f9418a2a2ff0470291b53caa8805a361af6..f75e3142c214317f321e55a9aa2f77fe2b779e13 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,10 +1,15 @@ use rustmex::{message::AdHoc, mxArray, FromMatlabError, MxArray}; +/// Type alias for the result of the serialbridge functions pub type SResult<T> = std::result::Result<T, Error>; +/// Utility trait to map errors to `rustmex::Error` more easily pub trait MapMexError<T, E> { + /// Map the error to an ad-hoc error with the given `id` and `msg` fn map_err_adhoc(self, id: &str, msg: &str) -> Result<T, rustmex::Error>; + /// Map the error to the given crate Error fn mexerr(self, err: Error) -> Result<T, rustmex::Error>; + /// Map the error to the given function that returns a crate Error fn map_mexerr<O>(self, err: O) -> Result<T, rustmex::Error> where O: FnOnce(E) -> Error; @@ -44,6 +49,7 @@ impl<T> MapMexError<T, ()> for Option<T> { } } +/// Error type for the serialbridge functions #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Too many parameters passed")] @@ -89,6 +95,7 @@ pub enum Error { } impl Error { + /// Get the error id (useful for `rustmex::Error` AdHoc Message construction) fn id(&self) -> &str { match self { Error::TooManyParameters => "serialbridge:invalid_input", diff --git a/src/lib.rs b/src/lib.rs index 02185c575ee7a6acdb88a90d6706c173bf6665a4..a482570a032729d91220fb7dd6b965275525d726 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,7 @@ mod types; use std::sync::RwLock; -use rustmex::{char::CharArray, prelude::*, warning, MatlabClass}; +use rustmex::prelude::*; use error::{Error, MapMexError, SResult}; use types::{IntoMatlabType, IntoRustType, Mode}; @@ -17,22 +17,25 @@ lazy_static::lazy_static! { /// this compiles only in debug mode #[cfg(debug_assertions)] -#[macro_export] macro_rules! warn_debug { ($msg:literal, $($arg:expr),*) => { rustmex::warning("serialbridge:debug", format!($msg, $($arg),*)); }; } +/// in release mode, this macro does nothing #[cfg(not(debug_assertions))] macro_rules! warn_debug { () => {}; } +/// This is a wrapper to the arguments passed to the MEX function struct Args<'a>(Rhs<'a, 'a>); +/// This is a wrapper to the output arguments passed to the MEX function struct Output<'a>(Lhs<'a>); impl Args<'_> { + /// Asserts that the number of parameters is less than or equal to `len` fn assert_params_max_len(&self, len: usize) -> SResult<()> { if self.0.len() - 1 > len { Err(Error::TooManyParameters) @@ -41,12 +44,14 @@ impl Args<'_> { } } + /// Get the `ix`-th parameter as owned MxArray, returning `missing_err` if it does not exist fn get(&self, ix: usize, missing_err: Error) -> SResult<MxArray> { Ok(self.0.get(ix).mexerr(missing_err)?.to_owned().to_owned()) } } impl Output<'_> { + /// Set the `ix`-th parameter to `val` fn set<T: IntoMatlabType>(&mut self, val: T) -> SResult<()> { let Some(ret) = self.0.get_mut(0) else { return Err(Error::ReturnType); @@ -57,8 +62,10 @@ impl Output<'_> { } } +/// The entrypoint of the MEX function #[rustmex::entrypoint] fn serialbridge(lhs: Lhs, rhs: Rhs) -> rustmex::Result<()> { + // Create wrappers for the input and output arguments let args = Args(rhs); let out = Output(lhs); @@ -78,16 +85,17 @@ fn serialbridge(lhs: Lhs, rhs: Rhs) -> rustmex::Result<()> { Ok(()) } +/// Get the mode from the input argument (Open, Close, Read, Write) fn get_mode(arg: MxArray) -> SResult<Mode> { - CharArray::from_mx_array(arg)? - .get_cstring() - .into_string()? - .parse() + IntoRustType::<String>::into_rust(arg)?.parse() } +/// Open the serial port specified by the first argument with the baudrate +/// specified by the second argument fn open_serial(args: Args<'_>) -> SResult<()> { args.assert_params_max_len(2)?; + // Get the port name and baudrate from the input arguments let port: String = args .get(1, Error::MissingPortName)? .into_rust() @@ -114,6 +122,7 @@ fn open_serial(args: Args<'_>) -> SResult<()> { Ok(()) } +/// Close the serial port if it is open (didn't expect any arguments) fn close_serial(args: Args<'_>) -> SResult<()> { args.assert_params_max_len(0)?; @@ -121,9 +130,11 @@ fn close_serial(args: Args<'_>) -> SResult<()> { Ok(()) } +/// Read `n` doubles from the serial port and return them as a vector fn read_from_serial(mut outputs: Output<'_>, args: Args<'_>) -> SResult<()> { args.assert_params_max_len(1)?; + // Get the number of doubles to read from the input argument let arg: f64 = args .get(1, Error::MissingReadAmount)? .into_rust() @@ -139,6 +150,7 @@ fn read_from_serial(mut outputs: Output<'_>, args: Args<'_>) -> SResult<()> { // Read n_doubles from the serial port let bytes = SERIAL.read().unwrap().read_n_bytes(n_doubles)?; + // Here we use unsafe to cast the bytes to a slice of f64 (maybe a safer alternative helps) let doubles = unsafe { std::slice::from_raw_parts(bytes.as_ptr() as *const f64, n_doubles) }; warn_debug!("Read {} bytes from serial port", n_doubles); @@ -146,14 +158,17 @@ fn read_from_serial(mut outputs: Output<'_>, args: Args<'_>) -> SResult<()> { Ok(()) } +/// Write the doubles from the input argument to the serial port as stream of bytes fn write_to_serial(args: Args<'_>) -> SResult<()> { args.assert_params_max_len(1)?; + // Get the doubles to write from the input argument let data: Vec<f64> = args .get(1, Error::MissingWriteData)? .into_rust() .map_mexerr(|e| Error::InvalidWriteData(Box::new(e)))?; + // Convert the doubles to a stream of bytes and write them to the serial port let data: Vec<u8> = data.iter().flat_map(|&x| x.to_be_bytes()).collect(); SERIAL.read().unwrap().write_bytes(&data)?; warn_debug!("Wrote {} bytes to serial port", data.len()); diff --git a/src/serial.rs b/src/serial.rs index 3365897a2c26e118f75d8960ee1c8afbe9d64e78..38826e7e2edd34639c2bcd26f533a1ed5aa04d16 100644 --- a/src/serial.rs +++ b/src/serial.rs @@ -2,11 +2,9 @@ use std::sync::Mutex; use serialport::SerialPort; -use crate::{ - error::{Error, SResult}, - warn_debug, -}; +use crate::error::{Error, SResult}; +/// Handler of the serial port pub struct SerialManager { serial: Option<Mutex<Box<dyn SerialPort>>>, } @@ -16,17 +14,24 @@ impl SerialManager { Self { serial: None } } + /// Open the serial port with the given `port` device path and `baudrate` pub fn open(&mut self, port: &str, baudrate: u32) -> SResult<()> { let port = serialport::new(port, baudrate).open()?; self.serial.replace(Mutex::new(port)); Ok(()) } + /// Close the serial port if it is open pub fn close(&mut self) -> SResult<()> { - self.serial.take(); - Ok(()) + if self.serial.is_some() { + self.serial.take(); + Ok(()) + } else { + Err(Error::SerialNotOpen) + } } + /// Read `n` bytes from the serial port pub fn read_n_bytes(&self, n: usize) -> SResult<Vec<u8>> { let mut port = self .serial @@ -39,6 +44,7 @@ impl SerialManager { Ok(buf) } + /// Write `data` to the serial port pub fn write_bytes(&self, data: &[u8]) -> SResult<()> { let mut port = self .serial diff --git a/src/types.rs b/src/types.rs index 7060416c2e8c7ff1b0a70ee1470818ee0a8c6543..eb9173b92fb182e810374f805b514d241c803926 100644 --- a/src/types.rs +++ b/src/types.rs @@ -8,6 +8,7 @@ use rustmex::{ use crate::error::{Error, MapMexError, SResult}; +/// Trait to convert a Matlab type into a Rust type pub trait IntoRustType<T> { fn into_rust(self) -> SResult<T>; } @@ -53,6 +54,7 @@ impl IntoRustType<Vec<f64>> for MxArray { } } +/// Trait to convert a Rust type into a Matlab type pub trait IntoMatlabType { fn into_matlab(self) -> SResult<MxArray>; } @@ -67,6 +69,7 @@ impl IntoMatlabType for Vec<f64> { } } +/// Functions available to the user #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Mode { Open,