diff --git a/rust/tests/commands.rs b/rust/tests/commands.rs new file mode 100644 index 00000000..2cc93b3c --- /dev/null +++ b/rust/tests/commands.rs @@ -0,0 +1,68 @@ +use std::{sync::MutexGuard, thread, time}; + +use libosdp::{ + channel::{MemoryChannel, Read, Write}, + ControlPanel, OsdpCommand, OsdpCommandBuzzer, OsdpEvent, OsdpEventCardRead, PeripheralDevice, +}; + +use crate::common::{device::CpDevice, device::PdDevice, threadbus::ThreadBus}; + +mod common; + +type Result = core::result::Result; + +fn send_command(mut cp: MutexGuard<'_, ControlPanel>, command: OsdpCommand) -> Result<()> { + cp.send_command(0, command) +} + +fn notify_event(mut pd: MutexGuard<'_, PeripheralDevice>, event: OsdpEvent) -> Result<()> { + pd.notify_event(event) +} + +#[test] +fn test_thread_bus_channel() -> Result<()> { + let mut a = ThreadBus::new("conn-0"); + let mut b = a.clone(); + let mut c = a.clone(); + + let buf_write = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + let mut buf_read = [0; 100]; + assert_eq!(a.write(&buf_write)?, buf_write.len()); + assert_eq!(b.read(&mut buf_read)?, buf_write.len()); + assert_eq!(c.read(&mut buf_read)?, buf_write.len()); + Ok(()) +} + +#[test] +fn test_commands() -> Result<()> { + common::setup(); + let (cp_bus, pd_bus) = MemoryChannel::new(); + let pd = PdDevice::new(pd_bus)?; + let cp = CpDevice::new(cp_bus)?; + + thread::sleep(time::Duration::from_secs(2)); + loop { + if pd.get_device().is_sc_active() { + break; + } + println!("Waiting for devices to establish a secure channel"); + thread::sleep(time::Duration::from_secs(1)); + } + + let command = OsdpCommand::Buzzer(OsdpCommandBuzzer::default()); + send_command(cp.get_device(), command.clone())?; + println!("Send, waiting for cmd in PD"); + let cmd_rx = pd.receiver.recv().unwrap(); + println!("Got command"); + assert_eq!(cmd_rx, command, "Buzzer command check failed"); + + let event = OsdpEvent::CardRead(OsdpEventCardRead::new_ascii(vec![0x55, 0xAA])); + notify_event(pd.get_device(), event.clone())?; + assert_eq!( + cp.receiver.recv().unwrap(), + (0 as i32, event), + "Cardread event check failed" + ); + + Ok(()) +} \ No newline at end of file diff --git a/rust/tests/common/device.rs b/rust/tests/common/device.rs new file mode 100644 index 00000000..93a79822 --- /dev/null +++ b/rust/tests/common/device.rs @@ -0,0 +1,125 @@ +use std::{ + sync::{mpsc::Receiver, Arc, Mutex, MutexGuard}, + thread, time, +}; + +use libosdp::{ + channel::{Channel, OsdpChannel}, + ControlPanel, OsdpCommand, OsdpEvent, OsdpFlag, PdCapEntity, PdCapability, PdId, PdInfo, + PeripheralDevice, +}; +type Result = core::result::Result; + +pub struct CpDevice { + dev: Arc>, + pub receiver: Receiver<(i32, OsdpEvent)>, +} + +impl CpDevice { + pub fn new(bus: T) -> Result { + let pd_info = vec![PdInfo::for_cp( + "PD 101", + 101, + 115200, + OsdpFlag::empty(), + OsdpChannel::new::(Box::new(bus)), + [ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f, + ], + )]; + let mut cp = ControlPanel::new(pd_info)?; + let (event_tx, event_rx) = std::sync::mpsc::channel::<(i32, OsdpEvent)>(); + + cp.set_event_callback(|pd, event| { + event_tx.send((pd, event)).unwrap(); + 0 + }); + + let dev = Arc::new(Mutex::new(cp)); + let dev_clone = dev.clone(); + let _ = thread::Builder::new() + .name("CP Thread".to_string()) + .spawn(move || { + let dev = dev_clone; + let sender = event_tx; + dev.lock().unwrap().set_event_callback(|pd, event| { + sender.send((pd, event)).expect("CP event send"); + 0 + }); + loop { + dev.lock().unwrap().refresh(); + thread::sleep(time::Duration::from_millis(10)); + } + }); + Ok(Self { + dev, + receiver: event_rx, + }) + } + + pub fn get_device(&self) -> MutexGuard<'_, ControlPanel> { + self.dev.lock().unwrap() + } +} + +pub struct PdDevice { + dev: Arc>, + pub receiver: Receiver, +} + +impl PdDevice { + pub fn new(bus: T) -> Result { + let pd_info = PdInfo::for_pd( + "PD 101", + 101, + 115200, + OsdpFlag::empty(), + PdId::from_number(101), + vec![ + PdCapability::CommunicationSecurity(PdCapEntity::new(1, 1)), + PdCapability::AudibleOutput(PdCapEntity::new(1, 1)), + PdCapability::LedControl(PdCapEntity::new(1, 1)), + ], + OsdpChannel::new::(Box::new(bus)), + [ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f, + ], + ); + let mut pd = PeripheralDevice::new(pd_info)?; + let (cmd_tx, cmd_rx) = std::sync::mpsc::channel::(); + pd.set_command_callback(|command| { + cmd_tx.send(command).unwrap(); + 0 + }); + + let dev = Arc::new(Mutex::new(pd)); + let dev_clone = dev.clone(); + let _ = thread::Builder::new() + .name("PD Thread".to_string()) + .spawn(move || { + let dev = dev_clone; + let sender = cmd_tx; + dev.lock().unwrap().set_command_callback(|command| { + println!("--> Got here! {:?}", command); + sender.send(command).expect("PD command send"); + println!("<-- Got out!"); + 0 + }); + loop { + dev.lock().unwrap().refresh(); + thread::sleep(time::Duration::from_millis(10)); + } + }); + + Ok(Self { + dev, + receiver: cmd_rx, + }) + } + + pub fn get_device(&self) -> MutexGuard<'_, PeripheralDevice> { + self.dev.lock().unwrap() + } +} diff --git a/rust/tests/common/mod.rs b/rust/tests/common/mod.rs new file mode 100644 index 00000000..4ff8c2b8 --- /dev/null +++ b/rust/tests/common/mod.rs @@ -0,0 +1,10 @@ +pub mod device; +pub mod threadbus; + +pub fn setup() { + env_logger::builder() + .filter_level(log::LevelFilter::Debug) + .format_target(false) + .format_timestamp(None) + .init(); +} diff --git a/rust/tests/common/threadbus.rs b/rust/tests/common/threadbus.rs new file mode 100644 index 00000000..40cbf204 --- /dev/null +++ b/rust/tests/common/threadbus.rs @@ -0,0 +1,94 @@ +use multiqueue::{BroadcastReceiver, BroadcastSender}; +use std::{ + collections::hash_map::DefaultHasher, + fmt::Debug, + hash::{Hash, Hasher}, + io::Error, + io::ErrorKind, + sync::Mutex, +}; + +fn str_to_channel_id(key: &str) -> i32 { + let mut hasher = DefaultHasher::new(); + key.hash(&mut hasher); + let mut id: u64 = hasher.finish(); + id = (id >> 32) ^ id & 0xffffffff; + id as i32 +} + +pub struct ThreadBus { + name: String, + id: i32, + send: Mutex>>, + recv: Mutex>>, +} + +impl ThreadBus { + pub fn new(name: &str) -> Self { + let (send, recv) = multiqueue::broadcast_queue(4); + Self { + name: name.into(), + id: str_to_channel_id(name), + send: Mutex::new(send), + recv: Mutex::new(recv), + } + } +} + +impl Clone for ThreadBus { + fn clone(&self) -> Self { + let send = Mutex::new(self.send.lock().unwrap().clone()); + let recv = Mutex::new(self.recv.lock().unwrap().add_stream()); + Self { + name: self.name.clone(), + id: self.id, + send, + recv, + } + } +} + +impl Debug for ThreadBus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ThreadBus") + .field("name", &self.name) + .field("id", &self.id) + .finish() + } +} + +impl std::io::Read for ThreadBus { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + let v = self.recv.lock().unwrap().try_recv().map_err(|e| match e { + std::sync::mpsc::TryRecvError::Empty => Error::new(ErrorKind::WouldBlock, "No data"), + std::sync::mpsc::TryRecvError::Disconnected => { + Error::new(ErrorKind::ConnectionReset, "disconnected") + } + })?; + buf[..v.len()].copy_from_slice(&v[..]); + Ok(v.len()) + } +} + +impl std::io::Write for ThreadBus { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + let v = buf.into(); + self.send.lock().unwrap().try_send(v).map_err(|e| match e { + std::sync::mpsc::TrySendError::Full(_) => Error::new(ErrorKind::WouldBlock, "No space"), + std::sync::mpsc::TrySendError::Disconnected(_) => { + Error::new(ErrorKind::ConnectionReset, "disconnected") + } + })?; + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +impl libosdp::channel::Channel for ThreadBus { + fn get_id(&self) -> i32 { + self.id + } +}