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 Mar 17, 2020
1 parent 1f89d98 commit de798b0
Show file tree
Hide file tree
Showing 12 changed files with 246 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
* @author Samuel Leisering - added API-Version
*/
public class Config {
private String bridgeid;
private String name;
private String swversion;
private String apiversion;
Expand All @@ -45,6 +46,15 @@ public class Config {
Config() {
}

/**
* Returns the bridge ID.
*
* @return unique ID of the bridge
*/
public String getBridgeId() {
return bridgeid;
}

/**
* Returns the name.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public class HueBindingConstants {

// bridge
public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge");
public static final ThingTypeUID THING_TYPE_GATEWAY = new ThingTypeUID(BINDING_ID, "gateway");

// generic thing types
public static final ThingTypeUID THING_TYPE_ON_OFF_LIGHT = new ThingTypeUID(BINDING_ID, "0000");
Expand Down Expand Up @@ -71,6 +72,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 @@ -82,12 +84,15 @@ 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";
public static final String UNIQUE_ID = "uniqueId";
public static final String FADETIME = "fadetime";

// Gateway config properties
public static final String BRIDGE_ID = "bridgeId";

public static final String NORMALIZE_ID_REGEX = "[^a-zA-Z0-9_]";
}
Original file line number Diff line number Diff line change
Expand Up @@ -601,13 +601,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 @@ -848,6 +847,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,13 +35,14 @@
import org.eclipse.smarthome.core.thing.binding.ThingHandlerFactory;
import org.openhab.binding.hue.internal.discovery.HueLightDiscoveryService;
import org.openhab.binding.hue.internal.handler.HueBridgeHandler;
import org.openhab.binding.hue.internal.handler.HueGatewayHandler;
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;
import org.openhab.binding.hue.internal.handler.sensors.LightLevelHandler;
import org.openhab.binding.hue.internal.handler.sensors.PresenceHandler;
import org.openhab.binding.hue.internal.handler.sensors.TapSwitchHandler;
import org.openhab.binding.hue.internal.handler.sensors.TemperatureHandler;
import org.openhab.binding.hue.internal.handler.sensors.ClipHandler;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.annotations.Component;

Expand All @@ -57,11 +58,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()).flatMap(i -> i).collect(Collectors.toSet()));
Stream.of(HueBridgeHandler.SUPPORTED_THING_TYPES.stream(), HueGatewayHandler.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()).flatMap(i -> i).collect(Collectors.toSet()));

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

Expand All @@ -70,6 +73,10 @@ public class HueThingHandlerFactory extends BaseThingHandlerFactory {
@Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) {
if (HueBridgeHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
return super.createThing(thingTypeUID, configuration, thingUID, null);
} else if (HueGatewayHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
ThingUID uid = thingUID != null ? thingUID
: getThingUID(thingTypeUID, configuration.get(BRIDGE_ID).toString(), bridgeUID);
return super.createThing(thingTypeUID, configuration, uid, bridgeUID);
} else if (HueLightHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
ThingUID hueLightUID = getLightUID(thingTypeUID, thingUID, configuration, bridgeUID);
return super.createThing(thingTypeUID, configuration, hueLightUID, bridgeUID);
Expand Down Expand Up @@ -123,6 +130,8 @@ private ThingUID getThingUID(ThingTypeUID thingTypeUID, String id, @Nullable Thi
HueBridgeHandler handler = new HueBridgeHandler((Bridge) thing);
registerLightDiscoveryService(handler);
return handler;
} else if (HueGatewayHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
return new HueGatewayHandler(thing);
} else if (HueLightHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
return new HueLightHandler(thing);
} else if (DimmerSwitchHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
Expand All @@ -136,7 +145,7 @@ private ThingUID getThingUID(ThingTypeUID thingTypeUID, String id, @Nullable Thi
} else if (LightLevelHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
return new LightLevelHandler(thing);
} else if (ClipHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
return new ClipHandler(thing);
return new ClipHandler(thing);
} else {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,21 +202,31 @@ public StateUpdate setTransitionTime(long timeMillis) {
* @param flag on if true, off otherwise
* @return this object for chaining calls
*/

public StateUpdate setFlag(boolean flag) {
commands.add(new Command("flag", flag));
return this;
}

/**
* Set status of sensor.
*
* @param status status
* @param status status
* @return this object for chaining calls
*/
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 @@ -50,7 +50,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 Expand Up @@ -132,6 +132,24 @@ public void startScan() {
}
// search for unpaired lights
hueBridgeHandler.startSearch();

addBridgeAsGateway();
}

private void addBridgeAsGateway() {
ThingUID bridgeUID = hueBridgeHandler.getThing().getUID();
String bridgeId = hueBridgeHandler.getBridgeId();
ThingUID gatewayUID = new ThingUID(THING_TYPE_GATEWAY, bridgeUID, bridgeId);
Map<String, Object> properties = new HashMap<>();
properties.put(BRIDGE_ID, bridgeId);

DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(gatewayUID)//
.withThingType(THING_TYPE_GATEWAY)//
.withProperties(properties)//
.withBridge(bridgeUID)//
.withRepresentationProperty(BRIDGE_ID)//
.withLabel(hueBridgeHandler.getThing().getLabel()).build();
thingDiscovered(discoveryResult);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,34 +124,35 @@ 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 static final String STATE_ADDED = "added";
private static final String STATE_GONE = "gone";
private static final String STATE_CHANGED = "changed";
private static final String STATE_REMOVED = "removed";

public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_BRIDGE);

Expand Down Expand Up @@ -194,21 +195,14 @@ protected void doConnectedRun() throws IOException, ApiException {
lastSensorStates.put(sensorId, sensor);
logger.debug("Hue sensor '{}' added.", sensorId);
notifySensorStatusListeners(sensor, STATE_ADDED);

}
}

