hermes_five/devices/output/
led.rs1use std::fmt::{Display, Formatter};
2use std::sync::Arc;
3
4use parking_lot::RwLock;
5
6use crate::animations::{Animation, Easing, Keyframe, Segment, Track};
7use crate::devices::{Device, Output};
8use crate::errors::HardwareError::IncompatiblePin;
9use crate::errors::{Error, StateError};
10use crate::hardware::Hardware;
11use crate::io::{IoProtocol, Pin, PinMode, PinModeId};
12use crate::utils::{Scalable, State};
13
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19#[derive(Clone, Debug)]
20pub struct Led {
21 pin: u8,
25 #[cfg_attr(feature = "serde", serde(with = "crate::devices::arc_rwlock_serde"))]
27 state: Arc<RwLock<u16>>,
28 default: u16,
30
31 brightness: u16,
35
36 #[cfg_attr(feature = "serde", serde(skip))]
40 pwm_mode: Option<PinMode>,
41 #[cfg_attr(feature = "serde", serde(skip))]
42 protocol: Box<dyn IoProtocol>,
43 #[cfg_attr(feature = "serde", serde(skip))]
45 animation: Arc<Option<Animation>>,
46}
47
48impl Led {
49 pub fn new(board: &dyn Hardware, pin: u8, default: bool) -> Result<Self, Error> {
55 let mut protocol = board.get_protocol();
56
57 let hardware_pin = {
59 let hardware = protocol.get_io().read();
60 hardware.get_pin(pin)?.clone()
61 };
62
63 let pwm_mode = hardware_pin.supports_mode(PinModeId::PWM);
65
66 let pin_mode = match pwm_mode {
68 None => PinModeId::OUTPUT,
69 Some(_) => PinModeId::PWM,
70 };
71 protocol.set_pin_mode(pin, pin_mode)?;
72
73 let default = match default {
75 false => 0,
76 true => 0xFF,
77 };
78
79 let mut led = Self {
80 pin,
81 state: Arc::new(RwLock::new(default)),
82 default,
83 brightness: 0xFF,
84 pwm_mode,
85 protocol,
86 animation: Arc::new(None),
87 };
88
89 led.reset()?;
90
91 Ok(led)
92 }
93
94 pub fn turn_on(&mut self) -> Result<&Self, Error> {
96 self.set_state(State::Integer(self.brightness as u64))?;
97 Ok(self)
98 }
99
100 pub fn turn_off(&mut self) -> Result<&Self, Error> {
102 self.set_state(State::Integer(0))?;
103 Ok(self)
104 }
105
106 pub fn toggle(&mut self) -> Result<&Self, Error> {
108 match self.is_on() {
109 true => self.turn_off(),
110 false => self.turn_on(),
111 }
112 }
113
114 pub fn blink(&mut self, ms: u64) -> &Self {
117 let mut animation = Animation::from(
118 Segment::from(
119 Track::new(self.clone())
120 .with_keyframe(Keyframe::new(true, 0, ms))
121 .with_keyframe(Keyframe::new(false, ms, ms * 2)),
122 )
123 .set_repeat(true),
124 );
125 animation.play();
126 self.animation = Arc::new(Some(animation));
127
128 self
129 }
130
131 pub fn pulse(&mut self, ms: u64) -> &Self {
134 let mut animation = Animation::from(
135 Segment::from(
136 Track::new(self.clone())
137 .with_keyframe(Keyframe::new(0xFFu16, 0, ms))
138 .with_keyframe(Keyframe::new(0u16, ms, ms * 2)),
139 )
140 .set_repeat(true),
141 );
142 animation.play();
143 self.animation = Arc::new(Some(animation));
144
145 self
146 }
147
148 pub fn get_pin(&self) -> u8 {
153 self.pin
154 }
155
156 pub fn get_pin_info(&self) -> Result<Pin, Error> {
158 let lock = self.protocol.get_io().read();
159 Ok(lock.get_pin(self.pin)?.clone())
160 }
161
162 pub fn get_brightness(&self) -> u8 {
164 match self.pwm_mode {
165 None => 100,
166 Some(pwm_mode) => self
168 .brightness
169 .scale(0, pwm_mode.get_max_possible_value(), 0, 100),
170 }
171 }
172
173 pub fn set_brightness(mut self, brightness: u8) -> Result<Self, Error> {
180 let brightness = brightness.clamp(0, 100) as u16;
182
183 let pwm_mode = self.pwm_mode.ok_or(IncompatiblePin {
185 mode: PinModeId::PWM,
186 pin: self.pin,
187 context: "set LED brightness",
188 })?;
189
190 let brightness = brightness.scale(0, 100, 0, pwm_mode.get_max_possible_value());
192
193 self.brightness = brightness;
195
196 if self.state.read().ne(&self.brightness) {
198 self.set_state(State::Integer(self.brightness as u64))?;
199 }
200
201 Ok(self)
202 }
203
204 pub fn is_on(&self) -> bool {
206 self.state.read().gt(&0)
207 }
208
209 pub fn is_off(&self) -> bool {
211 self.state.read().eq(&0)
212 }
213}
214
215impl Display for Led {
216 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
217 write!(
218 f,
219 "LED (pin={}) [state={}, default={}, brightness={}]",
220 self.pin,
221 self.state.read(),
222 self.default,
223 self.brightness
224 )
225 }
226}
227
228#[cfg_attr(feature = "serde", typetag::serde)]
229impl Device for Led {}
230
231#[cfg_attr(feature = "serde", typetag::serde)]
232impl Output for Led {
233 fn get_state(&self) -> State {
235 (*self.state.read()).into()
236 }
237
238 fn set_state(&mut self, state: State) -> Result<State, Error> {
240 let value = match state {
241 State::Boolean(value) => match value {
242 true => Ok(self.brightness),
243 false => Ok(0),
244 },
245 State::Integer(value) => Ok(value as u16),
246 State::Float(value) => Ok(value as u16),
247 State::Signed(value) => Ok(value.max(0) as u16),
248 _ => Err(StateError),
249 }?;
250
251 match self.get_pin_info()?.mode.id {
252 PinModeId::OUTPUT => self.protocol.digital_write(self.pin, value > 0),
254 PinModeId::PWM => self.protocol.analog_write(self.pin, value),
256 id => Err(Error::from(IncompatiblePin {
257 mode: id,
258 pin: self.pin,
259 context: "update LED",
260 })),
261 }?;
262 *self.state.write() = value;
263 Ok(value.into())
264 }
265 fn get_default(&self) -> State {
266 self.default.into()
267 }
268 fn animate<S: Into<State>>(&mut self, state: S, duration: u64, transition: Easing) {
269 let mut animation = Animation::from(
270 Track::new(self.clone())
271 .with_keyframe(Keyframe::new(state, 0, duration).set_transition(transition)),
272 );
273 animation.play();
274 self.animation = Arc::new(Some(animation));
275 }
276 fn is_busy(&self) -> bool {
277 self.animation.is_some()
278 }
279 fn stop(&mut self) {
280 if let Some(animation) = Arc::get_mut(&mut self.animation).and_then(Option::as_mut) {
281 animation.stop();
282 }
283 self.animation = Arc::new(None);
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use crate::hardware::Board;
290 use crate::mocks::plugin_io::MockIoProtocol;
291 use crate::pause;
292
293 use super::*;
294
295 fn _setup_led(pin: u8) -> Led {
296 let board = Board::new(MockIoProtocol::default()); Led::new(&board, pin, false).unwrap()
298 }
299
300 #[test]
301 fn test_led_creation() {
302 let led = _setup_led(13);
303 assert_eq!(led.get_pin(), 13); assert_eq!(*led.state.read(), 0); assert_eq!(led.brightness, 0xFF); }
307
308 #[test]
309 fn test_turn_on() {
310 let mut led = _setup_led(13);
311 assert!(led.turn_on().is_ok()); assert_eq!(*led.state.read(), 0xFF); }
314
315 #[test]
316 fn test_turn_off() {
317 let mut led = _setup_led(13);
318 led.turn_on().unwrap(); assert!(led.turn_off().is_ok()); assert_eq!(*led.state.read(), 0); }
322
323 #[test]
324 fn test_toggle() {
325 let mut led = _setup_led(13);
326 assert!(led.toggle().is_ok()); assert_eq!(*led.state.read(), 0xFF); assert!(led.toggle().is_ok()); assert_eq!(*led.state.read(), 0); }
331
332 #[test]
333 fn test_set_state() {
334 let mut led = _setup_led(13);
335
336 assert!(led.set_state(State::Boolean(true)).is_ok());
337 assert_eq!(*led.state.read(), 0xFF); assert!(led.set_state(State::Boolean(false)).is_ok());
339 assert_eq!(*led.state.read(), 0x00); assert!(led.set_state(State::Integer(50)).is_ok());
342 assert_eq!(*led.state.read(), 50);
343 assert!(led.set_state(State::Float(60.0)).is_ok());
344 assert_eq!(*led.state.read(), 60);
345 assert!(led.set_state(State::Signed(70)).is_ok());
346 assert_eq!(*led.state.read(), 70);
347 assert!(led.set_state(State::Signed(-70)).is_ok());
348 assert_eq!(*led.state.read(), 0);
349
350 assert!(led
352 .set_state(State::String(String::from("incorrect format")))
353 .is_err()); let _ = led.protocol.set_pin_mode(led.pin, PinModeId::UNSUPPORTED);
358 assert!(led.set_state(State::Boolean(false)).is_err()); }
360
361 #[test]
362 fn test_brightness_calculation() {
363 let mut led = _setup_led(8);
364
365 led.pwm_mode = Some(PinMode {
367 id: Default::default(),
368 resolution: 10,
369 });
370
371 let led = led.set_brightness(0).unwrap();
373 assert_eq!(led.get_brightness(), 0);
374 assert_eq!(led.brightness, 0);
375 assert_eq!(*led.state.read(), 0);
376
377 let led = led.set_brightness(50).unwrap();
379 assert_eq!(led.get_brightness(), 50);
380 assert_eq!(led.brightness, 512);
381 assert_eq!(*led.state.read(), 512);
382
383 let led = led.set_brightness(100).unwrap();
385 assert_eq!(led.get_brightness(), 100);
386 assert_eq!(led.brightness, 1023);
387 assert_eq!(*led.state.read(), 1023);
388
389 let led = led.set_brightness(120).unwrap();
391 assert_eq!(led.get_brightness(), 100);
392 assert_eq!(led.brightness, 1023);
393 assert_eq!(*led.state.read(), 1023);
394 }
395
396 #[test]
397 fn test_set_brightness_valid() {
398 let result = _setup_led(8).set_brightness(50);
399 assert!(result.is_ok()); let mut led = result.unwrap();
401
402 assert_eq!(led.get_brightness(), 50); assert_eq!(led.brightness, 128); assert_eq!(*led.state.read(), 128); assert_eq!(led.get_brightness(), 50); assert_eq!(led.brightness, 128); assert_eq!(*led.state.read(), 128); assert!(led.set_state(State::Boolean(false)).is_ok());
411 assert_eq!(*led.state.read(), 0x00);
412 assert!(led.set_state(State::Boolean(true)).is_ok());
413 assert_eq!(*led.state.read(), 128); }
415
416 #[test]
417 fn test_set_brightness_incompatible_mode() {
418 let led = _setup_led(13);
419 assert_eq!(led.get_brightness(), 100);
420 let result = led.set_brightness(50);
421 assert!(result.is_err()); }
423
424 #[test]
425 fn test_default_value() {
426 let led = _setup_led(13);
427 assert_eq!(led.get_state().as_integer(), 0); let led = Led::new(&Board::new(MockIoProtocol::default()), 13, true).unwrap(); assert_eq!(led.get_default().as_integer(), 0xFF); assert_eq!(led.get_state().as_integer(), 0xFF); }
432
433 #[test]
434 fn test_get_pin_info() {
435 let led = _setup_led(13);
436 let pin_info = led.get_pin_info();
437 assert!(pin_info.is_ok()); }
439
440 #[hermes_five_macros::test]
441 fn test_led_blink() {
442 let mut led = _setup_led(13);
443 assert!(!led.is_busy());
444 led.stop(); led.blink(50); pause!(100);
447 assert!(led.is_busy()); led.stop();
449 assert!(!led.is_busy());
450 }
451
452 #[hermes_five_macros::test]
453 fn test_led_pulse() {
454 let mut led = _setup_led(8);
455 assert!(!led.is_busy());
456 led.stop(); led.pulse(50); pause!(100);
459 assert!(led.is_busy()); led.stop();
461 assert!(!led.is_busy());
462 }
463
464 #[hermes_five_macros::test]
465 fn test_animation() {
466 let mut led = _setup_led(8);
467 assert!(!led.is_busy());
468 led.stop();
470 led.animate(led.get_brightness(), 500, Easing::Linear);
472 pause!(100);
473 assert!(led.is_busy()); led.stop();
475 }
476
477 #[test]
478 fn test_is_on_off() {
479 let mut led = _setup_led(13);
480 assert!(!led.is_on()); assert!(led.is_off());
482 led.turn_on().unwrap();
483 assert!(led.is_on()); assert!(!led.is_off());
485 }
486
487 #[test]
488 fn test_display_impl() {
489 let led = _setup_led(13);
490 let display_str = format!("{}", led);
491 assert!(display_str.contains("LED (pin=13) [state=0, default=0, brightness=255]"));
492 }
493}