hermes_five/devices/output/
digital.rs1use std::fmt::{Display, Formatter};
2use std::sync::Arc;
3
4use parking_lot::RwLock;
5
6use crate::animations::{Animation, Easing, Keyframe, Track};
7use crate::devices::{Device, Output};
8use crate::errors::{Error, HardwareError, StateError};
9use crate::hardware::Hardware;
10use crate::io::{IoProtocol, Pin, PinIdOrName, PinModeId};
11use crate::utils::State;
12
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17#[derive(Clone, Debug)]
18pub struct DigitalOutput {
19 pin: u8,
23 #[cfg_attr(feature = "serde", serde(with = "crate::devices::arc_rwlock_serde"))]
25 state: Arc<RwLock<bool>>,
26 default: bool,
28
29 #[cfg_attr(feature = "serde", serde(skip))]
32 protocol: Box<dyn IoProtocol>,
33 #[cfg_attr(feature = "serde", serde(skip))]
35 animation: Arc<Option<Animation>>,
36}
37
38impl DigitalOutput {
39 pub fn new<T: Into<PinIdOrName>>(
45 board: &dyn Hardware,
46 pin: T,
47 default: bool,
48 ) -> Result<Self, Error> {
49 let pin = board.get_io().read().get_pin(pin)?.clone();
50
51 let mut output = Self {
52 pin: pin.id,
53 state: Arc::new(RwLock::new(default)),
54 default,
55 protocol: board.get_protocol(),
56 animation: Arc::new(None),
57 };
58
59 output
61 .protocol
62 .set_pin_mode(output.pin, PinModeId::OUTPUT)?;
63
64 output.reset()?;
66
67 Ok(output)
68 }
69
70 pub fn turn_on(&mut self) -> Result<&Self, Error> {
72 self.set_state(State::Boolean(true))?;
73 Ok(self)
74 }
75
76 pub fn turn_off(&mut self) -> Result<&Self, Error> {
78 self.set_state(State::Boolean(false))?;
79 Ok(self)
80 }
81
82 pub fn toggle(&mut self) -> Result<&Self, Error> {
84 match self.is_high() {
85 true => self.turn_off(),
86 false => self.turn_on(),
87 }
88 }
89
90 pub fn get_pin(&self) -> u8 {
95 self.pin
96 }
97
98 pub fn get_pin_info(&self) -> Result<Pin, Error> {
100 let lock = self.protocol.get_io().read();
101 Ok(lock.get_pin(self.pin)?.clone())
102 }
103
104 pub fn is_high(&self) -> bool {
106 *self.state.read()
107 }
108
109 pub fn is_low(&self) -> bool {
111 !*self.state.read()
112 }
113}
114
115impl Display for DigitalOutput {
116 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
117 write!(
118 f,
119 "DigitalOutput (pin={}) [state={}, default={}]",
120 self.pin,
121 self.state.read(),
122 self.default,
123 )
124 }
125}
126
127#[cfg_attr(feature = "serde", typetag::serde)]
128impl Device for DigitalOutput {}
129
130#[cfg_attr(feature = "serde", typetag::serde)]
131impl Output for DigitalOutput {
132 fn get_state(&self) -> State {
133 (*self.state.read()).into()
134 }
135
136 fn set_state(&mut self, state: State) -> Result<State, Error> {
138 let value = match state {
139 State::Boolean(value) => Ok(value),
140 State::Integer(value) => match value {
141 0 => Ok(false),
142 1 => Ok(true),
143 _ => Err(StateError),
144 },
145 _ => Err(StateError),
146 }?;
147
148 match self.get_pin_info()?.mode.id {
149 PinModeId::OUTPUT => self.protocol.digital_write(self.pin, value),
151 id => Err(Error::from(HardwareError::IncompatiblePin {
152 mode: id,
153 pin: self.pin,
154 context: "update digital output",
155 })),
156 }?;
157 *self.state.write() = value;
158 Ok(value.into())
159 }
160
161 fn get_default(&self) -> State {
162 self.default.into()
163 }
164
165 fn animate<S: Into<State>>(&mut self, state: S, duration: u64, transition: Easing) {
166 let mut animation = Animation::from(
167 Track::new(self.clone())
168 .with_keyframe(Keyframe::new(state, 0, duration).set_transition(transition)),
169 );
170 animation.play();
171 self.animation = Arc::new(Some(animation));
172 }
173
174 fn is_busy(&self) -> bool {
175 self.animation.is_some()
176 }
177
178 fn stop(&mut self) {
179 if let Some(animation) = Arc::get_mut(&mut self.animation).and_then(Option::as_mut) {
180 animation.stop();
181 }
182 self.animation = Arc::new(None);
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use crate::animations::Easing;
189 use crate::devices::output::digital::DigitalOutput;
190 use crate::devices::Output;
191 use crate::hardware::Board;
192 use crate::io::PinModeId;
193 use crate::mocks::plugin_io::MockIoProtocol;
194 use crate::pause;
195 use crate::utils::State;
196
197 #[test]
198 fn test_creation() {
199 let board = Board::new(MockIoProtocol::default());
200
201 let output = DigitalOutput::new(&board, 13, false).unwrap();
203 assert_eq!(output.get_pin(), 13);
204 assert!(!*output.state.read());
205 assert!(!output.get_state().as_bool());
206 assert!(!output.get_default().as_bool());
207 assert!(output.is_low());
208 assert!(!output.is_high());
209
210 let output = DigitalOutput::new(&board, 4, true).unwrap();
212 assert_eq!(output.get_pin(), 4);
213 assert!(*output.state.read());
214 assert!(output.get_state().as_bool());
215 assert!(output.get_default().as_bool());
216 assert!(output.is_high());
217 assert!(!output.is_low());
218
219 let output = DigitalOutput::new(&board, "D13", true).unwrap();
221 assert_eq!(output.get_pin(), 13);
222
223 let output = DigitalOutput::new(&board, "A14", false).unwrap();
225 assert_eq!(output.get_pin(), 14);
226 }
227
228 #[test]
229 fn test_set_high() {
230 let mut output =
231 DigitalOutput::new(&Board::new(MockIoProtocol::default()), 4, false).unwrap();
232 output.turn_on().unwrap();
233 assert!(output.turn_on().is_ok());
234 assert!(*output.state.read());
235 }
236
237 #[test]
238 fn test_set_low() {
239 let mut output =
240 DigitalOutput::new(&Board::new(MockIoProtocol::default()), 5, true).unwrap();
241 assert!(output.turn_off().is_ok());
242 assert!(!*output.state.read());
243 }
244
245 #[test]
246 fn test_toggle() {
247 let mut output =
248 DigitalOutput::new(&Board::new(MockIoProtocol::default()), 5, false).unwrap();
249 assert!(output.toggle().is_ok()); assert!(*output.state.read());
251 assert!(output.toggle().is_ok()); assert!(!*output.state.read());
253 }
254
255 #[test]
256 fn test_set_state() {
257 let mut output =
258 DigitalOutput::new(&Board::new(MockIoProtocol::default()), 13, false).unwrap();
259 assert!(output.set_state(State::Boolean(true)).is_ok());
260 assert!(*output.state.read());
261 assert!(output.set_state(State::Boolean(false)).is_ok());
262 assert!(!*output.state.read());
263
264 assert!(output.set_state(State::Integer(1)).is_ok());
265 assert!(*output.state.read());
266 assert!(output.set_state(State::Integer(0)).is_ok());
267 assert!(!*output.state.read());
268 assert!(output.set_state(State::Integer(42)).is_err());
269
270 assert!(output
271 .set_state(State::String(String::from("incorrect format")))
272 .is_err()); let _ = output
275 .protocol
276 .set_pin_mode(output.pin, PinModeId::UNSUPPORTED);
277 assert!(output.set_state(State::Boolean(false)).is_err()); }
279
280 #[test]
281 fn test_get_pin_info() {
282 let output = DigitalOutput::new(&Board::new(MockIoProtocol::default()), 13, false).unwrap();
283 let pin_info = output.get_pin_info();
284 assert!(pin_info.is_ok());
285 assert_eq!(pin_info.unwrap().id, 13);
286 }
287
288 #[hermes_five_macros::test]
289 fn test_animation() {
290 let mut output =
291 DigitalOutput::new(&Board::new(MockIoProtocol::default()), 13, false).unwrap();
292 assert!(!output.is_busy());
293 output.stop();
295 output.animate(true, 500, Easing::Linear);
297 pause!(100);
298 assert!(output.is_busy()); output.stop();
300 }
301
302 #[test]
303 fn test_display_impl() {
304 let mut output =
305 DigitalOutput::new(&Board::new(MockIoProtocol::default()), 13, true).unwrap();
306 let _ = output.turn_off();
307 let display_str = format!("{}", output);
308 assert_eq!(
309 display_str,
310 "DigitalOutput (pin=13) [state=false, default=true]"
311 );
312 }
313}