diff --git a/src/error.rs b/src/error.rs index 2b27c3470d164c36be17eba3fa39c402eab8958b..bb3b7f9418a2a2ff0470291b53caa8805a361af6 100644 --- a/src/error.rs +++ b/src/error.rs @@ -54,6 +54,10 @@ pub enum Error { MissingPortName, #[error("Missing baudrate (third argument)")] MissingBaudrate, + #[error("Missing read amount (second argument)")] + MissingReadAmount, + #[error("Missing write data (second argument)")] + MissingWriteData, #[error("String contains invalid characters")] String(#[from] std::ffi::IntoStringError), #[error("Invalid Matlab type used, {0}")] @@ -64,14 +68,24 @@ pub enum Error { InvalidPortName(Box<Self>), #[error("Invalid baud rate (3rd argument): {0}")] InvalidBaudrate(Box<Self>), + #[error("Invalid read amount (2nd argument): {0}")] + InvalidReadAmount(Box<Self>), + #[error("Invalid write data (2nd argument): {0}")] + InvalidWriteData(Box<Self>), #[error("Serial port error: {0}")] SerialPort(#[from] serialport::Error), + #[error("I/O error: {0}")] + IO(#[from] std::io::Error), #[error("Parse error")] Parse, #[error("Matlab error: {0}")] Matlab(#[from] rustmex::FromMatlabError<MxArray>), #[error("{0}")] Rustmex(#[from] rustmex::Error), + #[error("Return type cannot be assigned")] + ReturnType, + #[error("Serial port is not open")] + SerialNotOpen, } impl Error { @@ -81,15 +95,22 @@ impl Error { Error::MissingSerialMode => "serialbridge:missing_input", Error::MissingPortName => "serialbridge:missing_input", Error::MissingBaudrate => "serialbridge:missing_input", + Error::MissingReadAmount => "serialbridge:missing_input", + Error::MissingWriteData => "serialbridge:missing_input", Error::String(_) => "serialbridge:invalid_input", Error::InvalidMatlabType(_) => "serialbridge:invalid_input", Error::InvalidMode => "serialbridge:invalid_input", Error::InvalidPortName(_) => "serialbridge:invalid_input", Error::InvalidBaudrate(_) => "serialbridge:invalid_input", + Error::InvalidReadAmount(_) => "serialbridge:invalid_input", + Error::InvalidWriteData(_) => "serialbridge:invalid_input", Error::SerialPort(_) => "serialbridge:serial_error", + Error::IO(_) => "serialbridge:serial_error", Error::Parse => "serialbridge:parse_error", Error::Matlab(_) => "serialbridge:matlab_error", Error::Rustmex(err) => err.id(), + Error::ReturnType => "serialbridge:invalid_output", + Error::SerialNotOpen => "serialbridge:serial_error", } } diff --git a/src/lib.rs b/src/lib.rs index fe8b0bf1a9b24c5e0a60ef2a0f4776610a0cc28f..02185c575ee7a6acdb88a90d6706c173bf6665a4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,24 +2,25 @@ mod error; mod serial; mod types; -use std::sync::Mutex; +use std::sync::RwLock; use rustmex::{char::CharArray, prelude::*, warning, MatlabClass}; use error::{Error, MapMexError, SResult}; -use types::{MatlabType, Mode}; +use types::{IntoMatlabType, IntoRustType, Mode}; use crate::serial::SerialManager; lazy_static::lazy_static! { - static ref SERIAL: Mutex<SerialManager> = Mutex::new(SerialManager::new()); + static ref SERIAL: RwLock<SerialManager> = RwLock::new(SerialManager::new()); } /// this compiles only in debug mode #[cfg(debug_assertions)] +#[macro_export] macro_rules! warn_debug { ($msg:literal, $($arg:expr),*) => { - warning("serialbridge:debug", format!($msg, $($arg),*)); + rustmex::warning("serialbridge:debug", format!($msg, $($arg),*)); }; } @@ -31,11 +32,7 @@ macro_rules! warn_debug { struct Args<'a>(Rhs<'a, 'a>); struct Output<'a>(Lhs<'a>); -impl<'a> Args<'a> { - fn new(rhs: Rhs<'a, 'a>) -> Self { - Self(rhs) - } - +impl Args<'_> { fn assert_params_max_len(&self, len: usize) -> SResult<()> { if self.0.len() - 1 > len { Err(Error::TooManyParameters) @@ -49,9 +46,20 @@ impl<'a> Args<'a> { } } +impl Output<'_> { + fn set<T: IntoMatlabType>(&mut self, val: T) -> SResult<()> { + let Some(ret) = self.0.get_mut(0) else { + return Err(Error::ReturnType); + }; + + ret.replace(val.into_matlab()?); + Ok(()) + } +} + #[rustmex::entrypoint] fn serialbridge(lhs: Lhs, rhs: Rhs) -> rustmex::Result<()> { - let args = Args::new(rhs); + let args = Args(rhs); let out = Output(lhs); // Get the mode argument ("Open", "Close", "Read", "Write") @@ -82,13 +90,13 @@ fn open_serial(args: Args<'_>) -> SResult<()> { let port: String = args .get(1, Error::MissingPortName)? - .convert() + .into_rust() .map_mexerr(|e| Error::InvalidPortName(Box::new(e)))?; // Matlab defaults to f64 when inserting numbers, to improve UX we take a // f64 and cast to a u32 let arg2: f64 = args .get(2, Error::MissingBaudrate)? - .convert() + .into_rust() .map_mexerr(|e| Error::InvalidBaudrate(Box::new(e)))?; // Check for arg2 to resemble a baud rate (this type mismatch should be // fixed later on) @@ -101,7 +109,7 @@ fn open_serial(args: Args<'_>) -> SResult<()> { warn_debug!("Open serial port {} with baudrate {}", port, baudrate); - SERIAL.lock().unwrap().open(&port, baudrate)?; + SERIAL.write().unwrap().open(&port, baudrate)?; Ok(()) } @@ -109,22 +117,45 @@ fn open_serial(args: Args<'_>) -> SResult<()> { fn close_serial(args: Args<'_>) -> SResult<()> { args.assert_params_max_len(0)?; - SERIAL.lock().unwrap().close()?; + SERIAL.write().unwrap().close()?; Ok(()) } -fn read_from_serial(outputs: Output<'_>, args: Args<'_>) -> SResult<()> { +fn read_from_serial(mut outputs: Output<'_>, args: Args<'_>) -> SResult<()> { args.assert_params_max_len(1)?; - todo!(); + let arg: f64 = args + .get(1, Error::MissingReadAmount)? + .into_rust() + .map_mexerr(|e| Error::InvalidReadAmount(Box::new(e)))?; + // Check for arg to resemble a unsigned integer (this type mismatch should be + // fixed later on) + if arg != arg.floor() || arg < 0.0 { + return Err(Error::InvalidReadAmount(Box::new( + Error::InvalidMatlabType("do not use decimal units, use a positive integer".into()), + ))); + } + let n_doubles = arg as usize; + + // Read n_doubles from the serial port + let bytes = SERIAL.read().unwrap().read_n_bytes(n_doubles)?; + 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); + outputs.set(doubles.to_vec())?; Ok(()) } fn write_to_serial(args: Args<'_>) -> SResult<()> { args.assert_params_max_len(1)?; - todo!(); + let data: Vec<f64> = args + .get(1, Error::MissingWriteData)? + .into_rust() + .map_mexerr(|e| Error::InvalidWriteData(Box::new(e)))?; + 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()); Ok(()) } diff --git a/src/serial.rs b/src/serial.rs index 454313b3eb6f7d08f5f7cfec979bbc08c651aff9..3365897a2c26e118f75d8960ee1c8afbe9d64e78 100644 --- a/src/serial.rs +++ b/src/serial.rs @@ -1,11 +1,14 @@ -use std::sync::RwLock; +use std::sync::Mutex; use serialport::SerialPort; -use crate::error::SResult; +use crate::{ + error::{Error, SResult}, + warn_debug, +}; pub struct SerialManager { - serial: Option<RwLock<Box<dyn SerialPort>>>, + serial: Option<Mutex<Box<dyn SerialPort>>>, } impl SerialManager { @@ -15,7 +18,7 @@ impl SerialManager { pub fn open(&mut self, port: &str, baudrate: u32) -> SResult<()> { let port = serialport::new(port, baudrate).open()?; - self.serial.replace(RwLock::new(port)); + self.serial.replace(Mutex::new(port)); Ok(()) } @@ -23,4 +26,27 @@ impl SerialManager { self.serial.take(); Ok(()) } + + pub fn read_n_bytes(&self, n: usize) -> SResult<Vec<u8>> { + let mut port = self + .serial + .as_ref() + .ok_or(Error::SerialNotOpen)? + .lock() + .unwrap(); + let mut buf = vec![0; n]; + port.read_exact(&mut buf)?; + Ok(buf) + } + + pub fn write_bytes(&self, data: &[u8]) -> SResult<()> { + let mut port = self + .serial + .as_ref() + .ok_or(Error::SerialNotOpen)? + .lock() + .unwrap(); + port.write_all(data)?; + Ok(()) + } } diff --git a/src/types.rs b/src/types.rs index 4862e6c8b243ef10ffd1977418b258e3a13748d1..7060416c2e8c7ff1b0a70ee1470818ee0a8c6543 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,4 +1,4 @@ -use std::{ffi::CString, str::FromStr}; +use std::{ffi::CString, ops::Deref, str::FromStr}; use rustmex::{ char::CharArray, @@ -8,27 +8,27 @@ use rustmex::{ use crate::error::{Error, MapMexError, SResult}; -pub trait MatlabType<T> { - fn convert(self) -> SResult<T>; +pub trait IntoRustType<T> { + fn into_rust(self) -> SResult<T>; } -impl MatlabType<CString> for MxArray { - fn convert(self) -> SResult<CString> { +impl IntoRustType<CString> for MxArray { + fn into_rust(self) -> SResult<CString> { Ok(CharArray::from_mx_array(self) .mexerr(Error::InvalidMatlabType("use a string instead".into()))? .get_cstring()) } } -impl MatlabType<String> for MxArray { - fn convert(self) -> SResult<String> { - let c: CString = self.convert()?; +impl IntoRustType<String> for MxArray { + fn into_rust(self) -> SResult<String> { + let c: CString = self.into_rust()?; Ok(c.into_string()?) } } -impl MatlabType<f64> for MxArray { - fn convert(self) -> SResult<f64> { +impl IntoRustType<f64> for MxArray { + fn into_rust(self) -> SResult<f64> { let out = Numeric::<f64, _>::from_mx_array(self) .mexerr(Error::InvalidMatlabType( "use a numerical type instead".into(), @@ -41,6 +41,32 @@ impl MatlabType<f64> for MxArray { } } +impl IntoRustType<Vec<f64>> for MxArray { + fn into_rust(self) -> SResult<Vec<f64>> { + let out = Numeric::<f64, _>::from_mx_array(self) + .mexerr(Error::InvalidMatlabType( + "use a numerical type instead".into(), + ))? + .data() + .to_owned(); + Ok(out) + } +} + +pub trait IntoMatlabType { + fn into_matlab(self) -> SResult<MxArray>; +} + +impl IntoMatlabType for Vec<f64> { + fn into_matlab(self) -> SResult<MxArray> { + let len = self.len(); + Ok(Numeric::<f64, _>::new(self.into_boxed_slice(), &[len]) + .unwrap() + .deref() + .to_owned()) + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Mode { Open,