2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.hue.internal.discovery;
15 import static org.openhab.binding.hue.internal.HueBindingConstants.*;
17 import java.util.AbstractMap.SimpleEntry;
18 import java.util.Collections;
19 import java.util.Date;
20 import java.util.HashMap;
21 import java.util.List;
24 import java.util.stream.Collectors;
25 import java.util.stream.Stream;
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.openhab.binding.hue.internal.FullGroup;
30 import org.openhab.binding.hue.internal.FullHueObject;
31 import org.openhab.binding.hue.internal.FullLight;
32 import org.openhab.binding.hue.internal.FullSensor;
33 import org.openhab.binding.hue.internal.handler.HueBridgeHandler;
34 import org.openhab.binding.hue.internal.handler.HueGroupHandler;
35 import org.openhab.binding.hue.internal.handler.HueLightHandler;
36 import org.openhab.binding.hue.internal.handler.sensors.ClipHandler;
37 import org.openhab.binding.hue.internal.handler.sensors.DimmerSwitchHandler;
38 import org.openhab.binding.hue.internal.handler.sensors.LightLevelHandler;
39 import org.openhab.binding.hue.internal.handler.sensors.PresenceHandler;
40 import org.openhab.binding.hue.internal.handler.sensors.TapSwitchHandler;
41 import org.openhab.binding.hue.internal.handler.sensors.TemperatureHandler;
42 import org.openhab.core.config.discovery.AbstractDiscoveryService;
43 import org.openhab.core.config.discovery.DiscoveryResult;
44 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
45 import org.openhab.core.thing.Thing;
46 import org.openhab.core.thing.ThingTypeUID;
47 import org.openhab.core.thing.ThingUID;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
52 * The {@link HueBridgeServiceTracker} tracks for hue lights, sensors and groups which are connected
53 * to a paired hue bridge. The default search time for hue is 60 seconds.
55 * @author Kai Kreuzer - Initial contribution
56 * @author Andre Fuechsel - changed search timeout, changed discovery result creation to support generic thing types;
57 * added representationProperty to discovery result
58 * @author Thomas Höfer - Added representation
59 * @author Denis Dudnik - switched to internally integrated source of Jue library
60 * @author Samuel Leisering - Added support for sensor API
61 * @author Christoph Weitkamp - Added support for sensor API
62 * @author Meng Yiqi - Added support for CLIP sensor
63 * @author Laurent Garnier - Added support for groups
66 public class HueLightDiscoveryService extends AbstractDiscoveryService {
67 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.unmodifiableSet(Stream
68 .of(HueLightHandler.SUPPORTED_THING_TYPES.stream(), DimmerSwitchHandler.SUPPORTED_THING_TYPES.stream(),
69 TapSwitchHandler.SUPPORTED_THING_TYPES.stream(), PresenceHandler.SUPPORTED_THING_TYPES.stream(),
70 TemperatureHandler.SUPPORTED_THING_TYPES.stream(), LightLevelHandler.SUPPORTED_THING_TYPES.stream(),
71 ClipHandler.SUPPORTED_THING_TYPES.stream(), HueGroupHandler.SUPPORTED_THING_TYPES.stream())
72 .flatMap(i -> i).collect(Collectors.toSet()));
74 private final Logger logger = LoggerFactory.getLogger(HueLightDiscoveryService.class);
76 private static final int SEARCH_TIME = 10;
79 private static final Map<String, @Nullable String> TYPE_TO_ZIGBEE_ID_MAP = Stream.of(
80 new SimpleEntry<>("on_off_light", "0000"),
81 new SimpleEntry<>("on_off_plug_in_unit", "0010"),
82 new SimpleEntry<>("dimmable_light", "0100"),
83 new SimpleEntry<>("dimmable_plug_in_unit", "0110"),
84 new SimpleEntry<>("color_light", "0200"),
85 new SimpleEntry<>("extended_color_light", "0210"),
86 new SimpleEntry<>("color_temperature_light", "0220"),
87 new SimpleEntry<>("zllswitch", "0820"),
88 new SimpleEntry<>("zgpswitch", "0830"),
89 new SimpleEntry<>("clipgenericstatus", "0840"),
90 new SimpleEntry<>("clipgenericflag", "0850"),
91 new SimpleEntry<>("zllpresence", "0107"),
92 new SimpleEntry<>("zlltemperature", "0302"),
93 new SimpleEntry<>("zlllightlevel", "0106")
94 ).collect(Collectors.toMap((e) -> e.getKey(), (e) -> e.getValue()));
97 private final HueBridgeHandler hueBridgeHandler;
99 public HueLightDiscoveryService(HueBridgeHandler hueBridgeHandler) {
101 this.hueBridgeHandler = hueBridgeHandler;
104 public void activate() {
105 hueBridgeHandler.registerDiscoveryListener(this);
109 public void deactivate() {
110 removeOlderResults(new Date().getTime(), hueBridgeHandler.getThing().getUID());
111 hueBridgeHandler.unregisterDiscoveryListener();
115 public Set<ThingTypeUID> getSupportedThingTypes() {
116 return SUPPORTED_THING_TYPES;
120 public void startScan() {
121 List<FullLight> lights = hueBridgeHandler.getFullLights();
122 for (FullLight l : lights) {
123 addLightDiscovery(l);
125 List<FullSensor> sensors = hueBridgeHandler.getFullSensors();
126 for (FullSensor s : sensors) {
127 addSensorDiscovery(s);
129 List<FullGroup> groups = hueBridgeHandler.getFullGroups();
130 for (FullGroup g : groups) {
131 addGroupDiscovery(g);
133 // search for unpaired lights
134 hueBridgeHandler.startSearch();
138 protected synchronized void stopScan() {
140 removeOlderResults(getTimestampOfLastScan(), hueBridgeHandler.getThing().getUID());
143 public void addLightDiscovery(FullLight light) {
144 ThingUID thingUID = getThingUID(light);
145 ThingTypeUID thingTypeUID = getThingTypeUID(light);
147 String modelId = light.getNormalizedModelID();
149 if (thingUID != null && thingTypeUID != null) {
150 ThingUID bridgeUID = hueBridgeHandler.getThing().getUID();
151 Map<String, Object> properties = new HashMap<>();
152 properties.put(LIGHT_ID, light.getId());
153 if (modelId != null) {
154 properties.put(Thing.PROPERTY_MODEL_ID, modelId);
156 String uniqueID = light.getUniqueID();
157 if (uniqueID != null) {
158 properties.put(UNIQUE_ID, uniqueID);
161 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID)
162 .withProperties(properties).withBridge(bridgeUID).withRepresentationProperty(UNIQUE_ID)
163 .withLabel(light.getName()).build();
165 thingDiscovered(discoveryResult);
167 logger.debug("discovered unsupported light of type '{}' and model '{}' with id {}", light.getType(),
168 modelId, light.getId());
172 public void removeLightDiscovery(FullLight light) {
173 ThingUID thingUID = getThingUID(light);
175 if (thingUID != null) {
176 thingRemoved(thingUID);
180 private @Nullable ThingUID getThingUID(FullHueObject hueObject) {
181 ThingUID bridgeUID = hueBridgeHandler.getThing().getUID();
182 ThingTypeUID thingTypeUID = getThingTypeUID(hueObject);
184 if (thingTypeUID != null && getSupportedThingTypes().contains(thingTypeUID)) {
185 return new ThingUID(thingTypeUID, bridgeUID, hueObject.getId());
191 private @Nullable ThingTypeUID getThingTypeUID(FullHueObject hueObject) {
192 String thingTypeId = TYPE_TO_ZIGBEE_ID_MAP
193 .get(hueObject.getType().replaceAll(NORMALIZE_ID_REGEX, "_").toLowerCase());
194 return thingTypeId != null ? new ThingTypeUID(BINDING_ID, thingTypeId) : null;
197 public void addSensorDiscovery(FullSensor sensor) {
198 ThingUID thingUID = getThingUID(sensor);
199 ThingTypeUID thingTypeUID = getThingTypeUID(sensor);
201 String modelId = sensor.getNormalizedModelID();
202 if (thingUID != null && thingTypeUID != null) {
203 ThingUID bridgeUID = hueBridgeHandler.getThing().getUID();
204 Map<String, Object> properties = new HashMap<>();
205 properties.put(SENSOR_ID, sensor.getId());
206 if (modelId != null) {
207 properties.put(Thing.PROPERTY_MODEL_ID, modelId);
209 String uniqueID = sensor.getUniqueID();
210 if (uniqueID != null) {
211 properties.put(UNIQUE_ID, uniqueID);
214 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID)
215 .withProperties(properties).withBridge(bridgeUID).withRepresentationProperty(UNIQUE_ID)
216 .withLabel(sensor.getName()).build();
218 thingDiscovered(discoveryResult);
220 logger.debug("discovered unsupported sensor of type '{}' and model '{}' with id {}", sensor.getType(),
221 modelId, sensor.getId());
225 public void removeSensorDiscovery(FullSensor sensor) {
226 ThingUID thingUID = getThingUID(sensor);
228 if (thingUID != null) {
229 thingRemoved(thingUID);
233 public void addGroupDiscovery(FullGroup group) {
234 // Ignore the Hue Entertainment Areas
235 if ("Entertainment".equalsIgnoreCase(group.getType())) {
239 ThingUID bridgeUID = hueBridgeHandler.getThing().getUID();
240 ThingUID thingUID = new ThingUID(THING_TYPE_GROUP, bridgeUID, group.getId());
242 Map<String, Object> properties = new HashMap<>();
243 properties.put(GROUP_ID, group.getId());
245 String name = String.format("%s (%s)", "0".equals(group.getId()) ? "All lights" : group.getName(),
247 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(THING_TYPE_GROUP)
248 .withProperties(properties).withBridge(bridgeUID).withRepresentationProperty(GROUP_ID).withLabel(name)
251 thingDiscovered(discoveryResult);
254 public void removeGroupDiscovery(FullGroup group) {
255 ThingUID bridgeUID = hueBridgeHandler.getThing().getUID();
256 ThingUID thingUID = new ThingUID(THING_TYPE_GROUP, bridgeUID, group.getId());
257 thingRemoved(thingUID);