Shorts or Jeans — Revisited
Make your Hue light’s color show the outside temperature — take two
It’s finally time to revisit the amazing piece of Clojure code created way back in 2017 to control Hue connected lights based on outside temperature. Join in as we bring the code up to date and add support for the temperature sensor tucked away inside the Hue motion sensors!
A Little Bit of Background
To quickly reiterate, the idea here is to let one of our multicolor Hue lights change color based on the outside temperature. This all started as a quick weekend project and the code has now been running for almost four years, updating us on the outside temperature. It has become a part of our home. I pass by it every day on the way to the shower and, although maybe still not super-useful, it tends to brighten my mornings.
The initial setup required three main components:
- The hew program — a piece of software written in Clojure and running on a Raspberry Pi.
- A Philips Hue bridge with a connected light source.
- A 1-Wire server (OWFS) running on another Raspberry Pi with a connected sensor for acquiring the outside temperature.
There is no particular reason for not running both hew and OWFS on the same Raspberry Pi, it just happened to be a good setup in my specific case.
The hew program polls the temperature source every five minutes and updates the light to a matching color. The program doesn’t switch on or off the light, it just sets the color. When the light should be on and off is controlled by a separate schedule, set up using the Hue mobile app.
Since Last Time
Opening up an old code project that hasn’t been touched for several years is always somewhat exciting. First, it takes a moment to get back in to it, it’s alway interesting to see your own code with fresh eyes. And then, you never really know what’s going to happen as you start upgrading languages, dependencies, and tools.
Upgrading Languages and Dependencies
Since 2017 Java has gone from version 1.8 to 16, and Clojure from 1.8 to 1.10. Upgrading Clojure projects tend to be rather painless but this time some issues related to more strict parsing of name spaces surfaced. Fortunately it was all sorted out when the dependencies were upgraded as well. The only library update that required a few lines to be changed was Chime — the scheduler used for polling the temperature and updating the light’s color — as it had moved from using clj-time (backed by Joda-Time) to using java.time.
A Little Bit of CI
GitHub Actions is another thing that has been added since the original project was concieved. The GitHub Actions Virtual Environments already comes with support for Java and the Leiningen build tool, so setting up a workflow for building, testing and packaging the code was done in no-time.
Philips has been particularly busy adding a bunch of new products and enhancements to the Hue lighting system (including changing its company name to Signify). The bridge now supports turning on/off lights at dusk and dawn, which can be scheduled in the app. The bridge now also supports SSL connections using a self-signed certificate, and even appears to have support for remote connections using a cloud API. However, since hew is designed to run on the local network, I haven’t investigated what is required for getting access to the remote API.
In addition to this, a plethora of new lights and accessories have been released.
I know many of you have been tirelessly hitting Reload in your browsers, hoping for a followup to the ground-breaking Jeans or Shorts article. And who am I to disappoint? I left you with a few candidate updates last time, including more vibrant colors, finer color gradients, and using external APIs as temperature sources. Well, we won’t be doing any of that, instead we’ll add more hardware to the mix and clean up configuration.
Hue Temperature Sensor
One of the new product categories that has been added to the Hue line is motion sensors, there is now both an indoor and an outdoor sensor available. These sensors are primarily designed for sensing motion and triggering lights. But, there are two somewhat hidden secrets inside these products: They both come with an ambient light sensor and a temperature sensor. The ambient light sensor is used to prevent motion to trigger the sensor during daylight. The temperature sensor however, remains a bit of a secret. It doesn’t show up anywhere in the app, but it is there and can be accessed using the API. Certain third-party systems already surface this information to the end user, like Apple’s HomeKit in its Home app.
Using this new sensor, we can stay within the Hue ecosystem and run hew even without access to a 1-wire temperature sensor net. The Hue sensor seems to update about every five minutes, so it will not be as current (and probably not as accurate) as using 1-wire, but for our purpose it should be more than enough.
Since the temperature sensor is not showing up in the app, it requires a little bit of effort on our part to figure out its internal ID, which we need when configuring hew.
To get started interacting with the Hue bridge a username is needed. Physical access to the bridge is required to set this up. Instructions on how to find the Hue bridge on the network and create the username is available on the Hue developer site.
With the user ID ready, we can proceed with listing all the sensors available on the bridge:
The bridge has a fairly liberal idea of what a “sensor” is, so chances are this list can be quite long. We look for sensors of type
ZLLTemperature, and the ID to look for is the associated int, i.e.
79 in this example response fragment:
"name": "Hue outdoor temp. sensor 1",
"manufacturername": "Signify Netherlands B.V.",
If you have jq installed on your system, you can try something like this to list all temperature sensor IDs known to the bridge, along with the latest captured temperature:
Externalizing Configuration Using Environ
In the previous version of hew, all configuration happened in a regular Clojure name space,
settings where configuration was defined as a Clojure map:
While this is kind of neat, expressing the config directly in clojure code, it also has a some obvious drawbacks. First, keeping config like this just begs for a careless commit to add your configuration secrets to a remote repository. Secondly, it can require a recompilation of the code to change the configuration, depending on how you run it. A better way is to add the configuration using environment variables at runtime.
So, in the updated version of hew we now use the environ library to help manage configuration for us. This allows for using leiningen profiles to provide configuration during development and also supplying the configuration using environment variables at runtime. Local development configuration is kept in a
profiles.clj file, next to the
projects.clj leiningen project file. Adding
.gitignore prevents potentially sensitive configuration data to mistakenly end up in the source repository.
In our code,
environ.core/env now provides a map of all loaded environment variables, and we can easily retrieve the one we want like this:
Hacking the Philips Hue API Using Clojure
The main changes in the hew Clojure code is the possibility to read temperature from either OWFS or a Hue sensor:
With the part that fetches the temperature from the Hue bridge:
let clause, we’re pulling the
state part of the JSON response, converted to a Clojure map. This is the part of the sensor data that contains the temperature and timestamp (refer to the JSON fragment above for details). We then do a little bit of parsing to get the timestamp as a
Date and convert the temperature as needed. There is also a bit of a sanity check just to make sure we’re actually dealing with a temperature sensor.
The entire project can be found at GitHub.
Building and Running
%> cd hew
%> lein uberjar
Gather all the config you need:
- Hue host (HUE_HOST)
- Hue user ID (HUE_USER_ID)
- Hue temperature sensor ID (HUE_SENSOR_ID)
- Name of the Hue light to control (HUE_LIGHT_NAME)
Instructions on how to get 1–3 can be found above. The name of the light is the same as the name of the light in the Hue mobile app.
USE_OWFS=false HUE_HOST=<host.ip> HUE_USER_ID=<hue-user-id> HUE_LIGHT_NAME=<name> HUE_SENSOR_ID=<sensor-id> java -jar target/uberjar/hew.jar
(yeah, that should all be on the same line, and the <…> replaced with actual parameters…)