/**
* Copyright (c) 2010-2016 by the respective copyright holders.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.openhab.binding.hue.internal.hardware;
import java.io.IOException;
import org.apache.commons.lang.StringUtils;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;
import org.openhab.binding.hue.internal.data.HueSettings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientHandlerException;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
/**
* Represents a physical Hue bridge and grants access to data the bridge
* contains.
*
* @author Roman Hartmann
* @author Kai Kreuzer
* @since 1.2.0
*
*/
public class HueBridge {
private static final Logger logger = LoggerFactory.getLogger(HueBridge.class);
private final String ip;
private final String secret;
private Client client;
private static final int RETRY_INTERVAL_IN_SEC = 5;
/**
* Constructor for the HueBridge.
*
* @param ip
* The IP of the Hue bridge.
* @param secret
* A unique identifier, that is used as a kind of password. This
* password is registered at the Hue bridge while connecting to
* it the first time. To do this initial connect the connect
* button the the Hue bridge as to be pressed.
*/
public HueBridge(String ip, String secret) {
this.ip = ip;
this.secret = secret;
client = Client.create();
client.setConnectTimeout(5000);
}
/**
* Pings the bridge for an initial connect. This pinging will take place for 100 seconds.
* In this time the connect button on the Hue bridge has to be pressed to enable the pairing.
*/
public void pairBridge() {
Thread pairingThread = new Thread(new BridgePairingProcessor());
pairingThread.start();
}
/**
* Checks if the configured secret/user is authorized on the hue bridge.
*/
public boolean isAuthorized() {
boolean isAuthorized = false;
if (!StringUtils.isBlank(secret)) {
HueSettings settings = getSettings();
isAuthorized = (settings != null && settings.isAuthorized());
}
return isAuthorized;
}
/**
* Requests the settings of the Hue bridge that also contains the settings
* of all connected Hue devices.
*
* @return The settings determined from the bridge. Null if they could not
* be requested.
*/
public HueSettings getSettings() {
String json = getSettingsJson();
return json != null ? new HueSettings(json) : null;
}
/**
* Pings the Hue bridge for 100 seconds to establish a first pairing
* connection to the bridge. This requires the button on the Hue bridge to
* be pressed.
*
*/
private void pingForPairing() {
int countdownInSeconds = 100;
boolean isPaired = false;
while (countdownInSeconds > 0 && !isPaired) {
logger.info("Please press the connect button on the Hue bridge. Waiting for pairing for "
+ countdownInSeconds + " seconds...");
WebResource webResource = client.resource("http://" + ip + "/api");
String input = "{\"devicetype\":\"openHAB_binding\"}";
ClientResponse response = webResource.type("application/json").post(ClientResponse.class, input);
if (response.getStatus() != 200) {
logger.error("Failed to connect to Hue bridge with IP '" + ip + "': HTTP error code: "
+ response.getStatus());
return;
}
String output = response.getEntity(String.class);
logger.debug("Received pairing response: {}", output);
isPaired = processPairingResponse(output);
if (!isPaired) {
try {
Thread.sleep(RETRY_INTERVAL_IN_SEC * 1000);
countdownInSeconds -= RETRY_INTERVAL_IN_SEC;
} catch (InterruptedException e) {
}
}
}
}
private boolean processPairingResponse(String response) {
try {
JsonNode node = convertToJsonNode(response);
if (node.has("success")) {
String userName = node.path("success").path("username").getTextValue();
logger.info("########################################");
logger.info("# Hue bridge successfully paired!");
logger.info("# Please set the following secret in your openhab.cfg (and restart openHAB):");
logger.info("# " + userName);
logger.info("########################################");
return true;
}
} catch (IOException e) {
logger.error("Could not read Settings-Json from Hue Bridge.", e);
}
return false;
}
private JsonNode convertToJsonNode(String response) throws IOException {
JsonNode rootNode;
ObjectMapper mapper = new ObjectMapper();
JsonNode arrayWrappedNode = mapper.readTree(response);
// Hue bridge returns the complete JSON response wrapped in an array, therefore the first
// element of the array has to be extracted
if (arrayWrappedNode.has(0)) {
rootNode = arrayWrappedNode.get(0);
} else {
throw new IOException("Unexpected response from hue bridge (not an array)");
}
return rootNode;
}
/**
* Determines the settings of the Hue bridge as a Json raw data String.
*
* @return The settings of the bridge if they could be determined. Null
* otherwise.
*/
private String getSettingsJson() {
WebResource webResource = client.resource(getUrl());
try {
ClientResponse response = webResource.accept("application/json").get(ClientResponse.class);
String settingsString = response.getEntity(String.class);
if (response.getStatus() != 200) {
logger.warn("Failed to connect to Hue bridge: HTTP error code: " + response.getStatus());
return null;
}
logger.trace("Received Hue Bridge Settings: {}", settingsString);
return settingsString;
} catch (ClientHandlerException e) {
logger.warn("Failed to connect to Hue bridge: HTTP request timed out.");
return null;
}
}
/**
* @return The IP of the Hue bridge.
*/
public String getUrl() {
return "http://" + ip + "/api/" + secret + "/";
}
/**
* Thread to ping the Hue bridge for pairing.
*
*/
class BridgePairingProcessor implements Runnable {
@Override
public void run() {
pingForPairing();
}
}
}