Auto Office
Joe Angell
Update: January 2021
I’ve switched from SmartThings to HomeKit via Homebridge, and reimplemented the macOS daemon completely. The new setup is a bit cleaner and more flexible, but leans heavily on what I present here.
On my desk I have three separate computers with a total of five displays. I also have two lights. Every time I go to do work I have to wake all the computers, manually turn on two of the monitors, and turn on the lights. But I'm very lazy, and can't be bothered to do all that, so I automated it.
The goal is that when any of my computers sleep (well, my iMac, anyway) or I turn the lights off, all the displays will turn off and the lights will turn off. If I turn on the lights or wake any computer, all the computers wake and the lights go on.
I've taken to calling it "Aziz", after the character at the beginning of The Fifth Element.
Because of the varied hardware I'm controlling, this requires custom software for Windows, Mac, SmartThings, and an ESP8266.
The Problem
I have the following devices that I want to toggle:
iMac, hooked up to an external display. Sleeping the Mac's display sleeps the external display as well (since it supports DPMS), so I just ned to be able to control and monitor it's sleep state. This is the easy one.
Mac mini (and various game consoles) routed through an AV receiver to a monitor. While the monitor supports DPMS, the input on the AV receiver may be switched to something besides the Mac mini, and I always want it to turn off no matter the input. Luckily, if you cut power to the Asus monitor and then plug it in again, the monitor returns to whatever state it was in when power was cut. A SmartThings-controlled Z-Wave appliance module can control the power to the monitor easily.
Windows laptop hooked up to a 55" Samsung UHD TV as a monitor. I originally had a 50" Seiki UHD TV set up, but it posed a problem as it wouldn't turn back on automatically after the power was cut. The DPMS solution used for display sleep on the iMac doesn't work here, as no TVs seems to support DPMS. I briefly looked into IR and HDMI-CEC control of the TV before getting a good deal on the Samsung, which luckily will turn back on automatically after the power is cut. I simply plugged it into the same Z-Wave module as the Asus monitor.
The lights were initially pretty easy: just use a SmartThings-controlled Z-Wave module. But then I decided that I'd like replace my two weak floor lamps with five 2'x2' LED panels. And since I had five of them, they obviously need to flip on and off in sequence, which was accomplished with some custom code, a relay board, and an Adafruit HUZZAH Arduino-compatible ESP8266 wifi module.
Z-Wave Switches: Basic Lighting and the Monitors
When I started, I had two floor laps for lighting. I was able to simply plug these into a Z-Wave switch and control them with SmartThings. Similarly, the Asus monitor and Samsung TV (both of which will turn itself back on when power is restored) shared another Z-Wave switch, allowing them to be on the UPS while the lights are plugged more directly into the wall. For the light's, I modified the Z-Wave switch slightly by soldering a second bottom to the contacts on the built-in button. This was mounted through a hole in my desk so that I could press that single button to turn everything on or off.
At this stage the SmartThings Smart Lighting app is all that's needed to control the two devices, where turning on the lights would turn on the monitors. I even put a motion sensor on my desk so that when I walk in front of it everything turns on automatically. But this only covers two devices; the rest need something a bit more involved than a simple switch.
SmartThings SmartApp
The next step was to write the SmartApp to allow for more complex control. I had the following goals here:
Turning on any of a specific set of SmartThings-controlled switches should turn everything on.
Turning off any of a second specific set of SmartThings-controlled switches should turn everything off. Often these are the same as the "on" switches, but not always.
Motion detected by a specific set of motions sensors should turn everything on. Motion sensors should not turn anything off, though, since lack of motion is not the same as lack of presence.
A list of switches that are turned on or off by the app.
A list of device IPs and port numbers that SmartThings will send wake/sleep commands to via HTTP PUT requests.
The computers and HUZZAH will also have to communicate with SmartThings, which means setting up authentication with SmartThings. While this is well documented, I didn't want to bother with the OAuth web page and all that for a one-off project, so I instead borrowed the technique used by the HomeBridge "JSON Complete API", where the SmartApp generates a JSON config providing the API key, application ID and URL of the SmartApp. These would then be used by the computers and HUZZAH-controlled lights to send commands to SmartThings.
The HTTP endpoint for the command is "/do/:command". I had originally intended it to just be "/do" with JSON in the body to , but at the time I wasn't quite sure how all that worked, so I just stayed with passing it as sub-path (i.e.: "/do/wake"). The computers do send JSON data as well, though, in the form of a "command" with a "wake" or "sleep" value.
One other small tweak was to make sure that motion sensor events are ignored if they arrive within one minute of everything being put to sleep. This ensures that after I push the button to go into sleep mode, that the motion sensor won't detect me walking away and wake everything up again.
The full source for the SmartApp can be found on GitHub.
iMac Daemon
The Mac Mini acts as a server, and is set to never sleep; I just turn the monitor on and off directly through the Z-Wave switch, so there's no need to run any special software on it. The iMac required a different solution, though.
For this I wrote a simple web server in Swift using the Vapor framework. This is my first real Swift app, and my first time using Vapor, but I found it to be pretty straight-forward, requiring very little code. The app does two things:
Monitor the system for wake and sleep events, sending HTTP PUT requests to the SmartThings app to wake or sleep everything else.
Listen for incoming HTTP PUT requests from SmartThings to wake or sleep the display.
It's worth noting here that I only wake and sleep the displays, not the computers -- the computers never sleep, but I don't want the displays on. If the computer is actually asleep, we'd have to do Wake on LAN or something similar to wake it up, and I tend to like my computers to do random background tasks, so they are never slept or powered down unless I need to reboot them.
I found the Vapor framework to be very easy to use, but briefly got stuck on the fact that Vapor wants to be the entire application loop, which meant that I couldn't listen for system events at the same time. Luckily, it's really easy to spin of Vapor into a thread, where it has no problem running and serving up web pages and processing requests, while the main thread handles
Protocol
The SmartApp/computer protocol is very simple. For testing, I used /sleep and /wake paths with HTTP GET, but that's not ideal, as web browsers will pre-fetch the pages -- meaning, as you type the URL a second time it will auto-complete and pre-load the page, triggering the sleep/wake action without you actually pressing "enter" to go to the page. PUT requests solve this problem, as they are not pre-loaded by the browser, and are really the correct way to do it.
The SmartApp itself also sends a /do PUT request to the computer to order it wake or sleep. It provides the command via JSON as well as though the URL, which the Vapor server decodes with its built-in JSON support.
Each of the URLs also returns some simple JSON indicating the new wake/sleep state. There's also a /status GET path that just returns this JSON and does nothing else. Nothing actually uses this path, but it's useful for testing.
Vapor's client class is also used to send HTTP requests to SmartThings when the computer sleeps due to inactivity or wakes due to user input.
So:
When the computer goes to sleep, it composes the SmartThings app URL with /do/sleep
When the computer wakes, it tells SmartThings at /do/wake
SmartThings will wake the computer at its /do URL, providing JSON data with the "command" to "wake"
SmartThings will sleep the computer at its /do URL with JSON data containing the "command" to "sleep"
And that's it. I also added a "sleep delay' to the daemon that waits a certain amount of time before sending the sleep message to SmartThings. This is because I'll often be using one of the other computers, and the iMac sleeps I want a bit of time to wake it before all the lights go off on me.
Deployment
The sleep delay and the SmartThings SmartApp URL, authentication token and app ID are stored in a JSON-based config (Vapor has an easy config API) for easy maintenance. The JSON is generated by the SmartApp and can easily be copied from there to the config.
One of the weird things about Vapor is that the app expects to be run on the machine it is built on, going so far as to store the path to the config files based on where the app's source exists at build time. It doesn't seems to have a way to deploy like a conventional application, where you compile it and than give it to whoever you like to run it. This seemingly unusual behavior does makes sense for the point of view of a deployable web app, where you copy the source to a server, build it and run it, and never give it directly to a customer. It's just odd from the standpoint of a conventional application developer.
I have the daemon run on startup with launchd. I detail how this works on the GitHub page, which includes the full source to the daemon (everything except Vapor itself, which you can download separately).
UPDATE: February 2019
Vapor 3 is a completely redesigned version of Vapor, and the old version I was using doesn’t build on Xcode 10, so it was time to update. I updated to Vapor 3 as per the docs, installing the new OpenSSL dependency and copying and reformatting the old code to the new API, re-implementing it as needed for the new . This wasn’t terribly hard, and took me about half a day’s worth of work. It seems Vapor also names its app “Run” instead of “App” now.
macOS 10.14 Mojave is rather picky about letting apps access the network without permission from the user, and every time I launched my daemon it wanted approval. To work around this, I code signed my app with a self-signed certificate. After doing this manually a few times, I added it as a custom build step on the “run” application.
The source for the new daemon can be found here.
Windows Daemon
ADDED: January 2020
I finally got around to writing a daemon for my Windows machine. It is very crude, but it gets the job done. It is only a listener that turns the monitors on and off; it does not send on/off notifications, as I want that to be limited to my Mac.
The server is written in C/C++ based on a Microsoft HTTP server sample. You have to jump through a few hoops to get it to work properly, such as running the app as admin, updating Windows ACL to allowing incoming HTTP requests, that kind of thing. It works, though.
Lighting Control
Initially I had a pair of somewhat dim floor lamps connected to a Z-Wave appliance module controlled with SmartThings. It worked, but I wanted more light.
Choosing New Lights
I bought six 2'x2' LED panels from superbrightleds.com. Each panel is about 5000 lumens, and includes its own AC to DC power supply mounted on the back. The LEDs ring the perimeter of the frame, pointing inward towards a diffuser setup. This provides a bright, even light throughout the panel. I only wound up using five of the light, mostly due to a measuring error when I was setting them up, but they're more than bright enough as is.
Mounting the Panels
The panels are designed to be installed in drop ceilings, which are not something I have. Instead, I wanted to mount them on the sloped ceiling/wall of my office loft. I didn't want to cutting holes in the wall, installing junction boxes, drilling through studs etc, instead opting to hang the panels.
The first trick was that the power supply is mounted in a 2" deep metal box on the back of each light. 24 screws hold the sheetmetal backing onto the light; with those off, the two rivets holding the power supply boxes on can be drilled out. The power supply itself is self-contained and attached to the box with two screws. With the power supply out and the box off, the metal backing can be screwed back down, leaving a nice, flat back suitable for wall mounting.
To actually mount the panels, I bought a set of six french cleats. I measured out the location of the two center-most screws on the back of the lights and noticed each of the cleats with an angle grinder. The idea is that I'd replace the two screws on the panel with longer ones, and simply slip them onto the french cleat. I bought a bunch of #8 x 32 screws that were 3/8" long; they weren't an exact match for the original screw thread and size but they worked just fine, and stuck out the extra bit I needed to hang them on the cleats.
Since the walls were sloped, I needed to secure the bottom of the lights as well. A french cleat wouldn't work here as it would be visible below the light. For this I bought a bunch of cheap Z-clips. I notched one end of each Z-clip (by mounting it in a vice and cutting it with an angle grinder) so that the hole was now more of a "U" shape. I then mounted one on each of the two central screws on the bottom of the light, pointing left and right. After hanging and leveling the light from the french cheat, I was able to mark holes for the Z-clips with pencil, drill into the wall, and install screws. Then it was a simple matter of just sliding the panel over both the cleat at the top and the screws at the bottom to complete the mounting.
To secure the french cleat to the wall, and for the screws on the bottom, I used Snap Toggles. These are similar to toggle bolts, but are much easier to install, reusable (if you remove the screw the back piece doesn't fall into the wall), and able to support more weight. I bought 50 from Amazon, along with a box of 2 1/4" #10 x 24 screws. In the rare instance where I hit a stud, I just used a long drywall screw instead of a Snap Toggle.
Wiring the Lights
I wasn't going to cut into the walls, which meant that the wires would be exposed from the panels to the floor. I originally planned to make some neat pattern with the wires, but it just looked like a mess. I went the simple way instead, running the wires straight down from the lights to the vertical part of the wall, and then around the room until they reached the electronics box. I used 18 gauge two wire speaker cable, which was more than enough for the 1 A of power each light required. Each of these ended in DC power plug pigtails that I bought from Amazon, and tacked to the wall with screw-in cable clips.
HUZZAH (ESP8266) Wiring
The HUZZAH controls the lights through a relay board, switching AC and DC power. Since the ESP8266 can't sink enough power to drive the relay board directly, I had to run it through some MOSFETs. The lit button brightens and dims using PWM. The USB connection is purely for power, with programming occurring over the serial bus on the end of the module. The relay board is connected to the HUZZAH with short jumper wires to make everything easier to maintain.
Electronics Box
I bought a decent sized plastic electrical wall box from Amazon. I went with plastic to avoid any possible electrical shorts, but this may have been at the expense of cooling, as a plastic box absorbs heat more readily than a metal box would have. This box has mounting holes on the back, five pop-out cable access holes in the top and bottom (plus some more on the sides and back), a hinged door across the front with a hole through which a pin or lock can be inserted to keep it closed.
Once I was finished with everything, I mounted the box on the wall below the lights using two screws. I can easily take the box off the wall for maintenance just by lifting it off the screws. The cables in the board stick out a tiny bit too much for the door to close on its own, so I had to apply a bit of pressure and slide a bolt through the hole on the door to secure it.
Power Supply Mounting
The wires were connected to DC power jacks mounted along the top edge of the plastic wall box. While I was clever enough to use a template to evenly space the power jacks, I forgot to actually align the template with the side of the box, so they are along a slight diagonal line. Oh well.
Inside, I used White Gorilla Glue to secure two strips of wood along the back of the box, each running from the bottom to the top. The five separate power supplies are screwed into these pieces of wood. This both avoided the need to drill through the case, and provides extra airflow under the power supplies. The DC output wires from the power supplies connected to the power jacks on the top of the box. I bought the DC jacks from Amazon.
AC Power Connections
In my original setup, I ran the neutral AC input wires from the power supplies to a lighted switch that is part of a fused AC male power socket I got from Amazon. I hooked these up with with automative blade connectors for ease of maintenance. This makes it easy making it it easy cut off the light power supplies while testing the HUZZAH module.
The hot wires were run to a 5v 8 channel relay board, and then to the hot terminal on the power switch. The relays are controlled the HUZZAH, and let me turn the lights on and off in sequence. These relay boards are easy to find for around $10 online, and support 120v AC -- yes, I'm switching 120v AC, as I don't want the power supplies running when the lights are off.
I soon discovered that there is a slight delay between when the power supplies are powered to when the lights come on. It also seems to be a bit different for each power supply. This ruined the timing of the sequential power-on of the lights. To work around this, I changed the wiring so that the hot AC wire from all five power supplies went to the first relay. This means that this relay now powers on all of the power supplies at once.
To get my sequential lighting, I wired the positive DC output of each power supply to the next five relays. The software nows turns on the AC relay first, then waits one second before turning the other relays on in sequence. This gives the power supplies enough time to initialize before their output is used to turn on the lights themselves. This pause isn't visually noticeable because the computer displays are coming on at the same time, so it just seems like more of the power-on sequence.
The total AC power draw (based on the power supplies being 0.6 A AC and, 0.35 A AC for the USB supply for the HUZZAH) is under 3.5 A, so the stranded 18 gauge speaker wire (about 5 A max) that I was using throughout was more than adequate.
DC Power Connections
The HUZZAH and relay board are both powered from a dedicated 2.1 A USB power supply. I wanted this to always have power even when the box's switch was off, so I connected it to the un-switched side of the power socket. This means that I can switch off the AC power to the power supplies without cutting power to the HUZZAH, which let me experiment with the software more easily. Rather than butchering the USB power supply, I instead butchered a short extension cable and hardwired it into the power socket, and just plugged the USB power supply into that. It's a little ugly, but it gets the job done.
The HUZZAH and relay board were mounted on two more pieces of wood clued on top of the power supplies. In retrospect, I probably should have screwed them down instead, as I have no good way to get the power supplies out now. The two boards are connected with some jumpers, which is an idea I got from this Instructable about how to build a web-controlled power strip. Unfortunately, the ESP8266 can't sink quite enough current to safely trigger the relays, so I had to wire some MOSFETs between the microcontroller pins and the relay headers to make sure there were no problems.
Sticking Relays
After a few days of operation, I started noticing a intermittent issue where some of the relays would stick. It was always the last relays in the power-on sequence, often taking a number of extra seconds for them to finally flip, even though they was energized. Tapping those relays caused them to flip immediately. Some Googling suggested they just weren't getting enough power. In fact, with the case open you could hear each successive relay click on more quietly than the last due to the reduced available current. The 5v power supply was strong enough (about 1.5 A is recommended for the relay board, and mine was 2.1 A, and I was only using 6 of the 8 relays -- even while also powering the 500 mA HUZZAH board it should be fine). The solution here was simple: add a second pair of wires from my USB connector to the relay board's power pins. This immediately fixed the problem and the lights came on reliably. Apparently the 22 gauge wires I was using for this were simply too thin to carry enough current for everything.
Manual Control
I wired an illuminated button from Adafruit to the HUZZAH so that I could manually turn everything on or off. When the lights are off, the ring around the button slowly pulses in the same manner as a Mac's sleep light. When on, the light goes solid. I built a very simple connector from some headers so that I could disconnect the switch from the board the HUZZAH was mounted to. Pushing this button causes the ESP8266 in the HUZZAH to send a wake or sleep command to SmartThings, as well as turn the lights on or off. It also responds to wake and sleep requests just like a computer does.
Temperature Management
It turns out those power supplies get pretty warm. Even though I left some space between them, I was reading nearly 50C on the surface of the topmost with the case door cracked open a couple of inches. In the interest of keeping things cool, I bought small, very quiet brushless 5 V 0.14 A fan from Amazon that was basically silent. It had two pins that conveniently mated right up to the header I'd put on the board for the relays.
Unfortunately, I had already used all the GPIO pins on the HUZZAH. I came up with a plan to turn on the fan when the AC relay was energized, or when light 4's relay was energized. The idea is that after I turned the AC power off, I could turn on light 4's relay for a few minutes to turn on the fan (but not the light, since it had no AC power) to cool down the case. Two diodes running from those MOSFETs to a new MOSFET that switched the fan would create a kind of "OR" gate while not feeding current back to the AC relay when light 4 relay was on, and visa versa.
For reasons I don't understand, this didn't work. I seemed to be getting about 0.5v on the back end of the diodes, which caused both the AC pin and the light 4 pin to be on all the time. Worse, the AC pin is pin 2, which is used to tell the HUZZAH to expect a firmware upload, which meant that powering up the board caused it wait for firmware and not actually boot. In the end, I wound up removing the diodes and transistor, and just wired up the fan so that it ran all the time.
I mounted the fan inside the top of the box, arranged to pull air out of the top of the case. I popped out one of the cable accesses and crudely drilled two more holes to make a kind of Mickey Mouse shape to provide more airflow. The fan is mounted directly under those holes. I also popped out two more of the accesses from the bottom to cold suck air in.
While the fan is whisper quiet when outside of the case, once mounted there was a noticeable sound of air moving though the case. It was still quite quiet, but it was the loudest thing in the room (my laptop fans and drive enclosure fans are notably quieter, being barely audible at all). I looked into muffling it in some way, but that didn't really seem practical or necessarily effective. I wound up installing a 100 ohm resistor on the fan, which slowed it down and roughly halved the volume, but it was still too loud for my taste.
I decided to try just not using a fan. With three lights on, the topmost power supply read 47 C on the surface with the fan running. Without the fan, but with all five cable accesses popped out of the top of the case, I read about 53 C on the label of the power supply with an IR thermometer. The air temperature (Ta) near that power supply is 33 C; the supply is rated for 40 C (written as Ta: 40 C on the power supply) before its efficiency drops. The metal back of the LED panel says that the area around (when installed in a drop ceiling, presumably) it should be able to survive 90 C, so I'm guessing that the surface of the power supply itself can get quite hot and still be fine. The ESP8266 is good to 120 C, so I'm not worried about that. After a few months of use, I've found no problems with the temperature in the case.
In retrospect, a metal box that the power supplies were directly attached to might have kept things cooler, as the entire case would act as a kind of heat sink. I went with plastic because I didn't want the AC power line accidentally energizing the box, but all you have to do then is ground the box with the ground wire from the power cable to mitigate those risks.
ESP8266 Notes
I learned a few things about the ESP8266 to be aware of, at least when using it with the Arduino IDE:
It remembers the network it was connected to, and automatically connects to it again on power up. This can be annoying if you need to change the network. You can use used WiFi.persistent(false) to disable this behavior.
It auto-reconnects if the connection drops. I left this on (WiFi.autoreconnect(true)), because my attempts to do this manually failed. Basically, the ESP8266 seems to lie, and doesn't return WL_IDLE_STATUS while it's trying to connect like the docs say instead just returning WL_DISCONNECTED. This caused me problems because I'd try to re-connect while it was still trying to connect, and thus never actually connect. I changed my connect attempt retry time to 10 seconds and left on auto-reconnect so that I wouldn't have to worry about this anymore.
It's sensitive to power input, and will need more than 500 mA. Don't try to drive it solely off of the USB port; it's not going to be reliable and you'll get weird errors on startup from the watchdog system. Hook up dedicated power to the V In or Battery pins, and it should be much more reliable.
You can build with certain debug options from the Tools menu in the Arduino IDE, choosing a Debug Port and a Debug Level. This can help a lot when trying to figure out why your connection won't work or why your HTTP request was rejected.
Static IPs are a bit hard to get working. While I did finally get it to connect statically, I couldn't get it to accept any manually-provided DNS servers, so I couldn't send any messages to SmartThings. I finally gave up and used DHCP, and set a static IP from the router based on MAC address. A static IP is needed because that is how you identify it from the SmartThings SmartApp.
One thing I didn't figure out was how to force all the digital pins high on power up. Without that, the relays will be in random positions on initialization. It's not a huge deal, since it should be rare that the power is cut, but it's not ideal.
Light Sequences
I couldn't just have all the lights turn on or off at once -- I had to run them in sequence. That's why we have the relay board, after all. I set them up to turn on from one end to the other, about 250 ms apart. I set up a second sequence to turn them on from the center out as well, although I found I liked the "one at a time" sequence the most and stuck to that. It all works pretty well, and looks neat. I can easily program more sequences if I want with the simple transition engine I wrote. I've also been considering adding special button presses (double-tap or hold, or another button) to do alternate behaviors, like turning on only every other light, or to ignore signals to turn off the lights when the computers sleep..
As I mentioned above, I first turn on the AC power supplies, then wait one second for them to warm up before running the sequence. This ensures the sequence runs as expected, as without this the lights would turn on at seemingly random offsets from when they were triggered. When the power turns off I don't need to do this, and the HUZZAH starts immediately turning the lights off.
I did forget to take into account that sending the HTTP request would take some time. Initially, I recorded the start time for the transition and then sent my HTTP request to SmartThings, and then started running the transition itself. This wasn't a problem for power-on, since the one second delay hid the timing problem, but for power off it would cause the first two lights to switch rapidly one after the other. Simply making sure to send the HTTP request before recording the transition start time fixed the problem.
Final Light Locations
I found that having all five lights on the same side of the room was really, really bright, so I moved two of them to the other side. This provided more even lighting with fewer shadowed areas.
In Use
I've been running this for a few months now, and it works great. I decided not to implement a sleep/wake app on Windows, as my Windows laptop had issues waking from display sleep and would kernel panic sometimes. I just the laptop display on all the time, using the SmartThings module to turn off the external display. For the iMac, waking the display also triggers the auto-unlock via my Apple Watch. The other machines aren't normally locked, so there's no problems there (really, my iMac only locks because that's what I had my older MacBook Pro do; there's little real need to lock my desktop machine).
In July I noticed that the HUZZAH stopped sending commands to SmartThings. It would receive them, but the button on the desk didn't work anymore. It turns out this is because the security certificate that SmartThings uses for HTTPS changed. This isn't a problem for the Vapor server on the iMac, because the Mac has a complete list of root certificates. The ESP8266 doesn't have enough RAM for that, so you have to use the fingerprint from the certificate of the web site itself. These expire much more frequently than root certificates (about every six months for SmartThings). It's not a huge deal for me to update the fingerprint every six months. An alternate option is some newer ESP8266 libraries that let you store a single root certificate (the one used by SmartThings' certificate authority doesn't expire until 2022, which would be handy), but that looked like a lot more work than I wanted to deal with for now. So I'll be updating the fingerprint every six months instead.
Another option is to communicate with the SmartThings hub directly and create a new SmartThings device type for the lights. I didn't realize hub communication was an option when I started this. Since the hub is on the LAN, HTTPS isn't needed. I may change over to direct hub communication in the future, since it would be cleaner and avoid the need to update the fingerprint periodically. My next project will certainly go that route. But for now, I'm happy with it how it is.