diff --git a/bundles/org.openhab.binding.hue/README.md b/bundles/org.openhab.binding.hue/README.md index 93aeb290ff9a8..a349aa83ba4a5 100644 --- a/bundles/org.openhab.binding.hue/README.md +++ b/bundles/org.openhab.binding.hue/README.md @@ -183,7 +183,7 @@ The devices support some of the following channels: | status | Number | This channel save status state for a CLIP sensor. | 0840 | | last_updated | DateTime | This channel the date and time when the sensor was last updated. | 0820, 0830, 0840, 0850, 0106, 0107, 0302| | battery_level | Number | This channel shows the battery level. | 0820, 0106, 0107, 0302 | -| battery_low | Switch | This channel indicates whether the battery is low or not. | 0820, 0106, 0107, 0302 | +| battery_low | Switch | This channel indicates whether the battery is low or not. | 0820, 0106, 0107, 0302 | ### Trigger Channels @@ -224,6 +224,17 @@ The `tap_switch_event` can trigger one of the following events: | Button 3 | Button 3 | 17 | | Button 4 | Button 4 | 18 | +### Scene Channel + +The bridge itself supports a channel to activate scenes: + +| Channel Type ID | Item Type | Description | +| --------------- | --------- | ----------- | +| scene | String | This channel activates the scene with the given ID String. | + +The scenes are identified by an ID String that is assigned by the Hue bridge. These must be aquired directly from [Hue REST API](https://developers.meethue.com/develop/hue-api/). + +This channel can then be used in the sitemap, for example as a switch or selection. ## Rule Actions @@ -301,6 +312,9 @@ Switch MotionSensorLowBattery { channel="hue:0107:1:motion-sensor:battery_lo // Temperature Sensor Number:Temperature TemperatureSensorTemperature { channel="hue:0302:temperature-sensor:temperature" } + +// Scenes +String LightScene { channel="hue:bridge:1:scene"} ``` Note: The bridge ID is in this example **1** but can be different in each system. @@ -336,6 +350,9 @@ sitemap demo label="Main Menu" Text item=MotionSensorLastUpdate Text item=MotionSensorBatteryLevel Switch item=MotionSensorLowBattery + + // Light Scenes + Switch item=LightScene label="Scene []" mappings=[abcdefgh1234567="Relax", ABCDEFGH1234567="Concentrate"] } } ``` diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueBindingConstants.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueBindingConstants.java index 66eb7a84e89b0..2eb2584572cb5 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueBindingConstants.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueBindingConstants.java @@ -75,6 +75,7 @@ public class HueBindingConstants { public static final String CHANNEL_DAYLIGHT = "daylight"; public static final String CHANNEL_STATUS = "status"; public static final String CHANNEL_FLAG = "flag"; + public static final String CHANNEL_SCENE = "scene"; // List all triggers public static final String EVENT_DIMMER_SWITCH = "dimmer_switch_event"; @@ -86,7 +87,7 @@ public class HueBindingConstants { public static final String PROTOCOL = "protocol"; public static final String USER_NAME = "userName"; - // Light config properties + // Light and sensor config properties public static final String LIGHT_ID = "lightId"; public static final String SENSOR_ID = "sensorId"; public static final String PRODUCT_NAME = "productName"; diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueBridge.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueBridge.java index dffcf7223479e..15075aac59afa 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueBridge.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueBridge.java @@ -605,13 +605,12 @@ public String setGroupAttributes(Group group, String name, List light * @throws UnauthorizedException thrown if the user no longer exists * @throws EntityNotAvailableException thrown if the specified group no longer exists */ - public void setGroupState(Group group, StateUpdate update) throws IOException, ApiException { + public CompletableFuture setGroupState(Group group, StateUpdate update) { requireAuthentication(); String body = update.toJson(); - Result result = http.put(getRelativeURL("groups/" + enc(group.getId()) + "/action"), body); - - handleErrors(result); + return http.putAsync(getRelativeURL("groups/" + enc(group.getId()) + "/action"), body, update.getMessageDelay(), + scheduler); } /** @@ -852,6 +851,17 @@ public void deleteSchedule(Schedule schedule) throws IOException, ApiException { handleErrors(result); } + /** + * Activate scene to all lights that belong to the scene. + * + * @param id the scene to be activated + * @throws IOException if the bridge cannot be reached + */ + public CompletableFuture activateScene(String id) { + Group allLightsGroup = new Group(); + return setGroupState(allLightsGroup, new StateUpdate().setScene(id)); + } + /** * Authenticate on the bridge as the specified user. * This function verifies that the specified username is valid and will use diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueObject.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueObject.java index 7ae7aa494fc25..c6f1b54a74e32 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueObject.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueObject.java @@ -18,7 +18,7 @@ import com.google.gson.reflect.TypeToken; /** - * Basic light information. + * Basic hue object information. * * @author Q42 - Initial contribution * @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueThingHandlerFactory.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueThingHandlerFactory.java index 2c0bf3df8a545..2073cf59d57fc 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueThingHandlerFactory.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueThingHandlerFactory.java @@ -59,6 +59,7 @@ @NonNullByDefault @Component(service = ThingHandlerFactory.class, configurationPid = "binding.hue") public class HueThingHandlerFactory extends BaseThingHandlerFactory { + public static final Set SUPPORTED_THING_TYPES = Collections.unmodifiableSet( Stream.of(HueBridgeHandler.SUPPORTED_THING_TYPES.stream(), HueLightHandler.SUPPORTED_THING_TYPES.stream(), DimmerSwitchHandler.SUPPORTED_THING_TYPES.stream(), TapSwitchHandler.SUPPORTED_THING_TYPES.stream(), diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/StateUpdate.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/StateUpdate.java index 0d92d5e7f15f7..e1a65eb1c8907 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/StateUpdate.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/StateUpdate.java @@ -218,4 +218,15 @@ public StateUpdate setStatus(int status) { commands.add(new Command("status", status)); return this; } + + /** + * Recall the given scene. + * + * @param sceneId Identifier of the scene + * @return this object for chaining calls + */ + public StateUpdate setScene(String sceneId) { + commands.add(new Command("scene", sceneId)); + return this; + } } diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueLightDiscoveryService.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueLightDiscoveryService.java index cac98bd813176..0d7458398f6fc 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueLightDiscoveryService.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueLightDiscoveryService.java @@ -53,7 +53,7 @@ import org.slf4j.LoggerFactory; /** - * The {@link HueBridgeServiceTracker} tracks for hue lights which are connected + * The {@link HueBridgeServiceTracker} tracks for hue lights and sensors which are connected * to a paired hue bridge. The default search time for hue is 60 seconds. * * @author Kai Kreuzer - Initial contribution diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueBridgeHandler.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueBridgeHandler.java index 5cf1585634604..0cbb27d87795c 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueBridgeHandler.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueBridgeHandler.java @@ -36,6 +36,7 @@ import org.eclipse.smarthome.config.core.status.ConfigStatusMessage; import org.eclipse.smarthome.core.library.types.HSBType; import org.eclipse.smarthome.core.library.types.OnOffType; +import org.eclipse.smarthome.core.library.types.StringType; import org.eclipse.smarthome.core.thing.Bridge; import org.eclipse.smarthome.core.thing.ChannelUID; import org.eclipse.smarthome.core.thing.ThingStatus; @@ -144,29 +145,29 @@ public void run() { } protected abstract void doConnectedRun() throws IOException, ApiException; + } - private boolean isReachable(String ipAddress) { - try { - // note that InetAddress.isReachable is unreliable, see - // http://stackoverflow.com/questions/9922543/why-does-inetaddress-isreachable-return-false-when-i-can-ping-the-ip-address - // That's why we do an HTTP access instead + private boolean isReachable(String ipAddress) { + try { + // note that InetAddress.isReachable is unreliable, see + // http://stackoverflow.com/questions/9922543/why-does-inetaddress-isreachable-return-false-when-i-can-ping-the-ip-address + // That's why we do an HTTP access instead - // If there is no connection, this line will fail - hueBridge.authenticate("invalid"); - } catch (IOException e) { + // If there is no connection, this line will fail + hueBridge.authenticate("invalid"); + } catch (IOException e) { + return false; + } catch (ApiException e) { + if (e.getMessage().contains("SocketTimeout") || e.getMessage().contains("ConnectException") + || e.getMessage().contains("SocketException") + || e.getMessage().contains("NoRouteToHostException")) { return false; - } catch (ApiException e) { - if (e.getMessage().contains("SocketTimeout") || e.getMessage().contains("ConnectException") - || e.getMessage().contains("SocketException") - || e.getMessage().contains("NoRouteToHostException")) { - return false; - } else { - // this seems to be only an authentication issue - return true; - } + } else { + // this seems to be only an authentication issue + return true; } - return true; } + return true; } private final Runnable sensorPollingRunnable = new PollingRunnable() { @@ -326,7 +327,9 @@ public HueBridgeHandler(Bridge bridge) { @Override public void handleCommand(ChannelUID channelUID, Command command) { - // not needed + if (CHANNEL_SCENE.equals(channelUID.getId()) && command instanceof StringType) { + activateScene(command.toString()); + } } @Override @@ -386,11 +389,17 @@ public void updateSensorConfig(FullSensor sensor, ConfigUpdate configUpdate) { @Override public void updateGroupState(FullGroup group, StateUpdate stateUpdate) { if (hueBridge != null) { - try { - hueBridge.setGroupState(group, stateUpdate); - } catch (IOException | ApiException e) { + hueBridge.setGroupState(group, stateUpdate).thenAccept(result -> { + try { + hueBridge.handleErrors(result); + } catch (Exception e) { + handleStateUpdateException(group, stateUpdate, e); + } + }).exceptionally(e -> { handleStateUpdateException(group, stateUpdate, e); - } + return null; + }); + } else { logger.debug("No bridge connected or selected. Cannot set group state."); } @@ -723,6 +732,29 @@ public boolean unregisterGroupStatusListener(GroupStatusListener groupStatusList return groupStatusListeners.remove(groupStatusListener); } + /** + * Activate scene to all lights that belong to the scene. + * + * @param id the ID of the scene to activate + */ + @Override + public void activateScene(String id) { + if (hueBridge != null) { + hueBridge.activateScene(id).thenAccept(result -> { + try { + hueBridge.handleErrors(result); + } catch (Exception e) { + logger.warn("Error while activating scene: {}", e.getMessage(), e); + } + }).exceptionally(e -> { + logger.warn("Error while activating scene: {}", e.getMessage(), e); + return null; + }); + } else { + logger.warn("No bridge connected or selected. Cannot activate scene."); + } + } + @Override public @Nullable FullLight getLightById(String lightId) { return lastLightStates.get(lightId); @@ -773,6 +805,7 @@ public void startSearch(List serialNumbers) { }); } + @Nullable private T withReAuthentication(String taskDescription, Callable runnable) { if (hueBridge != null) { try { @@ -893,7 +926,7 @@ public Collection getConfigStatus() { Collection configStatusMessages; // Check whether an IP address is provided - if (hueBridgeConfig.getIpAddress() == null || hueBridgeConfig.getIpAddress().isEmpty()) { + if (hueBridgeConfig.getIpAddress().isEmpty()) { configStatusMessages = Collections.singletonList(ConfigStatusMessage.Builder.error(HOST) .withMessageKeySuffix(HueConfigStatusMessage.IP_ADDRESS_MISSING).withArguments(HOST).build()); } else { diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueClient.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueClient.java index 1bd3c83d2142a..83818b2ee255b 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueClient.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueClient.java @@ -83,7 +83,7 @@ public interface HueClient { * Get the light by its ID. * * @param lightId the light ID - * @return the full light representation of {@code null} if it could not be found + * @return the full light representation or {@code null} if it could not be found */ @Nullable FullLight getLightById(String lightId); @@ -92,7 +92,7 @@ public interface HueClient { * Get the sensor by its ID. * * @param sensorId the sensor ID - * @return the full sensor representation of {@code null} if it could not be found + * @return the full sensor representation or {@code null} if it could not be found */ @Nullable FullSensor getSensorById(String sensorId); @@ -137,4 +137,11 @@ public interface HueClient { * @param stateUpdate the state update */ void updateGroupState(FullGroup group, StateUpdate stateUpdate); + + /** + * Activate scene to all lights that belong to the scene. + * + * @param id the ID of the scene to activate + */ + void activateScene(String id); } diff --git a/bundles/org.openhab.binding.hue/src/main/resources/ESH-INF/thing/bridge.xml b/bundles/org.openhab.binding.hue/src/main/resources/ESH-INF/thing/bridge.xml index 8c2faf491819c..8f7193c30e701 100644 --- a/bundles/org.openhab.binding.hue/src/main/resources/ESH-INF/thing/bridge.xml +++ b/bundles/org.openhab.binding.hue/src/main/resources/ESH-INF/thing/bridge.xml @@ -8,6 +8,10 @@ The Hue bridge represents the Philips Hue bridge. + + + + Philips diff --git a/bundles/org.openhab.binding.hue/src/main/resources/ESH-INF/thing/channels.xml b/bundles/org.openhab.binding.hue/src/main/resources/ESH-INF/thing/channels.xml index d6645d2f43401..9cf1bb1e3e2b5 100644 --- a/bundles/org.openhab.binding.hue/src/main/resources/ESH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.hue/src/main/resources/ESH-INF/thing/channels.xml @@ -212,5 +212,11 @@ Flag of CLIP sensor. - + + + + String + + The scene channel allows activating a scene to all lights that belong to the scene. +