Skip to content
Alexander Overvoorde edited this page Aug 16, 2013 · 20 revisions

Jue is designed to be a lightweight wrapper over the actual API. It provides some convenience functions and constructs, but in general it just wraps the API calls and responses into nice classes and exceptions, so you don't have to worry about all the gory network and JSON details.

Organization

The two most important classes are HueBridge and BridgeDiscovery. The HueBridge class represents a physical Hue bridge and has methods closely resembling API calls, such as getLights() and createGroup(). The BridgeDiscovery class gives you two methods of actually finding bridges on the local network. These are useful for developing apps that automatically discover bridges.

Then there're classes representing each entity:

All of these objects contain data, like the current state (on/off, color, ...) or configuration. All these types of data have their own classes as well.

Interaction with the bridge

All operations that require interaction with the bridge go through the HueBridge class. For example, you may find code like this:

bridge.setLightName(kitchenLight, "Kitchen");

rather than:

kitchenLight.setName("Kitchen");

This is done to make it more obvious to you as developer what methods will result in requests to the bridge, and which methods are free of side-effects.

Exceptions

All functions that interact with the bridge have the checked exceptions IOException and ApiException. The former is thrown if something goes wrong on the connection/HTTP protocol level and the latter if the bridge responds with an error through the API. There is a subclass for every type of API exception that can occur. The documentation for each function tells you exactly which exceptions you can expect.

Additionally, an IllegalArgumentException may be thrown if you don't meet API restrictions when passing certain parameters. For example, name and description parameters are often limited in length. The documentation specifies the exact byte length boundaries for each function parameter. Note that with UTF-8 encoding, a single character does not always correspondent to a single byte!

Getting started

Clone the repository and add the project to your Eclipse workspace. Add it to your project's Java buildpath using the project properties.

It all begins with connecting to your bridge. Construct a new HueBridge object with the IP address of your bridge.

HueBridge bridge = new HueBridge("192.168.1.101");

We haven't specified a username yet, so no interaction with the bridge is possible yet. You can call link to register a new user. It will throw an exception unless the link button on the bridge has been pressed within the last 30 seconds.

String newUser = bridge.link("aValidUser", "myApp");
System.out.println("You can now connect as " + newUser + "!");

After you've registered an account, you can either add it to the constructor or call authenticate on the existing bridge object.

HueBridge bridge = new HueBridge("192.168.1.101");
bridge.authenticate("aValidUser");

// or

HueBridge bridge = new HueBridge("192.168.1.101", "aValidUser");

If the username you specified is correct, the program should exit successfully, otherwise an UnauthorizedException will be thrown. Now that we've got access to the bridge, we can start doing things.

Object and FullObject

Let's start by seeing what lights are connected to the bridge by calling getLights.

for (Light light : bridge.getLights()) {
    System.out.println(light.getName());
}

You should see something like this printed to the console:

HueLamp 1
HueLamp 2
HueLamp 3

By inspecting the Light, you will notice that it only has the two methods getId and getName. That's because the API request behind the lights retrieval only returns this much information for each light. If you want to know more about a specific light, you will have to retrieve a FullLight object.

for (Light light : bridge.getLights()) {
    FullLight fullLight = bridge.getLight(light);
    System.out.println(fullLight.getName() + " (" + fullLight.getState().getBrightness() + ")");
}

This class has much more information, like the model ID, software version and the current state of the light. You will see this pattern with other types of objects as well.

Light -> FullLight
Group -> FullGroup
Config -> FullConfig
Schedule -> FullSchedule

This is an advantage, because it allows you to save bandwidth when you just need to list the names of the lights or groups that you have in your app. In general, try to design your app so that it only retrieves the data it needs.

If you need to synchronise state with the bridge, it is desirable to retrieve all information in one simple request, however. This can be accomplished by retrieving a FullConfig by calling getFullConfig on the bridge. This will return all data the bridge has to offer, but it's a fairly resource intensive operation and shouldn't be used too often.

Changing state

Now that we've seen how to retrieve state, let's try to change it. Most objects have many different kinds of properties that can be changed, like brightness, hue, effects, on/off, etc. The Hue API allows you to bundle these updates in single requests. It would be a bit of a waste if we were to throw this ability away by exposing functions like setLightBrightness, setLightHue and setLightEffect that each send a separate request.

Instead, there's a mechanism that allows you to set a few properties and then send it off as a single request. Let's see what that looks like by turning all lights on and setting their colour to red.

Group all = bridge.getAllGroup();
StateUpdate update = new StateUpdate().turnOn().setHue(0);
bridge.setGroupState(all, update);

