hermes_five/devices/output/
servo.rs

1use std::fmt::{Display, Formatter};
2use std::sync::Arc;
3use std::time::SystemTime;
4
5use parking_lot::RwLock;
6
7use crate::animations::{Animation, Easing, Keyframe, Segment, Track};
8use crate::devices::{Device, Output};
9use crate::errors::HardwareError::IncompatiblePin;
10use crate::errors::{Error, StateError};
11use crate::hardware::Hardware;
12use crate::io::{IoProtocol, Pin, PinModeId};
13use crate::utils::{task, Range, Scalable, State};
14use crate::{pause, pause_sync};
15
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
18pub enum ServoType {
19    #[default]
20    Standard,
21    Continuous,
22}
23
24/// Represents a Servo controlled by a PWM pin.
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26#[derive(Clone, Debug)]
27pub struct Servo {
28    // ########################################
29    // # Basics
30    /// The pin (id) of the [`Board`] used to control the Servo.
31    pin: u8,
32    /// The current Servo state.
33    #[cfg_attr(feature = "serde", serde(with = "crate::devices::arc_rwlock_serde"))]
34    state: Arc<RwLock<u16>>,
35    /// The LED default value (default: ON).
36    default: u16,
37
38    // ########################################
39    // # Settings
40    /// The servo type (default: ServoType::Standard).
41    servo_type: ServoType,
42    /// The servo range limitation in the physical world (default: [0, 180]).
43    range: Range<u16>,
44    /// The servo PWN range for control  (default: [600, 2400]).
45    pwm_range: Range<u16>,
46    /// The servo theoretical degree of movement  (default: [0, 180]).
47    degree_range: Range<u16>,
48    /// Specifies if the servo command is inverted (default: false).
49    #[cfg_attr(
50        feature = "serde",
51        serde(skip_serializing_if = "crate::utils::is_default")
52    )]
53    #[cfg_attr(feature = "serde", serde(default))]
54    inverted: bool,
55    /// Specifies if the servo should auto detach itself after a given delay (default: false).
56    #[cfg_attr(
57        feature = "serde",
58        serde(skip_serializing_if = "crate::utils::is_default")
59    )]
60    #[cfg_attr(feature = "serde", serde(default))]
61    auto_detach: bool,
62    /// The delay in ms before the servo can detach itself in auto-detach mode (default: 20000ms).
63    #[cfg_attr(
64        feature = "serde",
65        serde(skip_serializing_if = "crate::utils::is_default")
66    )]
67    #[cfg_attr(feature = "serde", serde(default))]
68    detach_delay: usize,
69
70    // ########################################
71    // # Volatile utility data.
72    /// Last move done by the servo.
73    #[cfg_attr(feature = "serde", serde(skip))]
74    previous: u16,
75    #[cfg_attr(feature = "serde", serde(skip))]
76    protocol: Box<dyn IoProtocol>,
77    /// Inner handler to the task running the animation.
78    #[cfg_attr(feature = "serde", serde(skip))]
79    animation: Arc<Option<Animation>>,
80    #[cfg_attr(feature = "serde", serde(skip))]
81    last_move: Arc<RwLock<Option<SystemTime>>>,
82}
83
84impl Servo {
85    /// Creates an instance of a Servo attached to a given board.
86    ///
87    /// # Errors
88    /// * `UnknownPin`: this function will bail an error if the pin does not exist for this board.
89    /// * `IncompatiblePin`: this function will bail an error if the pin does not support SERVO mode.
90    pub fn new(board: &dyn Hardware, pin: u8, default: u16) -> Result<Self, Error> {
91        Self::create(board, pin, default, false)
92    }
93
94    /// Creates an instance of an inverted Servo attached to a given board (see [`Self::set_inverted`]`).
95    ///
96    /// # Errors
97    /// * `UnknownPin`: this function will bail an error if the pin does not exist for this board.
98    /// * `IncompatiblePin`: this function will bail an error if the pin does not support SERVO mode.
99    pub fn new_inverted(board: &dyn Hardware, pin: u8, default: u16) -> Result<Self, Error> {
100        Self::create(board, pin, default, true)
101    }
102
103    /// Inner helper.
104    fn create(board: &dyn Hardware, pin: u8, default: u16, inverted: bool) -> Result<Self, Error> {
105        let pwm_range = Range::from([600, 2400]);
106
107        let mut servo = Self {
108            pin,
109            state: Arc::new(RwLock::new(default)),
110            default,
111            servo_type: ServoType::default(),
112            range: Range::from([0, 180]),
113            pwm_range,
114            degree_range: Range::from([0, 180]),
115            inverted,
116            auto_detach: false,
117            detach_delay: 20000,
118            previous: u16::MAX, // Ensure previous out-of-range: forces default at start
119            protocol: board.get_protocol(),
120            animation: Arc::new(None),
121            last_move: Arc::new(RwLock::new(None)),
122        };
123
124        // --
125        // The following may seem tedious, but it ensures we attach the servo with the default value already set.
126        // Check if SERVO MODE exists for this pin.
127        servo
128            .get_pin_info()?
129            .supports_mode(PinModeId::SERVO)
130            .ok_or(IncompatiblePin {
131                pin,
132                mode: PinModeId::SERVO,
133                context: "create a new Servo device",
134            })?;
135        servo.protocol.servo_config(pin, pwm_range)?;
136        servo.to(servo.default)?;
137        servo.protocol.set_pin_mode(pin, PinModeId::SERVO)?;
138        pause_sync!(100);
139        Ok(servo)
140    }
141
142    /// Moves the servo to the requested position at max speed.
143    pub fn to(&mut self, to: u16) -> Result<&Self, Error> {
144        // Stops any animation running.
145        self.stop();
146
147        self.set_state(to.into())?;
148        Ok(self)
149    }
150
151    /// Sweeps the servo in phases of ms (milliseconds) duration.
152    /// This is an animation and can be stopped by calling [`Self::stop()`].
153    ///
154    /// # Parameters
155    /// * `ms`: the blink duration in milliseconds
156    pub fn sweep(&mut self, ms: u64) -> &Self {
157        let mut animation = Animation::from(
158            Segment::from(
159                Track::new(self.clone())
160                    .with_keyframe(Keyframe::new(self.range.end, 0, ms))
161                    .with_keyframe(Keyframe::new(self.range.start, ms, ms * 2)),
162            )
163            .set_repeat(true),
164        );
165        animation.play();
166        self.animation = Arc::new(Some(animation));
167
168        self
169    }
170
171    // ########################################
172    // Setters and Getters.
173
174    /// Returns the pin (id) used by the device.
175    pub fn get_pin(&self) -> u8 {
176        self.pin
177    }
178
179    /// Returns [`Pin`] information.
180    pub fn get_pin_info(&self) -> Result<Pin, Error> {
181        let lock = self.protocol.get_io().read();
182        Ok(lock.get_pin(self.pin)?.clone())
183    }
184
185    /// Returns the servo type.
186    pub fn get_type(&self) -> ServoType {
187        self.servo_type
188    }
189
190    /// Sets the servo type.
191    pub fn set_type(mut self, servo_type: ServoType) -> Self {
192        self.servo_type = servo_type;
193        self
194    }
195
196    /// Returns the servo motion range limitation in degree.
197    ///
198    /// A servo has a physical range (cf [`Self::set_degree_range`]) corresponding to a command range
199    /// limitation (cf [`Self::set_pwn_range`]). Those are intrinsic top the servo itself. On the contrary,
200    /// the motion range limitation here is a limitation you want to set for your servo because of how
201    /// it is used in your robot: for example an arm that can turn only 20-40° in motion range.
202    pub fn get_range(&self) -> Range<u16> {
203        self.range
204    }
205
206    /// Sets the Servo motion range limitation in degree. This guarantee the servo to stays in the given
207    /// range at any time.
208    ///
209    /// - No matter the order given, the range will always have min <= max
210    /// - No matter the values given, the range will always stay within the Servo `degree_range`.
211    pub fn set_range<R: Into<Range<u16>>>(mut self, range: R) -> Self {
212        let input = range.into();
213
214        // Rearrange value: min <= max.
215        let input = Range {
216            start: input.start.min(input.end),
217            end: input.end.max(input.start),
218        };
219
220        // Clamp the range into the degree_range.
221        self.range = Range {
222            start: input
223                .start
224                .clamp(self.degree_range.start, self.degree_range.end),
225            end: input
226                .end
227                .clamp(self.degree_range.start, self.degree_range.end),
228        };
229        // Clamp the default position inside the range.
230        self.default = self.default.clamp(self.range.start, self.range.end);
231
232        self
233    }
234
235    /// Returns  the theoretical range of degrees of movement for the servo (some servos can range from 0 to 90°, 180°, 270°, 360°, etc.).
236    pub fn get_degree_range(&self) -> Range<u16> {
237        self.degree_range
238    }
239
240    /// Sets the theoretical range of degrees of movement for the servo (some servos can range from 0 to 90°, 180°, 270°, 360°, etc.).
241    ///
242    /// - No matter the order given, the range will always have min <= max
243    /// - This may impact the `range` since it will always stay within the given `degree_range`.
244    pub fn set_degree_range<R: Into<Range<u16>>>(mut self, degree_range: R) -> Self {
245        let input = degree_range.into();
246
247        // Rearrange value: min <= max.
248        let input = Range {
249            start: input.start.min(input.end),
250            end: input.end.max(input.start),
251        };
252
253        self.degree_range = input;
254
255        // Clamp the range into the degree_range.
256        self.range = Range {
257            start: self
258                .range
259                .start
260                .clamp(self.degree_range.start, self.degree_range.end),
261            end: self
262                .range
263                .end
264                .clamp(self.degree_range.start, self.degree_range.end),
265        };
266        // Clamp the default position inside the range.
267        self.default = self.default.clamp(self.range.start, self.range.end);
268
269        self
270    }
271
272    /// Returns the theoretical range of pwm controls the servo response to.
273    pub fn get_pwn_range(&self) -> Range<u16> {
274        self.pwm_range
275    }
276
277    /// Sets the theoretical range of pwm controls the servo response to.
278    ///
279    /// # Parameters
280    /// * `pwm_range`: the range limitation
281    pub fn set_pwn_range<R: Into<Range<u16>>>(mut self, pwm_range: R) -> Result<Self, Error> {
282        let input = pwm_range.into();
283        self.pwm_range = input;
284        self.protocol.servo_config(self.pin, input)?;
285        Ok(self)
286    }
287
288    /// Returns if the servo command is set to be inverted.
289    pub fn is_inverted(&self) -> bool {
290        self.inverted
291    }
292
293    /// Sets the servo command inversion mode.
294    pub fn set_inverted(mut self, inverted: bool) -> Self {
295        self.inverted = inverted;
296        self
297    }
298
299    /// Returns if the servo command is set to be auto_detach itself.
300    pub fn is_auto_detach(&self) -> bool {
301        self.auto_detach
302    }
303
304    /// Sets the servo to auto-detach itself after a given delay.
305    pub fn set_auto_detach(mut self, auto_detach: bool) -> Self {
306        self.auto_detach = match auto_detach {
307            false => {
308                self.protocol
309                    .set_pin_mode(self.pin, PinModeId::SERVO)
310                    .unwrap();
311                false
312            }
313            true => {
314                self.protocol
315                    .set_pin_mode(self.pin, PinModeId::OUTPUT)
316                    .unwrap();
317                true
318            }
319        };
320        self
321    }
322
323    /// Returns the delay (in ms) before the servo is auto-detach (if enabled).
324    pub fn get_detach_delay(&self) -> usize {
325        self.detach_delay
326    }
327
328    /// Sets the delay (in ms) before the servo is auto-detach (if enabled).
329    pub fn set_detach_delay(mut self, detach_delay: usize) -> Self {
330        self.detach_delay = detach_delay;
331        self
332    }
333}
334
335impl Display for Servo {
336    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
337        write!(
338            f,
339            "SERVO (pin={}) [state={}, default={}, range={}-{}]",
340            self.pin,
341            self.state.read(),
342            self.default,
343            self.range.start,
344            self.range.end
345        )
346    }
347}
348
349#[cfg_attr(feature = "serde", typetag::serde)]
350impl Device for Servo {}
351
352#[cfg_attr(feature = "serde", typetag::serde)]
353impl Output for Servo {
354    fn get_state(&self) -> State {
355        (*self.state.read()).into()
356    }
357    /// Internal only: you should rather use [`Self::to()`] function.
358    fn set_state(&mut self, state: State) -> Result<State, Error> {
359        // Convert from state.
360        let value = match state {
361            State::Integer(value) => Ok(value as u16),
362            State::Signed(value) => match value >= 0 {
363                true => Ok(value as u16),
364                false => Err(StateError),
365            },
366            State::Float(value) => match value >= 0.0 {
367                true => Ok(value as u16),
368                false => Err(StateError),
369            },
370            _ => Err(StateError),
371        }?;
372
373        // Clamp the request within the Servo range.
374        let value: u16 = value.clamp(self.range.start, self.range.end);
375        // No need to move if last move was already that one.
376        // if state == self.previous {
377        //     return Ok(state);
378        // }
379
380        let pwm: f64 = match self.inverted {
381            false => value.scale(
382                self.degree_range.start,
383                self.degree_range.end,
384                self.pwm_range.start,
385                self.pwm_range.end,
386            ),
387            true => value.scale(
388                self.degree_range.end,
389                self.degree_range.start,
390                self.pwm_range.start,
391                self.pwm_range.end,
392            ),
393        };
394
395        // Attach the pinMode if we are auto-detach mode.
396        match self.auto_detach {
397            false => self.protocol.analog_write(self.pin, pwm as u16)?,
398            true => {
399                self.protocol.set_pin_mode(self.pin, PinModeId::SERVO)?;
400                self.protocol.analog_write(self.pin, pwm as u16)?;
401                *self.last_move.write() = Some(SystemTime::now());
402
403                let mut self_clone = self.clone();
404                task::run(async move {
405                    pause!(self_clone.detach_delay);
406                    if let Some(last_move) = self_clone.last_move.read().as_ref() {
407                        if last_move.elapsed().unwrap().as_millis()
408                            >= (self_clone.detach_delay as u128)
409                        {
410                            self_clone
411                                .protocol
412                                .set_pin_mode(self_clone.pin, PinModeId::UNSUPPORTED)
413                                .unwrap();
414                        }
415                    }
416                })?;
417            }
418        }
419        let current = *self.state.read();
420        self.previous = current;
421        *self.state.write() = value;
422        Ok(value.into())
423    }
424    fn get_default(&self) -> State {
425        self.default.into()
426    }
427
428    fn animate<S: Into<State>>(&mut self, state: S, duration: u64, transition: Easing) {
429        let mut animation = Animation::from(
430            Track::new(self.clone())
431                .with_keyframe(Keyframe::new(state, 0, duration).set_transition(transition)),
432        );
433        animation.play();
434        self.animation = Arc::new(Some(animation));
435    }
436    fn is_busy(&self) -> bool {
437        self.animation.is_some()
438    }
439    fn stop(&mut self) {
440        if let Some(animation) = Arc::get_mut(&mut self.animation).and_then(Option::as_mut) {
441            animation.stop();
442        }
443        self.animation = Arc::new(None);
444    }
445}
446
447#[cfg(test)]
448mod tests {
449    use crate::animations::Easing;
450    use crate::devices::{Output, Servo};
451    use crate::hardware::Board;
452    use crate::io::PinModeId;
453    use crate::mocks::plugin_io::MockIoProtocol;
454    use crate::pause;
455    use crate::utils::{Range, State};
456    use hermes_five::devices::ServoType;
457
458    fn _setup_servo(pin: u8) -> Servo {
459        let board = Board::new(MockIoProtocol::default()); // Assuming a mock Board implementation
460        Servo::new(&board, pin, 90).unwrap()
461    }
462
463    #[test]
464    fn test_servo_creation() {
465        let board = Board::new(MockIoProtocol::default());
466
467        let servo = Servo::new(&board, 12, 90).unwrap();
468        assert_eq!(servo.get_pin(), 12);
469        assert_eq!(*servo.state.read(), 90);
470        assert!(!servo.is_inverted());
471
472        let inverted_servo = Servo::new_inverted(&board, 12, 90).unwrap();
473        assert!(inverted_servo.is_inverted());
474
475        let servo = Servo::new(&board, 12, 66).unwrap();
476        assert_eq!(servo.get_default(), State::Integer(66));
477        assert_eq!(servo.get_state(), State::Integer(66));
478    }
479
480    #[test]
481    fn test_servo_move() {
482        let mut servo = _setup_servo(12);
483        let result = servo.to(150); // Move the servo to position 150.
484        assert!(result.is_ok());
485        assert_eq!(*servo.state.read(), 150); // Servo state should be updated to 150.
486    }
487
488    #[test]
489    fn test_servo_range_setting() {
490        let mut servo = _setup_servo(12);
491        servo = servo.set_range([100, 200]); // Setting new range.
492        assert_eq!(servo.get_range(), Range::from([100, 180])); // Ensure the range is updated but clamp in the theoretical degree_range.
493        assert_eq!(servo.default, 100); // Default remains within the range.
494    }
495
496    #[test]
497    fn test_servo_degree_range_setting() {
498        let mut servo = _setup_servo(12);
499        servo = servo.set_degree_range([100, 200]); // Setting new range.
500        assert_eq!(servo.get_degree_range(), Range::from([100, 200]));
501        assert_eq!(servo.get_range(), Range::from([100, 180])); // Ensure the range is updated/clamped in the theoretical degree_range.
502        assert_eq!(servo.default, 100); // Default remains within the range.
503    }
504
505    #[test]
506    fn test_servo_pwm_range_setting() {
507        let servo = _setup_servo(12);
508        let result = servo.set_pwn_range([999, 9999]);
509        assert!(result.is_ok());
510        assert_eq!(result.unwrap().get_pwn_range(), Range::from([999, 9999]));
511    }
512
513    #[test]
514    fn test_servo_detach_delay() {
515        let mut servo = _setup_servo(12);
516        assert_eq!(servo.get_detach_delay(), 20000);
517        servo = servo.set_detach_delay(100);
518        assert_eq!(servo.get_detach_delay(), 100);
519    }
520
521    #[hermes_five_macros::test]
522    fn test_servo_auto_detach() {
523        let mut servo = _setup_servo(12).set_auto_detach(true).set_detach_delay(300);
524        assert!(servo.is_auto_detach());
525        assert_eq!(servo.get_pin_info().unwrap().mode.id, PinModeId::OUTPUT);
526
527        // Do not auto-detach: should reset pinMode to SERVO for proper use.
528        servo = servo.set_auto_detach(false);
529        assert!(!servo.is_auto_detach());
530        assert_eq!(servo.get_pin_info().unwrap().mode.id, PinModeId::SERVO);
531        let _ = servo.to(180);
532
533        // Auto-detach unmoved servo: it should detach right away.
534        servo = servo.set_auto_detach(true);
535        assert_eq!(servo.get_pin_info().unwrap().mode.id, PinModeId::OUTPUT);
536        assert!(servo.is_auto_detach());
537
538        // Moving should auto-reattach.
539        servo.to(180).expect("");
540        assert_eq!(servo.get_pin_info().unwrap().mode.id, PinModeId::SERVO);
541        pause!(80);
542        // Continue moving should reset detach timer
543        servo.to(180).expect("");
544        pause!(80);
545        assert_eq!(servo.get_pin_info().unwrap().mode.id, PinModeId::SERVO);
546        // No move ultimately leads to auto-detaching
547        pause!(3000);
548        assert!(servo.is_auto_detach());
549        assert_eq!(
550            servo.get_pin_info().unwrap().mode.id,
551            PinModeId::UNSUPPORTED
552        );
553    }
554
555    #[test]
556    fn test_servo_type() {
557        let mut servo = _setup_servo(12);
558        assert_eq!(servo.get_type(), ServoType::Standard);
559        servo = servo.set_type(ServoType::Continuous);
560        assert_eq!(servo.get_type(), ServoType::Continuous);
561    }
562
563    #[test]
564    fn test_servo_state() {
565        let mut servo = _setup_servo(12);
566
567        // Move in range
568        assert!(servo.set_state(State::Integer(66)).is_ok());
569        assert_eq!(servo.get_state(), State::Integer(66));
570
571        // Move outside range
572        assert!(servo.set_state(State::Integer(666)).is_ok());
573        assert_eq!(servo.get_state(), State::Integer(180));
574
575        // Move signed range
576        assert!(servo.set_state(State::Signed(-12)).is_err());
577        assert!(servo.set_state(State::Signed(12)).is_ok());
578        assert_eq!(servo.get_state(), State::Integer(12));
579
580        // Move float range
581        assert!(servo.set_state(State::Float(-12.5)).is_err());
582        assert!(servo.set_state(State::Float(12.5)).is_ok());
583        assert_eq!(servo.get_state(), State::Integer(12));
584
585        // Move unknown type
586        assert!(servo.set_state(State::Boolean(true)).is_err());
587
588        // Move inverted range
589        let mut servo = _setup_servo(12).set_inverted(true);
590        assert!(servo.set_state(State::Integer(66)).is_ok());
591        assert_eq!(servo.get_state(), State::Integer(66));
592    }
593
594    #[hermes_five_macros::test]
595    fn test_servo_sweep() {
596        let mut servo = _setup_servo(12);
597        assert!(!servo.is_busy());
598        servo.stop();
599        servo.sweep(200);
600        pause!(100);
601        assert!(servo.is_busy()); // Animation is currently running.
602        servo.stop();
603        assert!(!servo.is_busy());
604    }
605
606    #[hermes_five_macros::test]
607    fn test_animation() {
608        let mut servo = _setup_servo(12);
609        assert!(!servo.is_busy());
610        // Stop something not started should not fail.
611        servo.stop();
612        // Fade in the LED to brightness
613        servo.animate(66, 500, Easing::Linear);
614        pause!(100);
615        assert!(servo.is_busy()); // Animation is currently running.
616        servo.stop();
617        assert!(!servo.is_busy());
618    }
619
620    #[test]
621    fn test_servo_display() {
622        let servo = _setup_servo(12);
623        let display_output = format!("{}", servo);
624        let expected_output = "SERVO (pin=12) [state=90, default=90, range=0-180]";
625        assert_eq!(display_output, expected_output);
626    }
627}
628
629#[cfg(feature = "serde")]
630#[cfg(test)]
631mod serde_tests {
632    use crate::hardware::{Board, Hardware, PCA9685};
633    use crate::mocks::plugin_io::MockIoProtocol;
634    use hermes_five::devices::Servo;
635
636    #[test]
637    fn test_servo_serialize() {
638        let board = Board::new(MockIoProtocol::default());
639        let servo = Servo::new(&board, 12, 90).expect("servo");
640        let json = serde_json::to_string(&servo).unwrap();
641        assert_eq!(
642            json,
643            r#"{"pin":12,"state":90,"default":90,"servo_type":"Standard","range":[0,180],"pwm_range":[600,2400],"degree_range":[0,180],"detach_delay":20000}"#
644        );
645
646        let pca9685 = PCA9685::default(&board).expect("pca9685");
647        let servo = Servo::new(&pca9685, 12, 90).expect("servo");
648        let json = serde_json::to_string(&servo).unwrap();
649        assert_eq!(
650            json,
651            r#"{"pin":12,"state":90,"default":90,"servo_type":"Standard","range":[0,180],"pwm_range":[600,2400],"degree_range":[0,180],"detach_delay":20000}"#
652        );
653    }
654
655    #[test]
656    fn test_board_deserialize() {
657        let json =
658            r#"{"protocol":{"type":"RemoteIo","transport":{"type":"Serial","port":"mock"}}}"#;
659        let board: Board = serde_json::from_str(json).unwrap();
660        assert_eq!(board.get_protocol_name(), "RemoteIo");
661
662        let json = r#"{"protocol":{"type":"MockIoProtocol"}}"#;
663        let board: Board = serde_json::from_str(json).unwrap();
664        assert_eq!(board.get_protocol_name(), "MockIoProtocol");
665    }
666}