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}