hermes_five/devices/input/
digital.rs1use 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#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
18#[derive(Clone, Debug)]
19pub struct DigitalInput {
20 pin: u8,
24 #[cfg_attr(feature = "serde", serde(with = "crate::devices::arc_rwlock_serde"))]
26 state: Arc<RwLock<bool>>,
27
28 #[cfg_attr(feature = "serde", serde(skip))]
31 protocol: Box<dyn IoProtocol>,
32 #[cfg_attr(feature = "serde", serde(skip))]
34 handler: Arc<RwLock<Option<TaskHandler>>>,
35 #[cfg_attr(feature = "serde", serde(skip))]
37 events: EventManager,
38}
39
40impl DigitalInput {
41 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 sensor.protocol.set_pin_mode(sensor.pin, PinModeId::INPUT)?;
59
60 sensor.protocol.report_digital(sensor.pin, true)?;
62
63 sensor.attach();
65
66 Ok(sensor)
67 }
68
69 pub fn get_pin(&self) -> u8 {
74 self.pin
75 }
76
77 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 pause!(100);
108 }
109 #[allow(unreachable_code)]
110 Ok(())
111 })
112 .unwrap(),
113 );
114 }
115 }
116
117 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 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 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 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 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 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 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)); assert!(low_flag.load(Ordering::SeqCst));
311
312 button.detach();
313 board.close();
314 }
315}