In a
previous post I talked about the idea of "cheap code" to go with cheap hardware. Not every program has to be useful to the entire world or fit for general purpose -- it's fine to have a simple, ad-hoc script to do what you need to do quickly.
I've been using this idea to build a simple home automation system for the connected devices in my house. The code isn't elegant. There's no user interface to add new devices or change settings. It's not even secure in any way. But it
works and I have it
now, and if I were aiming to make something I could box up and ship as a real product, I'd still be working on it. (Actually, that's not true. I'd lose interest and never finish it.) But even if the code I've written isn't generally useful, the structure and ideas could be.
So with that in mind, here's a sketch of how all the pieces fit together.
Overview
|
Logical overview of my home automation remote system |
At a high level the architecture is pretty simple. At the center running the whole show is an
Onion Omega, a cheap wifi-enabled computer made for
prototyping and projects like these.
The Omega is running a webserver and PHP to serve up the remote control interface and process commands for the projector, which is connected via a USB serial interface. My stereo and lighting system are on the local network and can be controlled through HTTP requests.
The interface
The remote control interface is just a simple HTML page, not even processed through PHP. All of the actual control is done through Javascript, which means almost everything is actually executing on the device I access the remote control interface through (e.g. a phone, tablet, or laptop). This gives me a few advantages. I don't need a lot of processing power or memory on the Omega, as it's really doing very little as far as actual control goes. But it also lets me control devices and have a dynamic interface with no wait for a round-trip request to the Omega's webserver. The entire interface is loaded on the first connection, and then parts are simply displayed or hidden in reaction to what the user presses, so there's no frustrating lag after controlling one device before selecting another.
One other advantage that doing the whole thing in Javascript gave me was a complete accident. I realized after I'd started writing the remote control that I could have different looks or skins for it but share the same underlying code. This lets me develop and test in a bare-bones HTML interface but then get really silly for running it on the tablet in my living room.
|
Yes, that is a Star Trek LCARS interface. And yes, I am a geek. The CSS is available on Github. |
Javascript for REST devices
Both my Denon stereo and Philips Hue lights are controlled by sending an HTTP request to the device. Since I'm executing the control through Javascript, the easiest way to do this is to abuse AJAX, the functionality built into Javascript to allow it to make asynchronous requests to a webserver. Usually this is used to get data from a remote server and update something on a page in the browser. But in my case, all I want to do is send a bit of information to a device and I don't care what the device sends back. In fact, if it sends me anything, I'm just going to ignore it.
(Remember, this is all about doing things cheaply and quickly! If my stereo gets confused by something I send it, no one will get hurt or lose money. The worst that will happen is that I have to walk across the room and turn it on by hand. So why fret too much about checking for errors?)
The stereo is the easy one. It just requires an HTTP GET of a specific URL to set it to what I want. It does return some status information, but again I just ignore that.
function setStereo(status)
{
var xhr = new XMLHttpRequest();
var url;
switch (status)
{
case 'on':
//url = 'MainZone/index.put.asp?cmd0=PutSystem_OnStandby%2FON';
url = 'MainZone/index.put.asp?cmd0=PutZone_OnOff%2FON';
break;
case 'off':
url = 'MainZone/index.put.asp?cmd0=PutSystem_OnStandby%2FSTANDBY';
break;
case 'mute':
url = 'MainZone/index.put.asp?cmd0=PutVolumeMute%2Fon';
break;
...
}
xhr.open('GET', 'http://0005CD3A5CE1.udlunder/' + url);
xhr.send(null);
}
The Philips Hue system is only slightly more complex. It requires an HTTP PUT rather than a GET, and expects a JSON document. While you can control color and brightness of individual bulbs, I just made some presets stored in the Hue itself for different lighting scenes and so only need a very simple JSON document to recall them.
function setScene(scene)
{
var xhr = new XMLHttpRequest();
xhr.open('PUT',
encodeURI('http://philips-hue.udlunder/api/openHABRuntime/groups/0/action'));
xhr.send('{"scene":"' + scene + '"}');
}
Controlling the projector
Probably the ugliest part of the whole thing is the projector. It doesn't have a REST interface like the other devices, but it does have a serial port. This is intended for industrial applications or control of the projector when it's mounted somewhere inaccessible, but it works just as well for people like me who are too lazy to keep track of yet another remote control.
This is the only place where the Omega is doing anything more than serving static web pages. The PHP server on the Omega is used only to receive a projector command and pass it to
a shell script that runs it. The PHP script is truly ugly, doing no error control or security checks -- it's a great way for someone to take control of my Omega or run malicious code there. But frankly there's nothing of value stored on it and to attack it they'd have to be on my local network anyway, meaning I have bigger problems. So I'm okay with this.
<?php
system("stty -F /dev/ttyUSB0 115200");
system("/root/projector.sh " . $_GET["c"]);
?>
(That's it -- the entire PHP script is two lines of code.)
Putting it all together
These individual pieces are all simple and useful, but combined they're fantastic! I can reuse those Javascript functions all over my remote control and have one button do many things. It looks more complicated than it really is when you diagram out what's going on inside:
|
Flow to set up multiple devices for a "movie" preset from a single remote control button press |
There are ways to build dynamic Javascript code, but again I took the easy way out. I made a short function for every preset that I wanted and just call the appropriate functions. For example, when I select the "Movie" button, the lights are dimmed, the stereo and projector are turned on and both are set to the proper input:
function scene_lr_movietime()
{
setProjector('on');
setProjector('srchdmi');
setStereo('on');
setStereo('inputdefault');
lights_lr_movie();
}
(I broke the light settings into a separate function just so that I could have another button on the remote that would change the lights without turning on the projector or stereo and not have to write more code.)
Wrapping up
That's really it. Nothing here is what I would call robust or beautiful, but it all works and was pretty easy to do. It's a great sort of project to tackle if you want to learn more about the Internet of Things or find a use for some of the cheap hardware that's available today. And these components can be further built on as you add new devices or want to add new functionality.