rustmo_server/
virtual_device.rs

1use serde_json::Error;
2use std::fmt::Debug;
3use std::sync::{Arc, Mutex};
4
5pub struct VirtualDeviceError(pub String);
6
7impl VirtualDeviceError {
8    pub fn new(message: &'static str) -> Self {
9        VirtualDeviceError(message.to_string())
10    }
11
12    pub fn from(message: String) -> Self {
13        VirtualDeviceError(message)
14    }
15}
16
17impl std::convert::From<std::io::Error> for VirtualDeviceError {
18    fn from(e: std::io::Error) -> Self {
19        VirtualDeviceError::from(e.to_string())
20    }
21}
22
23impl std::convert::From<std::ffi::FromBytesWithNulError> for VirtualDeviceError {
24    fn from(e: std::ffi::FromBytesWithNulError) -> Self {
25        VirtualDeviceError::from(e.to_string())
26    }
27}
28
29impl std::convert::From<reqwest::Error> for VirtualDeviceError {
30    fn from(e: reqwest::Error) -> Self {
31        VirtualDeviceError::from(e.to_string())
32    }
33}
34
35impl std::convert::From<serde_json::error::Error> for VirtualDeviceError {
36    fn from(e: Error) -> Self {
37        VirtualDeviceError::from(e.to_string())
38    }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq)]
42pub enum VirtualDeviceState {
43    /// the device is on
44    On,
45
46    /// the device is off
47    Off,
48}
49
50///
51/// `WrappedVirtualDevice` represents a `VirtualDevice` implementaiton
52/// that is reference counted and guarded by a mutex, so that it can
53/// be shared across threads
54///
55pub type WrappedVirtualDevice = Arc<Mutex<Box<dyn VirtualDevice>>>;
56
57///
58/// The `VirtualDevice` trait allows implementors to create devices that
59/// can be exposed to Alexa via `RustmoServer`
60///
61/// Rustmo pretends that devices are a "plug", so they only have two states:
62/// On and Off.
63///
64/// Some implementation notes:
65///
66///   1) Alexa will consider a device to be unresponsive if a request takes longer than 5 seconds.
67///
68///   2) When Alexa changes the state ("Alexa, turn $device ON/OFF") via `::turn_on()` or `::turn_off`,
69/// it will then immediately check the state via `::check_is_on()`.  If that request doesn't match
70/// what you just told Alexa to do, it will consider the device to be malfunctioning.
71///
72///   3) `RustmoServer` provides helper methods for wrapped devices so they can automatically poll
73/// to make sure the desired state matches reality, or to just blindly pretend that the
74/// state change worked.
75///
76///   4) It's best to implement `::turn_on()` and `::turn_off()` to execute as quickly as possible
77/// and use one of the helper methods in `RustmoServer` to provide (slightly) more sophisticated
78/// status verification.
79///
80pub trait VirtualDevice: Sync + Send + 'static {
81    /// turn the device on
82    fn turn_on(&mut self) -> Result<VirtualDeviceState, VirtualDeviceError>;
83
84    /// turn the device off
85    fn turn_off(&mut self) -> Result<VirtualDeviceState, VirtualDeviceError>;
86
87    /// is the device on?
88    fn check_is_on(&mut self) -> Result<VirtualDeviceState, VirtualDeviceError>;
89}
90
91pub(crate) mod wrappers {
92    use std::sync::atomic::{AtomicBool, Ordering};
93    use std::thread;
94    use std::time::Duration;
95
96    use rayon::prelude::*;
97
98    use crate::virtual_device::{
99        VirtualDevice, VirtualDeviceError, VirtualDeviceState, WrappedVirtualDevice,
100    };
101
102    ///
103    /// Wrapper for `VirtualDevice` that pretends the device is instantly turned on when
104    /// Alexa calls `::turn_on()`.
105    ///
106    pub(crate) struct InstantOnDevice {
107        pub(crate) device: Box<dyn VirtualDevice>,
108        pub(crate) instant: bool,
109    }
110
111    impl VirtualDevice for InstantOnDevice {
112        fn turn_on(&mut self) -> Result<VirtualDeviceState, VirtualDeviceError> {
113            let result = self.device.turn_on();
114            self.instant = true;
115
116            result
117        }
118
119        fn turn_off(&mut self) -> Result<VirtualDeviceState, VirtualDeviceError> {
120            let result = self.device.turn_off();
121            self.instant = false;
122
123            result
124        }
125
126        fn check_is_on(&mut self) -> Result<VirtualDeviceState, VirtualDeviceError> {
127            let result = self.device.check_is_on();
128
129            if self.instant {
130                if let VirtualDeviceState::On = result.unwrap_or(VirtualDeviceState::Off) {
131                    self.instant = false;
132                }
133                return Ok(VirtualDeviceState::On);
134            }
135
136            result
137        }
138    }
139
140    ///
141    /// Wrapper for `VirtualDevice` that polls the device for its status, up to ~4 seconds, to
142    /// ensure the state has changed to what Alexa requested
143    ///
144    pub(crate) struct PollingDevice {
145        pub(crate) device: Box<dyn VirtualDevice>,
146    }
147
148    impl VirtualDevice for PollingDevice {
149        fn turn_on(&mut self) -> Result<VirtualDeviceState, VirtualDeviceError> {
150            self.device.turn_on()?;
151
152            let mut state = self.device.check_is_on().unwrap_or(VirtualDeviceState::Off);
153            match state {
154                VirtualDeviceState::Off => {
155                    let mut cnt = 0;
156                    while state.eq(&VirtualDeviceState::Off) {
157                        println!("POLLING for 'on': cnt={}", cnt);
158
159                        thread::sleep(Duration::from_millis(400));
160                        state = self.device.check_is_on().unwrap_or(VirtualDeviceState::Off);
161                        cnt += 1;
162                        if cnt == 10 {
163                            break;
164                        }
165                    }
166                    Ok(state)
167                }
168                _ => Ok(state),
169            }
170        }
171
172        fn turn_off(&mut self) -> Result<VirtualDeviceState, VirtualDeviceError> {
173            self.device.turn_off()?;
174
175            let mut state = self.device.check_is_on().unwrap_or(VirtualDeviceState::On);
176            match state {
177                VirtualDeviceState::On => {
178                    let mut cnt = 0;
179                    while state.eq(&VirtualDeviceState::On) {
180                        println!("POLLING for 'off': cnt={}", cnt);
181                        thread::sleep(Duration::from_millis(400));
182
183                        state = self.device.check_is_on().unwrap_or(VirtualDeviceState::On);
184                        cnt += 1;
185                        if cnt == 10 {
186                            break;
187                        }
188                    }
189                    Ok(state)
190                }
191                _ => Ok(state),
192            }
193        }
194
195        fn check_is_on(&mut self) -> Result<VirtualDeviceState, VirtualDeviceError> {
196            self.device.check_is_on()
197        }
198    }
199
200    ///
201    /// Wrapper for `VirtualDevice` that allows a list of devices to work together as a single
202    /// device.
203    ///
204    /// All state changes and inqueries to the underlying devices happen in parallel
205    ///
206    pub(crate) struct CompositeDevice {
207        pub(crate) devices: Vec<WrappedVirtualDevice>,
208    }
209
210    impl VirtualDevice for CompositeDevice {
211        fn turn_on(&mut self) -> Result<VirtualDeviceState, VirtualDeviceError> {
212            self.devices.par_iter_mut().for_each(|device| {
213                if let Ok(mut device) = device.lock() {
214                    if device.check_is_on().unwrap_or(VirtualDeviceState::Off)
215                        == VirtualDeviceState::Off
216                    {
217                        device.turn_on().ok().unwrap();
218                    }
219                }
220            });
221
222            self.check_is_on()
223        }
224
225        fn turn_off(&mut self) -> Result<VirtualDeviceState, VirtualDeviceError> {
226            self.devices.par_iter_mut().for_each(|device| {
227                if let Ok(mut device) = device.lock() {
228                    if device.check_is_on().unwrap_or(VirtualDeviceState::Off)
229                        == VirtualDeviceState::On
230                    {
231                        device.turn_off().ok().unwrap();
232                    }
233                }
234            });
235
236            self.check_is_on()
237        }
238
239        fn check_is_on(&mut self) -> Result<VirtualDeviceState, VirtualDeviceError> {
240            let on = AtomicBool::new(true);
241            self.devices.par_iter_mut().for_each(|device| {
242                if let Ok(mut device) = device.lock() {
243                    match device.check_is_on().unwrap_or(VirtualDeviceState::Off) {
244                        VirtualDeviceState::On => {
245                            on.compare_and_swap(true, true, Ordering::SeqCst);
246                        }
247                        VirtualDeviceState::Off => {
248                            on.store(false, Ordering::SeqCst);
249                        }
250                    }
251                }
252            });
253
254            if on.load(Ordering::SeqCst) {
255                Ok(VirtualDeviceState::On)
256            } else {
257                Ok(VirtualDeviceState::Off)
258            }
259        }
260    }
261
262    ///
263    /// Wrapper for `VirtualDevice` that allows a device to be implemented using closures
264    pub(crate) struct FunctionalDevice<TurnOn, TurnOff, CheckIsOn>
265    where
266        TurnOn: FnMut() -> Result<VirtualDeviceState, VirtualDeviceError> + Sync + Send + 'static,
267        TurnOff: FnMut() -> Result<VirtualDeviceState, VirtualDeviceError> + Sync + Send + 'static,
268        CheckIsOn:
269            FnMut() -> Result<VirtualDeviceState, VirtualDeviceError> + Sync + Send + 'static,
270    {
271        pub(crate) turn_on: TurnOn,
272        pub(crate) turn_off: TurnOff,
273        pub(crate) check_is_on: CheckIsOn,
274    }
275
276    impl<TurnOn, TurnOff, CheckIsOn> VirtualDevice for FunctionalDevice<TurnOn, TurnOff, CheckIsOn>
277    where
278        TurnOn: FnMut() -> Result<VirtualDeviceState, VirtualDeviceError> + Sync + Send + 'static,
279        TurnOff: FnMut() -> Result<VirtualDeviceState, VirtualDeviceError> + Sync + Send + 'static,
280        CheckIsOn:
281            FnMut() -> Result<VirtualDeviceState, VirtualDeviceError> + Sync + Send + 'static,
282    {
283        fn turn_on(&mut self) -> Result<VirtualDeviceState, VirtualDeviceError> {
284            (self.turn_on)()
285        }
286
287        fn turn_off(&mut self) -> Result<VirtualDeviceState, VirtualDeviceError> {
288            (self.turn_off)()
289        }
290
291        fn check_is_on(&mut self) -> Result<VirtualDeviceState, VirtualDeviceError> {
292            (self.check_is_on)()
293        }
294    }
295}