]> git.basschouten.com Git - openhab-addons.git/blob
f83fc78eec1111bf02665310cebd21a89a3c492c
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.hue.internal.discovery;
14
15 import static org.openhab.binding.hue.internal.HueBindingConstants.*;
16
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;
22 import java.util.Map;
23 import java.util.Set;
24 import java.util.stream.Collectors;
25 import java.util.stream.Stream;
26
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;
50
51 /**
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.
54  *
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
64  */
65 @NonNullByDefault
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()));
73
74     private final Logger logger = LoggerFactory.getLogger(HueLightDiscoveryService.class);
75
76     private static final int SEARCH_TIME = 10;
77
78     // @formatter:off
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()));
95     // @formatter:on
96
97     private final HueBridgeHandler hueBridgeHandler;
98
99     public HueLightDiscoveryService(HueBridgeHandler hueBridgeHandler) {
100         super(SEARCH_TIME);
101         this.hueBridgeHandler = hueBridgeHandler;
102     }
103
104     public void activate() {
105         hueBridgeHandler.registerDiscoveryListener(this);
106     }
107
108     @Override
109     public void deactivate() {
110         removeOlderResults(new Date().getTime(), hueBridgeHandler.getThing().getUID());
111         hueBridgeHandler.unregisterDiscoveryListener();
112     }
113
114     @Override
115     public Set<ThingTypeUID> getSupportedThingTypes() {
116         return SUPPORTED_THING_TYPES;
117     }
118
119     @Override
120     public void startScan() {
121         List<FullLight> lights = hueBridgeHandler.getFullLights();
122         for (FullLight l : lights) {
123             addLightDiscovery(l);
124         }
125         List<FullSensor> sensors = hueBridgeHandler.getFullSensors();
126         for (FullSensor s : sensors) {
127             addSensorDiscovery(s);
128         }
129         List<FullGroup> groups = hueBridgeHandler.getFullGroups();
130         for (FullGroup g : groups) {
131             addGroupDiscovery(g);
132         }
133         // search for unpaired lights
134         hueBridgeHandler.startSearch();
135     }
136
137     @Override
138     protected synchronized void stopScan() {
139         super.stopScan();
140         removeOlderResults(getTimestampOfLastScan(), hueBridgeHandler.getThing().getUID());
141     }
142
143     public void addLightDiscovery(FullLight light) {
144         ThingUID thingUID = getThingUID(light);
145         ThingTypeUID thingTypeUID = getThingTypeUID(light);
146
147         String modelId = light.getNormalizedModelID();
148
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);
155             }
156             String uniqueID = light.getUniqueID();
157             if (uniqueID != null) {
158                 properties.put(UNIQUE_ID, uniqueID);
159             }
160
161             DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID)
162                     .withProperties(properties).withBridge(bridgeUID).withRepresentationProperty(UNIQUE_ID)
163                     .withLabel(light.getName()).build();
164
165             thingDiscovered(discoveryResult);
166         } else {
167             logger.debug("discovered unsupported light of type '{}' and model '{}' with id {}", light.getType(),
168                     modelId, light.getId());
169         }
170     }
171
172     public void removeLightDiscovery(FullLight light) {
173         ThingUID thingUID = getThingUID(light);
174
175         if (thingUID != null) {
176             thingRemoved(thingUID);
177         }
178     }
179
180     private @Nullable ThingUID getThingUID(FullHueObject hueObject) {
181         ThingUID bridgeUID = hueBridgeHandler.getThing().getUID();
182         ThingTypeUID thingTypeUID = getThingTypeUID(hueObject);
183
184         if (thingTypeUID != null && getSupportedThingTypes().contains(thingTypeUID)) {
185             return new ThingUID(thingTypeUID, bridgeUID, hueObject.getId());
186         } else {
187             return null;
188         }
189     }
190
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;
195     }
196
197     public void addSensorDiscovery(FullSensor sensor) {
198         ThingUID thingUID = getThingUID(sensor);
199         ThingTypeUID thingTypeUID = getThingTypeUID(sensor);
200
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);
208             }
209             String uniqueID = sensor.getUniqueID();
210             if (uniqueID != null) {
211                 properties.put(UNIQUE_ID, uniqueID);
212             }
213
214             DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID)
215                     .withProperties(properties).withBridge(bridgeUID).withRepresentationProperty(UNIQUE_ID)
216                     .withLabel(sensor.getName()).build();
217
218             thingDiscovered(discoveryResult);
219         } else {
220             logger.debug("discovered unsupported sensor of type '{}' and model '{}' with id {}", sensor.getType(),
221                     modelId, sensor.getId());
222         }
223     }
224
225     public void removeSensorDiscovery(FullSensor sensor) {
226         ThingUID thingUID = getThingUID(sensor);
227
228         if (thingUID != null) {
229             thingRemoved(thingUID);
230         }
231     }
232
233     public void addGroupDiscovery(FullGroup group) {
234         // Ignore the Hue Entertainment Areas
235         if ("Entertainment".equalsIgnoreCase(group.getType())) {
236             return;
237         }
238
239         ThingUID bridgeUID = hueBridgeHandler.getThing().getUID();
240         ThingUID thingUID = new ThingUID(THING_TYPE_GROUP, bridgeUID, group.getId());
241
242         Map<String, Object> properties = new HashMap<>();
243         properties.put(GROUP_ID, group.getId());
244
245         String name = String.format("%s (%s)", "0".equals(group.getId()) ? "All lights" : group.getName(),
246                 group.getType());
247         DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(THING_TYPE_GROUP)
248                 .withProperties(properties).withBridge(bridgeUID).withRepresentationProperty(GROUP_ID).withLabel(name)
249                 .build();
250
251         thingDiscovered(discoveryResult);
252     }
253
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);
258     }
259 }