Skip to content

Commit

Permalink
Add hue gateway as thing to activate predefined scenes
Browse files Browse the repository at this point in the history
Signed-off-by: leluna <[email protected]>
  • Loading branch information
leluna committed May 3, 2020
1 parent 2621ff1 commit 516dae7
Show file tree
Hide file tree
Showing 10 changed files with 107 additions and 36 deletions.
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 @@ -35,7 +35,11 @@
import org.eclipse.smarthome.core.thing.binding.ThingHandlerFactory;
import org.openhab.binding.hue.internal.discovery.HueLightDiscoveryService;
import org.openhab.binding.hue.internal.handler.HueBridgeHandler;
<<<<<<< HEAD
import org.openhab.binding.hue.internal.handler.HueGatewayHandler;
import org.openhab.binding.hue.internal.handler.HueGroupHandler;
=======
>>>>>>> 7e2a3bdfe9... Extend hue bridge with scene channel
import org.openhab.binding.hue.internal.handler.HueLightHandler;
import org.openhab.binding.hue.internal.handler.sensors.ClipHandler;
import org.openhab.binding.hue.internal.handler.sensors.DimmerSwitchHandler;
Expand All @@ -59,12 +63,13 @@
@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(),
PresenceHandler.SUPPORTED_THING_TYPES.stream(), TemperatureHandler.SUPPORTED_THING_TYPES.stream(),
LightLevelHandler.SUPPORTED_THING_TYPES.stream(), ClipHandler.SUPPORTED_THING_TYPES.stream(),
HueGroupHandler.SUPPORTED_THING_TYPES.stream()).flatMap(i -> i).collect(Collectors.toSet()));

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(), PresenceHandler.SUPPORTED_THING_TYPES.stream(),
TemperatureHandler.SUPPORTED_THING_TYPES.stream(), LightLevelHandler.SUPPORTED_THING_TYPES.stream(),
ClipHandler.SUPPORTED_THING_TYPES.stream(), HueGroupHandler.SUPPORTED_THING_TYPES.stream())
.flatMap(i -> i).collect(Collectors.toSet()));

private final Map<ThingUID, @Nullable ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();

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 @@ -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>

0 comments on commit 516dae7

Please sign in to comment.