Philips Hue Entertainment API

Cover Image for Philips Hue Entertainment API

Introduction

Philips Hue's Entertainment API enabled streaming of light commands over the local network. Applications can use this to produce real-time lighting effects to go along with things like games, music, television, etc. The Hue Bridge is using a UDP based API to achieve this low latency streaming, secured with Datagram Transport Layer Security (DTLS). Then the Hue Bridge converts the UDP messages into Zigbee messages and sends them to the bulbs. According to Philips' developer documentation, this is done by building a Hue Entertainment command in the following manner:

  1. The command packages together the target state of up to 10 light channels in a custom Zigbee message.
  2. This message is sent as a unicast over the mesh network to an automatically chosen proxy node in the target entertainment room.
  3. This proxy node sends out a non-repeating low level MAC layer broadcast which all nearby lights will hear.
  4. The nearby lights unbundle the message and act on their states.

The Zigbee side of things is handled internally by the Hue Bridge, so this article is going to focus on the UDP API exposed for developers. For an overview of DTLS, you can view my previous article here.

Prerequisites

There are a few things that must be done before you can start using the Hue Entertainment API.

  1. Authenticate with the Hue Hub Press the button on the Hue Hub and then run the following command to authenticate:
curl -X POST https://<HUB_IP_ADDRESS>/api -H "Content-Type: application/json" -d '{"devicetype":"<APPLICATION_NAME>","generateclientkey":true}' --insecure

This will return a username and clientkey. The username is used to authorize calls made to the Hue Hub, and the clientkey is the Pre-Shared Key (PSK) used in the DTLS connection. Make sure not to lose the clientkey because it cannot be retrieved again. If it is lost, you will need to create a new one.

Note: I am using --insecure in each curl command to skip the TLS certificate checks just for simplicity.

  1. Use the Hue app to create an entertainment area

  2. Get the identifier of the entertainment area You can retrieve the entertainment areas from your Hue Hub with the following command:

curl https://<HUB_IP_ADDRESS>/clip/v2/resource/entertainment_configuration --header "hue-application-key: <USERNAME>" --insecure

It will return a response like this, and you will need to take the top level id field for later use, which in this example is 6eaf3b98-418d-48f3-89e4-a374cf9ef290.

{
	"errors": [],
	"data": [{
		"id": "6eaf3b98-418d-48f3-89e4-a374cf9ef290",
		"type": "entertainment_configuration",
		"id_v1": "/groups/200",
		"name": "entertainmentArea1",
		"status": "inactive",
		"configuration_type": "music",
		"metadata": {
			"name": "entertainmentArea1"
		},
		"stream_proxy": {...},
		"channels": [...],
		"locations": {...},
		"light_services": [...]
	}]
}

Starting the Entertainment Area

With all of that set up, you will then need to start the entertainment area before making the DTLS connection. Ideally you should start the entertainment area in the same code that sets up the DTLS connection because the entertainment area will quickly stop if there is no streaming activity to it (within a few seconds). However, the following CURL command will be used for this example:

curl -X PUT https://<HUB_IP_ADDRESS>/clip/v2/resource/entertainment_configuration/6eaf3b98-418d-48f3-89e4-a374cf9ef290 -H "hue-application-key: <USERNAME>" -H "Content-Type: application/json" -d '{"action":"start"}' --insecure

Initializing the DTLS Connection

It is now time to initialize the DTLS Connection. I used the node-dtls-client NPM package to handle the DTLS connection. The Hue Hub only supports DTLS 1.2 with pre-shared key exchange and the TLS_PSK_WITH_AES_128_GCM_SHA256 cipher suite. Here is what the code looks like:

var dtls = require("node-dtls-client");

const socket = dtls.dtls
	// create a socket and initialize the secure connection
	.createSocket({
        type: "udp4",
        address: "<HUB_IP_ADDRESS>",
        port: 2100,
        psk: { "<USERNAME>": Buffer.from("<CLIENTKEY>", 'hex') },
        timeout: 10000,
        ciphers: [ "TLS_PSK_WITH_AES_128_GCM_SHA256" ], // The cipher suite used by Philips for DTLS
    })
	// subscribe events
	.on("connected", () => {
        console.log("connected!")
        /* start sending data */
    })
	.on("error", (e: any /* Error */) => { console.log(e)})
	.on("message", (msg: any /* Buffer */) => { console.log(msg)})
	.on("close", () => { })

Sending Control Commands

Once the DTLS connection has been made, you can start streaming control commands over it. The commands are in a binary, big endian format. A command consists of the following:

  1. 16 byte header
  2. 36 byte entertainment area id
  3. 7 byte light channel data (max of 20)
    • Each defines the color / brightness for a light channel in the entertainment area

Here is an example command:

// Static protocol name used by the API
const protocolName = Buffer.from("HueStream", "ascii")

const restOfHeader = Buffer.from([
    0x02, 0x00, /* Streaming API version 2.0 */
    0x01, /* sequence number 1 (This is currently not used by the Hue Hub) */
    0x00, 0x00, /* Reserved - just fill with 0's */
    0x00, /* specifies RGB color (set 0x01 for xy + brightness) */
    0x00, /* Reserved - just fill with 0's */
])

// Id of the entertainment area to control
const entertainmentConfigurationId = Buffer.from("6eaf3b98-418d-48f3-89e4-a374cf9ef290", "ascii")

const channelOne = Buffer.from([
    0x00, /* channel 0 */
    0x00, 0x00, 0xff, 0xff, 0x00, 0x00, /* green */
])

const channelTwo = Buffer.from([
    0x01, /* channel 1 */
    0x00, 0x00, 0x00, 0x00, 0xff, 0xff, /* blue */
])

// Concat everything together to build the command
const buffer = Buffer.concat([
                protocolName, 
                restOfHeader, 
                entertainmentConfigurationId, 
                channelOne, 
                channelTwo])

Now you just need to update the previous code to send this command after the DTLS connection is made.

// subscribe events
	.on("connected", () => {
        console.log("connected!")
        /* start sending data */
        socket.send(buffer, (error: Error, bytes: number) => {
            console.log(error)
            console.log(bytes)
        })
    })

This example will only send a single command. To make this more interesting, you will need to stream different commands to the Hue Hub at a fixed rate. The API documentation recommends streaming at a rate of 50-60Hz. The bridge itself only sends commands over Zigbee at a rate of 25Hz. With the faster rate of streaming to the Hue Hub, you can duplicate commands to account for potential UDP packet loss.

Wrapping Up

This post gave a basic overview of how to connect with and use the Philips Hue Entertainment API for real-time streaming of light control commands. For more thorough API documentation, you can sign up for a Hue developer account and visit here. In the future, I may go further into this and explore how to create different types of lighting effects through this API.