rustmo_server/
lib.rs

1#[macro_use]
2extern crate serde_derive;
3
4use std::fmt::Debug;
5use std::net::{IpAddr, Ipv4Addr, SocketAddr};
6use std::sync::{Arc, Mutex};
7use std::thread;
8
9use uuid::Uuid;
10
11use crate::ssdp::SsdpListener;
12use crate::upnp::*;
13use crate::virtual_device::wrappers::*;
14use crate::virtual_device::*;
15
16mod ssdp;
17mod upnp;
18pub mod virtual_device;
19
20#[derive(Clone)]
21pub(crate) struct RustmoDevice {
22    pub(crate) name: String,
23    pub(crate) ip_address: IpAddr,
24    pub(crate) port: u16,
25    pub(crate) uuid: Uuid,
26    pub(crate) virtual_device: WrappedVirtualDevice,
27}
28
29impl RustmoDevice {
30    pub fn new<T: VirtualDevice>(
31        name: &str,
32        ip_address: IpAddr,
33        port: u16,
34        virtual_device: T,
35    ) -> Self {
36        let mut bytes = Vec::from(name.as_bytes());
37        while bytes.len() < 16 {
38            bytes.push(bytes.len() as u8);
39        }
40        while bytes.len() > 16 {
41            bytes.pop();
42        }
43
44        let device = RustmoDevice {
45            name: name.to_string(),
46            ip_address,
47            port,
48            uuid: Uuid::from_slice(bytes.as_slice())
49                .expect("failed to generate UUID"),
50            virtual_device: Arc::new(Mutex::new(Box::new(virtual_device))),
51        };
52
53        let cloned = device.clone();
54        thread::spawn(move || {
55            let server = hyper::Server::http(SocketAddr::new(ip_address, port)).unwrap();
56            server.handle(DeviceHttpServerHandler::new(cloned)).unwrap();
57        });
58
59        device
60    }
61}
62
63///
64/// Create a `RustmoServer` and add devices to make them discoverable, and controllable via Alexa.
65///
66/// Each `VirtualDevice` you wish to expose requires a backing HTTP server (Rustmo takes care of
67/// this for you).  This is the reason why the various `::add_xxx_device()` methods require a `port`
68/// number.
69///
70/// Note that each device must be assigned a unique `port` number.
71///
72/// `RustmoServer` also creates a multicast UDP socket listener to implement the SSDP-based device
73/// discovery protocol required by Alexa -- this listens on port 1900.
74///
75pub struct RustmoServer {
76    devices: Arc<Mutex<Vec<RustmoDevice>>>,
77    ip_address: Ipv4Addr,
78    ssdp_listener: SsdpListener,
79}
80
81#[derive(Debug)]
82pub enum RustmoError {
83    DeviceAlreadyExistsOnPort(u16),
84    DeviceAlreadyExistsByName(String),
85}
86
87impl RustmoServer {
88    ///
89    /// Create a new `RustmoServer` and listen for SSDP requests on the specified network interface
90    ///
91    pub fn new(interface: Ipv4Addr) -> Self {
92        let devices = Arc::new(Mutex::new(Vec::new()));
93
94        RustmoServer {
95            devices: devices.clone(),
96            ip_address: interface,
97            ssdp_listener: SsdpListener::listen(interface, devices),
98        }
99    }
100
101    ///
102    /// Add a `VirtualDevice` to make it discoverable and controllable.
103    ///
104    /// `@name`:  The word or phrase you'll use when talking to Alexa to control this device
105    /// `@port`:  The port on which the backing HTTP server will listen for UPNP requests
106    /// `@virtual_device`:  A `VirtualDevice` implementation
107    ///
108    pub fn add_device<T: VirtualDevice>(
109        &mut self,
110        name: &str,
111        port: u16,
112        virtual_device: T,
113    ) -> Result<WrappedVirtualDevice, RustmoError> {
114        self.internal_add_device(name, IpAddr::V4(self.ip_address), port, virtual_device)
115    }
116
117    ///
118    /// Add a `VirtualDevice` to make it discoverable and controllable.
119    ///
120    /// This version wraps the provided `VirtualDevice` such that it will poll (via
121    /// `::check_is_on()`), up to 4 seconds, whenever `::turn_on()` or `::turn_off()` is called.
122    ///
123    /// This form is useful when controlling a physical device that takes a few seconds (but
124    /// less than 5) for its state to register as changed.
125    ///
126    /// `@name`:  The word or phrase you'll use when talking to Alexa to control this device
127    /// `@port`:  The port on which the backing HTTP server will listen for UPNP requests
128    /// `@virtual_device`:  A `VirtualDevice` implementation
129    ///
130    pub fn add_polling_device<T: VirtualDevice>(
131        &mut self,
132        name: &str,
133        port: u16,
134        virtual_device: T,
135    ) -> Result<WrappedVirtualDevice, RustmoError> {
136        self.internal_add_device(
137            name,
138            IpAddr::V4(self.ip_address),
139            port,
140            PollingDevice {
141                device: Box::new(virtual_device),
142            },
143        )
144    }
145
146    ///
147    /// Add a `VirtualDevice` to make it discoverable and controllable.
148    ///
149    /// This version wraps the provided `VirtualDevice` and pretends that the `::turn_on()` and
150    /// `::turn_off()` calls happen immediately.
151    ///
152    /// This form is useful when controlling a physical device that takes more than 5 seconds for
153    /// its state to register as changed but is otherwise "guaranteed" to eventually happen.
154    ///
155    /// The implementation detail here is that calls to `::check_is_on()` will lie and return "ON"
156    /// after a call to `::turn_on()` until your underlying implementation for `::check_is_on()`
157    /// actually does return "ON".
158    ///
159    /// `@name`:  The word or phrase you'll use when talking to Alexa to control this device
160    /// `@port`:  The port on which the backing HTTP server will listen for UPNP requests
161    /// `@virtual_device`:  A `VirtualDevice` implementation
162    ///
163    pub fn add_instant_on_device<T: VirtualDevice>(
164        &mut self,
165        name: &str,
166        port: u16,
167        virtual_device: T,
168    ) -> Result<WrappedVirtualDevice, RustmoError> {
169        self.internal_add_device(
170            name,
171            IpAddr::V4(self.ip_address),
172            port,
173            InstantOnDevice {
174                device: Box::new(virtual_device),
175                instant: false,
176            },
177        )
178    }
179
180    ///
181    /// Add an anonymous device to make it discoverable and controllable.
182    ///
183    /// This version allows for the anonymous implementation of a device.
184    ///
185    /// `@name`:  The word or phrase you'll use when talking to Alexa to control this device
186    /// `@port`:  The port on which the backing HTTP server will listen for UPNP requests
187    /// `@turn_on:` A closure that knows how to turn the device on
188    /// `@turn_off:` A closure that knows how to turn the device off
189    /// `@check_is_on:` A closure that knows how to determine if the device is on or off
190    ///
191    pub fn add_functional_device<TurnOn, TurnOff, CheckIsOn>(
192        &mut self,
193        name: &str,
194        port: u16,
195        turn_on: TurnOn,
196        turn_off: TurnOff,
197        check_is_on: CheckIsOn,
198    ) -> Result<WrappedVirtualDevice, RustmoError>
199    where
200        TurnOn: FnMut() -> Result<VirtualDeviceState, VirtualDeviceError> + Sync + Send + 'static,
201        TurnOff: FnMut() -> Result<VirtualDeviceState, VirtualDeviceError> + Sync + Send + 'static,
202        CheckIsOn:
203            FnMut() -> Result<VirtualDeviceState, VirtualDeviceError> + Sync + Send + 'static,
204    {
205        self.internal_add_device(
206            name,
207            IpAddr::V4(self.ip_address),
208            port,
209            FunctionalDevice {
210                turn_on,
211                turn_off,
212                check_is_on,
213            },
214        )
215    }
216
217    ///
218    /// Add a device that is a composite of multiple other devices.
219    ///
220    /// This is useful if you wish to create a "Living Room Lights" device ("Alexa, turn on
221    /// Living Roomt Lights"), that necessitates controlling multiple other devices that you've
222    /// already added because they can also be controlled independently.
223    ///
224    /// Note that communication with the list of devices in a device group happens in parallel, so
225    /// make sure you don't have any kind of state dependencies between devices.
226    ///
227    /// An example of this might be turning a receiver on (one device) and changing its input to
228    /// "DVD".  The receiver would need to be guaranteed "on" before its input source can be changed
229    /// and this function does not guarantee that.
230    ///
231    /// `@name`:  The word or phrase you'll use when talking to Alexa to control this device
232    /// `@port`:  The port on which the backing HTTP server will listen for UPNP requests
233    /// `@devices`:  A vector of `WrappedVirtualDevice` instances that have previously been added
234    /// to this `RustmoServer`
235    ///
236    pub fn add_device_group(
237        &mut self,
238        name: &str,
239        port: u16,
240        devices: Vec<WrappedVirtualDevice>,
241    ) -> Result<WrappedVirtualDevice, RustmoError> {
242        self.internal_add_device(
243            name,
244            IpAddr::V4(self.ip_address),
245            port,
246            CompositeDevice { devices },
247        )
248    }
249
250    fn internal_add_device<T: VirtualDevice>(
251        &mut self,
252        name: &str,
253        ip_address: IpAddr,
254        port: u16,
255        virtual_device: T,
256    ) -> Result<WrappedVirtualDevice, RustmoError> {
257        let mut device_list = self.devices.lock().unwrap();
258        for existing_device in device_list.iter() {
259            if existing_device.port == port {
260                return Err(RustmoError::DeviceAlreadyExistsOnPort(port));
261            } else if existing_device.name.to_lowercase().eq(&name.to_lowercase()) {
262                return Err(RustmoError::DeviceAlreadyExistsByName(name.to_string()));
263            }
264        }
265
266        let device = RustmoDevice::new(name, ip_address, port, virtual_device);
267        let wrapped_device = device.virtual_device.clone();
268        device_list.push(device);
269
270        Ok(wrapped_device)
271    }
272}
273
274impl Drop for RustmoServer {
275    fn drop(&mut self) {
276        self.ssdp_listener.stop()
277    }
278}