use std::fmt::{Display, Formatter};
use std::sync::Arc;
use std::time::SystemTime;
use parking_lot::RwLock;
use crate::animations::{Animation, Easing, Keyframe, Segment, 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, PinModeId};
use crate::utils::{task, Range, Scalable, State};
use crate::{pause, pause_sync};
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
pub enum ServoType {
#[default]
Standard,
Continuous,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug)]
pub struct Servo {
pin: u8,
#[cfg_attr(feature = "serde", serde(with = "crate::devices::arc_rwlock_serde"))]
state: Arc<RwLock<u16>>,
default: u16,
servo_type: ServoType,
range: Range<u16>,
pwm_range: Range<u16>,
degree_range: Range<u16>,
#[cfg_attr(
feature = "serde",
serde(skip_serializing_if = "crate::utils::is_default")
)]
#[cfg_attr(feature = "serde", serde(default))]
inverted: bool,
#[cfg_attr(
feature = "serde",
serde(skip_serializing_if = "crate::utils::is_default")
)]
#[cfg_attr(feature = "serde", serde(default))]
auto_detach: bool,
#[cfg_attr(
feature = "serde",
serde(skip_serializing_if = "crate::utils::is_default")
)]
#[cfg_attr(feature = "serde", serde(default))]
detach_delay: usize,
#[cfg_attr(feature = "serde", serde(skip))]
previous: u16,
#[cfg_attr(feature = "serde", serde(skip))]
protocol: Box<dyn IoProtocol>,
#[cfg_attr(feature = "serde", serde(skip))]
animation: Arc<Option<Animation>>,
#[cfg_attr(feature = "serde", serde(skip))]
last_move: Arc<RwLock<Option<SystemTime>>>,
}
impl Servo {
pub fn new(board: &dyn Hardware, pin: u8, default: u16) -> Result<Self, Error> {
Self::create(board, pin, default, false)
}
pub fn new_inverted(board: &dyn Hardware, pin: u8, default: u16) -> Result<Self, Error> {
Self::create(board, pin, default, true)
}
fn create(board: &dyn Hardware, pin: u8, default: u16, inverted: bool) -> Result<Self, Error> {
let pwm_range = Range::from([600, 2400]);
let mut servo = Self {
pin,
state: Arc::new(RwLock::new(default)),
default,
servo_type: ServoType::default(),
range: Range::from([0, 180]),
pwm_range,
degree_range: Range::from([0, 180]),
inverted,
auto_detach: false,
detach_delay: 20000,
previous: u16::MAX, protocol: board.get_protocol(),
animation: Arc::new(None),
last_move: Arc::new(RwLock::new(None)),
};
servo
.get_pin_info()?
.supports_mode(PinModeId::SERVO)
.ok_or(IncompatiblePin {
pin,
mode: PinModeId::SERVO,
context: "create a new Servo device",
})?;
servo.protocol.servo_config(pin, pwm_range)?;
servo.to(servo.default)?;
servo.protocol.set_pin_mode(pin, PinModeId::SERVO)?;
pause_sync!(100);
Ok(servo)
}
pub fn to(&mut self, to: u16) -> Result<&Self, Error> {
self.stop();
self.set_state(to.into())?;
Ok(self)
}
pub fn sweep(&mut self, ms: u64) -> &Self {
let mut animation = Animation::from(
Segment::from(
Track::new(self.clone())
.with_keyframe(Keyframe::new(self.range.end, 0, ms))
.with_keyframe(Keyframe::new(self.range.start, ms, ms * 2)),
)
.set_repeat(true),
);
animation.play();
self.animation = Arc::new(Some(animation));
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_type(&self) -> ServoType {
self.servo_type
}
pub fn set_type(mut self, servo_type: ServoType) -> Self {
self.servo_type = servo_type;
self
}
pub fn get_range(&self) -> Range<u16> {
self.range
}
pub fn set_range<R: Into<Range<u16>>>(mut self, range: R) -> Self {
let input = range.into();
let input = Range {
start: input.start.min(input.end),
end: input.end.max(input.start),
};
self.range = Range {
start: input
.start
.clamp(self.degree_range.start, self.degree_range.end),
end: input
.end
.clamp(self.degree_range.start, self.degree_range.end),
};
self.default = self.default.clamp(self.range.start, self.range.end);
self
}
pub fn get_degree_range(&self) -> Range<u16> {
self.degree_range
}
pub fn set_degree_range<R: Into<Range<u16>>>(mut self, degree_range: R) -> Self {
let input = degree_range.into();
let input = Range {
start: input.start.min(input.end),
end: input.end.max(input.start),
};
self.degree_range = input;
self.range = Range {
start: self
.range
.start
.clamp(self.degree_range.start, self.degree_range.end),
end: self
.range
.end
.clamp(self.degree_range.start, self.degree_range.end),
};
self.default = self.default.clamp(self.range.start, self.range.end);
self
}
pub fn get_pwn_range(&self) -> Range<u16> {
self.pwm_range
}
pub fn set_pwn_range<R: Into<Range<u16>>>(mut self, pwm_range: R) -> Result<Self, Error> {
let input = pwm_range.into();
self.pwm_range = input;
self.protocol.servo_config(self.pin, input)?;
Ok(self)
}
pub fn is_inverted(&self) -> bool {
self.inverted
}
pub fn set_inverted(mut self, inverted: bool) -> Self {
self.inverted = inverted;
self
}
pub fn is_auto_detach(&self) -> bool {
self.auto_detach
}
pub fn set_auto_detach(mut self, auto_detach: bool) -> Self {
self.auto_detach = match auto_detach {
false => {
self.protocol
.set_pin_mode(self.pin, PinModeId::SERVO)
.unwrap();
false
}
true => {
self.protocol
.set_pin_mode(self.pin, PinModeId::OUTPUT)
.unwrap();
true
}
};
self
}
pub fn get_detach_delay(&self) -> usize {
self.detach_delay
}
pub fn set_detach_delay(mut self, detach_delay: usize) -> Self {
self.detach_delay = detach_delay;
self
}
}
impl Display for Servo {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"SERVO (pin={}) [state={}, default={}, range={}-{}]",
self.pin,
self.state.read(),
self.default,
self.range.start,
self.range.end
)
}
}
#[cfg_attr(feature = "serde", typetag::serde)]
impl Device for Servo {}
#[cfg_attr(feature = "serde", typetag::serde)]
impl Output for Servo {
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),
}?;
let value: u16 = value.clamp(self.range.start, self.range.end);
let pwm: f64 = match self.inverted {
false => value.scale(
self.degree_range.start,
self.degree_range.end,
self.pwm_range.start,
self.pwm_range.end,
),
true => value.scale(
self.degree_range.end,
self.degree_range.start,
self.pwm_range.start,
self.pwm_range.end,
),
};
match self.auto_detach {
false => self.protocol.analog_write(self.pin, pwm as u16)?,
true => {
self.protocol.set_pin_mode(self.pin, PinModeId::SERVO)?;
self.protocol.analog_write(self.pin, pwm as u16)?;
*self.last_move.write() = Some(SystemTime::now());
let mut self_clone = self.clone();
task::run(async move {
pause!(self_clone.detach_delay);
if let Some(last_move) = self_clone.last_move.read().as_ref() {
if last_move.elapsed().unwrap().as_millis()
>= (self_clone.detach_delay as u128)
{
self_clone
.protocol
.set_pin_mode(self_clone.pin, PinModeId::UNSUPPORTED)
.unwrap();
}
}
})?;
}
}
let current = *self.state.read();
self.previous = current;
*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, Servo};
use crate::hardware::Board;
use crate::io::PinModeId;
use crate::mocks::plugin_io::MockIoProtocol;
use crate::pause;
use crate::utils::{Range, State};
use hermes_five::devices::ServoType;
fn _setup_servo(pin: u8) -> Servo {
let board = Board::new(MockIoProtocol::default()); Servo::new(&board, pin, 90).unwrap()
}
#[test]
fn test_servo_creation() {
let board = Board::new(MockIoProtocol::default());
let servo = Servo::new(&board, 12, 90).unwrap();
assert_eq!(servo.get_pin(), 12);
assert_eq!(*servo.state.read(), 90);
assert!(!servo.is_inverted());
let inverted_servo = Servo::new_inverted(&board, 12, 90).unwrap();
assert!(inverted_servo.is_inverted());
let servo = Servo::new(&board, 12, 66).unwrap();
assert_eq!(servo.get_default(), State::Integer(66));
assert_eq!(servo.get_state(), State::Integer(66));
}
#[test]
fn test_servo_move() {
let mut servo = _setup_servo(12);
let result = servo.to(150); assert!(result.is_ok());
assert_eq!(*servo.state.read(), 150); }
#[test]
fn test_servo_range_setting() {
let mut servo = _setup_servo(12);
servo = servo.set_range([100, 200]); assert_eq!(servo.get_range(), Range::from([100, 180])); assert_eq!(servo.default, 100); }
#[test]
fn test_servo_degree_range_setting() {
let mut servo = _setup_servo(12);
servo = servo.set_degree_range([100, 200]); assert_eq!(servo.get_degree_range(), Range::from([100, 200]));
assert_eq!(servo.get_range(), Range::from([100, 180])); assert_eq!(servo.default, 100); }
#[test]
fn test_servo_pwm_range_setting() {
let servo = _setup_servo(12);
let result = servo.set_pwn_range([999, 9999]);
assert!(result.is_ok());
assert_eq!(result.unwrap().get_pwn_range(), Range::from([999, 9999]));
}
#[test]
fn test_servo_detach_delay() {
let mut servo = _setup_servo(12);
assert_eq!(servo.get_detach_delay(), 20000);
servo = servo.set_detach_delay(100);
assert_eq!(servo.get_detach_delay(), 100);
}
#[hermes_five_macros::test]
fn test_servo_auto_detach() {
let mut servo = _setup_servo(12).set_auto_detach(true).set_detach_delay(300);
assert!(servo.is_auto_detach());
assert_eq!(servo.get_pin_info().unwrap().mode.id, PinModeId::OUTPUT);
servo = servo.set_auto_detach(false);
assert!(!servo.is_auto_detach());
assert_eq!(servo.get_pin_info().unwrap().mode.id, PinModeId::SERVO);
let _ = servo.to(180);
servo = servo.set_auto_detach(true);
assert_eq!(servo.get_pin_info().unwrap().mode.id, PinModeId::OUTPUT);
assert!(servo.is_auto_detach());
servo.to(180).expect("");
assert_eq!(servo.get_pin_info().unwrap().mode.id, PinModeId::SERVO);
pause!(80);
servo.to(180).expect("");
pause!(80);
assert_eq!(servo.get_pin_info().unwrap().mode.id, PinModeId::SERVO);
pause!(3000);
assert!(servo.is_auto_detach());
assert_eq!(
servo.get_pin_info().unwrap().mode.id,
PinModeId::UNSUPPORTED
);
}
#[test]
fn test_servo_type() {
let mut servo = _setup_servo(12);
assert_eq!(servo.get_type(), ServoType::Standard);
servo = servo.set_type(ServoType::Continuous);
assert_eq!(servo.get_type(), ServoType::Continuous);
}
#[test]
fn test_servo_state() {
let mut servo = _setup_servo(12);
assert!(servo.set_state(State::Integer(66)).is_ok());
assert_eq!(servo.get_state(), State::Integer(66));
assert!(servo.set_state(State::Integer(666)).is_ok());
assert_eq!(servo.get_state(), State::Integer(180));
assert!(servo.set_state(State::Signed(-12)).is_err());
assert!(servo.set_state(State::Signed(12)).is_ok());
assert_eq!(servo.get_state(), State::Integer(12));
assert!(servo.set_state(State::Float(-12.5)).is_err());
assert!(servo.set_state(State::Float(12.5)).is_ok());
assert_eq!(servo.get_state(), State::Integer(12));
assert!(servo.set_state(State::Boolean(true)).is_err());
let mut servo = _setup_servo(12).set_inverted(true);
assert!(servo.set_state(State::Integer(66)).is_ok());
assert_eq!(servo.get_state(), State::Integer(66));
}
#[hermes_five_macros::test]
fn test_servo_sweep() {
let mut servo = _setup_servo(12);
assert!(!servo.is_busy());
servo.stop();
servo.sweep(200);
pause!(100);
assert!(servo.is_busy()); servo.stop();
assert!(!servo.is_busy());
}
#[hermes_five_macros::test]
fn test_animation() {
let mut servo = _setup_servo(12);
assert!(!servo.is_busy());
servo.stop();
servo.animate(66, 500, Easing::Linear);
pause!(100);
assert!(servo.is_busy()); servo.stop();
assert!(!servo.is_busy());
}
#[test]
fn test_servo_display() {
let servo = _setup_servo(12);
let display_output = format!("{}", servo);
let expected_output = "SERVO (pin=12) [state=90, default=90, range=0-180]";
assert_eq!(display_output, expected_output);
}
}
#[cfg(feature = "serde")]
#[cfg(test)]
mod serde_tests {
use crate::hardware::{Board, Hardware, PCA9685};
use crate::mocks::plugin_io::MockIoProtocol;
use hermes_five::devices::Servo;
#[test]
fn test_servo_serialize() {
let board = Board::new(MockIoProtocol::default());
let servo = Servo::new(&board, 12, 90).expect("servo");
let json = serde_json::to_string(&servo).unwrap();
assert_eq!(
json,
r#"{"pin":12,"state":90,"default":90,"servo_type":"Standard","range":[0,180],"pwm_range":[600,2400],"degree_range":[0,180],"detach_delay":20000}"#
);
let pca9685 = PCA9685::default(&board).expect("pca9685");
let servo = Servo::new(&pca9685, 12, 90).expect("servo");
let json = serde_json::to_string(&servo).unwrap();
assert_eq!(
json,
r#"{"pin":12,"state":90,"default":90,"servo_type":"Standard","range":[0,180],"pwm_range":[600,2400],"degree_range":[0,180],"detach_delay":20000}"#
);
}
#[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");
}
}