Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[hue] Add channel to the hue bridge to activate predefined scenes #6044

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion bundles/org.openhab.binding.hue/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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"]
}
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -605,13 +605,12 @@ public String setGroupAttributes(Group group, String name, List<HueObject> 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<Result> 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);
}

/**
Expand Down Expand Up @@ -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<Result> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
@NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.hue")
public class HueThingHandlerFactory extends BaseThingHandlerFactory {

public static final Set<ThingTypeUID> 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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -386,11 +389,11 @@ 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).exceptionally(e -> {
handleStateUpdateException(group, stateUpdate, e);
}
return null;
});

} else {
logger.debug("No bridge connected or selected. Cannot set group state.");
}
Expand Down Expand Up @@ -723,6 +726,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);
Expand Down Expand Up @@ -773,6 +799,7 @@ public void startSearch(List<String> serialNumbers) {
});
}

@Nullable
private <T> T withReAuthentication(String taskDescription, Callable<T> runnable) {
if (hueBridge != null) {
try {
Expand Down Expand Up @@ -893,7 +920,7 @@ public Collection<ConfigStatusMessage> getConfigStatus() {
Collection<ConfigStatusMessage> 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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
<label>Hue Bridge</label>
<description>The Hue bridge represents the Philips Hue bridge.</description>

<channels>
<channel id="scene" typeId="scene" />
</channels>

<properties>
<property name="vendor">Philips</property>
</properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,5 +212,11 @@
<label>Flag</label>
<description>Flag of CLIP sensor.</description>
</channel-type>


<!-- Scene Channel -->
<channel-type id="scene">
<item-type>String</item-type>
<label>Scene</label>
<description>The scene channel allows activating a scene to all lights that belong to the scene.</description>
</channel-type>
</thing:thing-descriptions>