hermes_five/devices/output/
pwm.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::HardwareError::IncompatiblePin;
9use crate::errors::{Error, StateError};
10use crate::hardware::Hardware;
11use crate::io::{IoProtocol, Pin, PinIdOrName, PinModeId};
12use crate::utils::State;
13
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17#[derive(Clone, Debug)]
18pub struct PwmOutput {
19 pin: u8,
23 #[cfg_attr(feature = "serde", serde(with = "crate::devices::arc_rwlock_serde"))]
25 state: Arc<RwLock<u16>>,
26 default: u16,
28
29 #[cfg_attr(feature = "serde", serde(skip))]
33 max_value: u16,
34 #[cfg_attr(feature = "serde", serde(skip))]
36 protocol: Box<dyn IoProtocol>,
37 #[cfg_attr(feature = "serde", serde(skip))]
39 animation: Arc<Option<Animation>>,
40}
41
42impl PwmOutput {
43 pub fn new<T: Into<PinIdOrName>>(
49 board: &dyn Hardware,
50 pin: T,
51 default: u16,
52 ) -> Result<Self, Error> {
53 let pin = board.get_io().read().get_pin(pin)?.clone();
54
55 let mut output = Self {
56 pin: pin.id,
57 state: Arc::new(RwLock::new(default)),
58 default,
59 max_value: 0,
60 protocol: board.get_protocol(),
61 animation: Arc::new(None),
62 };
63
64 output.protocol.set_pin_mode(output.pin, PinModeId::PWM)?;
66
67 output.max_value = board
69 .get_io()
70 .read()
71 .get_pin(pin.id)?
72 .get_max_possible_value();
73
74 output.reset()?;
76
77 Ok(output)
78 }
79
80 pub fn set_value(&mut self, value: u16) -> Result<&Self, Error> {
82 self.set_state(value.into())?;
83 Ok(self)
84 }
85
86 pub fn set_percentage(&mut self, percentage: u8) -> Result<&Self, Error> {
89 let percentage = percentage.min(100) as u16;
90 let value = (percentage * self.max_value) / 100;
91 self.set_state(value.into())?;
92 Ok(self)
93 }
94
95 pub fn get_pin(&self) -> u8 {
100 self.pin
101 }
102
103 pub fn get_pin_info(&self) -> Result<Pin, Error> {
105 let lock = self.protocol.get_io().read();
106 Ok(lock.get_pin(self.pin)?.clone())
107 }
108
109 pub fn get_value(&self) -> u16 {
111 *self.state.read()
112 }
113
114 pub fn get_percentage(&self) -> u8 {
116 let value = *self.state.read();
117 ((value as f32 * 100.0) / self.max_value as f32).round() as u8
118 }
119}
120
121impl Display for PwmOutput {
122 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
123 write!(
124 f,
125 "PwmOutput (pin={}) [state={} ({}%), default={}]",
126 self.pin,
127 self.state.read(),
128 self.get_percentage(),
129 self.default,
130 )
131 }
132}
133
134#[cfg_attr(feature = "serde", typetag::serde)]
135impl Device for PwmOutput {}
136
137#[cfg_attr(feature = "serde", typetag::serde)]
138impl Output for PwmOutput {
139 fn get_state(&self) -> State {
140 (*self.state.read()).into()
141 }
142
143 fn set_state(&mut self, state: State) -> Result<State, Error> {
145 let value = match state {
146 State::Integer(value) => Ok(value as u16),
147 State::Signed(value) => match value >= 0 {
148 true => Ok(value as u16),
149 false => Err(StateError),
150 },
151 State::Float(value) => match value >= 0.0 {
152 true => Ok(value as u16),
153 false => Err(StateError),
154 },
155 _ => Err(StateError),
156 }?;
157
158 match self.get_pin_info()?.mode.id {
159 PinModeId::PWM => self.protocol.analog_write(self.pin, value),
160 id => Err(Error::from(IncompatiblePin {
161 mode: id,
162 pin: self.pin,
163 context: "update pwm output",
164 })),
165 }?;
166 *self.state.write() = value;
167 Ok(value.into())
168 }
169 fn get_default(&self) -> State {
170 self.default.into()
171 }
172 fn animate<S: Into<State>>(&mut self, state: S, duration: u64, transition: Easing) {
173 let mut animation = Animation::from(
174 Track::new(self.clone())
175 .with_keyframe(Keyframe::new(state, 0, duration).set_transition(transition)),
176 );
177 animation.play();
178 self.animation = Arc::new(Some(animation));
179 }
180 fn is_busy(&self) -> bool {
181 self.animation.is_some()
182 }
183 fn stop(&mut self) {
184 if let Some(animation) = Arc::get_mut(&mut self.animation).and_then(Option::as_mut) {
185 animation.stop();
186 }
187 self.animation = Arc::new(None);
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use crate::animations::Easing;
194 use crate::devices::output::pwm::PwmOutput;
195 use crate::devices::Output;
196 use crate::hardware::Board;
197 use crate::io::PinModeId;
198 use crate::mocks::plugin_io::MockIoProtocol;
199 use crate::pause;
200 use crate::utils::State;
201
202 #[test]
203 fn test_creation() {
204 let board = Board::new(MockIoProtocol::default());
205
206 let output = PwmOutput::new(&board, 8, 0).unwrap();
208 assert_eq!(output.get_pin(), 8);
209 assert_eq!(*output.state.read(), 0);
210 assert_eq!(output.get_state().as_integer(), 0);
211 assert_eq!(output.get_default().as_integer(), 0);
212
213 let output = PwmOutput::new(&board, 8, 50).unwrap();
215 assert_eq!(output.get_pin(), 8);
216 assert_eq!(*output.state.read(), 50);
217 assert_eq!(output.get_state().as_integer(), 50);
218 assert_eq!(output.get_default().as_integer(), 50);
219
220 let output = PwmOutput::new(&board, "D11", 50).unwrap();
222 assert_eq!(output.get_pin(), 11);
223 }
224
225 #[test]
226 fn test_set_value() {
227 let mut output = PwmOutput::new(&Board::new(MockIoProtocol::default()), 8, 0).unwrap();
228 output.set_value(127).unwrap();
229 assert_eq!(*output.state.read(), 127);
230 assert_eq!(output.get_value(), 127);
231 }
232
233 #[test]
234 fn test_set_percent() {
235 let mut output = PwmOutput::new(&Board::new(MockIoProtocol::default()), 8, 0).unwrap();
236 output.set_percentage(50).unwrap();
237 assert_eq!(*output.state.read(), 127);
238 assert_eq!(output.get_value(), 127);
239 assert_eq!(output.get_percentage(), 50);
240 output.set_percentage(200).unwrap();
241 assert_eq!(*output.state.read(), 0xFF);
242 assert_eq!(output.get_value(), 255);
243 assert_eq!(output.get_percentage(), 100);
244 }
245
246 #[test]
247 fn test_set_state() {
248 let mut output = PwmOutput::new(&Board::new(MockIoProtocol::default()), 11, 127).unwrap();
249 assert!(output.set_state(State::Integer(0)).is_ok());
250 assert_eq!(*output.state.read(), 0);
251 assert!(output.set_state(State::Integer(127)).is_ok());
252 assert_eq!(*output.state.read(), 127);
253
254 assert!(output.set_state(State::Signed(0)).is_ok());
255 assert_eq!(*output.state.read(), 0);
256 assert!(output.set_state(State::Signed(127)).is_ok());
257 assert_eq!(*output.state.read(), 127);
258 assert!(output.set_state(State::Signed(-42)).is_err());
259
260 assert!(output.set_state(State::Float(0.0)).is_ok());
261 assert_eq!(*output.state.read(), 0);
262 assert!(output.set_state(State::Float(127.0)).is_ok());
263 assert_eq!(*output.state.read(), 127);
264 assert!(output.set_state(State::Float(-42.0)).is_err());
265
266 assert!(output
267 .set_state(State::String(String::from("incorrect format")))
268 .is_err()); let _ = output
271 .protocol
272 .set_pin_mode(output.pin, PinModeId::UNSUPPORTED);
273 assert!(output.set_state(State::Integer(1)).is_err()); }
275
276 #[test]
277 fn test_get_pin_info() {
278 let output = PwmOutput::new(&Board::new(MockIoProtocol::default()), 11, 20).unwrap();
279 let pin_info = output.get_pin_info();
280 assert!(pin_info.is_ok());
281 assert_eq!(pin_info.unwrap().id, 11);
282 }
283
284 #[hermes_five_macros::test]
285 fn test_animation() {
286 let mut output = PwmOutput::new(&Board::new(MockIoProtocol::default()), 11, 20).unwrap();
287 assert!(!output.is_busy());
288 output.stop();
290 output.animate(true, 500, Easing::Linear);
292 pause!(100);
293 assert!(output.is_busy()); output.stop();
295 }
296
297 #[test]
298 fn test_display_impl() {
299 let mut output = PwmOutput::new(&Board::new(MockIoProtocol::default()), 11, 212).unwrap();
300 let _ = output.set_value(127);
301 let display_str = format!("{}", output);
302 assert_eq!(
303 display_str,
304 "PwmOutput (pin=11) [state=127 (50%), default=212]"
305 );
306 }
307}