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 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 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!

Shopping basket

Need something for this project? You can use the links below to add products to your Pimoroni Shop basket for easy checkout.

Enviro pHAT
£16.00
Raspberry Pi Zero W
Zero W only £9.60
Raspberry Pi Zero W
Zero W + Adaptors £14.00
Raspberry Pi Zero W
Zero W + Adaptors + Pibow Zero W £19.00
Raspberry Pi 3 Only
£34.00
Raspberry Pi Universal Power Supply
£8.00
NOOBS 8GB SD Card (2.2.0)
£6.50
Antex XS25 Soldering Iron (UK Plug)
£30.00
Antex Lead Free Solder 2m
£3.00
GPIO Hammer Header (Solderless)
Male + Female + Installation Jig £6.00
GPIO Hammer Header (Solderless)
Male £2.00
GPIO Hammer Header (Solderless)
Female £3.00
Want to checkout or change something? Click here to view your cart.

Tutorial
Intermediate
LEDs, Raspberry Pi, Pi Zero

Sandy Macdonald

sandy@pimoroni.com
@sandyjmacdonald
http://sandyjmacdonald.github.io
Formerly, Sandy worked at the University of York, in the biology department, analysing data and telling people that they should have used more replicates. Now a fully-fledged crew member at Pimoroni - head of digital content - working on learning materials and digital chunterings. Find him on Twitter and most everywhere else, as sandyjmacdonald.