diff --git a/Cargo.lock b/Cargo.lock index 56a11438f5606a7d94fd6e971f5983790db6a4b5..4c3c0a79741afd2cf1b41a577a32b46c5033d042 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,18 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ahash" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42cd52102d3df161c77a887b608d7a4897d7cc112886a9537b738a887a03aaff" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -11,6 +23,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + [[package]] name = "autocfg" version = "1.1.0" @@ -41,6 +59,16 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash", + "allocator-api2", +] + [[package]] name = "io-kit-sys" version = "0.4.0" @@ -175,6 +203,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + [[package]] name = "parking_lot" version = "0.12.1" @@ -338,6 +372,7 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" name = "serial-bridge" version = "0.1.1" dependencies = [ + "hashbrown", "lazy_static", "parking_lot", "rustmex", @@ -427,6 +462,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "winapi" version = "0.3.9" @@ -505,3 +546,23 @@ name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] diff --git a/Cargo.toml b/Cargo.toml index f4c7ea97da13efa1d847524a40e57b97d79ad30f..210be2b8d8c303dd1836e08cc95a8f4df394fbf3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ license = "MIT" crate-type = ["cdylib"] [dependencies] +hashbrown = "0.14.3" lazy_static = "1.4.0" parking_lot = "0.12.1" serialport = "4.3.0" diff --git a/README.md b/README.md index 29a42003bcdf7c963fb1a3a21fb9373cd5c787a4..b3b0bb07b54105a8762d9bc4ec0cb7131571268b 100644 --- a/README.md +++ b/README.md @@ -33,22 +33,22 @@ serialbridge can be used in matlab in the following way: - **"Open"**: specifies we want to open the serial port - string_serialPort: is the serial port we want to open (eg: "COM6") - uint_baudrate: is the baudrate of the port (eg: 256000) -- `serialbridge("Write", singleArray_Data)`: +- `serialbridge("Write", string_serialPort, singleArray_Data)`: - **"Write"**: specifies that we want to write to the serial port - singleArray_Data: is the array of singles we want to write on serial (eg: [1 2 3 4.5 5.4]) -- `singleArray_Data = serialbridge("Read", uint_nData)`; +- `singleArray_Data = serialbridge("Read", string_serialPort, uint_nData)`; - **"Read"**: specifies that we want to read from the serial port - uint_nData: How many floats to read from serial (eg: 1) - singleArray_Data: array of floats read from the serial (eg: actuatorData) -- `serialbridge("Close", string_serialPort, uint_baudrate)`: +- `serialbridge("Close", string_serialPort)`: - **"Close"**: specifies we want to close the serial port **Example** in Matlab: -``` -serialbridge("Open", "COM6", 256000); % Opens the serial port -serialbridge("Write", [1 2 3 4]); % Sends the array "[1 2 3 4]" to the serial device -data = serialbridge("Read", 2); % Receives 2 floats and stores them in the variable "data" -serialbridge("Close"); % Closes the serial port +```matlab +serialbridge("Open", "dev/COM6", 256000); % Opens the serial port +serialbridge("Write", "dev/COM6", [1 2 3 4]); % Sends the array "[1 2 3 4]" to the serial device +data = serialbridge("Read", "dev/COM6", 2); % Receives 2 floats and stores them in the variable "data" +serialbridge("Close", "dev/COM6"); % Closes the serial port ``` Procedure in order to use the **MatlabTransceiver** module: https://git.skywarder.eu/scs/hermes/r2a-obsw/-/blob/Serial4Simulations-dev/src/tests/hardware_in_the_loop/README.md diff --git a/src/lib.rs b/src/lib.rs index 218efaaeee664e699a7a728b7140160953f2ce79..dc4ab4f4847213231d1e94d9640cb305b0cf649e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,21 +85,11 @@ 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> { - 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() - .map_mexerr(|e| Error::InvalidPortName(Box::new(e)))?; + let port = get_port_name(1, &args)?; // Matlab defaults to f64 when inserting numbers, to improve UX we take a // f64 and cast to a u32 let arg2: f64 = args @@ -124,19 +114,20 @@ fn open_serial(args: Args<'_>) -> SResult<()> { /// 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)?; + args.assert_params_max_len(1)?; + let port = get_port_name(1, &args)?; - SERIAL.write().unwrap().close()?; + SERIAL.write().unwrap().close(&port)?; 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)?; - + args.assert_params_max_len(2)?; + let port = get_port_name(1, &args)?; // Get the number of doubles to read from the input argument let arg: f32 = args - .get(1, Error::MissingReadAmount)? + .get(2, 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 @@ -149,7 +140,7 @@ fn read_from_serial(mut outputs: Output<'_>, args: Args<'_>) -> SResult<()> { let n_doubles = arg as usize; // Read n_doubles from the serial port - let bytes = SERIAL.read().unwrap().read_n_bytes(n_doubles * 8)?; + let bytes = SERIAL.read().unwrap().read_n_bytes(&port, n_doubles * 8)?; let doubles = bytes .chunks_exact(8) .map(|chunk| f32::from_be_bytes(chunk.try_into().unwrap())) @@ -162,17 +153,30 @@ fn read_from_serial(mut outputs: Output<'_>, args: Args<'_>) -> SResult<()> { /// 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)?; - + args.assert_params_max_len(2)?; + let port = get_port_name(1, &args)?; // Get the doubles to write from the input argument let data: Vec<f32> = args - .get(1, Error::MissingWriteData)? + .get(2, 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)?; + SERIAL.read().unwrap().write_bytes(&port, &data)?; warn_debug!("Wrote {} bytes to serial port", data.len()); Ok(()) } + +/// Get the mode from the input argument (Open, Close, Read, Write) +fn get_mode(arg: MxArray) -> SResult<Mode> { + IntoRustType::<String>::into_rust(arg)?.parse() +} + +/// Get port name from the input argument +fn get_port_name(ix: usize, args: &Args<'_>) -> SResult<String> { + Ok(args + .get(ix, Error::MissingPortName)? + .into_rust() + .map_mexerr(|e| Error::InvalidPortName(Box::new(e)))?) +} diff --git a/src/serial.rs b/src/serial.rs index 38826e7e2edd34639c2bcd26f533a1ed5aa04d16..aa1d6fde6ee9d8f0b7efc833f5705c2a4397a32b 100644 --- a/src/serial.rs +++ b/src/serial.rs @@ -1,30 +1,34 @@ -use std::sync::Mutex; - +use hashbrown::HashMap; +use parking_lot::{Mutex, RwLock}; use serialport::SerialPort; use crate::error::{Error, SResult}; /// Handler of the serial port pub struct SerialManager { - serial: Option<Mutex<Box<dyn SerialPort>>>, + serial: RwLock<HashMap<String, Mutex<Box<dyn SerialPort>>>>, } impl SerialManager { pub fn new() -> Self { - Self { serial: None } + Self { + serial: RwLock::new(HashMap::new()), + } } /// 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)); + let serial = serialport::new(port, baudrate).open()?; + self.serial + .write() + .insert(port.to_owned(), Mutex::new(serial)); Ok(()) } /// Close the serial port if it is open - pub fn close(&mut self) -> SResult<()> { - if self.serial.is_some() { - self.serial.take(); + pub fn close(&mut self, port: &str) -> SResult<()> { + if self.serial.read().contains_key(port) { + self.serial.write().remove(port); Ok(()) } else { Err(Error::SerialNotOpen) @@ -32,26 +36,18 @@ impl SerialManager { } /// Read `n` bytes from the serial port - pub fn read_n_bytes(&self, n: usize) -> SResult<Vec<u8>> { - let mut port = self - .serial - .as_ref() - .ok_or(Error::SerialNotOpen)? - .lock() - .unwrap(); + pub fn read_n_bytes(&self, port: &str, n: usize) -> SResult<Vec<u8>> { + let map = self.serial.read(); + let mut port = map.get(port).ok_or(Error::SerialNotOpen)?.lock(); let mut buf = vec![0; n]; port.read_exact(&mut buf)?; Ok(buf) } /// Write `data` to the serial port - pub fn write_bytes(&self, data: &[u8]) -> SResult<()> { - let mut port = self - .serial - .as_ref() - .ok_or(Error::SerialNotOpen)? - .lock() - .unwrap(); + pub fn write_bytes(&self, port: &str, data: &[u8]) -> SResult<()> { + let map = self.serial.read(); + let mut port = map.get(port).as_ref().ok_or(Error::SerialNotOpen)?.lock(); port.write_all(data)?; Ok(()) }