use std::fmt::{Display, Formatter};
use std::sync::Arc;
use parking_lot::RwLock;
use crate::animations::{Animation, Easing, Keyframe, Track};
use crate::devices::{Device, Output};
use crate::errors::{Error, HardwareError, StateError};
use crate::hardware::Hardware;
use crate::io::{IoProtocol, Pin, PinIdOrName, PinModeId};
use crate::utils::State;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug)]
pub struct DigitalOutput {
pin: u8,
#[cfg_attr(feature = "serde", serde(with = "crate::devices::arc_rwlock_serde"))]
state: Arc<RwLock<bool>>,
default: bool,
#[cfg_attr(feature = "serde", serde(skip))]
protocol: Box<dyn IoProtocol>,
#[cfg_attr(feature = "serde", serde(skip))]
animation: Arc<Option<Animation>>,
}
impl DigitalOutput {
pub fn new<T: Into<PinIdOrName>>(
board: &dyn Hardware,
pin: T,
default: bool,
) -> Result<Self, Error> {
let pin = board.get_io().read().get_pin(pin)?.clone();
let mut output = Self {
pin: pin.id,
state: Arc::new(RwLock::new(default)),
default,
protocol: board.get_protocol(),
animation: Arc::new(None),
};
output
.protocol
.set_pin_mode(output.pin, PinModeId::OUTPUT)?;
output.reset()?;
Ok(output)
}
pub fn turn_on(&mut self) -> Result<&Self, Error> {
self.set_state(State::Boolean(true))?;
Ok(self)
}
pub fn turn_off(&mut self) -> Result<&Self, Error> {
self.set_state(State::Boolean(false))?;
Ok(self)
}
pub fn toggle(&mut self) -> Result<&Self, Error> {
match self.is_high() {
true => self.turn_off(),
false => self.turn_on(),
}
}
pub fn get_pin(&self) -> u8 {
self.pin
}
pub fn get_pin_info(&self) -> Result<Pin, Error> {
let lock = self.protocol.get_io().read();
Ok(lock.get_pin(self.pin)?.clone())
}
pub fn is_high(&self) -> bool {
*self.state.read()
}
pub fn is_low(&self) -> bool {
!*self.state.read()
}
}
impl Display for DigitalOutput {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"DigitalOutput (pin={}) [state={}, default={}]",
self.pin,
self.state.read(),
self.default,
)
}
}
#[cfg_attr(feature = "serde", typetag::serde)]
impl Device for DigitalOutput {}
#[cfg_attr(feature = "serde", typetag::serde)]
impl Output for DigitalOutput {
fn get_state(&self) -> State {
(*self.state.read()).into()
}
fn set_state(&mut self, state: State) -> Result<State, Error> {
let value = match state {
State::Boolean(value) => Ok(value),
State::Integer(value) => match value {
0 => Ok(false),
1 => Ok(true),
_ => Err(StateError),
},
_ => Err(StateError),
}?;
match self.get_pin_info()?.mode.id {
PinModeId::OUTPUT => self.protocol.digital_write(self.pin, value),
id => Err(Error::from(HardwareError::IncompatiblePin {
mode: id,
pin: self.pin,
context: "update digital output",
})),
}?;
*self.state.write() = value;
Ok(value.into())
}
fn get_default(&self) -> State {
self.default.into()
}
fn animate<S: Into<State>>(&mut self, state: S, duration: u64, transition: Easing) {
let mut animation = Animation::from(
Track::new(self.clone())
.with_keyframe(Keyframe::new(state, 0, duration).set_transition(transition)),
);
animation.play();
self.animation = Arc::new(Some(animation));
}
fn is_busy(&self) -> bool {
self.animation.is_some()
}
fn stop(&mut self) {
if let Some(animation) = Arc::get_mut(&mut self.animation).and_then(Option::as_mut) {
animation.stop();
}
self.animation = Arc::new(None);
}
}
#[cfg(test)]
mod tests {
use crate::animations::Easing;
use crate::devices::output::digital::DigitalOutput;
use crate::devices::Output;
use crate::hardware::Board;
use crate::io::PinModeId;
use crate::mocks::plugin_io::MockIoProtocol;
use crate::pause;
use crate::utils::State;
#[test]
fn test_creation() {
let board = Board::new(MockIoProtocol::default());
let output = DigitalOutput::new(&board, 13, false).unwrap();
assert_eq!(output.get_pin(), 13);
assert!(!*output.state.read());
assert!(!output.get_state().as_bool());
assert!(!output.get_default().as_bool());
assert!(output.is_low());
assert!(!output.is_high());
let output = DigitalOutput::new(&board, 4, true).unwrap();
assert_eq!(output.get_pin(), 4);
assert!(*output.state.read());
assert!(output.get_state().as_bool());
assert!(output.get_default().as_bool());
assert!(output.is_high());
assert!(!output.is_low());
let output = DigitalOutput::new(&board, "D13", true).unwrap();
assert_eq!(output.get_pin(), 13);
let output = DigitalOutput::new(&board, "A14", false).unwrap();
assert_eq!(output.get_pin(), 14);
}
#[test]
fn test_set_high() {
let mut output =
DigitalOutput::new(&Board::new(MockIoProtocol::default()), 4, false).unwrap();
output.turn_on().unwrap();
assert!(output.turn_on().is_ok());
assert!(*output.state.read());
}
#[test]
fn test_set_low() {
let mut output =
DigitalOutput::new(&Board::new(MockIoProtocol::default()), 5, true).unwrap();
assert!(output.turn_off().is_ok());
assert!(!*output.state.read());
}
#[test]
fn test_toggle() {
let mut output =
DigitalOutput::new(&Board::new(MockIoProtocol::default()), 5, false).unwrap();
assert!(output.toggle().is_ok()); assert!(*output.state.read());
assert!(output.toggle().is_ok()); assert!(!*output.state.read());
}
#[test]
fn test_set_state() {
let mut output =
DigitalOutput::new(&Board::new(MockIoProtocol::default()), 13, false).unwrap();
assert!(output.set_state(State::Boolean(true)).is_ok());
assert!(*output.state.read());
assert!(output.set_state(State::Boolean(false)).is_ok());
assert!(!*output.state.read());
assert!(output.set_state(State::Integer(1)).is_ok());
assert!(*output.state.read());
assert!(output.set_state(State::Integer(0)).is_ok());
assert!(!*output.state.read());
assert!(output.set_state(State::Integer(42)).is_err());
assert!(output
.set_state(State::String(String::from("incorrect format")))
.is_err()); let _ = output
.protocol
.set_pin_mode(output.pin, PinModeId::UNSUPPORTED);
assert!(output.set_state(State::Boolean(false)).is_err()); }
#[test]
fn test_get_pin_info() {
let output = DigitalOutput::new(&Board::new(MockIoProtocol::default()), 13, false).unwrap();
let pin_info = output.get_pin_info();
assert!(pin_info.is_ok());
assert_eq!(pin_info.unwrap().id, 13);
}
#[hermes_five_macros::test]
fn test_animation() {
let mut output =
DigitalOutput::new(&Board::new(MockIoProtocol::default()), 13, false).unwrap();
assert!(!output.is_busy());
output.stop();
output.animate(true, 500, Easing::Linear);
pause!(100);
assert!(output.is_busy()); output.stop();
}
#[test]
fn test_display_impl() {
let mut output =
DigitalOutput::new(&Board::new(MockIoProtocol::default()), 13, true).unwrap();
let _ = output.turn_off();
let display_str = format!("{}", output);
assert_eq!(
display_str,
"DigitalOutput (pin=13) [state=false, default=true]"
);
}
}