Controlling IKEA Trådfri Lights from your Pi
The IKEA Trådfri lights are a new range of smart bulbs and controllers that are affordable, work well, and, as we'll see here, are hackable! The Trådfri system uses a gateway connected by ethernet to your network that speaks using ZigBee wireless to the bulbs and controllers. There are two types of controller
- the remote, which can toggle the lights on and off, dim them, or change their colour temperature, and the dimmer.
The commands sent to the gateway are DTLS (Datagram Transport Layer Security) over CoAP (Constrained Application Protocol). DTLS is essentially an implementation of SSL over UDP, but we don't need to worry about the details here. There are a number of API endpoints that you can hit to perform all the same functions as the physical remote does, and we'll see them later.
This tutorial will cover installation of libcoap, a library that can be compiled with support for DTLS, that allows you to send commands to your Trådfri gateway and hence to the bulbs. We'll also look at some nice Python code from Harald van der Laan that simplifies the process of sending requests to your Trådfri devices, and makes it trivial to hook into our Pi pHATs and HATs. Finally, we'll use the light sensor on our Enviro pHAT board to trigger the Trådfri lights to switch on when the light level drops below a given threshold.
Installing libcoap with support for DTLS
We used a Pi Zero W for our setup, as it provides a stress-free (and wire-free) way to hook into your home network, but a Pi 3 would work equally well, as would a non-wireless-equipped Pi connected to your network with an ethernet cable. The benefit of a wireless Pi is that you can place it anywhere in your space that is within reach of a power socket.
We'll assume that you've set up your Pi, soldered a header to it if it's a Zero or Zero W, and have booted into Raspbian or Raspbian Lite. If you need help soldering the header to your Pi or pHAT, then we have a tutorial that covers that here, and we have a tutorial that shows your how to set up your Pi Zero W and connect it to your Wi-Fi network completely headless-ly here.
The following instructions for compiling and installing libcoap come from GitHub user Hedda in this GitHub issue thread which has a wealth of information on hacking the Trådfri lights.
In the terminal, type the following lines of code to install everything:
sudo apt-get install build-essential autoconf automake libtool
git clone --recursive https://github.com/obgm/libcoap.git
cd libcoap
git checkout dtls
git submodule update --init --recursive
./autogen.sh
./configure --disable-documentation --disable-shared
make
sudo make install
That should have installed globally the libcoap library, particularly the
coap-client
which you can use to send commands to the hub. The Python scripts
and libraries that have been written thus far just spit out shell commands using
the coap-client
; they're Python wrappers around the client.
Setting up your Trådfri lights
We won't go into detail about setting up the Trådfri lights, as IKEA cover this in detail in the manuals and in the iOS and Android apps. We found that using the app to set them up was the easiest and most intuitive way to do it.
We got the kit that contains two colour temperature controllable bulbs, a hub, and a remote, so this tutorial will assume that you have that kit.
Once you have it all set up, test that the lights are responding as expected
with either the remote or the app. We'll begin by looking at how the endpoints
are structured, and then test whether we can toggle one of the lights on and off
from the terminal with coap-client
.
API endpoints
To talk to the gateway, you'll need to know its IP address. You should be able
to use either your router's admin page to find it, or an app that scans your
network and identifies connected devices like Fing or Angry IP Scanner. Ours
appears with a hostname of the form gw:xx-xx-xx-xx-xx-xx
, where the last bit
is the MAC address of the gateway. Let's assume that the IP address of the
gateway is 192.168.0.10
for the purposes of this tutorial.
You'll also need the key (security code) that is on the bottom of your gateway. It'll be an alphanumeric code of length 16, listed below the serial number (which is actually the MAC address). Make a note of it, as you'll need it later.
The endpoints all begin with coaps://192.168.0.10:5684/
followed by either
15001
to control bulbs individually or 15004
to control them as a group.
We'll focus on controlling an individual bulb here. The individual bulbs will
have addresses beginning at 65537
for the first bulb, 65538
for the second,
and so on. So, to control the first bulb, as we will here, you would hit the
following endpoint:
coaps://192.168.0.10:5684/15001/65537
Now that we know the endpoint to control that first bulb, we need to structure
a payload to send to the endpoint with the coap-client
. We'll send a put
request to that endpoint to toggle the light off (make sure that it's switched
on first, so that we know whether it worked or not!). Here's the payload:
{ "3311": [{ "5850": 0 }] }
The structure of that payload is as follows: the 3311
represents a dimmer
and the 5850
is the toggle for on/off, with 0
being off and 1
being on.
Let's put that all together into a call to the coap-client
that will toggle
the light off:
coap-client -m put -u "Client_identity" -k "1a2b3c4d5e6f7g8h" -e '{ "3311": [{ "5850": 0 }] }' "coaps://192.168.0.10:5684/15001/65537"
You'll need to replace 1a2b3c4d5e6f7g8h
with your security code that you
should have noted down. Notice that we specify that it's a put request, -m put
,
and the user is Client_identity
. That username is used for all requests sent
to the gateway. It's also important that you get the single and double quotes
exactly as above, since the payload includes double quotes meaning that it needs
to be wrapped in a set of single quotes.
As well as sending put requests, we can send get requests to query the state of either a single bulb, a group, or the whole shebang. Try the following to query the state of the first bulb that we're controlling:
coap-client -m get -u "Client_identity" -k "1a2b3c4d5e6f7g8h" "coaps://192.168.0.10:5684/15001/65537"
You'll get a response back with a bunch of information, that we won't get into
here, but one tidbit is that if you send a payload substituting the 5850
for
5851
and pass in a value of 0
to 254
then you can control the brightness
of your bulb, as follows:
coap-client -m put -u "Client_identity" -k "1a2b3c4d5e6f7g8h" -e '{ "3311": [{ "5851": 127 }] }' "coaps://192.168.0.10:5684/15001/65537"
That should set your bulb to 50% brightness.
Using a Python wrapper to send requests
We'll be using some neat code
from Harald van der Laan
(@hvanderlaan on Twitter) that wraps up the
calls to coap-client
in Python. Sandy
forked Harald's GitHub
repo and tweaked a couple of bits to make it work more reliably on the Pi
(basically just removing the requirement for libcoap to be built within the
repo, and changing the path for it to a global one).
Type the following to install the tqdm
requirement and clone the repo:
sudo apt-get install python-pip
sudo pip install tqdm
git clone https://github.com/sandyjmacdonald/ikea-smartlight
cd ikea-smartlight
Let's create a config file that lists the IP address of the gateway and the
security code. Type nano tradfri.cfg
to open up the nano text editor. Paste in
the following, replacing the IP address and security code with yours:
[tradfri]
hubip = 192.168.0.10
securityid = 1a2b3c4d5e6f7g8h
Press control
and x
, then y
, then enter
to save and exit nano.
Now, let's test that the Python script to control the lights is working as expected. Type the following to toggle the first light on:
python tradfri-lights.py -l 65537 -a power -v on
The syntax for these commands is fairly straightforward. The -l
flag specifies
the bulb you want to control, in this case the first one, 65537
. The -a
flag
specifies the action to call, in this case power
to toggle the bulb on or off.
And the -v
flag is the value to pass to that action, in this case on
(or
off
).
The other actions are brightness
which can be passed a value between 0
and
100
to set the bulb's brightness as a percentage. There's also color
(sic)
to change the colour temperature of the bulb, which is passed warm
, normal
,
or cold
.
If you have more than one bulb, it will likely be in a group. We mentioned in
passing earlier that there was a different endpoint to control the bulbs as a
group. There's also a different script to control your grouped bulbs. The
syntax for this script differs very slightly to the tradfri-lights.py
script,
in that it needs to be given the group ID. The group ID, unlike the bulb ID,
does not appear to be standardised, so you'll need to run the
tradfri-status.py
script as follows to find the ID of your group:
python tradfri-status.py
It should show something like the following output:
bulbid 65538, name: TRADFRI bulb E27 WS opal 980lm 2, bightness: 245, state: off
bulbid 65537, name: TRADFRI bulb E27 WS opal 980lm, bightness: 254, state: off
groupid: 155563, name: TRADFRI GROUP, state: off
You'll see that the two bulbs, 65537
and 65538
are listed, as well as the
group which, in our case, is 155563
. We'll use that ID to toggle both bulbs in
the group on and off.
python tradfri-groups.py -g 155563 -a power -v on
You'll see that instead of -l
that we used to specify the ID of the bulb, we
use -g
to pass in the group ID 155563
. Also note that there isn't control of
the colour temperature of the group at the current time, only power and
brightness.
Installing the Enviro pHAT Python library
You'll need to install the Enviro pHAT Python library, if you haven't already. We've made it really simple with our one-line-installer. Just open a terminal and type:
curl https://get.pimoroni.com/envirophat | bash
Type y
when prompted and note that it may reboot once installed.
We've a detailed tutorial here on getting started with Enviro pHAT, if you need more help.
Triggering the lights in low light
Because Harald has done all the heavy lifting of writing a wrapper for
coap-client
, it's trivial to hook in our Enviro pHAT to trigger the lights to
come on in low light.
Create a new file within the ikea-smartlight
directory. We called ours
tradfri-envirophat.py
, but call yours whatever you'd like.
nano tradfri-envirophat.py
Paste the following code into that file, and then press control
and x
, then
y
, then enter
to save and exit nano. We'll go through exactly what it does
after.
import time
import ConfigParser
from envirophat import light
from tradfri import tradfriActions
conf = ConfigParser.ConfigParser()
conf.read('tradfri.cfg')
hubip = conf.get('tradfri', 'hubip')
securityid = conf.get('tradfri', 'securityid')
bulbid = 65537
state = 0
def switch_on(hubip, securityid, bulbid):
tradfriActions.tradfri_power_light(hubip, securityid, bulbid, 'on')
print('Lights switched on!')
def switch_off(hubip, securityid, bulbid):
tradfriActions.tradfri_power_light(hubip, securityid, bulbid, 'off')
print('Lights switched off!')
threshold = 500
while True:
if light.light() < threshold and state != 1:
switch_on(hubip, securityid, bulbid)
state = 1
elif light.light() >= threshold and state == 1:
switch_off(hubip, securityid, bulbid)
state = 0
time.sleep(0.05)
At the top, we import a few libraries that we'll need within our script. time
allows us to introduce delays into our while
loop so as not to overwhelm the
API. ConfigParser
does exactly as it says, and allows us to grab the gateway
IP address and security code from the tradfri.cfg
file. We'll use just the
light
class from the envirophat
library to take ambient light readings.
Last, we'll import tradfriActions
to allow us to use its functions to send
calls to coap-client
.
We read the values from the config file and specify the bulb ID as follows:
conf = ConfigParser.ConfigParser()
conf.read('tradfri.cfg')
hubip = conf.get('tradfri', 'hubip')
securityid = conf.get('tradfri', 'securityid')
bulbid = 65537
We'll also create a variable called state
and initialise it with a value of
0
, using it to keep track of the state of the bulb.
Next, we have a couple of functions to toggle the bulb on or off, and print
a message to the standard output telling us when the function has been called.
It's worth noting that you could condense these down into a single function and
pass in an on
/off
command but, for clarity here, we'll keep it as two
separate functions.
def switch_on(hubip, securityid, bulbid):
tradfriActions.tradfri_power_light(hubip, securityid, bulbid, 'on')
print('Lights switched on!')
We pass in the hubip
, securityid
, and bulbid
. These functions will be
called within our final while True:
loop that will take a light reading twenty
times every second and toggle the lights on or off.
threshold = 500
while True:
if light.light() < threshold and state != 1:
switch_on(hubip, securityid, bulbid)
state = 1
elif light.light() >= threshold and state == 1:
switch_off(hubip, securityid, bulbid)
state = 0
time.sleep(0.05)
Our loop has an if
and an elif
that toggle the bulb on or off respectively.
To avoid switching the bulb on or off on every iteration of the loop, we check
what the current state of the bulb is with the if
/elif
as well as having a
conditional that checks the light level.
if light.light() < threshold and state != 1
checks both whether the light level
in lumens is less than 500 and that the state is not already on. If both of those
conditions apply, then we switch the bulb on with
switch_on(hubip, securityid, bulbid)
and then update the state with state = 1
.
elif light.light() >= threshold and state == 1
will apply both if the light
level is greater to or equal to 500 lumens and the state is on, and switch the
bulb off as a result - switch_off(hubip, securityid, bulbid)
- and finally set
the state to off with state = 0
.
We include a small delay of a 20th of a second, with time.sleep(0.05)
, to give
the gateway API time to keep up with the requests. It's worth noting that you
may get some error responses back occasionally when the API is most likely busy
responding to a request already.
Try running the script now, by typing python tradfri-envirophat.py
, assuming
that's what you named it. Cover the Enviro pHAT with your hand and watch in
amazement as the light turns on, as if by magic.
To kill the script, press control
and c
.
Taking it further
The obvious limitation of the approach here is that the threshold at which the light is triggered to come on is somewhat subjective. Also, assuming your Pi Zero W and Enviro pHAT are fairly close to your light/s then they will probably switch off as soon as they've come on because the light level will be higher than 500 lumens - DISCO TIME!
The solution to this is to experiment with the placement of the Pi Zero W and Enviro pHAT, and the correct ambient light level at which to trigger the lights to come on. We'll leave that up to you.
You can use the same techniques applied here to do any number of things with your IKEA Trådfri lights. You could hook into Twitter and have it send a tweet when your lights are switched on, by querying their state, or send a push notification to your phone. You could use our Touch pHAT to create your own customisable controller for your lights. Or why not have them flash a couple of times when your doorbell rings? The world is your lobster!
Search above to find more great tutorials and guides.