]> git.basschouten.com Git - openhab-addons.git/commitdiff
[hue] Refactored discovery service to 'ThingHandlerService' (#8729)
authorChristoph Weitkamp <github@christophweitkamp.de>
Thu, 15 Oct 2020 22:51:41 +0000 (00:51 +0200)
committerGitHub <noreply@github.com>
Thu, 15 Oct 2020 22:51:41 +0000 (15:51 -0700)
* Refactored discovery service to ThingHandlerService
* Fixed discovery for Geofence Sensor

Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullHueObject.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullLight.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/FullSensor.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/HueThingHandlerFactory.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueDeviceDiscoveryService.java [new file with mode: 0644]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueLightDiscoveryService.java [deleted file]
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueBridgeHandler.java
bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/handler/HueClient.java
itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/HueDeviceDiscoveryServiceOSGiTest.java [new file with mode: 0644]
itests/org.openhab.binding.hue.tests/src/main/java/org/openhab/binding/hue/internal/HueLightDiscoveryServiceOSGiTest.java [deleted file]

index 2f5e07711b7b1a90902e8c65b8ba6c16c4f0d7e3..a5fb4f1ae7ae301c6a0cf8a7fb37cb740c4f471c 100644 (file)
@@ -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;
+    }
 }
index d0a48e63395a950bd152e112e0d100ca9b12336d..f3a1d83a62d04c8c0e9bf40f38ef1595da157c11 100644 (file)
@@ -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<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.
      *
index 800e47d451f7b7d7131194d06b81b5253980b12c..497dc2c641ea91767f179c4b4200d5a8ca9fb5af 100644 (file)
@@ -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<Map<String, FullSensor>>() {
+    }.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<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;
index 21fdc431ebee226f751fbb3ac264b1cdb2ba6389..5724bb2c225dac1490c627f68ede0b870c40a78d 100644 (file)
@@ -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<ThingUID, @Nullable ServiceRegistration<?>> 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 (file)
index 0000000..c77c928
--- /dev/null
@@ -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<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);
+        }
+    }
+}
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 (file)
index 7d301df..0000000
+++ /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<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);
-    }
-}
index d08b89218018a404f3dc9960ba5354acd5f3337b..163ceae2383282d8db615f94be3dfcbdb342db99 100644 (file)
@@ -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<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;
 
@@ -102,7 +103,7 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
     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<>();
@@ -183,7 +184,7 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
         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();
@@ -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<FullGroup> 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<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) {
@@ -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<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() {
@@ -1024,17 +1030,13 @@ public class HueBridgeHandler extends ConfigStatusBridgeHandler implements HueCl
     @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;
     }
 }
index 92463d1bf2be58d24ddbb121feef688f27ece3cc..94115d5d881007536d875ae2fb70ae1a601de265 100644 (file)
@@ -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 (file)
index 0000000..c7a8ab0
--- /dev/null
@@ -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<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();
+    }
+}
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 (file)
index 4f12f29..0000000
+++ /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<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();
-    }
-}