/**
* 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;
import java.io.IOException;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.hue.HueBindingProvider;
import org.openhab.binding.hue.internal.HueBindingConfig.BindingType;
import org.openhab.binding.hue.internal.data.HueSettings;
import org.openhab.binding.hue.internal.hardware.HueBridge;
import org.openhab.binding.hue.internal.hardware.HueBulb;
import org.openhab.binding.hue.internal.tools.SsdpDiscovery;
import org.openhab.core.binding.AbstractActiveBinding;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.IncreaseDecreaseType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.types.Command;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This binding is able to do the following tasks with the Philips hue system:
* <ul>
* <li>Switching bulbs on and off.</li>
* <li>Change color temperature of a bulb, what results in a white color.</li>
* <li>Change the brightness of a bulb without changing the color.</li>
* <li>Change the RGB values of a bulb.</li>
* </ul>
*
* @author Roman Hartmann
* @author Jos Schering
* @since 1.2.0
*/
public class HueBinding extends AbstractActiveBinding<HueBindingProvider>implements ManagedService {
static final Logger logger = LoggerFactory.getLogger(HueBinding.class);
/** refresh interval is only set by configuration */
private long refreshInterval;
private HueBridge activeBridge = null;
private String bridgeIP = null;
// Caches all bulbs controlled to prevent the recreation of the bulbs which
// triggers a rereading of the settings from the bridge which is very
// expensive.
private HashMap<String, HueBulb> bulbCache = new HashMap<String, HueBulb>();
/**
* Default constructor for the Hue binding.
*/
public HueBinding() {
}
/**
* @{inheritDoc}
*/
@Override
protected long getRefreshInterval() {
return refreshInterval;
}
/**
* @{inheritDoc}
*/
@Override
protected String getName() {
return "Hue Refresh Service";
}
/**
* Get current hue settings of the bulbs and update the items that are connected with the bulb.
* The refreshinterval determines the polling frequency.
*/
@Override
public void execute() {
if (activeBridge != null) {
// Get settings and update the bulbs
// Observation : If the power of a hue lamp is removed, the status is not updated in hue hub.
// The heartbeat functionality should fix this, but
logger.debug("Start Hue data refresh");
HueSettings settings = activeBridge.getSettings();
if (settings == null) {
logger.warn("Hue settings were null, maybe misconfigured bridge IP.");
return;
} else if (!settings.isAuthorized()) {
logger.warn("openHAB not authorized to access Hue bridge");
return;
}
Set<String> keys = settings.getKeys();
for (String key : keys) {
try {
HueBulb bulb = bulbCache.get(key);
if (bulb == null) {
bulb = new HueBulb(activeBridge, key, settings);
bulbCache.put(key, bulb);
}
bulb.getStatus(settings);
} catch (NumberFormatException e) {
logger.warn("lights index {} is not a number", key);
}
}
// Update the items that are linked with the bulbs.
// Multiple items of different types can be linked to one bulb.
for (HueBindingProvider provider : this.providers) {
for (String hueItemName : provider.getInBindingItemNames()) {
HueBindingConfig deviceConfig = getConfigForItemName(hueItemName);
if (deviceConfig != null) {
HueBulb bulb = bulbCache.get(deviceConfig.getDeviceId());
if (bulb != null) {
// Enhancement: only send a postUpdate for items that have changed.
// Tried to use item.getState() as found in enOcean binding, but the state value was always
// uninitialized
// State actualState = provider.getItem(itemName).getState(); --> always return
// Uninitialized
// Workaround for now, store the OnOff state in deviceConfig
//
if ((bulb.getIsOn() == true) && (bulb.getIsReachable() == true)) {
if ((deviceConfig.itemStateOnOffType == null)
|| (deviceConfig.itemStateOnOffType.equals(OnOffType.ON) == false)) {
eventPublisher.postUpdate(hueItemName, OnOffType.ON);
deviceConfig.itemStateOnOffType = OnOffType.ON;
}
} else {
if ((deviceConfig.itemStateOnOffType == null)
|| (deviceConfig.itemStateOnOffType.equals(OnOffType.OFF) == false)) {
eventPublisher.postUpdate(hueItemName, OnOffType.OFF);
deviceConfig.itemStateOnOffType = OnOffType.OFF;
}
}
if (deviceConfig.getType().equals(BindingType.brightness)) {
if ((bulb.getIsOn() == true) && (bulb.getIsReachable() == true)) {
// Only postUpdate when bulb is on, otherwise dimmer item is not retaining state and
// shows to max brightness value
PercentType newPercent = new PercentType((int) Math
.round((bulb.getBrightness() * (double) 100) / HueBulb.MAX_BRIGHTNESS));
if ((deviceConfig.itemStatePercentType == null)
|| (deviceConfig.itemStatePercentType.equals(newPercent) == false)) {
eventPublisher.postUpdate(hueItemName, newPercent);
deviceConfig.itemStatePercentType = newPercent;
}
}
} else if (deviceConfig.getType().equals(BindingType.rgb)) {
if ((bulb.getIsOn() == true) && (bulb.getIsReachable() == true)) {
// Only postUpdate when bulb is on, otherwise color item is not retaining state and
// shows to max brightness value
DecimalType decimalHue = new DecimalType(bulb.getHue() / (double) 182);
PercentType percentBrightness = new PercentType((int) Math
.round((bulb.getBrightness() * (double) 100) / HueBulb.MAX_BRIGHTNESS));
PercentType percentSaturation = new PercentType((int) Math
.round((bulb.getSaturation() * (double) 100) / HueBulb.MAX_SATURATION));
HSBType newHsb = new HSBType(decimalHue, percentSaturation, percentBrightness);
if ((deviceConfig.itemStateHSBType == null)
|| (deviceConfig.itemStateHSBType.equals(newHsb) == false)) {
eventPublisher.postUpdate(hueItemName, newHsb);
deviceConfig.itemStateHSBType = newHsb;
}
}
}
}
}
}
}
logger.debug("Done Hue data refresh.");
}
}
@Override
public void internalReceiveCommand(String itemName, Command command) {
super.internalReceiveCommand(itemName, command);
logger.debug("Hue binding received command '" + command + "' for item '" + itemName + "'");
if (activeBridge != null) {
computeCommandForItemOnBridge(command, itemName, activeBridge);
} else {
logger.warn("Hue binding skipped command because no Hue bridge is connected.");
}
}
/**
* Checks whether the command is for one of the configured Hue bulbs. If
* this is the case, the command is translated to the corresponding action
* which is then sent to the given bulb.
*
* @param command
* The command from the openHAB bus.
* @param itemName
* The name of the targeted item.
* @param bridge
* The Hue bridge the Hue bulb is connected to
*/
private void computeCommandForItemOnBridge(Command command, String itemName, HueBridge bridge) {
HueBindingConfig deviceConfig = getConfigForItemName(itemName);
if (deviceConfig == null) {
return;
}
HueBulb bulb = bulbCache.get(deviceConfig.getDeviceId());
if (bulb == null) {
bulb = new HueBulb(bridge, deviceConfig.getDeviceId());
bulbCache.put(deviceConfig.getDeviceId(), bulb);
}
if (command instanceof OnOffType) {
bulb.switchOn(OnOffType.ON.equals(command));
}
if (command instanceof HSBType) {
HSBType hsbCommand = (HSBType) command;
DecimalType hue = hsbCommand.getHue();
PercentType sat = hsbCommand.getSaturation();
PercentType bri = hsbCommand.getBrightness();
bulb.colorizeByHSB(hue.doubleValue() / 360, sat.doubleValue() / 100, bri.doubleValue() / 100);
}
if (deviceConfig.getType().equals(BindingType.brightness) || deviceConfig.getType().equals(BindingType.rgb)) {
if (IncreaseDecreaseType.INCREASE.equals(command)) {
int resultingValue = bulb.increaseBrightness(deviceConfig.getStepSize());
eventPublisher.postUpdate(itemName, new PercentType(resultingValue));
} else if (IncreaseDecreaseType.DECREASE.equals(command)) {
int resultingValue = bulb.decreaseBrightness(deviceConfig.getStepSize());
eventPublisher.postUpdate(itemName, new PercentType(resultingValue));
} else if ((command instanceof PercentType) && !(command instanceof HSBType)) {
bulb.setBrightness((int) Math
.round((double) HueBulb.MAX_BRIGHTNESS / (double) 100 * ((PercentType) command).intValue()));
}
}
if (deviceConfig.getType().equals(BindingType.colorTemperature)) {
if (IncreaseDecreaseType.INCREASE.equals(command)) {
bulb.increaseColorTemperature(deviceConfig.getStepSize());
} else if (IncreaseDecreaseType.DECREASE.equals(command)) {
bulb.decreaseColorTemperature(deviceConfig.getStepSize());
} else if (command instanceof PercentType) {
bulb.setColorTemperature(
(int) Math.round((((double) 346 / (double) 100) * ((PercentType) command).intValue()) + 154));
}
}
}
/**
* Lookup of the configuration of the named item.
*
* @param itemName
* The name of the item.
* @return The configuration, null otherwise.
*/
private HueBindingConfig getConfigForItemName(String itemName) {
for (HueBindingProvider provider : this.providers) {
if (provider.getItemConfig(itemName) != null) {
return provider.getItemConfig(itemName);
}
}
return null;
}
@SuppressWarnings("rawtypes")
@Override
public void updated(Dictionary config) throws ConfigurationException {
if (config != null) {
String ip = (String) config.get("ip");
if (StringUtils.isNotBlank(ip)) {
this.bridgeIP = ip;
} else {
try {
this.bridgeIP = new SsdpDiscovery().findIpForResponseKeywords("description.xml", "FreeRTOS");
} catch (IOException e) {
logger.warn("Could not find hue bridge automatically. "
+ "Please make sure it is switched on and connected to the same network as openHAB. "
+ "If it permanently fails you may configure the IP address of your hue bridge manually in the openHAB configuration.");
}
}
// connect the Hue bridge with the new configs
if (this.bridgeIP != null) {
activeBridge = null;
String secret = (String) config.get("secret");
HueBridge bridge = new HueBridge(bridgeIP, secret);
if (bridge.isAuthorized()) {
activeBridge = bridge;
} else {
logger.info("No secret configured or secret rejected by bridge. Starting pairing with bridge.");
bridge.pairBridge();
}
}
String refreshIntervalString = (String) config.get("refresh");
if (StringUtils.isNotBlank(refreshIntervalString)) {
refreshInterval = Long.parseLong(refreshIntervalString);
// RefreshInterval is specified in openhap.cfg, therefore enable polling
setProperlyConfigured(true);
}
}
}
}