hermes_five/devices/output/
mod.rs

1use crate::animations::Easing;
2use crate::devices::Device;
3use crate::errors::Error;
4use crate::utils::{Scalable, State};
5
6pub mod digital;
7pub mod led;
8pub mod pwm;
9pub mod servo;
10
11/// A trait for devices that can act on the world: the board "outputs" some state onto them.
12///
13/// This trait extends [`Device`] and is intended for actuators that requires the same capabilities
14/// as devices, including debugging, cloning, and concurrency support.
15#[cfg_attr(feature = "serde", typetag::serde(tag = "type"))]
16pub trait Output: Device {
17    /// Returns  the actuator current state.
18    fn get_state(&self) -> State;
19    /// Internal only: you should use the specific device state modifier functions instead.
20    fn set_state(&mut self, state: State) -> Result<State, Error>;
21    /// Returns  the actuator default (or neutral) state.
22    fn get_default(&self) -> State;
23    /// Resets the actuator to default (or neutral) state.
24    fn reset(&mut self) -> Result<State, Error> {
25        self.stop();
26        self.set_state(self.get_default())
27    }
28    /// Animates the output of the device. In other word: the state of the device will be animated from
29    /// current step to targeted step through an interpolation of in-between states.
30    /// The function will last for the required duration and the interpolation will follow an easing
31    /// transition function.
32    ///
33    /// # Arguments
34    /// - `state`: the targeted step to meet
35    /// - `duration`: the duration (in ms) the animation is expected to last
36    /// - `transition`: a transition [`Easing`] function to apply on the state.
37    fn animate<S: Into<State>>(&mut self, state: S, duration: u64, transition: Easing)
38    where
39        Self: Sized;
40    /// Indicates the busy status, ie if the device is running an animation.
41    fn is_busy(&self) -> bool;
42    /// Stops the current animation, if any.
43    fn stop(&mut self);
44    /// Internal only.
45    fn scale_state(&mut self, previous: State, target: State, progress: f32) -> State {
46        match target {
47            State::Integer(value) => {
48                State::Integer(progress.scale(0, 1, previous.as_integer(), value))
49            }
50            State::Signed(value) => {
51                State::Signed(progress.scale(0, 1, previous.as_signed_integer(), value))
52            }
53            State::Float(value) => State::Float(progress.scale(0, 1, previous.as_float(), value)),
54            _ => match progress {
55                0.0 => previous,
56                _ => target,
57            },
58        }
59    }
60}
61dyn_clone::clone_trait_object!(Output);
62
63#[cfg(test)]
64mod tests {
65    use crate::mocks::output_device::MockOutputDevice;
66
67    use super::*;
68
69    #[test]
70    fn test_scale_state_integer() {
71        let mut device = MockOutputDevice::new(0);
72
73        // Halfway between 10 and 20
74        let result = device.scale_state(State::Integer(10), State::Integer(20), 0.5);
75        assert_eq!(result, State::Integer(15));
76
77        // 75% between 10 and 20
78        let result = device.scale_state(State::Integer(10), State::Integer(20), 0.75);
79        assert_eq!(result, State::Integer(18));
80
81        // 120% between 10 and 20
82        let result = device.scale_state(State::Integer(10), State::Integer(20), 1.2);
83        assert_eq!(result, State::Integer(22));
84    }
85
86    #[test]
87    fn test_scale_state_signed() {
88        let mut device = MockOutputDevice::new(0);
89
90        // Halfway between 10 and 20
91        let result = device.scale_state(State::Signed(-10), State::Signed(10), 0.5);
92        assert_eq!(result, State::Signed(0));
93
94        // 75% between 10 and 20
95        let result = device.scale_state(State::Signed(-10), State::Signed(10), 0.75);
96        assert_eq!(result, State::Signed(5));
97
98        // 120% between 10 and 20
99        let result = device.scale_state(State::Signed(-10), State::Signed(10), 1.2);
100        assert_eq!(result, State::Signed(14));
101    }
102
103    #[test]
104    fn test_scale_state_float() {
105        let mut device = MockOutputDevice::new(0);
106
107        // Halfway between 10 and 20
108        let result = device.scale_state(State::Float(1.0), State::Float(2.0), 0.5);
109        assert_eq!(result, State::Float(1.5));
110
111        // 75% between 10 and 20
112        let result = device.scale_state(State::Float(1.0), State::Float(2.0), 0.75);
113        assert_eq!(result, State::Float(1.75));
114
115        // 120% between 10 and 20
116        let result = device.scale_state(State::Float(1.0), State::Float(2.0), 1.2);
117        assert_eq!(result, State::Float(2.200000047683716));
118    }
119
120    #[test]
121    fn test_scale_state_non_numeric() {
122        let mut device = MockOutputDevice::new(0);
123
124        let result = device.scale_state(State::Boolean(false), State::Boolean(true), 0.0);
125        assert_eq!(result, State::Boolean(false));
126
127        let result = device.scale_state(State::Boolean(false), State::Boolean(true), 0.5);
128        assert_eq!(result, State::Boolean(true));
129    }
130
131    #[test]
132    fn test_reset() {
133        let mut device = MockOutputDevice::new(42);
134        assert_eq!(device.get_state(), State::Integer(42));
135        assert!(device.reset().is_ok());
136        assert_eq!(device.get_state(), State::Integer(0))
137    }
138}