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::HardwareError::IncompatiblePin;
use crate::errors::{Error, 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 PwmOutput {
pin: u8,
#[cfg_attr(feature = "serde", serde(with = "crate::devices::arc_rwlock_serde"))]
state: Arc<RwLock<u16>>,
default: u16,
#[cfg_attr(feature = "serde", serde(skip))]
max_value: u16,
#[cfg_attr(feature = "serde", serde(skip))]
protocol: Box<dyn IoProtocol>,
#[cfg_attr(feature = "serde", serde(skip))]
animation: Arc<Option<Animation>>,
}
impl PwmOutput {
pub fn new<T: Into<PinIdOrName>>(
board: &dyn Hardware,
pin: T,
default: u16,
) -> 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,
max_value: 0,
protocol: board.get_protocol(),
animation: Arc::new(None),
};
output.protocol.set_pin_mode(output.pin, PinModeId::PWM)?;
output.max_value = board
.get_io()
.read()
.get_pin(pin.id)?
.get_max_possible_value();
output.reset()?;
Ok(output)
}
pub fn set_value(&mut self, value: u16) -> Result<&Self, Error> {
self.set_state(value.into())?;
Ok(self)
}
pub fn set_percentage(&mut self, percentage: u8) -> Result<&Self, Error> {
let percentage = percentage.min(100) as u16;
let value = (percentage * self.max_value) / 100;
self.set_state(value.into())?;
Ok(self)
}
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 get_value(&self) -> u16 {
*self.state.read()
}
pub fn get_percentage(&self) -> u8 {
let value = *self.state.read();
((value as f32 * 100.0) / self.max_value as f32).round() as u8
}
}
impl Display for PwmOutput {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"PwmOutput (pin={}) [state={} ({}%), default={}]",
self.pin,
self.state.read(),
self.get_percentage(),
self.default,
)
}
}
#[cfg_attr(feature = "serde", typetag::serde)]
impl Device for PwmOutput {}
#[cfg_attr(feature = "serde", typetag::serde)]
impl Output for PwmOutput {
fn get_state(&self) -> State {
(*self.state.read()).into()
}
fn set_state(&mut self, state: State) -> Result<State, Error> {
let value = match state {
State::Integer(value) => Ok(value as u16),
State::Signed(value) => match value >= 0 {
true => Ok(value as u16),
false => Err(StateError),
},
State::Float(value) => match value >= 0.0 {
true => Ok(value as u16),
false => Err(StateError),
},
_ => Err(StateError),
}?;
match self.get_pin_info()?.mode.id {
PinModeId::PWM => self.protocol.analog_write(self.pin, value),
id => Err(Error::from(IncompatiblePin {
mode: id,
pin: self.pin,
context: "update pwm 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::pwm::PwmOutput;
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 = PwmOutput::new(&board, 8, 0).unwrap();
assert_eq!(output.get_pin(), 8);
assert_eq!(*output.state.read(), 0);
assert_eq!(output.get_state().as_integer(), 0);
assert_eq!(output.get_default().as_integer(), 0);
let output = PwmOutput::new(&board, 8, 50).unwrap();
assert_eq!(output.get_pin(), 8);
assert_eq!(*output.state.read(), 50);
assert_eq!(output.get_state().as_integer(), 50);
assert_eq!(output.get_default().as_integer(), 50);
let output = PwmOutput::new(&board, "D11", 50).unwrap();
assert_eq!(output.get_pin(), 11);
}
#[test]
fn test_set_value() {
let mut output = PwmOutput::new(&Board::new(MockIoProtocol::default()), 8, 0).unwrap();
output.set_value(127).unwrap();
assert_eq!(*output.state.read(), 127);
assert_eq!(output.get_value(), 127);
}
#[test]
fn test_set_percent() {
let mut output = PwmOutput::new(&Board::new(MockIoProtocol::default()), 8, 0).unwrap();
output.set_percentage(50).unwrap();
assert_eq!(*output.state.read(), 127);
assert_eq!(output.get_value(), 127);
assert_eq!(output.get_percentage(), 50);
output.set_percentage(200).unwrap();
assert_eq!(*output.state.read(), 0xFF);
assert_eq!(output.get_value(), 255);
assert_eq!(output.get_percentage(), 100);
}
#[test]
fn test_set_state() {
let mut output = PwmOutput::new(&Board::new(MockIoProtocol::default()), 11, 127).unwrap();
assert!(output.set_state(State::Integer(0)).is_ok());
assert_eq!(*output.state.read(), 0);
assert!(output.set_state(State::Integer(127)).is_ok());
assert_eq!(*output.state.read(), 127);
assert!(output.set_state(State::Signed(0)).is_ok());
assert_eq!(*output.state.read(), 0);
assert!(output.set_state(State::Signed(127)).is_ok());
assert_eq!(*output.state.read(), 127);
assert!(output.set_state(State::Signed(-42)).is_err());
assert!(output.set_state(State::Float(0.0)).is_ok());
assert_eq!(*output.state.read(), 0);
assert!(output.set_state(State::Float(127.0)).is_ok());
assert_eq!(*output.state.read(), 127);
assert!(output.set_state(State::Float(-42.0)).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::Integer(1)).is_err()); }
#[test]
fn test_get_pin_info() {
let output = PwmOutput::new(&Board::new(MockIoProtocol::default()), 11, 20).unwrap();
let pin_info = output.get_pin_info();
assert!(pin_info.is_ok());
assert_eq!(pin_info.unwrap().id, 11);
}
#[hermes_five_macros::test]
fn test_animation() {
let mut output = PwmOutput::new(&Board::new(MockIoProtocol::default()), 11, 20).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 = PwmOutput::new(&Board::new(MockIoProtocol::default()), 11, 212).unwrap();
let _ = output.set_value(127);
let display_str = format!("{}", output);
assert_eq!(
display_str,
"PwmOutput (pin=11) [state=127 (50%), default=212]"
);
}
}