Google Assistant support for IKEA TRÅDFRI lights

Earlier this year I got some IKEA smart lights from their TRÅDFRI line. They can be controlled by a remote and also through a smartphone app, if you have IKEA's gateway. Around the same time I also got a Google Home and naturally I wanted them all to work together. IKEA is planning to add official support for Google Assistant very soon, but in the meantime I rolled out my own. So while all this may be obsolete soon, I still wanted to publish it in the hope that it might be useful for someone (perhaps for adding Assistant support to some other smart lighting system without official support). Below I try to provide a step-by-step description of how I got it to work, but if you just want to see the code, here it is.

Let's get started. We're going to need a computer on the same network as the IKEA gateway (or it must be able to reach it though some clever routing). I used a Raspberry Pi, but any computer running Linux will do. This computer needs to have its 443 port accessible from the Internet, so if it's behind a router, you need to configure port forwarding. We're also going to need a domain name that will point to our Linux box and a proper SSL/TLS certificate for that domain. I'm not going to go into details about these, but you can easily get a certificate for free at Let's Encrypt. Below I'm using home.example.com as the domain name, obviously you should change it to your actual domain wherever it appears.

The general architecture of the solution is as follows. We're going to create a Smart Home App on Actions on Google and deploy it in testing mode. Then when we talk to the Google Assistant (on Google Home or a phone or a smartwatch), Google's cloud magic will make a call to our server, asking what lights are available or telling it to turn a certain light on or off. Then our app will talk to the IKEA gateway, which in turn talks to the light bulbs themselves. Using the Actions SDK instead of IFTTT means we will get to use more natural language commands without defining each phrase manually.

Google's documentation likes to give all the examples in node.js, but the app can by anything as long as it speaks the proper API over HTTP. I wrote mine in Python using Django and deployed it using nginx and uwsgi, but any other way of deploying a Django app would also work.

Let's get the code (below I assume we're running as the pi user, change to your actual user and group if they're different):
sudo mkdir /var/www/home.example.com
sudo chown pi:pi /var/www/home.example.com
cd /var/www/home.example.com/
git clone https://github.com/jfedor2/ikea-tradfri-google-assistant.git .
One more thing we're going to need is the coap-client tool, compiled with DTLS (encryption) support. It's what we're going to use to talk to the IKEA gateway. I'm going to borrow the instructions for compiling it from here:
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
./configure --disable-documentation --disable-shared
sudo make install
Let's get back to our app and actually make it work. First, let's create a Python virtual environment for the app so that we can install the necessary modules locally:
cd /var/www/home.example.com/
virtualenv .virtualenv
. .virtualenv/bin/activate
pip install -r requirements.txt
Then we need to edit the Django settings file. You can either edit /var/www/home.example.com/tradfri/settings.py or leave that file unchanged and create a new file, /var/www/home.example.com/tradfri/local_settings.py, with just the settings that need changing locally. (That way it's easier to keep settings.py under version control.) You need to provide the following settings:
SECRET_KEY = 'insert a long string of random characters here that you will keep private'
ALLOWED_HOSTS = ['home.example.com']
COAP_CLIENT = '/usr/local/bin/coap-client' # or wherever you installed it
IKEA_SECRET = 'Security Code from the bottom of your IKEA gateway'
GATEWAY_ADDRESS = 'IP address of your IKEA gateway'
Depending on your local network configuration, it may also be useful to add the local IP address of your Linux computer to ALLOWED_HOSTS (if you're using port forwarding on the router, accessing the server through the proper domain name might not work from inside the network):
ALLOWED_HOSTS = ['home.example.com', '192.168.0.x']
Then, create the necessary tables in the database (run the following commands in the Python virtual environment created above - run the activate command before if you haven't already in this shell):
./manage.py migrate
And let's create a user that we will use for OAuth2 account linking:
./manage.py createsuperuser
Choose whatever username and password you want and remember them.

In the extras directory, I provided example configuration files for nginx and uwsgi, home.example.com.conf and home.example.com.ini. Edit them with your specific settings and put them in /etc/nginx/sites-enabled/ and /etc/uwsgi/apps-enabled/, respectively.

After updating the configuration files, restart uwsgi and nginx (install them first if necessary):
sudo apt-get install nginx uwsgi-plugin-python
sudo service uwsgi restart
sudo service nginx restart
Whenever you change something in the Django app, you need to restart uwsgi.

In the extras directory there's also an actions.json file, that we will use to tell Google where to look for our app. But first we need to create a project for it.

Go to the Actions on Google console. Click "Add/import project", choose a name and click "create project". In the "Add actions to your app" section, click on "Actions SDK". It will give you an example command that looks something like this:
gactions update --action_package PACKAGE_NAME --project smartlights-abc12
smartlights-abc12 is your project ID, which you will need in a moment.

Update the /var/www/home.example.com/extras/action.json file with your actual domain name, download the gactions command-line tool and run it, substituting the actual project ID of course (you may have to provide a path to the tool if it's not in your PATH):
gactions test --action_package /var/www/home.example.com/extras/action.json --project smartlights-abc12
This will put your app in testing mode and make it accessible in the Google Home app on your phone (you have to be using the same Google account). Google says an app only works in testing mode for 3 days, but I found that in reality it works indefinitely.

In the Google Actions console you probably also need to provide some text and graphics for your app. It doesn't matter what you put there (no-one else is going to see it).

Now all that is left is to set up OAuth2 for account linking. We need to configure it on both our app and the Actions on Google console. Go to https://home.example.com/oauth2/applications/ or http://192.168.0.x/oauth2/applications/ (substituting the correct address of course). Log in using the user and password you created with createsuperuser. Click "New Application". Put some name in (I used "google"), note the client ID and client secret (you're going to need them in a moment), choose "client type": "confidential", "authorization grant type": "authorization code" and put https://oauth-redirect.googleusercontent.com/r/smartlights-abc12 in "redirect uris" (substituting your project ID from above). Click "Save". Now go to the Actions on Google console and in your project go to "Account linking". Choose "Authorization code" under "Grant type" as before. Under "Client information", paste the client ID and client secret that you saw in our app's web interface a moment ago. Then put https://home.example.com/oauth2/authorize/ under "Authorization URL" and https://home.example.com/oauth2/token/ under "Token URL". Click "save".

We're almost done. Open the Google Home app on your phone. Open the hamburger menu and tap "Home control". Tap the plus icon in the bottom right corner. This should give you a long list of devices that are officially supported by the Google Assistant, but also our humble app should be there on the very top, with its name prefixed with "[test]". Tapping it should take you to a login form on our app. Login with the user and password you created with createsuperuser a few minutes ago and on the next screen confirm that you want to give Google access. (You may have to disable wifi on your phone and use the cellular network connection for this step if https://home.example.com/ doesn't work from inside your local network.)

Boom, that's it. You should get a list of your IKEA lights and be able to control them with Google Assistant.