Controlling Philips Hue with Siri

Software

I’ve jumped onto the home automation bandwagon and purchased some Philips Hue light bulbs. In this post, I’ll show my approach for the customized setup in my apartment.

The first step was to do some research. What are the possibilities? I looked around on the App Store, and found some cool ideas…

Lighting Presets

It’s a bad experience to open an app and twiddle with a dozen different knobs just to tweak the lights. During normal use, there’s only a handful of things one would want to do with the lights: on, off, dim, and a few mood / color settings for fun. Each of these could easily be a separate preset. Just tap a button and let the app adjust the rest.

One of the better apps had a feature that allowed users to save a lighting preset as a Safari bookmark on the iOS home screen. When the bookmark opened, it would direct you to a website, which would trigger app launch, which would change your lights. This all worked, but had a delay of several seconds between tapping and seeing the light change. I liked triggering the lights straight from the home screen, but there must be a way to cut out the middle man…

Voice Control

This is the killer feature. I saw one app that was ambitious enough to include voice control, but this presented a few issues. There is no way to have Siri interface with your iOS apps (at least not at the time of this writing). For the app to listen to your voice, the user needs to have already opened it. At that point, it’s easier to manipulate the app with your thumb than with your voice.

Wrangling Siri

Siri doesn’t have an API yet, but that doesn’t mean she is totally useless in this endeavor. Siri has been able to open apps since iOS 6. You can test this yourself by saying something like, “Open Safari”, “Launch Safari”, or more concisely, “Safari”. Using this information, I set up an Xcode project which had several different targets. Each target had a unique name and would function as a separate lighting preset.

An example of one of these targets is the “HueLightsOn” app. As you might have guessed, the app’s only function is to turn all of the lights on. I tried giving it names like “SiriPleaseTurnTheLightsOn”, but Siri will not interpret that as a request to open an app. Because the name sounds like a grammatically valid sentence, she will likely attempt to do something else.

Hue API

Let’s take a look at how this app works. Hue has a full-fledged iOS SDK, but I decided to keep it as simple as possible by using the Hue bridge’s web API. To set up the Hue wi-fi bridge, I followed Philips’s documentation for new developers here. I now had my Hue bridge’s IP address and a working username.

My Xcode project uses the following code to send commands to the Hue bridge:

//NOTE: don't call this from the UI thread
void updateLight(NSURL* url, NSString* method, NSString* jsonBody)
{
    NSMutableURLRequest* request = [[NSMutableURLRequest alloc] init];
    [request setURL:url];
    NSData* bodyData = [jsonBody dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
    NSString* postLength = [NSString stringWithFormat:@"%lu", (unsigned long)[bodyData length]];
 
    [request setHTTPMethod:method];
    [request setValue:postLength forHTTPHeaderField:@"Content-Length"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setHTTPBody:bodyData];
 
    [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
}

And sends the command like this:

int lightNum = 1;
NSString* onCommand = @"{\"on\":true,\"bri\":200,\"sat\":100,\"transitiontime\":20,\"effect\":\"none\",\"hue\":15000}";
NSString* lightStateURL = [NSString stringWithFormat:@"http://%@/api/%@/lights/%d/state", ipAddress, kHueAPIUserName, lightNum];
updateLight([NSURL URLWithString:lightStateURL], @"PUT", onCommand);

This example shows how to update only one bulb, so we would want to add a similar JSON command for each of the other bulbs.

Notice that the URL for updating the bulb’s state requires the bridge IP and the username that I set up earlier. Also, some of the arguments included in the JSON string are optional, like ‘transitiontime’, which adds an aesthetically pleasing fade effect.

Getting out of the Way

I want to update the lights, then have the app get out of the way. To accomplish this, the last step each app performs after updating the lights is to crash itself.

//kill the app
exit(0);

This is a big no-no if you’re planning to submit your app to Apple’s App Store, but I’m using this setup only for myself.

The Final Result

I created a few other apps with similar presets, and got a result that looks like this:

hueLights

Ta-da! It works like a charm.

 

Advertisements

3 thoughts on “Controlling Philips Hue with Siri

  1. I took your idea and extended it a little. This code toggle the state of groups of lights:

    
    toggleGroup(@"10.0.0.111", @"APIUSERNAME", 4);
    
    void toggleGroup(NSString* ipAddress, NSString* apiUser, int groupNum) {
        NSString* groupURL = [NSString stringWithFormat: @"http://%@/api/%@/groups/%d", ipAddress, apiUser, groupNum];
        NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString: groupURL]];
        NSURLResponse* response = nil;
        NSError* error = nil;
        NSData* data = [NSURLConnection sendSynchronousRequest: request returningResponse: &response error: &error];
        
        // got the response
        if (error == nil) {
            NSMutableDictionary *result = [NSJSONSerialization JSONObjectWithData: data options: NSJSONReadingMutableContainers error: &error];
            
            // no problem parsing JSON
            if (error == nil) {
                NSString* currentState = [NSString stringWithFormat: @"%@", result[@"action"][@"on"]];
                NSMutableString* json = [NSMutableString stringWithString: @"{\"on\":true}"];
                
                // toggle state, above we default to true
                if ([currentState isEqualToString: @"1"]) {
                    [json setString: @"{\"on\":false}"];
                }
                
                NSData* bodyData = [json dataUsingEncoding: NSASCIIStringEncoding allowLossyConversion: YES];
                NSString* postLength = [NSString stringWithFormat:@"%lu", (unsigned long)[bodyData length]];
                
                [request setURL: [NSURL URLWithString: [groupURL stringByAppendingString: @"/action"]]];
                [request setHTTPMethod: @"PUT"];
                [request setValue: postLength forHTTPHeaderField: @"Content-Length"];
                [request setValue: @"application/json" forHTTPHeaderField: @"Content-Type"];
                [request setHTTPBody: bodyData];
                
                [NSURLConnection sendSynchronousRequest: request returningResponse: &response error: &error];
            }
        }
    }
    
    
  2. Since I wanted to do an exercise in using the Swift programming language, I ported my above function:

    func toggleGroup(ipAddress: String, apiUser: String, groupNum: Int) {
        let groupURL = "http://\(ipAddress)/api/\(apiUser)/groups/\(groupNum)"
    
        let reqTaskGet = NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: "\(groupURL)")!) {(respData, response, error) in
            var error: NSError?
            
            let result = NSJSONSerialization.JSONObjectWithData(respData,
                options: NSJSONReadingOptions.AllowFragments,
                error: &error) as NSDictionary
            
            // got the response and no problem parsing JSON
            if (error == nil) {
                // get the action item
                if let action = result["action"] as? NSDictionary {
                    // get the current state
                    if let currentState = action["on"] as? Int {
                        var json = "{\"on\":true}"
                        
                        // toggle state, above we default to true
                        if (currentState == 1) {
                            json = "{\"on\":false}"
                        }
                        
                        let request = NSMutableURLRequest(URL: NSURL(string: "\(groupURL)/action")!)
                        
                        // set request parameters
                        request.HTTPMethod = "PUT"
                        request.HTTPBody = json.dataUsingEncoding(NSUTF8StringEncoding);
                        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
                        request.addValue(String(countElements(json)), forHTTPHeaderField: "Content-Length")
                        
                        let reqTaskPost = NSURLSession.sharedSession().dataTaskWithRequest(request) {
                            data, response, error in
                            
                            if error != nil {
                                println("error=\(error)")
                                return
                            }
                        }
                        
                        reqTaskPost.resume()
                    }
                }
            }
        }
        
        reqTaskGet.resume()
    }
    

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s