use crate::errors::Error;
use crate::hardware::Hardware;
use crate::io::{IoData, IoTransport, RemoteIo, IO};
use crate::io::{IoProtocol, PinModeId};
use crate::utils::{task, Range};
use crate::utils::{EventHandler, EventManager};
use parking_lot::RwLock;
use std::fmt::Display;
use std::sync::Arc;
pub enum BoardEvent {
OnReady,
OnClose,
}
impl From<BoardEvent> for String {
fn from(value: BoardEvent) -> Self {
let event = match value {
BoardEvent::OnReady => "ready",
BoardEvent::OnClose => "close",
};
event.into()
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone)]
pub struct Board {
#[cfg_attr(feature = "serde", serde(skip))]
events: EventManager,
protocol: Box<dyn IoProtocol>,
}
impl Default for Board {
fn default() -> Self {
Self::new(RemoteIo::default())
}
}
impl<T: IoTransport> From<T> for Board {
fn from(transport: T) -> Self {
Self {
events: Default::default(),
protocol: Box::new(RemoteIo::from(transport)),
}
}
}
impl Board {
pub fn run() -> Self {
Self::default().open()
}
pub fn new<P: IoProtocol + 'static>(protocol: P) -> Self {
Self {
events: EventManager::default(),
protocol: Box::new(protocol),
}
}
pub fn open(self) -> Self {
let events_clone = self.events.clone();
let callback_board = self.clone();
task::run(async move {
let board = callback_board.blocking_open()?;
events_clone.emit(BoardEvent::OnReady, board);
Ok(())
})
.expect("Task failed");
self
}
pub fn close(self) -> Self {
let events = self.events.clone();
let callback_board = self.clone();
task::run(async move {
let board = callback_board.blocking_close()?;
events.emit(BoardEvent::OnClose, board);
Ok(())
})
.expect("Task failed");
self
}
pub fn blocking_open(mut self) -> Result<Self, Error> {
self.protocol.open()?;
Ok(self)
}
pub fn blocking_close(mut self) -> Result<Self, Error> {
let pins: Vec<u8> = self.get_io().read().pins.keys().copied().collect();
for id in pins {
let _ = self.set_pin_mode(id, PinModeId::OUTPUT);
}
self.protocol.close()?;
Ok(self)
}
pub fn on<S, F, T, Fut>(&self, event: S, callback: F) -> EventHandler
where
S: Into<String>,
T: 'static + Send + Sync + Clone,
F: FnMut(T) -> Fut + Send + 'static,
Fut: std::future::Future<Output = Result<(), Error>> + Send + 'static,
{
self.events.on(event, callback)
}
}
impl Hardware for Board {
fn get_protocol(&self) -> Box<dyn IoProtocol> {
self.protocol.clone()
}
#[cfg(not(tarpaulin_include))]
fn set_protocol(&mut self, protocol: Box<dyn IoProtocol>) {
self.protocol = protocol;
}
}
#[cfg(not(tarpaulin_include))]
impl IO for Board {
fn get_io(&self) -> &Arc<RwLock<IoData>> {
self.protocol.get_io()
}
fn is_connected(&self) -> bool {
self.protocol.is_connected()
}
fn set_pin_mode(&mut self, pin: u8, mode: PinModeId) -> Result<(), Error> {
self.protocol.set_pin_mode(pin, mode)
}
fn digital_write(&mut self, pin: u8, level: bool) -> Result<(), Error> {
self.protocol.digital_write(pin, level)
}
fn analog_write(&mut self, pin: u8, level: u16) -> Result<(), Error> {
self.protocol.analog_write(pin, level)
}
#[cfg(not(tarpaulin_include))]
fn digital_read(&mut self, _: u8) -> Result<bool, Error> {
unimplemented!()
}
#[cfg(not(tarpaulin_include))]
fn analog_read(&mut self, _: u8) -> Result<u16, Error> {
unimplemented!()
}
fn servo_config(&mut self, pin: u8, pwm_range: Range<u16>) -> Result<(), Error> {
self.protocol.servo_config(pin, pwm_range)
}
fn i2c_config(&mut self, delay: u16) -> Result<(), Error> {
self.protocol.i2c_config(delay)
}
fn i2c_read(&mut self, address: u8, size: u16) -> Result<(), Error> {
self.protocol.i2c_read(address, size)
}
fn i2c_write(&mut self, address: u8, data: &[u16]) -> Result<(), Error> {
self.protocol.i2c_write(address, data)
}
}
impl Display for Board {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Board ({})", self.protocol)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::io::Serial;
use crate::io::IO;
use crate::mocks::plugin_io::MockIoProtocol;
use crate::mocks::transport_layer::MockTransportLayer;
use crate::pause;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
#[test]
fn test_board_default() {
let board = Board::default();
assert_eq!(
board.get_protocol_name(),
"RemoteIo",
"Default board uses the default protocol"
);
}
#[test]
fn test_board_from() {
let board = Board::new(MockIoProtocol::default());
assert_eq!(
board.get_protocol_name(),
"MockIoProtocol",
"Board can be created with a custom protocol"
);
let board = Board::from(Serial::default());
assert_eq!(
board.get_protocol_name(),
"RemoteIo",
"Board can be created with a custom transport"
);
}
#[hermes_five_macros::test]
async fn test_board_open() {
let mut transport = MockTransportLayer {
read_index: 10,
..Default::default()
};
transport.read_buf[10..15].copy_from_slice(&[0xF0, 0x79, 0x01, 0x0C, 0xF7]);
transport.read_buf[15..26].copy_from_slice(&[
0xF0, 0x6C, 0x00, 0x08, 0x7F, 0x00, 0x08, 0x01, 0x08, 0x7F, 0xF7,
]);
transport.read_buf[26..32].copy_from_slice(&[0xF0, 0x6A, 0x7F, 0x7F, 0x7F, 0xF7]);
let flag = Arc::new(AtomicBool::new(false));
let moved_flag = flag.clone();
let board = Board::new(RemoteIo::from(transport)).open();
board.on(BoardEvent::OnReady, move |board: Board| {
let captured_flag = moved_flag.clone();
async move {
captured_flag.store(true, Ordering::SeqCst);
assert!(board.is_connected());
Ok(())
}
});
pause!(500);
assert!(flag.load(Ordering::SeqCst));
}
#[test]
fn test_board_blocking_open() {
let mut transport = MockTransportLayer {
read_index: 10,
..Default::default()
};
transport.read_buf[10..15].copy_from_slice(&[0xF0, 0x79, 0x01, 0x0C, 0xF7]);
transport.read_buf[15..26].copy_from_slice(&[
0xF0, 0x6C, 0x00, 0x08, 0x7F, 0x00, 0x08, 0x01, 0x08, 0x7F, 0xF7,
]);
transport.read_buf[26..32].copy_from_slice(&[0xF0, 0x6A, 0x7F, 0x7F, 0x7F, 0xF7]);
let protocol = RemoteIo::from(transport);
let board = Board::new(protocol).blocking_open().unwrap();
assert!(board.is_connected());
}
#[hermes_five_macros::test]
async fn test_board_close() {
let flag = Arc::new(AtomicBool::new(false));
let moved_flag = flag.clone();
let board = Board::new(MockIoProtocol::default()).open().close();
board.on(BoardEvent::OnClose, move |board: Board| {
let captured_flag = moved_flag.clone();
async move {
captured_flag.store(true, Ordering::SeqCst);
assert!(!board.is_connected());
Ok(())
}
});
pause!(1000);
assert!(flag.load(Ordering::SeqCst));
assert!(!board.is_connected());
}
#[hermes_five_macros::test]
fn test_board_run() {
let board = Board::run();
assert_eq!(board.get_protocol_name(), "RemoteIo");
board.close();
}
#[test]
fn test_board_get_hardware() {
let board = Board::new(MockIoProtocol::default());
assert_eq!(board.get_io().read().protocol_version, "fake.1.0");
}
#[test]
fn test_board_display() {
let board = Board::new(MockIoProtocol::default());
let output = format!("{}", board);
assert_eq!(
output,
"Board (MockIoProtocol [firmware=Fake protocol, version=fake.2.3, protocol=fake.1.0])"
);
}
}
#[cfg(feature = "serde")]
#[cfg(test)]
mod serde_tests {
use crate::hardware::{Board, Hardware};
use crate::io::RemoteIo;
use crate::mocks::plugin_io::MockIoProtocol;
#[test]
fn test_board_serialize() {
let board = Board::new(RemoteIo::new("mock"));
let json = serde_json::to_string(&board).unwrap();
assert_eq!(
json,
r#"{"protocol":{"type":"RemoteIo","transport":{"type":"Serial","port":"mock"}}}"#
);
let board = Board::new(MockIoProtocol::default());
let json = serde_json::to_string(&board).unwrap();
assert_eq!(json, r#"{"protocol":{"type":"MockIoProtocol"}}"#);
}
#[test]
fn test_board_deserialize() {
let json =
r#"{"protocol":{"type":"RemoteIo","transport":{"type":"Serial","port":"mock"}}}"#;
let board: Board = serde_json::from_str(json).unwrap();
assert_eq!(board.get_protocol_name(), "RemoteIo");
let json = r#"{"protocol":{"type":"MockIoProtocol"}}"#;
let board: Board = serde_json::from_str(json).unwrap();
assert_eq!(board.get_protocol_name(), "MockIoProtocol");
}
}