Philips Hue API Hacking

Here it is! My Python client for the Philips Hue system is done.

The Short:

It's available on Github.

Sample Usage:

from hue import Hue
h = Hue() # Initialize the class
h.station_ip = "192.168.1.222" # your base station IP
h.get_state() # Authenticate, bootstrap your lighting system
# The first time, you have a minute between calling get_state() and pushing
# the button on your base station to authenticate your machine. It should
# Log and tell you if that is going to happen or not.
l = h.lights.get('l3') # get bulb #3
l.bri(0) # Dimmest
l.bri(255) # Brightest
l.rgb(120, 120, 0) # [0-255 rgb values]
l.rgb("#9af703") # Hex string
l.on()
l.off()
l.toggle()
l.alert() # short alert
l.alert("lselect") # long alert
l.setState({"bri": 220, "alert": "select"}) # Complex send

The Long

Philips Hue is a red/green/blue LED bulb and base-station system. The light quality is nice but the bulbs are pricey ($60 per after the base station, which is 3 bulbs and base for $200). You can also only buy them from apple right now, which is totally crazy because they have an android app too. I assume this is just a temporary exclusivity deal and they'll have wider availability soon.

As soon as I got them I wanted to hack them, the base station is ethernet on one end (between apps, the philips website) and zigbee on the other (to talk to the lights)

The API to the base station is actually quite nice, very RESTful, and much easier than the Wemo API. Straight HTTP over port 80 between the apps and device, but disappointingly on one hand (security), nice on the other (reverse engineering) also HTTP over port 80 to phone-home to two different Philips sites.

A typical request

A typical request to the Philips Hue base station API looks like this:

GET http://<base station ip>/api/<client id string>

This request returns the entire state of the system. Mine is

{
    "config": {
        "UTC": "2012-11-10T22:31:57", 
        "dhcp": true, 
        "gateway": "192.168.1.1", 
        "ipaddress": "192.168.1.130", 
        "linkbutton": false, 
        "mac": "<mac is here>", 
        "name": "Philips hue", 
        "netmask": "255.255.255.0", 
        "portalservices": true, 
        "proxyaddress": "", 
        "proxyport": 0, 
        "swupdate": {
            "notify": false, 
            "text": "", 
            "updatestate": 0, 
            "url": ""
        }, 
        "swversion": "01003542", 
        "whitelist": {
            "22a828f1898a4257c3f181e75324f557": {
                "create date": "2012-11-10T19:23:15", 
                "last use date": "2012-11-10T22:31:57", 
                "name": "python-hue"
            },
            ... # more client info
        }
    }, 
    "groups": {}, 
    "lights": {
        "1": {
            "modelid": "LCT001", 
            "name": "Hue Lamp 1", 
            "pointsymbol": {
                "1": "none", 
                "2": "none", 
                "3": "none", 
                "4": "none", 
                "5": "none", 
                "6": "none", 
                "7": "none", 
                "8": "none"
            }, 
            "state": {
                "alert": "none", 
                "bri": 20, 
                "colormode": "ct", 
                "ct": 369, 
                "effect": "none", 
                "hue": 14922, 
                "on": true, 
                "reachable": true, 
                "sat": 144, 
                "xy": [
                    0.4595, 
                    0.4105
                ]
            }, 
            "swversion": "65003148", 
            "type": "Extended color light"
        }, 
        "2": {
            "modelid": "LCT001", 
            "name": "Hue Lamp 2", 
            "pointsymbol": {
                "1": "none", 
                "2": "none", 
                "3": "none", 
                "4": "none", 
                "5": "none", 
                "6": "none", 
                "7": "none", 
                "8": "none"
            }, 
            "state": {
                "alert": "none", 
                "bri": 20, 
                "colormode": "ct", 
                "ct": 369, 
                "effect": "none", 
                "hue": 14922, 
                "on": true, 
                "reachable": true, 
                "sat": 144, 
                "xy": [
                    0.4595, 
                    0.4105
                ]
            }, 
            "swversion": "65003148", 
            "type": "Extended color light"
        }, 
        "3": {
            "modelid": "LCT001", 
            "name": "Hue Lamp 3", 
            "pointsymbol": {
                "1": "none", 
                "2": "none", 
                "3": "none", 
                "4": "none", 
                "5": "none", 
                "6": "none", 
                "7": "none", 
                "8": "none"
            }, 
            "state": {
                "alert": "none", 
                "bri": 254, 
                "colormode": "xy", 
                "ct": 153, 
                "effect": "none", 
                "hue": 34073, 
                "on": true, 
                "reachable": true, 
                "sat": 254, 
                "xy": [
                    0.3136, 
                    0.3297
                ]
            }, 
            "swversion": "65003148", 
            "type": "Extended color light"
        }
    }, 
    "schedules": {}
}

There are some interesting things in there, not the least of which being that it has a "schedules" and "alert" function which is not available to the app.

You won't get this request the first time just by shooting a request to your base with cURL, you first have to authenticate.

Authentication

The process looks like this:

  • Try to send a normal request
  • Get the error message about authentication
  • Send a put request with your device information
    • devicetype - some string
    • username - some hash (important that you keep it, python-hue uses the FQDN of your machine)
  • Push the button on the Hue Base Station to approve the authentication.

Example:

curl -XPOST http://192.168.1.130/api/ -d '{"devicetype": "1337 client", "username": "22a828f1898a4257c3f181e753241337" }'
[{"error":{"type":101,"address":"/","description":"link button not pressed"}}]
# Go press the button!
curl -XPOST http://192.168.1.130/api/ -d '{"devicetype": "1337 client", "username": "22a828f1898a4257c3f181e753241337" }'
[{"success":{"username":"22a828f1898a4257c3f181e753241337"}}]

Sending more requests

Once you've authenticated, your device ID is in the URI of every request. It's an interesting decision on the part of their API designers, but it works just fine once you realize that's what's going on.

Examples:

curl http://192.168.1.130/api/22a828f1898a4257c3f181e753241337/lights/3

{
    "modelid": "LCT001", 
    "name": "Hue Lamp 3", 
    "pointsymbol": {
        "1": "none", 
        "2": "none", 
        "3": "none", 
        "4": "none", 
        "5": "none", 
        "6": "none", 
        "7": "none", 
        "8": "none"
    }, 
    "state": {
        "alert": "none", 
        "bri": 254, 
        "colormode": "xy", 
        "ct": 153, 
        "effect": "none", 
        "hue": 34073, 
        "on": true, 
        "reachable": true, 
        "sat": 254, 
        "xy": [
            0.3136, 
            0.3297
        ]
    }, 
    "swversion": "65003148", 
    "type": "Extended color light"
}

curl -XPUT http://192.168.1.130/api/22a828f1898a4257c3f181e753241337/lights/3/state -d '{"bri": 0}'
[{"success":{"/lights/3/state/bri":0}}]

curl -XPUT http://192.168.1.130/api/22a828f1898a4257c3f181e753241337/lights/3/state -d '{"alert":"select"}'
[{"success":{"/lights/3/state/alert":"select"}}]

That's it!

Let me know if you're using it, or if you'd like to see more functionality out of the client, File issues on Github, or let me know what else you'd like to see me write about.


Comments and Messages

I won't ever give out your email address. I don't publish comments but if you'd like to write to me then you could use this form.

Issac Kelly