From: Christoph Weitkamp Date: Thu, 15 Oct 2020 22:51:41 +0000 (+0200) Subject: [hue] Refactored discovery service to 'ThingHandlerService' (#8729) X-Git-Url: https://git.basschouten.com/?a=commitdiff_plain;h=25826854b4ff83537de9bcb73b68d0abc73ff8bf;p=openhab-addons.git [hue] Refactored discovery service to 'ThingHandlerService' (#8729) * Refactored discovery service to ThingHandlerService * Fixed discovery for Geofence Sensor Signed-off-by: Christoph Weitkamp --- diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullHueObject.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullHueObject.java index 2f5e07711b..a5fb4f1ae7 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullHueObject.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullHueObject.java @@ -108,8 +108,14 @@ public class FullHueObject extends HueObject { * * @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; + } } diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullLight.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullLight.java index d0a48e6339..f3a1d83a62 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullLight.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullLight.java @@ -16,6 +16,8 @@ import java.lang.reflect.Type; import java.time.Duration; import java.util.Map; +import org.eclipse.jdt.annotation.NonNullByDefault; + import com.google.gson.reflect.TypeToken; /** @@ -26,16 +28,14 @@ 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>() { }.getType(); - private State state; + private @NonNullByDefault({}) State state; private final long fadetime = 400; // milliseconds - FullLight() { - } - /** * Returns the current state of the light. * diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullSensor.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullSensor.java index 800e47d451..497dc2c641 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullSensor.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullSensor.java @@ -15,6 +15,8 @@ package org.openhab.binding.hue.internal; import java.lang.reflect.Type; import java.util.Map; +import org.eclipse.jdt.annotation.NonNullByDefault; + import com.google.gson.reflect.TypeToken; /** @@ -23,7 +25,10 @@ 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>() { + }.getType(); public static final String STATE_LAST_UPDATED = "lastupdated"; public static final String STATE_BUTTON_EVENT = "buttonevent"; @@ -46,11 +51,8 @@ public class FullSensor extends FullHueObject { 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>() { - }.getType(); - - private Map state; - private Map config; + private @NonNullByDefault({}) Map state; + private @NonNullByDefault({}) Map config; public Map getState() { return state; 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 21fdc431eb..5724bb2c22 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 @@ -15,16 +15,12 @@ package org.openhab.binding.hue.internal; 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; @@ -37,7 +33,6 @@ 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.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; @@ -45,7 +40,6 @@ import org.openhab.core.thing.ThingUID; 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; @@ -75,8 +69,6 @@ public class HueThingHandlerFactory extends BaseThingHandlerFactory { private final HueStateDescriptionOptionProvider stateOptionProvider; - private final Map> discoveryServiceRegs = new HashMap<>(); - @Activate public HueThingHandlerFactory(final @Reference HueStateDescriptionOptionProvider stateOptionProvider) { this.stateOptionProvider = stateOptionProvider; @@ -150,9 +142,7 @@ public class HueThingHandlerFactory extends BaseThingHandlerFactory { @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())) { @@ -175,27 +165,4 @@ public class HueThingHandlerFactory extends BaseThingHandlerFactory { 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(); - } - } - } - } } diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueDeviceDiscoveryService.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueDeviceDiscoveryService.java new file mode 100644 index 0000000000..c77c9286aa --- /dev/null +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueDeviceDiscoveryService.java @@ -0,0 +1,295 @@ +/** + * 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 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 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 getSupportedThingTypes() { + return SUPPORTED_THING_TYPES; + } + + @Override + public void startScan() { + final HueBridgeHandler handler = hueBridgeHandler; + if (handler != null) { + List lights = handler.getFullLights(); + for (FullLight l : lights) { + addLightDiscovery(l); + } + List sensors = handler.getFullSensors(); + for (FullSensor s : sensors) { + addSensorDiscovery(s); + } + List 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 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 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 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); + } + } +} 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 deleted file mode 100644 index 7d301df4c8..0000000000 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueLightDiscoveryService.java +++ /dev/null @@ -1,262 +0,0 @@ -/** - * 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 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 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 getSupportedThingTypes() { - return SUPPORTED_THING_TYPES; - } - - @Override - public void startScan() { - List lights = hueBridgeHandler.getFullLights(); - for (FullLight l : lights) { - addLightDiscovery(l); - } - List sensors = hueBridgeHandler.getFullSensors(); - for (FullSensor s : sensors) { - addSensorDiscovery(s); - } - List 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 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 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 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); - } -} 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 d08b892180..163ceae238 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 @@ -46,7 +46,7 @@ import org.openhab.binding.hue.internal.Scene; 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; @@ -63,6 +63,7 @@ import org.openhab.core.thing.ThingStatus; 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; @@ -87,7 +88,7 @@ import org.slf4j.LoggerFactory; @NonNullByDefault public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueClient { - public static final Set SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_BRIDGE); + public static final Set SUPPORTED_THING_TYPES = Set.of(THING_TYPE_BRIDGE); private static final long BYPASS_MIN_DURATION_BEFORE_CMD = 1500L; @@ -102,7 +103,7 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl private final Map lastSensorStates = new ConcurrentHashMap<>(); private final Map lastGroupStates = new ConcurrentHashMap<>(); - private @Nullable HueLightDiscoveryService discoveryService; + private @Nullable HueDeviceDiscoveryService discoveryService; private final Map lightStatusListeners = new ConcurrentHashMap<>(); private final Map sensorStatusListeners = new ConcurrentHashMap<>(); private final Map groupStatusListeners = new ConcurrentHashMap<>(); @@ -183,7 +184,7 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl protected void doConnectedRun() throws IOException, ApiException { Map lastSensorStateCopy = new HashMap<>(lastSensorStates); - final HueLightDiscoveryService discovery = discoveryService; + final HueDeviceDiscoveryService discovery = discoveryService; for (final FullSensor sensor : hueBridge.getSensors()) { String sensorId = sensor.getId(); @@ -239,7 +240,7 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl lights = hueBridge.getFullConfig().getLights(); } - final HueLightDiscoveryService discovery = discoveryService; + final HueDeviceDiscoveryService discovery = discoveryService; for (final FullLight fullLight : lights) { final String lightId = fullLight.getId(); @@ -282,7 +283,7 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl List groups = hueBridge.getGroups(); - final HueLightDiscoveryService discovery = discoveryService; + final HueDeviceDiscoveryService discovery = discoveryService; for (final FullGroup fullGroup : groups) { State groupState = new State(); @@ -409,6 +410,11 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl this.stateDescriptionOptionProvider = stateDescriptionOptionProvider; } + @Override + public Collection> getServices() { + return Collections.singleton(HueDeviceDiscoveryService.class); + } + @Override public void handleCommand(ChannelUID channelUID, Command command) { if (CHANNEL_SCENE.equals(channelUID.getId()) && command instanceof StringType) { @@ -528,7 +534,7 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl } } 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); } @@ -541,7 +547,7 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl 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); } @@ -557,7 +563,7 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl 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); } @@ -840,7 +846,7 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl } @Override - public boolean registerDiscoveryListener(HueLightDiscoveryService listener) { + public boolean registerDiscoveryListener(HueDeviceDiscoveryService listener) { if (discoveryService == null) { discoveryService = listener; getFullLights().forEach(listener::addLightDiscovery); @@ -964,21 +970,21 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl List ret = withReAuthentication("search for new lights", () -> { return hueBridge.getFullLights(); }); - return ret != null ? ret : Collections.emptyList(); + return ret != null ? ret : List.of(); } public List getFullSensors() { List ret = withReAuthentication("search for new sensors", () -> { return hueBridge.getSensors(); }); - return ret != null ? ret : Collections.emptyList(); + return ret != null ? ret : List.of(); } public List getFullGroups() { List ret = withReAuthentication("search for new groups", () -> { return hueBridge.getGroups(); }); - return ret != null ? ret : Collections.emptyList(); + return ret != null ? ret : List.of(); } public void startSearch() { @@ -1024,17 +1030,13 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl @Override public Collection getConfigStatus() { // The bridge IP address to be used for checks - Collection 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; } } 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 92463d1bf2..94115d5d88 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 @@ -19,7 +19,7 @@ import org.openhab.binding.hue.internal.FullGroup; 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. @@ -33,15 +33,15 @@ import org.openhab.binding.hue.internal.discovery.HueLightDiscoveryService; 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 */ diff --git a/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/HueDeviceDiscoveryServiceOSGiTest.java b/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/HueDeviceDiscoveryServiceOSGiTest.java new file mode 100644 index 0000000000..c7a8ab08a4 --- /dev/null +++ b/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/HueDeviceDiscoveryServiceOSGiTest.java @@ -0,0 +1,226 @@ +/** + * 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 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 removeOlderResults(DiscoveryService source, long timestamp, + Collection 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(); + } +} diff --git a/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/HueLightDiscoveryServiceOSGiTest.java b/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/HueLightDiscoveryServiceOSGiTest.java deleted file mode 100644 index 4f12f297b4..0000000000 --- a/itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/HueLightDiscoveryServiceOSGiTest.java +++ /dev/null @@ -1,225 +0,0 @@ -/** - * 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 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 removeOlderResults(DiscoveryService source, long timestamp, - Collection 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(); - } -}