So I recently moved, in case you’re new to the blog, which has entailed me becoming comfortable in a new home. Or, making the new home mine.
It has also entailed me learning about apartments. This being my first one, I’m… a little surprised by the cost cutting measures. The apartment’s great, don’t get me wrong, but I think it would’ve been nice if during the recent renovation (which I’m paying premo for) they had bothered to install actual drawer mechanisms that, you know, actually open and close without sticking and going all over the place. I may be slightly spoiled from spending my entire life so far in a house with drawer slides.
But, more to the point, I pay for electricity and my thermostat doesn’t have a schedule feature. It just holds whatever temperature you set. So I could turn it off when I leave in the morning and on when I get back, but then I not only have to spend my first half-hour home from work in sweltering heat but I have to remember to turn it off, which I won’t. I barely remember how to get to my building, how am I supposed to remember to turn off my A/C?
So, solution time. I had a few weeks before work started to think something up. I could buy an off-the-shelf solution, but that’s both expensive and a cop-out and it won’t necessarily work with the installed HVAC system (the thermostat mounts onto a 4-wire header sticking out of the wall, so anything I buy would have to conform to that standard).
My second option would be to rig up some relays to the 4-pin header sticking out of the wall. The problem here is I’d risk blowing out the circuitry that’s hidden behind the wall, and that’s almost certain to not get me my security deposit back. Also I have to deal with all of the weird cases that thermostats deal with - things like compressor protection.
That leaves me with the third option: Build a device to press the buttons on the thermostat for me. And that is what I did, with the help of my trusty 3D printer.
Sorry the image is sideways, I still haven’t figured out image rotation.
As you can see, there are three parts: A servo (partially hidden) to press the buttons, a webcam to view the current temperature, and a Raspberry Pi to control the entire charade. The cost of parts was $0, because I still have all of those Pi 3s from when I built the Pi cluster.
The Pi 3 has WiFi, so I don’t need to run an ethernet cable to it - all I need to do is get 5V USB power to the Pi. This is how the Internet of Things is supposed to look, not to snub the people who use “Nest” or whatever. (machine learning thermostat? What is there to machine learn?)
Building the hardware was, as always, an iterative process, made vastly easier by owning a 3D printer because I can run a print, see if it worked, tweak it, and repeat. Productivity is inversely proportional to the edit/compile/run cycle, and cutting down the weeks and $10s of dollars spent outsourcing builds to hours and pennies has been possibly one of the greatest quality-of-life improvements I’ve experienced of all time.
For example, here’s the five and a half prototypes I printed for the servo rocker arm (the thermostat has two buttons, up and down - the servo is configured so that if it rotates one way it presses up and if it rotates the other it presses down):
The first iteration was the bottom-center one, which was printed such that the support material filled the hole for the servo. Then came the ones on the bottom left and right, which are similar save for a slight change in hole size. Once the rocker fit the servo, I printed the top-right one, which featured a redesign of the arms - the straight arms tended to snag on the edge of the raised buttons, and I didn’t want to damage the buttons so I put a curve on the end of the arms to prevent snag. The top-right one had the curve built such that the ends of the arm stuck together, so when one compressed against a button the other did too, pressing both buttons simultaneously. So I printed the top-center one, which fixed that, but then I realized that the method of mounting to the servo was flawed, since the 3D print couldn’t grip the grooves along the servo head. So, I prototyped a mount for cross-arms, and the final print is what’s mounted. (a nice thing about using OpenSCAD for designing is I can select just a region of the part to print, if I don’t want to waste time and filament printing a whole part just to test one bit)
The rest of the thermostat mount came together in a similar fashion, I won’t bore you with the details. There are two main parts, the left mount and the right mount, which bolt together to secure themselves to the thermostat. The Pi mount on the left half is a shortened form of the Pi mount from my cluster (another nice thing about OpenSCAD - copying/pasting libraries). The webcam mount took some clever design to fit on my printer, it’s exactly big enough to be running against the size limit.
The LED mount was an afterthough. Originally, there was no LED, but night tests showed that the webcam couldn’t read the display well enough, so I added a red LED.
Now that I’ve discussed the hardware at length, let’s dive into the software.
Oddly, the system is open-loop. That is, the system doesn’t make decisions based on the temperature. This is strange because the system knows the temperature, because of the webcam. Instead, the webcam and the servo control are separate entities software-wise.
The servo control works by storing the last-set setpoint in a file, and there is a Python script that checks what the setpoint is and what it should be and moves the servo to match (this is how I get around the open-loop problem: the system just remembers what the setpoint is supposed to be). The actual mechanics of managing the servo (driving PWM values, etc) are done through shell scripts using the wiring-pi command, because for some reason the Python module kept crashing my Pi when I re-initialized the PWM output. So the high level Python script calls out to the shell scripts via the subprocess module to manipulate the thermostat.
Of course, this raises the question of how does the script know what the setpoint should be? First off, a schedule is nice, but how should the schedule be represented, and what should happen for non-scheduled events? In terms of representation, the schedule should probably be able to at least support weekend/weekday differences, because that’s the point of the whole system (I don’t want the A/C to turn off on the weekends, because I’m home then). But should it support having different schedules on, say, Monday than Tuesday? Or Sunday from Saturday? (I don’t go to church, but that’s really the only use case I can think of there) If I do support having different schedules on different days, how do I want to represent that? If I want the same schedule for every day, do I have to copy and paste the schedule? (which would be bad design, because then a change to one day’s schedule won’t propagate to all)
If you like thinking about such things, then congratulations in your career in software engineering, and take a moment to think about how you would do it in your situation.
How I ended up doing it was by building an ordered-override system, where files specify time ranges and apply to specific days (of the week). A file does not need to specify the temperature for all of the times in a day. Files are named like 012-myname, where the number in the beginning is used to sort the files and the name is just a human-readable name. Higher-numbered files override lower-numbered files in areas where they overlap.
For example, here’s a set of configuration files:
pi@rpi06:~ $ cat schedules/000-night # This is the default schedule for overnight: 9PM to 6AM applies=* 21:00-0:00=78 0:00-6:00=84 pi@rpi06:~ $ cat schedules/010-workday # This is the monday-friday workday schedule for summer. applies=mon,tue,wed,thu,fri 6:30- 8:00=80 -16:30=92 # While we're away for the day -20:00=82 # When we're home -21:00=80 # Gradually decreasing for bedtime pi@rpi06:~ $ cat schedules/011-weekend # The saturday/sunday summer schedule applies=sat,sun 6:30-11:30=80 # Stay cool for the morning -18:00=84 # Moderately cool for the afternoon -20:00=82 -21:00=80
This is a hypothetical user, not me. Please don’t rob me while I’m away from home.
There are three files here, specifying the “overnight” schedule, the workday schedule, and the weekend schedule, respectively (and, they apply in that order). Note how the overnight schedule applies to all days, but only specifies temperatures from 9:00PM to 6:00AM, whereas the workday and weekday schedules both start at 6:30AM. This means that 6:00AM to 6:30AM is unspecified. In this case, the default is to leave the thermostat as it was (the goal there being to change the thermostat as little as possible).
This schedule system works pretty well - it allows day-level granularity, while also not duplicating schedules unnecessarily (notice how above, the night schedule is only specified once). It also allows temporarily dropping in a new schedule file, if I (for example) go on vacation for a few weeks.
However, it doesn’t account for one thing very well: events and outings. Sure, whenever I go somewhere I could drop a temporary “100-sallys-party” file in, but that requires me to know in advance when I’ll get home (if I want to cool my apartment off before I get back).
Introducing the notion of “holds.” A hold is just a temperature for a one-time range - for example, hold 92 degrees (i.e. turn off the A/C) until 6:00PM today.
This doesn’t immediately solve the problem, except with the addition of two things: The smart home controller and a cellphone.
The smart home controller is just a Pi with a touchscreen mounted next to my door (it isn’t mounted yet, hence no picture). The touchscreen shows some useful facts, like the time and the temperature and the forecast, but it also has a button that says “Leave”, which when pressed submits a 92-degree hold for several thousand hours (effectively forever). This means as I walk out the door I can turn off the A/C with a single press of the button.
That’s handy, but then to solve the opposite problem (cooling down the apartment on my way home) we turn to the wonderful API from Twilio.
Twilio lets you setup a fully featured voice (or SMS) app using nothing but a few bucks and a PHP server. When your phone number receives an incoming call, they send a request to your webserver to figure out what to do, and that goes back and forth (they have excellent documentation you can check out). You can configure it to do the whole “press a button for a menu” deal, so that’s what I did. Now I have a lovely British lady in my speed dial that I can call to cancel a hold (or, receive a weather forecast for the next day).
So that’s it. Press a button when I walk out the door, make a 15 second phone call when I’m heading back. It’s a perfect system.
Well, except for one thing. I haven’t discussed the webcam setup.
Partially because the webcam is much less elegant. Much, much less elegant.
Basically, it can be summed up by: Every minute, the Raspberry Pi captures an image with the webcam (script in a crontab). Every 15 minutes, the Raspberry Pi processes all of the images to read the current temperature and whether the A/C was running.
The image processing script works by comparing the average brightness of fixed regions in the image. For example, I’ve hard coded the position of each of the seven segments within the image. The reason for this is a) it works and b) I don’t have enough test data to come up with a cool machine learning algorithm to do it any other way. The reason for both of these is that the webcam is fixed relative to the thermostat, so it works.
It’s just hackish.
To be completely fair, there is an auto-tuning script which will go through a bunch of labelled samples and figure out the right coefficients for comparing region brightnesses, but I still have to hand-code the locations of the regions.
Either way, once the data are computed, the Pi sends it off to a mongo server running on my main server.
I can query the mongo server to make cool graphs such as this one:
Which graphs the temperature inside my apartment against the time for the past few days.
There are still a few bugs left to work out in the vision system. For one, the data is a little hard to read since it comes back on integer degrees. For two, I recently hung a Star Wars poster above my couch, in the reflection of the webcam, which is causing some spurious readings. Sometimes it’s hard to predict where bugs will come from.
The source code is a bit gnarly, paths are hardcoded, and so on, so I haven’t posted it online, but it is open-source. In the sense that if you ask me for it, I’ll happily post it.