hermes_five/devices/input/
digital.rs

1use std::fmt::{Display, Formatter};
2use std::sync::Arc;
3
4use parking_lot::RwLock;
5
6use crate::devices::input::{Input, InputEvent};
7use crate::devices::Device;
8use crate::errors::Error;
9use crate::hardware::Hardware;
10use crate::io::{IoProtocol, PinIdOrName, PinModeId};
11use crate::pause;
12use crate::utils::{task, EventHandler, EventManager, State, TaskHandler};
13
14/// Represents a digital sensor of unspecified type: an [`Input`] [`Device`] that reads digital values
15/// from an INPUT compatible pin.
16/// <https://docs.arduino.cc/built-in-examples/digital/DigitalInput>
17#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
18#[derive(Clone, Debug)]
19pub struct DigitalInput {
20    // ########################################
21    // # Basics
22    /// The pin (id) of the [`Board`] used to read the digital value.
23    pin: u8,
24    /// The current digital state.
25    #[cfg_attr(feature = "serde", serde(with = "crate::devices::arc_rwlock_serde"))]
26    state: Arc<RwLock<bool>>,
27
28    // ########################################
29    // # Volatile utility data.
30    #[cfg_attr(feature = "serde", serde(skip))]
31    protocol: Box<dyn IoProtocol>,
32    /// Inner handler to the task running the button value check.
33    #[cfg_attr(feature = "serde", serde(skip))]
34    handler: Arc<RwLock<Option<TaskHandler>>>,
35    /// The event manager for the DigitalInput.
36    #[cfg_attr(feature = "serde", serde(skip))]
37    events: EventManager,
38}
39
40impl DigitalInput {
41    /// Creates an instance of a [`DigitalInput`] attached to a given board.
42    ///
43    /// # Errors
44    /// * `UnknownPin`: this function will bail an error if the DigitalInput pin does not exist for this board.
45    /// * `IncompatiblePin`: this function will bail an error if the DigitalInput pin does not support ANALOG mode.
46    pub fn new<T: Into<PinIdOrName>>(board: &dyn Hardware, pin: T) -> Result<Self, Error> {
47        let pin = board.get_io().read().get_pin(pin)?.clone();
48
49        let mut sensor = Self {
50            pin: pin.id,
51            state: Arc::new(RwLock::new(pin.value != 0)),
52            protocol: board.get_protocol(),
53            handler: Arc::new(RwLock::new(None)),
54            events: Default::default(),
55        };
56
57        // Set pin mode to INPUT.
58        sensor.protocol.set_pin_mode(sensor.pin, PinModeId::INPUT)?;
59
60        // Set reporting for this pin.
61        sensor.protocol.report_digital(sensor.pin, true)?;
62
63        // Attaches the event handler.
64        sensor.attach();
65
66        Ok(sensor)
67    }
68
69    // ########################################
70    // Getters and Setters
71
72    /// Returns the pin (id) used by the device.
73    pub fn get_pin(&self) -> u8 {
74        self.pin
75    }
76
77    // ########################################
78    // Event related functions
79
80    /// Manually attaches the DigitalInput with the value change events.
81    /// This should never be needed unless you manually `detach()` the DigitalInput first for some reason
82    /// and want it to start being reactive to events again.
83    pub fn attach(&self) {
84        if self.handler.read().is_none() {
85            let self_clone = self.clone();
86            *self.handler.write() = Some(
87                task::run(async move {
88                    loop {
89                        let pin_value = self_clone
90                            .protocol
91                            .get_io()
92                            .read()
93                            .get_pin(self_clone.pin)?
94                            .value
95                            != 0;
96                        let state_value = *self_clone.state.read();
97                        if pin_value != state_value {
98                            *self_clone.state.write() = pin_value;
99                            self_clone.events.emit(InputEvent::OnChange, pin_value);
100                            match pin_value {
101                                true => self_clone.events.emit(InputEvent::OnHigh, ()),
102                                false => self_clone.events.emit(InputEvent::OnLow, ()),
103                            }
104                        }
105
106                        // Change can only be done 10x a sec. to avoid bouncing.
107                        pause!(100);
108                    }
109                    #[allow(unreachable_code)]
110                    Ok(())
111                })
112                .unwrap(),
113            );
114        }
115    }
116
117    /// Detaches the interval associated with the DigitalInput.
118    /// This means the DigitalInput won't react anymore to value changes.
119    pub fn detach(&self) {
120        if let Some(handler) = self.handler.read().as_ref() {
121            handler.abort();
122        }
123        *self.handler.write() = None
124    }
125
126    /// Registers a callback to be executed on a given event on the DigitalInput.
127    ///
128    /// Available events for a button are:
129    /// - **`InputEvent::OnChange` | `change`:** Triggered when the input value changes.    
130    ///   _The callback must receive the following parameter: `|value: bool| { ... }`_
131    /// - **`InputEvent::OnHigh` | `high`:** Triggered when the input value changes.     
132    ///   _The callback must receive the void parameter: `|_:()| { ... }`_
133    ///- **`InputEvent::OnLow` | `low`:** Triggered when the input value changes.    
134    ///   _The callback must receive the void parameter: `|_:()| { ... }`_
135    ///
136    /// # Example
137    ///
138    /// ```
139    /// use hermes_five::devices::{DigitalInput, InputEvent};
140    /// use hermes_five::hardware::{Board, BoardEvent};
141    ///
142    /// #[hermes_five::runtime]
143    /// async fn main() {
144    ///     let board = Board::run();
145    ///     board.on(BoardEvent::OnReady, |board: Board| async move {
146    ///
147    ///         // Register a sensor on pin 7.
148    ///         let sensor = DigitalInput::new(&board, 7)?;
149    ///         // Triggered function when the sensor state changes.
150    ///         sensor.on(InputEvent::OnChange, |value: bool| async move {
151    ///             println!("Sensor value changed: {}", value);
152    ///             Ok(())
153    ///         });
154    ///
155    ///         // The above code will run forever.
156    ///         // <do something useful>
157    ///
158    ///         // The above code will run forever runs a listener on the pin state under-the-hood.
159    ///         // It means the program will run forever listening to the InputEvent,
160    ///         // until we detach the device and close the board.
161    ///         sensor.detach();
162    ///         board.close();
163    ///
164    ///         Ok(())
165    ///     });
166    /// }
167    /// ```
168    pub fn on<S, F, T, Fut>(&self, event: S, callback: F) -> EventHandler
169    where
170        S: Into<String>,
171        T: 'static + Send + Sync + Clone,
172        F: FnMut(T) -> Fut + Send + 'static,
173        Fut: std::future::Future<Output = Result<(), Error>> + Send + 'static,
174    {
175        self.events.on(event, callback)
176    }
177}
178
179impl Display for DigitalInput {
180    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
181        write!(
182            f,
183            "DigitalInput (pin={}) [state={}]",
184            self.pin,
185            self.state.read(),
186        )
187    }
188}
189
190#[cfg_attr(feature = "serde", typetag::serde)]
191impl Device for DigitalInput {}
192
193#[cfg_attr(feature = "serde", typetag::serde)]
194impl Input for DigitalInput {
195    fn get_state(&self) -> State {
196        State::from(*self.state.read())
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use crate::devices::input::digital::DigitalInput;
203    use crate::devices::input::Input;
204    use crate::devices::input::InputEvent;
205    use crate::hardware::Board;
206    use crate::mocks::plugin_io::MockIoProtocol;
207    use crate::pause;
208    use std::sync::atomic::{AtomicBool, Ordering};
209    use std::sync::Arc;
210
211    #[hermes_five_macros::test]
212    fn test_new_digital_input() {
213        let board = Board::new(MockIoProtocol::default());
214        let sensor = DigitalInput::new(&board, 2).unwrap();
215        assert_eq!(sensor.get_pin(), 2);
216        assert!(sensor.get_state().as_bool());
217        sensor.detach();
218
219        let sensor = DigitalInput::new(&board, "D3").unwrap();
220        assert_eq!(sensor.get_pin(), 3);
221        assert!(sensor.get_state().as_bool());
222
223        sensor.detach();
224        board.close();
225    }
226
227    #[hermes_five_macros::test]
228    fn test_digital_display() {
229        let board = Board::new(MockIoProtocol::default());
230        let sensor = DigitalInput::new(&board, "D5").unwrap();
231        assert!(!sensor.get_state().as_bool());
232        assert_eq!(
233            format!("{}", sensor),
234            String::from("DigitalInput (pin=5) [state=false]")
235        );
236
237        sensor.detach();
238        board.close();
239    }
240
241    #[hermes_five_macros::test]
242    fn test_digital_events() {
243        let board = Board::new(MockIoProtocol::default());
244        let button = DigitalInput::new(&board, 5).unwrap();
245
246        // CHANGE
247        let change_flag = Arc::new(AtomicBool::new(false));
248        let moved_change_flag = change_flag.clone();
249        button.on(InputEvent::OnChange, move |new_state: bool| {
250            let captured_flag = moved_change_flag.clone();
251            async move {
252                captured_flag.store(new_state, Ordering::SeqCst);
253                Ok(())
254            }
255        });
256
257        // HIGH
258        let high_flag = Arc::new(AtomicBool::new(false));
259        let moved_high_flag = high_flag.clone();
260        button.on(InputEvent::OnHigh, move |_: ()| {
261            let captured_flag = moved_high_flag.clone();
262            async move {
263                captured_flag.store(true, Ordering::SeqCst);
264                Ok(())
265            }
266        });
267
268        // LOW
269        let low_flag = Arc::new(AtomicBool::new(false));
270        let moved_low_flag = low_flag.clone();
271        button.on(InputEvent::OnLow, move |_: ()| {
272            let captured_flag = moved_low_flag.clone();
273            async move {
274                captured_flag.store(true, Ordering::SeqCst);
275                Ok(())
276            }
277        });
278
279        assert!(!change_flag.load(Ordering::SeqCst));
280        assert!(!high_flag.load(Ordering::SeqCst));
281        assert!(!low_flag.load(Ordering::SeqCst));
282
283        // Simulate pin state change in the protocol => take value 0xFF
284        button
285            .protocol
286            .get_io()
287            .write()
288            .get_pin_mut(5)
289            .unwrap()
290            .value = 0xFF;
291
292        pause!(500);
293
294        assert!(change_flag.load(Ordering::SeqCst));
295        assert!(high_flag.load(Ordering::SeqCst));
296        assert!(!low_flag.load(Ordering::SeqCst));
297
298        // Simulate pin state change in the protocol => takes value 0
299        button
300            .protocol
301            .get_io()
302            .write()
303            .get_pin_mut(5)
304            .unwrap()
305            .value = 0;
306
307        pause!(500);
308
309        assert!(!change_flag.load(Ordering::SeqCst)); // change switched back to 0
310        assert!(low_flag.load(Ordering::SeqCst));
311
312        button.detach();
313        board.close();
314    }
315}