*
* @return the unique id, can be null for some virtual types like the daylight sensor
*/
-
public @Nullable String getUniqueID() {
return uniqueid;
}
+
+ /**
+ * Sets the unique id of the object.
+ */
+ protected void setUniqueID(final String uniqueid) {
+ this.uniqueid = uniqueid;
+ }
}
import java.time.Duration;
import java.util.Map;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
import com.google.gson.reflect.TypeToken;
/**
* @author Denis Dudnik - moved Jue library source code inside the smarthome Hue binding
* @author Samuel Leisering - added GSon Type to FullLight, refactored content to {@link FullHueObject}
*/
+@NonNullByDefault
public class FullLight extends FullHueObject {
public static final Type GSON_TYPE = new TypeToken<Map<String, FullLight>>() {
}.getType();
- private State state;
+ private @NonNullByDefault({}) State state;
private final long fadetime = 400; // milliseconds
- FullLight() {
- }
-
/**
* Returns the current state of the light.
*
import java.lang.reflect.Type;
import java.util.Map;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
import com.google.gson.reflect.TypeToken;
/**
* @author Samuel Leisering - Initial contribution
* @author Christoph Weitkamp - Initial contribution
*/
+@NonNullByDefault
public class FullSensor extends FullHueObject {
+ public static final Type GSON_TYPE = new TypeToken<Map<String, FullSensor>>() {
+ }.getType();
public static final String STATE_LAST_UPDATED = "lastupdated";
public static final String STATE_BUTTON_EVENT = "buttonevent";
public static final String CONFIG_LIGHT_LEVEL_THRESHOLD_DARK = "tholddark";
public static final String CONFIG_LIGHT_LEVEL_THRESHOLD_OFFSET = "tholdoffset";
- public static final Type GSON_TYPE = new TypeToken<Map<String, FullSensor>>() {
- }.getType();
-
- private Map<String, Object> state;
- private Map<String, Object> config;
+ private @NonNullByDefault({}) Map<String, Object> state;
+ private @NonNullByDefault({}) Map<String, Object> config;
public Map<String, Object> getState() {
return state;
import static org.openhab.binding.hue.internal.HueBindingConstants.*;
import java.util.Collections;
-import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.hue.internal.discovery.HueLightDiscoveryService;
import org.openhab.binding.hue.internal.handler.HueBridgeHandler;
import org.openhab.binding.hue.internal.handler.HueGroupHandler;
import org.openhab.binding.hue.internal.handler.HueLightHandler;
import org.openhab.binding.hue.internal.handler.sensors.TapSwitchHandler;
import org.openhab.binding.hue.internal.handler.sensors.TemperatureHandler;
import org.openhab.core.config.core.Configuration;
-import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
-import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
private final HueStateDescriptionOptionProvider stateOptionProvider;
- private final Map<ThingUID, @Nullable ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
-
@Activate
public HueThingHandlerFactory(final @Reference HueStateDescriptionOptionProvider stateOptionProvider) {
this.stateOptionProvider = stateOptionProvider;
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
if (HueBridgeHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
- HueBridgeHandler handler = new HueBridgeHandler((Bridge) thing, stateOptionProvider);
- registerLightDiscoveryService(handler);
- return handler;
+ return new HueBridgeHandler((Bridge) thing, stateOptionProvider);
} else if (HueLightHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
return new HueLightHandler(thing);
} else if (DimmerSwitchHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
return null;
}
}
-
- private synchronized void registerLightDiscoveryService(HueBridgeHandler bridgeHandler) {
- HueLightDiscoveryService discoveryService = new HueLightDiscoveryService(bridgeHandler);
- discoveryService.activate();
- this.discoveryServiceRegs.put(bridgeHandler.getThing().getUID(),
- bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
- }
-
- @Override
- protected synchronized void removeHandler(ThingHandler thingHandler) {
- if (thingHandler instanceof HueBridgeHandler) {
- ServiceRegistration<?> serviceReg = this.discoveryServiceRegs.remove(thingHandler.getThing().getUID());
- if (serviceReg != null) {
- // remove discovery service, if bridge handler is removed
- HueLightDiscoveryService service = (HueLightDiscoveryService) bundleContext
- .getService(serviceReg.getReference());
- serviceReg.unregister();
- if (service != null) {
- service.deactivate();
- }
- }
- }
- }
}
--- /dev/null
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.hue.internal.discovery;
+
+import static org.openhab.binding.hue.internal.HueBindingConstants.*;
+
+import java.util.AbstractMap.SimpleEntry;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.hue.internal.FullGroup;
+import org.openhab.binding.hue.internal.FullHueObject;
+import org.openhab.binding.hue.internal.FullLight;
+import org.openhab.binding.hue.internal.FullSensor;
+import org.openhab.binding.hue.internal.handler.HueBridgeHandler;
+import org.openhab.binding.hue.internal.handler.HueGroupHandler;
+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.GeofencePresenceHandler;
+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.core.config.discovery.AbstractDiscoveryService;
+import org.openhab.core.config.discovery.DiscoveryResult;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.config.discovery.DiscoveryService;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link HueBridgeServiceTracker} tracks for hue lights, sensors and groups which are connected
+ * to a paired hue bridge. The default search time for hue is 60 seconds.
+ *
+ * @author Kai Kreuzer - Initial contribution
+ * @author Andre Fuechsel - changed search timeout, changed discovery result creation to support generic thing types;
+ * added representationProperty to discovery result
+ * @author Thomas Höfer - Added representation
+ * @author Denis Dudnik - switched to internally integrated source of Jue library
+ * @author Samuel Leisering - Added support for sensor API
+ * @author Christoph Weitkamp - Added support for sensor API
+ * @author Meng Yiqi - Added support for CLIP sensor
+ * @author Laurent Garnier - Added support for groups
+ */
+@NonNullByDefault
+public class HueDeviceDiscoveryService extends AbstractDiscoveryService
+ implements DiscoveryService, ThingHandlerService {
+
+ public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.unmodifiableSet(Stream
+ .of(HueLightHandler.SUPPORTED_THING_TYPES.stream(), DimmerSwitchHandler.SUPPORTED_THING_TYPES.stream(),
+ TapSwitchHandler.SUPPORTED_THING_TYPES.stream(), PresenceHandler.SUPPORTED_THING_TYPES.stream(),
+ GeofencePresenceHandler.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()));
+
+ // @formatter:off
+ private static final Map<String, @Nullable String> TYPE_TO_ZIGBEE_ID_MAP = Map.ofEntries(
+ new SimpleEntry<>("on_off_light", "0000"),
+ new SimpleEntry<>("on_off_plug_in_unit", "0010"),
+ new SimpleEntry<>("dimmable_light", "0100"),
+ new SimpleEntry<>("dimmable_plug_in_unit", "0110"),
+ new SimpleEntry<>("color_light", "0200"),
+ new SimpleEntry<>("extended_color_light", "0210"),
+ new SimpleEntry<>("color_temperature_light", "0220"),
+ new SimpleEntry<>("zllswitch", "0820"),
+ new SimpleEntry<>("zgpswitch", "0830"),
+ new SimpleEntry<>("clipgenericstatus", "0840"),
+ new SimpleEntry<>("clipgenericflag", "0850"),
+ new SimpleEntry<>("zllpresence", "0107"),
+ new SimpleEntry<>("geofence", "geofencesensor"),
+ new SimpleEntry<>("zlltemperature", "0302"),
+ new SimpleEntry<>("zlllightlevel", "0106"));
+ // @formatter:on
+
+ private static final int SEARCH_TIME = 10;
+
+ private final Logger logger = LoggerFactory.getLogger(HueDeviceDiscoveryService.class);
+
+ private @Nullable HueBridgeHandler hueBridgeHandler;
+ private @Nullable ThingUID bridgeUID;
+
+ public HueDeviceDiscoveryService() {
+ super(SUPPORTED_THING_TYPES, SEARCH_TIME);
+ }
+
+ @Override
+ public void setThingHandler(@Nullable ThingHandler handler) {
+ if (handler instanceof HueBridgeHandler) {
+ hueBridgeHandler = (HueBridgeHandler) handler;
+ bridgeUID = handler.getThing().getUID();
+ }
+ }
+
+ @Override
+ public @Nullable ThingHandler getThingHandler() {
+ return hueBridgeHandler;
+ }
+
+ @Override
+ public void activate() {
+ final HueBridgeHandler handler = hueBridgeHandler;
+ if (handler != null) {
+ handler.registerDiscoveryListener(this);
+ }
+ }
+
+ @Override
+ public void deactivate() {
+ removeOlderResults(new Date().getTime(), bridgeUID);
+ final HueBridgeHandler handler = hueBridgeHandler;
+ if (handler != null) {
+ handler.unregisterDiscoveryListener();
+ }
+ }
+
+ @Override
+ public Set<ThingTypeUID> getSupportedThingTypes() {
+ return SUPPORTED_THING_TYPES;
+ }
+
+ @Override
+ public void startScan() {
+ final HueBridgeHandler handler = hueBridgeHandler;
+ if (handler != null) {
+ List<FullLight> lights = handler.getFullLights();
+ for (FullLight l : lights) {
+ addLightDiscovery(l);
+ }
+ List<FullSensor> sensors = handler.getFullSensors();
+ for (FullSensor s : sensors) {
+ addSensorDiscovery(s);
+ }
+ List<FullGroup> groups = handler.getFullGroups();
+ for (FullGroup g : groups) {
+ addGroupDiscovery(g);
+ }
+ // search for unpaired lights
+ handler.startSearch();
+ }
+ }
+
+ @Override
+ protected synchronized void stopScan() {
+ super.stopScan();
+ final HueBridgeHandler handler = hueBridgeHandler;
+ if (handler != null) {
+ removeOlderResults(getTimestampOfLastScan(), handler.getThing().getUID());
+ }
+ }
+
+ public void addLightDiscovery(FullLight light) {
+ ThingUID thingUID = getThingUID(light);
+ ThingTypeUID thingTypeUID = getThingTypeUID(light);
+
+ String modelId = light.getNormalizedModelID();
+
+ if (thingUID != null && thingTypeUID != null) {
+ Map<String, Object> properties = new HashMap<>();
+ properties.put(LIGHT_ID, light.getId());
+ if (modelId != null) {
+ properties.put(Thing.PROPERTY_MODEL_ID, modelId);
+ }
+ String uniqueID = light.getUniqueID();
+ if (uniqueID != null) {
+ properties.put(UNIQUE_ID, uniqueID);
+ }
+
+ DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID)
+ .withProperties(properties).withBridge(bridgeUID).withRepresentationProperty(UNIQUE_ID)
+ .withLabel(light.getName()).build();
+
+ thingDiscovered(discoveryResult);
+ } else {
+ logger.debug("discovered unsupported light of type '{}' and model '{}' with id {}", light.getType(),
+ modelId, light.getId());
+ }
+ }
+
+ public void removeLightDiscovery(FullLight light) {
+ ThingUID thingUID = getThingUID(light);
+
+ if (thingUID != null) {
+ thingRemoved(thingUID);
+ }
+ }
+
+ private @Nullable ThingUID getThingUID(FullHueObject hueObject) {
+ ThingUID localBridgeUID = bridgeUID;
+ if (localBridgeUID != null) {
+ ThingTypeUID thingTypeUID = getThingTypeUID(hueObject);
+
+ if (thingTypeUID != null && getSupportedThingTypes().contains(thingTypeUID)) {
+ return new ThingUID(thingTypeUID, localBridgeUID, hueObject.getId());
+ }
+ }
+ return null;
+ }
+
+ private @Nullable ThingTypeUID getThingTypeUID(FullHueObject hueObject) {
+ String thingTypeId = TYPE_TO_ZIGBEE_ID_MAP
+ .get(hueObject.getType().replaceAll(NORMALIZE_ID_REGEX, "_").toLowerCase());
+ return thingTypeId != null ? new ThingTypeUID(BINDING_ID, thingTypeId) : null;
+ }
+
+ public void addSensorDiscovery(FullSensor sensor) {
+ ThingUID thingUID = getThingUID(sensor);
+ ThingTypeUID thingTypeUID = getThingTypeUID(sensor);
+
+ String modelId = sensor.getNormalizedModelID();
+ if (thingUID != null && thingTypeUID != null) {
+ Map<String, Object> properties = new HashMap<>();
+ properties.put(SENSOR_ID, sensor.getId());
+ if (modelId != null) {
+ properties.put(Thing.PROPERTY_MODEL_ID, modelId);
+ }
+ String uniqueID = sensor.getUniqueID();
+ if (uniqueID != null) {
+ properties.put(UNIQUE_ID, uniqueID);
+ }
+
+ DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID)
+ .withProperties(properties).withBridge(bridgeUID).withRepresentationProperty(UNIQUE_ID)
+ .withLabel(sensor.getName()).build();
+
+ thingDiscovered(discoveryResult);
+ } else {
+ logger.debug("discovered unsupported sensor of type '{}' and model '{}' with id {}", sensor.getType(),
+ modelId, sensor.getId());
+ }
+ }
+
+ public void removeSensorDiscovery(FullSensor sensor) {
+ ThingUID thingUID = getThingUID(sensor);
+
+ if (thingUID != null) {
+ thingRemoved(thingUID);
+ }
+ }
+
+ public void addGroupDiscovery(FullGroup group) {
+ // Ignore the Hue Entertainment Areas
+ if ("Entertainment".equalsIgnoreCase(group.getType())) {
+ return;
+ }
+
+ ThingUID localBridgeUID = bridgeUID;
+ if (localBridgeUID != null) {
+ ThingUID thingUID = new ThingUID(THING_TYPE_GROUP, localBridgeUID, group.getId());
+
+ Map<String, Object> properties = new HashMap<>();
+ properties.put(GROUP_ID, group.getId());
+
+ String name = String.format("%s (%s)", "0".equals(group.getId()) ? "All lights" : group.getName(),
+ group.getType());
+ DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(THING_TYPE_GROUP)
+ .withProperties(properties).withBridge(localBridgeUID).withRepresentationProperty(GROUP_ID)
+ .withLabel(name).build();
+
+ thingDiscovered(discoveryResult);
+ }
+ }
+
+ public void removeGroupDiscovery(FullGroup group) {
+ ThingUID localBridgeUID = bridgeUID;
+ if (localBridgeUID != null) {
+ ThingUID thingUID = new ThingUID(THING_TYPE_GROUP, localBridgeUID, group.getId());
+ thingRemoved(thingUID);
+ }
+ }
+}
+++ /dev/null
-/**
- * Copyright (c) 2010-2020 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.hue.internal.discovery;
-
-import static org.openhab.binding.hue.internal.HueBindingConstants.*;
-
-import java.util.AbstractMap.SimpleEntry;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.hue.internal.FullGroup;
-import org.openhab.binding.hue.internal.FullHueObject;
-import org.openhab.binding.hue.internal.FullLight;
-import org.openhab.binding.hue.internal.FullSensor;
-import org.openhab.binding.hue.internal.handler.HueBridgeHandler;
-import org.openhab.binding.hue.internal.handler.HueGroupHandler;
-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.GeofencePresenceHandler;
-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.core.config.discovery.AbstractDiscoveryService;
-import org.openhab.core.config.discovery.DiscoveryResult;
-import org.openhab.core.config.discovery.DiscoveryResultBuilder;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingTypeUID;
-import org.openhab.core.thing.ThingUID;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The {@link HueBridgeServiceTracker} tracks for hue lights, sensors and groups which are connected
- * to a paired hue bridge. The default search time for hue is 60 seconds.
- *
- * @author Kai Kreuzer - Initial contribution
- * @author Andre Fuechsel - changed search timeout, changed discovery result creation to support generic thing types;
- * added representationProperty to discovery result
- * @author Thomas Höfer - Added representation
- * @author Denis Dudnik - switched to internally integrated source of Jue library
- * @author Samuel Leisering - Added support for sensor API
- * @author Christoph Weitkamp - Added support for sensor API
- * @author Meng Yiqi - Added support for CLIP sensor
- * @author Laurent Garnier - Added support for groups
- */
-@NonNullByDefault
-public class HueLightDiscoveryService extends AbstractDiscoveryService {
- public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.unmodifiableSet(Stream
- .of(HueLightHandler.SUPPORTED_THING_TYPES.stream(), DimmerSwitchHandler.SUPPORTED_THING_TYPES.stream(),
- TapSwitchHandler.SUPPORTED_THING_TYPES.stream(), PresenceHandler.SUPPORTED_THING_TYPES.stream(),
- GeofencePresenceHandler.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 Logger logger = LoggerFactory.getLogger(HueLightDiscoveryService.class);
-
- private static final int SEARCH_TIME = 10;
-
- // @formatter:off
- private static final Map<String, @Nullable String> TYPE_TO_ZIGBEE_ID_MAP = Stream.of(
- new SimpleEntry<>("on_off_light", "0000"),
- new SimpleEntry<>("on_off_plug_in_unit", "0010"),
- new SimpleEntry<>("dimmable_light", "0100"),
- new SimpleEntry<>("dimmable_plug_in_unit", "0110"),
- new SimpleEntry<>("color_light", "0200"),
- new SimpleEntry<>("extended_color_light", "0210"),
- new SimpleEntry<>("color_temperature_light", "0220"),
- new SimpleEntry<>("zllswitch", "0820"),
- new SimpleEntry<>("zgpswitch", "0830"),
- new SimpleEntry<>("clipgenericstatus", "0840"),
- new SimpleEntry<>("clipgenericflag", "0850"),
- new SimpleEntry<>("zllpresence", "0107"),
- new SimpleEntry<>("geofence", "0107"),
- new SimpleEntry<>("zlltemperature", "0302"),
- new SimpleEntry<>("zlllightlevel", "0106")
- ).collect(Collectors.toMap((e) -> e.getKey(), (e) -> e.getValue()));
- // @formatter:on
-
- private final HueBridgeHandler hueBridgeHandler;
-
- public HueLightDiscoveryService(HueBridgeHandler hueBridgeHandler) {
- super(SEARCH_TIME);
- this.hueBridgeHandler = hueBridgeHandler;
- }
-
- public void activate() {
- hueBridgeHandler.registerDiscoveryListener(this);
- }
-
- @Override
- public void deactivate() {
- removeOlderResults(new Date().getTime(), hueBridgeHandler.getThing().getUID());
- hueBridgeHandler.unregisterDiscoveryListener();
- }
-
- @Override
- public Set<ThingTypeUID> getSupportedThingTypes() {
- return SUPPORTED_THING_TYPES;
- }
-
- @Override
- public void startScan() {
- List<FullLight> lights = hueBridgeHandler.getFullLights();
- for (FullLight l : lights) {
- addLightDiscovery(l);
- }
- List<FullSensor> sensors = hueBridgeHandler.getFullSensors();
- for (FullSensor s : sensors) {
- addSensorDiscovery(s);
- }
- List<FullGroup> groups = hueBridgeHandler.getFullGroups();
- for (FullGroup g : groups) {
- addGroupDiscovery(g);
- }
- // search for unpaired lights
- hueBridgeHandler.startSearch();
- }
-
- @Override
- protected synchronized void stopScan() {
- super.stopScan();
- removeOlderResults(getTimestampOfLastScan(), hueBridgeHandler.getThing().getUID());
- }
-
- public void addLightDiscovery(FullLight light) {
- ThingUID thingUID = getThingUID(light);
- ThingTypeUID thingTypeUID = getThingTypeUID(light);
-
- String modelId = light.getNormalizedModelID();
-
- if (thingUID != null && thingTypeUID != null) {
- ThingUID bridgeUID = hueBridgeHandler.getThing().getUID();
- Map<String, Object> properties = new HashMap<>();
- properties.put(LIGHT_ID, light.getId());
- if (modelId != null) {
- properties.put(Thing.PROPERTY_MODEL_ID, modelId);
- }
- String uniqueID = light.getUniqueID();
- if (uniqueID != null) {
- properties.put(UNIQUE_ID, uniqueID);
- }
-
- DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID)
- .withProperties(properties).withBridge(bridgeUID).withRepresentationProperty(UNIQUE_ID)
- .withLabel(light.getName()).build();
-
- thingDiscovered(discoveryResult);
- } else {
- logger.debug("discovered unsupported light of type '{}' and model '{}' with id {}", light.getType(),
- modelId, light.getId());
- }
- }
-
- public void removeLightDiscovery(FullLight light) {
- ThingUID thingUID = getThingUID(light);
-
- if (thingUID != null) {
- thingRemoved(thingUID);
- }
- }
-
- private @Nullable ThingUID getThingUID(FullHueObject hueObject) {
- ThingUID bridgeUID = hueBridgeHandler.getThing().getUID();
- ThingTypeUID thingTypeUID = getThingTypeUID(hueObject);
-
- if (thingTypeUID != null && getSupportedThingTypes().contains(thingTypeUID)) {
- return new ThingUID(thingTypeUID, bridgeUID, hueObject.getId());
- } else {
- return null;
- }
- }
-
- private @Nullable ThingTypeUID getThingTypeUID(FullHueObject hueObject) {
- String thingTypeId = TYPE_TO_ZIGBEE_ID_MAP
- .get(hueObject.getType().replaceAll(NORMALIZE_ID_REGEX, "_").toLowerCase());
- return thingTypeId != null ? new ThingTypeUID(BINDING_ID, thingTypeId) : null;
- }
-
- public void addSensorDiscovery(FullSensor sensor) {
- ThingUID thingUID = getThingUID(sensor);
- ThingTypeUID thingTypeUID = getThingTypeUID(sensor);
-
- String modelId = sensor.getNormalizedModelID();
- if (thingUID != null && thingTypeUID != null) {
- ThingUID bridgeUID = hueBridgeHandler.getThing().getUID();
- Map<String, Object> properties = new HashMap<>();
- properties.put(SENSOR_ID, sensor.getId());
- if (modelId != null) {
- properties.put(Thing.PROPERTY_MODEL_ID, modelId);
- }
- String uniqueID = sensor.getUniqueID();
- if (uniqueID != null) {
- properties.put(UNIQUE_ID, uniqueID);
- }
-
- DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID)
- .withProperties(properties).withBridge(bridgeUID).withRepresentationProperty(UNIQUE_ID)
- .withLabel(sensor.getName()).build();
-
- thingDiscovered(discoveryResult);
- } else {
- logger.debug("discovered unsupported sensor of type '{}' and model '{}' with id {}", sensor.getType(),
- modelId, sensor.getId());
- }
- }
-
- public void removeSensorDiscovery(FullSensor sensor) {
- ThingUID thingUID = getThingUID(sensor);
-
- if (thingUID != null) {
- thingRemoved(thingUID);
- }
- }
-
- public void addGroupDiscovery(FullGroup group) {
- // Ignore the Hue Entertainment Areas
- if ("Entertainment".equalsIgnoreCase(group.getType())) {
- return;
- }
-
- ThingUID bridgeUID = hueBridgeHandler.getThing().getUID();
- ThingUID thingUID = new ThingUID(THING_TYPE_GROUP, bridgeUID, group.getId());
-
- Map<String, Object> properties = new HashMap<>();
- properties.put(GROUP_ID, group.getId());
-
- String name = String.format("%s (%s)", "0".equals(group.getId()) ? "All lights" : group.getName(),
- group.getType());
- DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(THING_TYPE_GROUP)
- .withProperties(properties).withBridge(bridgeUID).withRepresentationProperty(GROUP_ID).withLabel(name)
- .build();
-
- thingDiscovered(discoveryResult);
- }
-
- public void removeGroupDiscovery(FullGroup group) {
- ThingUID bridgeUID = hueBridgeHandler.getThing().getUID();
- ThingUID thingUID = new ThingUID(THING_TYPE_GROUP, bridgeUID, group.getId());
- thingRemoved(thingUID);
- }
-}
import org.openhab.binding.hue.internal.State;
import org.openhab.binding.hue.internal.StateUpdate;
import org.openhab.binding.hue.internal.config.HueBridgeConfig;
-import org.openhab.binding.hue.internal.discovery.HueLightDiscoveryService;
+import org.openhab.binding.hue.internal.discovery.HueDeviceDiscoveryService;
import org.openhab.binding.hue.internal.exceptions.ApiException;
import org.openhab.binding.hue.internal.exceptions.DeviceOffException;
import org.openhab.binding.hue.internal.exceptions.EntityNotAvailableException;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.ConfigStatusBridgeHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
import org.openhab.core.types.StateOption;
import org.slf4j.Logger;
@NonNullByDefault
public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueClient {
- public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_BRIDGE);
+ public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_BRIDGE);
private static final long BYPASS_MIN_DURATION_BEFORE_CMD = 1500L;
private final Map<String, @Nullable FullSensor> lastSensorStates = new ConcurrentHashMap<>();
private final Map<String, @Nullable FullGroup> lastGroupStates = new ConcurrentHashMap<>();
- private @Nullable HueLightDiscoveryService discoveryService;
+ private @Nullable HueDeviceDiscoveryService discoveryService;
private final Map<String, @Nullable LightStatusListener> lightStatusListeners = new ConcurrentHashMap<>();
private final Map<String, @Nullable SensorStatusListener> sensorStatusListeners = new ConcurrentHashMap<>();
private final Map<String, @Nullable GroupStatusListener> groupStatusListeners = new ConcurrentHashMap<>();
protected void doConnectedRun() throws IOException, ApiException {
Map<String, @Nullable FullSensor> lastSensorStateCopy = new HashMap<>(lastSensorStates);
- final HueLightDiscoveryService discovery = discoveryService;
+ final HueDeviceDiscoveryService discovery = discoveryService;
for (final FullSensor sensor : hueBridge.getSensors()) {
String sensorId = sensor.getId();
lights = hueBridge.getFullConfig().getLights();
}
- final HueLightDiscoveryService discovery = discoveryService;
+ final HueDeviceDiscoveryService discovery = discoveryService;
for (final FullLight fullLight : lights) {
final String lightId = fullLight.getId();
List<FullGroup> groups = hueBridge.getGroups();
- final HueLightDiscoveryService discovery = discoveryService;
+ final HueDeviceDiscoveryService discovery = discoveryService;
for (final FullGroup fullGroup : groups) {
State groupState = new State();
this.stateDescriptionOptionProvider = stateDescriptionOptionProvider;
}
+ @Override
+ public Collection<Class<? extends ThingHandlerService>> getServices() {
+ return Collections.singleton(HueDeviceDiscoveryService.class);
+ }
+
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (CHANNEL_SCENE.equals(channelUID.getId()) && command instanceof StringType) {
}
} else if (e instanceof EntityNotAvailableException) {
logger.debug("Error while accessing light: {}", e.getMessage(), e);
- final HueLightDiscoveryService discovery = discoveryService;
+ final HueDeviceDiscoveryService discovery = discoveryService;
if (discovery != null) {
discovery.removeLightDiscovery(light);
}
private void handleSensorUpdateException(FullSensor sensor, Throwable e) {
if (e instanceof EntityNotAvailableException) {
logger.debug("Error while accessing sensor: {}", e.getMessage(), e);
- final HueLightDiscoveryService discovery = discoveryService;
+ final HueDeviceDiscoveryService discovery = discoveryService;
if (discovery != null) {
discovery.removeSensorDiscovery(sensor);
}
private void handleGroupUpdateException(FullGroup group, Throwable e) {
if (e instanceof EntityNotAvailableException) {
logger.debug("Error while accessing group: {}", e.getMessage(), e);
- final HueLightDiscoveryService discovery = discoveryService;
+ final HueDeviceDiscoveryService discovery = discoveryService;
if (discovery != null) {
discovery.removeGroupDiscovery(group);
}
}
@Override
- public boolean registerDiscoveryListener(HueLightDiscoveryService listener) {
+ public boolean registerDiscoveryListener(HueDeviceDiscoveryService listener) {
if (discoveryService == null) {
discoveryService = listener;
getFullLights().forEach(listener::addLightDiscovery);
List<FullLight> ret = withReAuthentication("search for new lights", () -> {
return hueBridge.getFullLights();
});
- return ret != null ? ret : Collections.emptyList();
+ return ret != null ? ret : List.of();
}
public List<FullSensor> getFullSensors() {
List<FullSensor> ret = withReAuthentication("search for new sensors", () -> {
return hueBridge.getSensors();
});
- return ret != null ? ret : Collections.emptyList();
+ return ret != null ? ret : List.of();
}
public List<FullGroup> getFullGroups() {
List<FullGroup> ret = withReAuthentication("search for new groups", () -> {
return hueBridge.getGroups();
});
- return ret != null ? ret : Collections.emptyList();
+ return ret != null ? ret : List.of();
}
public void startSearch() {
@Override
public Collection<ConfigStatusMessage> getConfigStatus() {
// The bridge IP address to be used for checks
- Collection<ConfigStatusMessage> configStatusMessages;
-
// Check whether an IP address is provided
String ip = hueBridgeConfig.getIpAddress();
if (ip == null || ip.isEmpty()) {
- configStatusMessages = Collections.singletonList(ConfigStatusMessage.Builder.error(HOST)
+ return List.of(ConfigStatusMessage.Builder.error(HOST)
.withMessageKeySuffix(HueConfigStatusMessage.IP_ADDRESS_MISSING).withArguments(HOST).build());
} else {
- configStatusMessages = Collections.emptyList();
+ return List.of();
}
-
- return configStatusMessages;
}
}
import org.openhab.binding.hue.internal.FullLight;
import org.openhab.binding.hue.internal.FullSensor;
import org.openhab.binding.hue.internal.StateUpdate;
-import org.openhab.binding.hue.internal.discovery.HueLightDiscoveryService;
+import org.openhab.binding.hue.internal.discovery.HueDeviceDiscoveryService;
/**
* Access to the Hue system for light handlers.
public interface HueClient {
/**
- * Register {@link HueLightDiscoveryService} to bridge handler
+ * Register {@link HueDeviceDiscoveryService} to bridge handler
*
* @param listener the discovery service
* @return {@code true} if the new discovery service is accepted
*/
- boolean registerDiscoveryListener(HueLightDiscoveryService listener);
+ boolean registerDiscoveryListener(HueDeviceDiscoveryService listener);
/**
- * Unregister {@link HueLightDiscoveryService} from bridge handler
+ * Unregister {@link HueDeviceDiscoveryService} from bridge handler
*
* @return {@code true} if the discovery service was removed
*/
--- /dev/null
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.hue.internal;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.openhab.binding.hue.internal.HueBindingConstants.*;
+import static org.openhab.core.thing.Thing.PROPERTY_SERIAL_NUMBER;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.Collection;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.openhab.binding.hue.internal.discovery.HueDeviceDiscoveryService;
+import org.openhab.binding.hue.internal.handler.HueBridgeHandler;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.config.discovery.DiscoveryListener;
+import org.openhab.core.config.discovery.DiscoveryResult;
+import org.openhab.core.config.discovery.DiscoveryResultFlag;
+import org.openhab.core.config.discovery.DiscoveryService;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.ThingRegistry;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.ThingStatusInfo;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.binding.builder.ThingStatusInfoBuilder;
+
+/**
+ * Tests for {@link HueDeviceDiscoveryService}.
+ *
+ * @author Kai Kreuzer - Initial contribution
+ * @author Andre Fuechsel - added test 'assert start search is called()'
+ * - modified tests after introducing the generic thing types
+ * @author Denis Dudnik - switched to internally integrated source of Jue library
+ * @author Markus Rathgeb - migrated to plain Java test
+ */
+public class HueDeviceDiscoveryServiceOSGiTest extends AbstractHueOSGiTestParent {
+
+ protected HueThingHandlerFactory hueThingHandlerFactory;
+ protected DiscoveryListener discoveryListener;
+ protected ThingRegistry thingRegistry;
+ protected Bridge hueBridge;
+ protected HueBridgeHandler hueBridgeHandler;
+ protected HueDeviceDiscoveryService discoveryService;
+
+ protected final ThingTypeUID BRIDGE_THING_TYPE_UID = new ThingTypeUID("hue", "bridge");
+ protected final ThingUID BRIDGE_THING_UID = new ThingUID(BRIDGE_THING_TYPE_UID, "testBridge");
+
+ @BeforeEach
+ public void setUp() {
+ registerVolatileStorageService();
+
+ thingRegistry = getService(ThingRegistry.class, ThingRegistry.class);
+ assertThat(thingRegistry, is(notNullValue()));
+
+ Configuration configuration = new Configuration();
+ configuration.put(HOST, "1.2.3.4");
+ configuration.put(USER_NAME, "testUserName");
+ configuration.put(PROPERTY_SERIAL_NUMBER, "testSerialNumber");
+
+ hueBridge = (Bridge) thingRegistry.createThingOfType(BRIDGE_THING_TYPE_UID, BRIDGE_THING_UID, null, "Bridge",
+ configuration);
+
+ assertThat(hueBridge, is(notNullValue()));
+ thingRegistry.add(hueBridge);
+
+ hueBridgeHandler = getThingHandler(hueBridge, HueBridgeHandler.class);
+ assertThat(hueBridgeHandler, is(notNullValue()));
+
+ discoveryService = getService(DiscoveryService.class, HueDeviceDiscoveryService.class);
+ assertThat(discoveryService, is(notNullValue()));
+ }
+
+ @AfterEach
+ public void cleanUp() {
+ thingRegistry.remove(BRIDGE_THING_UID);
+ waitForAssert(() -> {
+ assertNull(getService(DiscoveryService.class, HueDeviceDiscoveryService.class));
+ });
+ }
+
+ private void registerDiscoveryListener(DiscoveryListener discoveryListener) {
+ unregisterCurrentDiscoveryListener();
+ this.discoveryListener = discoveryListener;
+ discoveryService.addDiscoveryListener(this.discoveryListener);
+ }
+
+ private void unregisterCurrentDiscoveryListener() {
+ if (this.discoveryListener != null) {
+ discoveryService.removeDiscoveryListener(this.discoveryListener);
+ }
+ }
+
+ @Test
+ public void hueLightRegistration() {
+ FullLight light = new FullLight();
+ light.setId("1");
+ light.setUniqueID("AA:BB:CC:DD:EE:FF:00:11-XX");
+ light.setModelID("LCT001");
+ light.setType("Extended color light");
+
+ AtomicReference<DiscoveryResult> resultWrapper = new AtomicReference<>();
+
+ registerDiscoveryListener(new DiscoveryListener() {
+ @Override
+ public void thingDiscovered(DiscoveryService source, DiscoveryResult result) {
+ resultWrapper.set(result);
+ }
+
+ @Override
+ public void thingRemoved(DiscoveryService source, ThingUID thingUID) {
+ }
+
+ @Override
+ public Collection<ThingUID> removeOlderResults(DiscoveryService source, long timestamp,
+ Collection<ThingTypeUID> thingTypeUIDs, ThingUID bridgeUID) {
+ return null;
+ }
+ });
+
+ discoveryService.addLightDiscovery(light);
+ waitForAssert(() -> {
+ assertTrue(resultWrapper.get() != null);
+ });
+
+ final DiscoveryResult result = resultWrapper.get();
+ assertThat(result.getFlag(), is(DiscoveryResultFlag.NEW));
+ assertThat(result.getThingUID().toString(), is("hue:0210:testBridge:" + light.getId()));
+ assertThat(result.getThingTypeUID(), is(THING_TYPE_EXTENDED_COLOR_LIGHT));
+ assertThat(result.getBridgeUID(), is(hueBridge.getUID()));
+ assertThat(result.getProperties().get(LIGHT_ID), is(light.getId()));
+ }
+
+ @Test
+ public void startSearchIsCalled() {
+ final AtomicBoolean searchHasBeenTriggered = new AtomicBoolean(false);
+
+ MockedHttpClient mockedHttpClient = new MockedHttpClient() {
+
+ @Override
+ public Result put(String address, String body) throws IOException {
+ return new Result("", 200);
+ }
+
+ @Override
+ public Result get(String address) throws IOException {
+ if (address.endsWith("testUserName")) {
+ String body = "{\"lights\":{}}";
+ return new Result(body, 200);
+ } else if (address.endsWith("lights") || address.endsWith("sensors") || address.endsWith("groups")) {
+ String body = "{}";
+ return new Result(body, 200);
+ } else if (address.endsWith("testUserName/config")) {
+ String body = "{ \"apiversion\": \"1.26.0\"}";
+ return new Result(body, 200);
+ } else {
+ return new Result("", 404);
+ }
+ }
+
+ @Override
+ public Result post(String address, String body) throws IOException {
+ if (address.endsWith("lights")) {
+ String bodyReturn = "{\"success\": {\"/lights\": \"Searching for new devices\"}}";
+ searchHasBeenTriggered.set(true);
+ return new Result(bodyReturn, 200);
+ } else {
+ return new Result("", 404);
+ }
+ }
+ };
+
+ installHttpClientMock(hueBridgeHandler, mockedHttpClient);
+
+ ThingStatusInfo online = ThingStatusInfoBuilder.create(ThingStatus.ONLINE, ThingStatusDetail.NONE).build();
+ waitForAssert(() -> {
+ assertThat(hueBridge.getStatusInfo(), is(online));
+ });
+
+ discoveryService.startScan();
+ waitForAssert(() -> {
+ assertTrue(searchHasBeenTriggered.get());
+ });
+ }
+
+ private void installHttpClientMock(HueBridgeHandler hueBridgeHandler, MockedHttpClient mockedHttpClient) {
+ waitForAssert(() -> {
+ try {
+ // mock HttpClient
+ final Field hueBridgeField = HueBridgeHandler.class.getDeclaredField("hueBridge");
+ hueBridgeField.setAccessible(true);
+ final Object hueBridgeValue = hueBridgeField.get(hueBridgeHandler);
+ assertThat(hueBridgeValue, is(notNullValue()));
+
+ final Field httpClientField = HueBridge.class.getDeclaredField("http");
+ httpClientField.setAccessible(true);
+ httpClientField.set(hueBridgeValue, mockedHttpClient);
+
+ final Field usernameField = HueBridge.class.getDeclaredField("username");
+ usernameField.setAccessible(true);
+ usernameField.set(hueBridgeValue, hueBridgeHandler.getThing().getConfiguration().get(USER_NAME));
+ } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException ex) {
+ fail("Reflection usage error");
+ }
+ });
+ hueBridgeHandler.initialize();
+ }
+}
+++ /dev/null
-/**
- * Copyright (c) 2010-2020 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.hue.internal;
-
-import static org.hamcrest.CoreMatchers.*;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.jupiter.api.Assertions.*;
-import static org.openhab.binding.hue.internal.HueBindingConstants.*;
-import static org.openhab.core.thing.Thing.PROPERTY_SERIAL_NUMBER;
-
-import java.io.IOException;
-import java.lang.reflect.Field;
-import java.util.Collection;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.openhab.binding.hue.internal.discovery.HueLightDiscoveryService;
-import org.openhab.binding.hue.internal.handler.HueBridgeHandler;
-import org.openhab.core.config.core.Configuration;
-import org.openhab.core.config.discovery.DiscoveryListener;
-import org.openhab.core.config.discovery.DiscoveryResult;
-import org.openhab.core.config.discovery.DiscoveryResultFlag;
-import org.openhab.core.config.discovery.DiscoveryService;
-import org.openhab.core.thing.Bridge;
-import org.openhab.core.thing.ThingRegistry;
-import org.openhab.core.thing.ThingStatus;
-import org.openhab.core.thing.ThingStatusDetail;
-import org.openhab.core.thing.ThingStatusInfo;
-import org.openhab.core.thing.ThingTypeUID;
-import org.openhab.core.thing.ThingUID;
-import org.openhab.core.thing.binding.builder.ThingStatusInfoBuilder;
-
-/**
- * Tests for {@link HueLightDiscoveryService}.
- *
- * @author Kai Kreuzer - Initial contribution
- * @author Andre Fuechsel - added test 'assert start search is called()'
- * - modified tests after introducing the generic thing types
- * @author Denis Dudnik - switched to internally integrated source of Jue library
- * @author Markus Rathgeb - migrated to plain Java test
- */
-public class HueLightDiscoveryServiceOSGiTest extends AbstractHueOSGiTestParent {
-
- protected HueThingHandlerFactory hueThingHandlerFactory;
- protected DiscoveryListener discoveryListener;
- protected ThingRegistry thingRegistry;
- protected Bridge hueBridge;
- protected HueBridgeHandler hueBridgeHandler;
- protected HueLightDiscoveryService discoveryService;
-
- protected final ThingTypeUID BRIDGE_THING_TYPE_UID = new ThingTypeUID("hue", "bridge");
- protected final ThingUID BRIDGE_THING_UID = new ThingUID(BRIDGE_THING_TYPE_UID, "testBridge");
-
- @BeforeEach
- public void setUp() {
- registerVolatileStorageService();
-
- thingRegistry = getService(ThingRegistry.class, ThingRegistry.class);
- assertThat(thingRegistry, is(notNullValue()));
-
- Configuration configuration = new Configuration();
- configuration.put(HOST, "1.2.3.4");
- configuration.put(USER_NAME, "testUserName");
- configuration.put(PROPERTY_SERIAL_NUMBER, "testSerialNumber");
-
- hueBridge = (Bridge) thingRegistry.createThingOfType(BRIDGE_THING_TYPE_UID, BRIDGE_THING_UID, null, "Bridge",
- configuration);
-
- assertThat(hueBridge, is(notNullValue()));
- thingRegistry.add(hueBridge);
-
- hueBridgeHandler = getThingHandler(hueBridge, HueBridgeHandler.class);
- assertThat(hueBridgeHandler, is(notNullValue()));
-
- discoveryService = getService(DiscoveryService.class, HueLightDiscoveryService.class);
- assertThat(discoveryService, is(notNullValue()));
- }
-
- @AfterEach
- public void cleanUp() {
- thingRegistry.remove(BRIDGE_THING_UID);
- waitForAssert(() -> {
- assertNull(getService(DiscoveryService.class, HueLightDiscoveryService.class));
- });
- }
-
- private void registerDiscoveryListener(DiscoveryListener discoveryListener) {
- unregisterCurrentDiscoveryListener();
- this.discoveryListener = discoveryListener;
- discoveryService.addDiscoveryListener(this.discoveryListener);
- }
-
- private void unregisterCurrentDiscoveryListener() {
- if (this.discoveryListener != null) {
- discoveryService.removeDiscoveryListener(this.discoveryListener);
- }
- }
-
- @Test
- public void hueLightRegistration() {
- FullLight light = new FullLight();
- light.setId("1");
- light.setModelID("LCT001");
- light.setType("Extended color light");
-
- AtomicReference<DiscoveryResult> resultWrapper = new AtomicReference<>();
-
- registerDiscoveryListener(new DiscoveryListener() {
- @Override
- public void thingDiscovered(DiscoveryService source, DiscoveryResult result) {
- resultWrapper.set(result);
- }
-
- @Override
- public void thingRemoved(DiscoveryService source, ThingUID thingUID) {
- }
-
- @Override
- public Collection<ThingUID> removeOlderResults(DiscoveryService source, long timestamp,
- Collection<ThingTypeUID> thingTypeUIDs, ThingUID bridgeUID) {
- return null;
- }
- });
-
- discoveryService.addLightDiscovery(light);
- waitForAssert(() -> {
- assertTrue(resultWrapper.get() != null);
- });
-
- final DiscoveryResult result = resultWrapper.get();
- assertThat(result.getFlag(), is(DiscoveryResultFlag.NEW));
- assertThat(result.getThingUID().toString(), is("hue:0210:testBridge:" + light.getId()));
- assertThat(result.getThingTypeUID(), is(THING_TYPE_EXTENDED_COLOR_LIGHT));
- assertThat(result.getBridgeUID(), is(hueBridge.getUID()));
- assertThat(result.getProperties().get(LIGHT_ID), is(light.getId()));
- }
-
- @Test
- public void startSearchIsCalled() {
- final AtomicBoolean searchHasBeenTriggered = new AtomicBoolean(false);
-
- MockedHttpClient mockedHttpClient = new MockedHttpClient() {
-
- @Override
- public Result put(String address, String body) throws IOException {
- return new Result("", 200);
- }
-
- @Override
- public Result get(String address) throws IOException {
- if (address.endsWith("testUserName")) {
- String body = "{\"lights\":{}}";
- return new Result(body, 200);
- } else if (address.endsWith("lights") || address.endsWith("sensors")) {
- String body = "{}";
- return new Result(body, 200);
- } else if (address.endsWith("testUserName/config")) {
- String body = "{ \"apiversion\": \"1.26.0\"}";
- return new Result(body, 200);
- } else {
- return new Result("", 404);
- }
- }
-
- @Override
- public Result post(String address, String body) throws IOException {
- if (address.endsWith("lights")) {
- String bodyReturn = "{\"success\": {\"/lights\": \"Searching for new devices\"}}";
- searchHasBeenTriggered.set(true);
- return new Result(bodyReturn, 200);
- } else {
- return new Result("", 404);
- }
- }
- };
-
- installHttpClientMock(hueBridgeHandler, mockedHttpClient);
-
- ThingStatusInfo online = ThingStatusInfoBuilder.create(ThingStatus.ONLINE, ThingStatusDetail.NONE).build();
- waitForAssert(() -> {
- assertThat(hueBridge.getStatusInfo(), is(online));
- });
-
- discoveryService.startScan();
- waitForAssert(() -> {
- assertTrue(searchHasBeenTriggered.get());
- });
- }
-
- private void installHttpClientMock(HueBridgeHandler hueBridgeHandler, MockedHttpClient mockedHttpClient) {
- waitForAssert(() -> {
- try {
- // mock HttpClient
- final Field hueBridgeField = HueBridgeHandler.class.getDeclaredField("hueBridge");
- hueBridgeField.setAccessible(true);
- final Object hueBridgeValue = hueBridgeField.get(hueBridgeHandler);
- assertThat(hueBridgeValue, is(notNullValue()));
-
- final Field httpClientField = HueBridge.class.getDeclaredField("http");
- httpClientField.setAccessible(true);
- httpClientField.set(hueBridgeValue, mockedHttpClient);
-
- final Field usernameField = HueBridge.class.getDeclaredField("username");
- usernameField.setAccessible(true);
- usernameField.set(hueBridgeValue, hueBridgeHandler.getThing().getConfiguration().get(USER_NAME));
- } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException ex) {
- fail("Reflection usage error");
- }
- });
- hueBridgeHandler.initialize();
- }
-}