// Check for removed sensors
for (Entry<String, FullSensor> fullSensorEntry : lastSensorStateCopy.entrySet()) {
lastSensorStates.remove(fullSensorEntry.getKey());
logger.debug("Hue sensor '{}' removed.", fullSensorEntry.getKey());
for (SensorStatusListener sensorStatusListener : sensorStatusListeners) {
try {
sensorStatusListener.onSensorRemoved(hueBridge, fullSensorEntry.getValue());
} catch (Exception e) {
logger.error("An exception occurred while calling the Sensor Listeners", e);
}
}
notifySensorStatusListeners(fullSensorEntry.getValue(), STATE_REMOVED);
}
}
};
Expand Down Expand Up @@ -246,13 +240,7 @@ protected void doConnectedRun() throws IOException, ApiException {
for (Entry<String, FullLight> fullLightEntry : lastLightStateCopy.entrySet()) {
lastLightStates.remove(fullLightEntry.getKey());
logger.debug("Hue light '{}' removed.", fullLightEntry.getKey());
for (LightStatusListener lightStatusListener : lightStatusListeners) {
try {
lightStatusListener.onLightRemoved(hueBridge, fullLightEntry.getValue());
} catch (Exception e) {
logger.error("An exception occurred while calling the BridgeHeartbeatListener", e);
}
}
notifyLightStatusListeners(fullLightEntry.getValue(), STATE_REMOVED);
}
}
};
Expand All @@ -263,7 +251,7 @@ public HueBridgeHandler(Bridge bridge) {

@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// not needed
// no commands
}

@Override
Expand Down Expand Up @@ -644,6 +632,24 @@ public boolean unregisterSensorStatusListener(SensorStatusListener sensorStatusL
return result;
}

@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 @@ -726,6 +732,9 @@ private void notifyLightStatusListeners(final FullLight fullLight, final String
logger.debug("Sending lightStateChanged for light '{}'", fullLight.getId());
lightStatusListener.onLightStateChanged(hueBridge, fullLight);
break;
case STATE_REMOVED:
lightStatusListener.onLightRemoved(hueBridge, fullLight);
break;
default:
throw new IllegalArgumentException(
"Could not notify lightStatusListeners for unknown event type " + type);
Expand Down Expand Up @@ -756,6 +765,9 @@ private void notifySensorStatusListeners(final FullSensor fullSensor, final Stri
logger.debug("Sending sensorStateChanged for sensor '{}'", fullSensor.getId());
sensorStatusListener.onSensorStateChanged(hueBridge, fullSensor);
break;
case STATE_REMOVED:
sensorStatusListener.onSensorRemoved(hueBridge, fullSensor);
break;
default:
throw new IllegalArgumentException(
"Could not notify sensorStatusListeners for unknown event type " + type);
Expand Down Expand Up @@ -788,7 +800,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 All @@ -797,4 +809,8 @@ public Collection<ConfigStatusMessage> getConfigStatus() {

return configStatusMessages;
}

public String getBridgeId() {
return withReAuthentication("get bridge ID", () -> hueBridge.getConfig().getBridgeId());
}
}
Loading

0 comments on commit de798b0

Please sign in to comment.