It's fairly simple to understand what's going on here. A StateUpdate object allows you to build a list of things you want to change. In this case, we first want to turn lights on and then set their hue to 0 (red). To actually apply these changes, you pass this object to setGroupState and it will turn the collection of changes into a single request.

You can combine these in a single line to reduce verbosity:

bridge.setGroupState(bridge.getAllGroup(), new StateUpdate().turnOn().setHue(0));

This StateUpdate object can be used both groups and individual lights. For other types of objects, there are other types of state objects with different setters.

Light/Group -> StateUpdate
Schedule -> ScheduleUpdate
Config -> ConfigUpdate

Some objects have so few different properties that they don't really need this system. They have separate functions that work just as you expect them to:

bridge.setLightName(light, "Awesome New Name");

You may be a bit confused here, because lights do actually have many types of state, but properties like name are usually not counted as state and can unfortunately not be updated together with things like colour in a single request.

Scheduling

The bridge allows you to schedule actions that change things on the bridge at a certain date and time. Just like changing state, it may be a bit confusing how this works when you see it for the first time.

For a change, let's just look at some code first.

HueBridge bridge = new HueBridge("192.168.1.101", "aValidUser");

// Get the config to retrieve the current bridge time
AuthenticatedConfig cfg = (AuthenticatedConfig) bridge.getConfig();

// Calculate the time 5 seconds after the current bridge time
Calendar cal = Calendar.getInstance();
cal.setTime(cfg.getUTCTime());
cal.add(Calendar.SECOND, 5);

// Schedule turning off all lights at that time
bridge.createSchedule(cal.getTime(), new ScheduleCallback() {
	@Override
	public void onScheduleCommand(HueBridge bridge) throws IOException, ApiException {
                // Will throw an exception in this callback, because it's not actually executed right now
                // and cannot return a response. Getters like getAllGroup() will still work normally, however!
		bridge.setGroupState(bridge.getAllGroup(), new StateUpdate().turnOff());
	}
});

There are a couple of new things here. We've used the getConfig function again, but this time we cast the result to an AuthenticatedConfig. If you look at the documentation for this function, you will see that it returns more extensive information if you're whitelisted on the bridge. To access this extra information, a cast is required.

Using Java's date and time functions, we add 5 seconds to the local time of the bridge and then create a scheduled action. Jue chooses to make things as simple as possible by allowing you to schedule any of the regular functions by simply calling them in a special callback.

This looks simple to understand, but what is actually happening here? The createSchedule function runs the callback to see which command should be scheduled and then puts this into a schedule creation API request. It does this by temporarily changing the bridge class internals to not send requests, but simply save them. After the callback finishes, it puts the saved request into the schedule creation request and sends it off to be scheduled.

The exception to this are GET requests, i.e. requests that solely retrieve information, like getLights. That means that you can still safely use those, because they're still executed and return a result directly. It wouldn't make sense to schedule those anyway!

Right after running a command that changes something on the bridge in the scheduling callback, it will throw an exception, because the command doesn't actually run at that point and can't return any data. Do not try to catch this exception, because it's simultaneously a safeguard against accidentally trying to put multiple commands into a single schedule.

Finding bridges on the local network

In all of the previous examples, we've assumed that the IP address of the bridge is known in advance. Jue offers two methods of finding bridges if you don't know the IP address in advance.

The most reliable method is UPnP, which broadcasts a request to the local network asking devices to identify themselves. Using it is simple:

try {
	BridgeDiscovery.searchUPnP(10000, new BridgeDiscoveryCallback() {
		@Override
		public void onBridgeDiscovered(HueBridge bridge) {
			System.out.println(bridge.getIPAddress());
		}
	});
} catch (IOException e) {
    e.printStackTrace();
}

It takes time for devices to respond to a request like this, so you need to specify how long you want to wait on them. After this time has passed, the function will return a list with all the bridges it found. For a normal application, it is more desirable to show bridges in the UI as they're being discovered, so the function also offers a callback that is called right after a new bridge has been discovered. The function is of course blocking, so make sure you run it in a separate thread.

The API also offers a way of discovering bridges much faster, but may not always return results whereas UPnP does always work. It has the advantage of instantly returning results, so it's definitely worth trying before falling back to UPnP.

try {
    List<HueBridge> bridges = BridgeDiscovery.searchPortal();
} catch (IOException e) {
    e.printStackTrace();
}

It's a regular web request, so it is much more convenient to use. See API documentation for more information.

More information

All of the different concepts used by this library have been explained now. To find documentation for specific functions, enums and classes, have a look at the Javadoc.

Have fun!