]> git.basschouten.com Git - openhab-addons.git/blob
19f9543d578a43f5c055f563f954e237352a9c00
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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.GeofencePresenceHandler;
39 import org.openhab.binding.hue.internal.handler.sensors.LightLevelHandler;
40 import org.openhab.binding.hue.internal.handler.sensors.PresenceHandler;
41 import org.openhab.binding.hue.internal.handler.sensors.TapSwitchHandler;
42 import org.openhab.binding.hue.internal.handler.sensors.TemperatureHandler;
43 import org.openhab.core.config.discovery.AbstractDiscoveryService;
44 import org.openhab.core.config.discovery.DiscoveryResult;
45 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
46 import org.openhab.core.config.discovery.DiscoveryService;
47 import org.openhab.core.thing.Thing;
48 import org.openhab.core.thing.ThingTypeUID;
49 import org.openhab.core.thing.ThingUID;
50 import org.openhab.core.thing.binding.ThingHandler;
51 import org.openhab.core.thing.binding.ThingHandlerService;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54
55 /**
56  * The {@link HueBridgeServiceTracker} tracks for hue lights, sensors and groups which are connected
57  * to a paired hue bridge. The default search time for hue is 60 seconds.
58  *
59  * @author Kai Kreuzer - Initial contribution
60  * @author Andre Fuechsel - changed search timeout, changed discovery result creation to support generic thing types;
61  *         added representationProperty to discovery result
62  * @author Thomas Höfer - Added representation
63  * @author Denis Dudnik - switched to internally integrated source of Jue library
64  * @author Samuel Leisering - Added support for sensor API
65  * @author Christoph Weitkamp - Added support for sensor API
66  * @author Meng Yiqi - Added support for CLIP sensor
67  * @author Laurent Garnier - Added support for groups
68  */
69 @NonNullByDefault
70 public class HueDeviceDiscoveryService extends AbstractDiscoveryService
71         implements DiscoveryService, ThingHandlerService {
72
73     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.unmodifiableSet(Stream
74             .of(HueLightHandler.SUPPORTED_THING_TYPES.stream(), DimmerSwitchHandler.SUPPORTED_THING_TYPES.stream(),
75                     TapSwitchHandler.SUPPORTED_THING_TYPES.stream(), PresenceHandler.SUPPORTED_THING_TYPES.stream(),
76                     GeofencePresenceHandler.SUPPORTED_THING_TYPES.stream(),
77                     TemperatureHandler.SUPPORTED_THING_TYPES.stream(), LightLevelHandler.SUPPORTED_THING_TYPES.stream(),
78                     ClipHandler.SUPPORTED_THING_TYPES.stream(), HueGroupHandler.SUPPORTED_THING_TYPES.stream())
79             .flatMap(i -> i).collect(Collectors.toSet()));
80
81     // @formatter:off
82     private static final Map<String, String> TYPE_TO_ZIGBEE_ID_MAP = Map.ofEntries(
83             new SimpleEntry<>("on_off_light", "0000"),
84             new SimpleEntry<>("on_off_plug_in_unit", "0010"),
85             new SimpleEntry<>("dimmable_light", "0100"),
86             new SimpleEntry<>("dimmable_plug_in_unit", "0110"),
87             new SimpleEntry<>("color_light", "0200"),
88             new SimpleEntry<>("extended_color_light", "0210"),
89             new SimpleEntry<>("color_temperature_light", "0220"),
90             new SimpleEntry<>("zllswitch", "0820"),
91             new SimpleEntry<>("zgpswitch", "0830"),
92             new SimpleEntry<>("clipgenericstatus", "0840"),
93             new SimpleEntry<>("clipgenericflag", "0850"),
94             new SimpleEntry<>("zllpresence", "0107"),
95             new SimpleEntry<>("geofence", "geofencesensor"),
96             new SimpleEntry<>("zlltemperature", "0302"),
97             new SimpleEntry<>("zlllightlevel", "0106"));
98     // @formatter:on
99
100     private static final int SEARCH_TIME = 10;
101
102     private final Logger logger = LoggerFactory.getLogger(HueDeviceDiscoveryService.class);
103
104     private @Nullable HueBridgeHandler hueBridgeHandler;
105     private @Nullable ThingUID bridgeUID;
106
107     public HueDeviceDiscoveryService() {
108         super(SUPPORTED_THING_TYPES, SEARCH_TIME);
109     }
110
111     @Override
112     public void setThingHandler(@Nullable ThingHandler handler) {
113         if (handler instanceof HueBridgeHandler) {
114             HueBridgeHandler localHandler = (HueBridgeHandler) handler;
115             hueBridgeHandler = localHandler;
116             bridgeUID = handler.getThing().getUID();
117             i18nProvider = localHandler.getI18nProvider();
118             localeProvider = localHandler.getLocaleProvider();
119         }
120     }
121
122     @Override
123     public @Nullable ThingHandler getThingHandler() {
124         return hueBridgeHandler;
125     }
126
127     @Override
128     public void activate() {
129         final HueBridgeHandler handler = hueBridgeHandler;
130         if (handler != null) {
131             handler.registerDiscoveryListener(this);
132         }
133     }
134
135     @Override
136     public void deactivate() {
137         removeOlderResults(new Date().getTime(), bridgeUID);
138         final HueBridgeHandler handler = hueBridgeHandler;
139         if (handler != null) {
140             handler.unregisterDiscoveryListener();
141         }
142     }
143
144     @Override
145     public Set<ThingTypeUID> getSupportedThingTypes() {
146         return SUPPORTED_THING_TYPES;
147     }
148
149     @Override
150     public void startScan() {
151         final HueBridgeHandler handler = hueBridgeHandler;
152         if (handler != null) {
153             List<FullLight> lights = handler.getFullLights();
154             for (FullLight l : lights) {
155                 addLightDiscovery(l);
156             }
157             List<FullSensor> sensors = handler.getFullSensors();
158             for (FullSensor s : sensors) {
159                 addSensorDiscovery(s);
160             }
161             List<FullGroup> groups = handler.getFullGroups();
162             for (FullGroup g : groups) {
163                 addGroupDiscovery(g);
164             }
165             // search for unpaired lights
166             handler.startSearch();
167         }
168     }
169
170     @Override
171     protected synchronized void stopScan() {
172         super.stopScan();
173         final HueBridgeHandler handler = hueBridgeHandler;
174         if (handler != null) {
175             removeOlderResults(getTimestampOfLastScan(), handler.getThing().getUID());
176         }
177     }
178
179     public void addLightDiscovery(FullLight light) {
180         ThingUID thingUID = getThingUID(light);
181         ThingTypeUID thingTypeUID = getThingTypeUID(light);
182
183         String modelId = light.getNormalizedModelID();
184
185         if (thingUID != null && thingTypeUID != null) {
186             Map<String, Object> properties = new HashMap<>();
187             properties.put(LIGHT_ID, light.getId());
188             if (modelId != null) {
189                 properties.put(Thing.PROPERTY_MODEL_ID, modelId);
190             }
191             String uniqueID = light.getUniqueID();
192             if (uniqueID != null) {
193                 properties.put(UNIQUE_ID, uniqueID);
194             }
195
196             DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID)
197                     .withProperties(properties).withBridge(bridgeUID).withRepresentationProperty(UNIQUE_ID)
198                     .withLabel(light.getName()).build();
199
200             thingDiscovered(discoveryResult);
201         } else {
202             logger.debug("discovered unsupported light of type '{}' and model '{}' with id {}", light.getType(),
203                     modelId, light.getId());
204         }
205     }
206
207     public void removeLightDiscovery(FullLight light) {
208         ThingUID thingUID = getThingUID(light);
209
210         if (thingUID != null) {
211             thingRemoved(thingUID);
212         }
213     }
214
215     private @Nullable ThingUID getThingUID(FullHueObject hueObject) {
216         ThingUID localBridgeUID = bridgeUID;
217         if (localBridgeUID != null) {
218             ThingTypeUID thingTypeUID = getThingTypeUID(hueObject);
219
220             if (thingTypeUID != null && getSupportedThingTypes().contains(thingTypeUID)) {
221                 return new ThingUID(thingTypeUID, localBridgeUID, hueObject.getId());
222             }
223         }
224         return null;
225     }
226
227     private @Nullable ThingTypeUID getThingTypeUID(FullHueObject hueObject) {
228         String thingTypeId = TYPE_TO_ZIGBEE_ID_MAP
229                 .get(hueObject.getType().replaceAll(NORMALIZE_ID_REGEX, "_").toLowerCase());
230         return thingTypeId != null ? new ThingTypeUID(BINDING_ID, thingTypeId) : null;
231     }
232
233     public void addSensorDiscovery(FullSensor sensor) {
234         ThingUID thingUID = getThingUID(sensor);
235         ThingTypeUID thingTypeUID = getThingTypeUID(sensor);
236
237         String modelId = sensor.getNormalizedModelID();
238         if (thingUID != null && thingTypeUID != null) {
239             Map<String, Object> properties = new HashMap<>();
240             properties.put(SENSOR_ID, sensor.getId());
241             if (modelId != null) {
242                 properties.put(Thing.PROPERTY_MODEL_ID, modelId);
243             }
244             String uniqueID = sensor.getUniqueID();
245             if (uniqueID != null) {
246                 properties.put(UNIQUE_ID, uniqueID);
247             }
248
249             DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID)
250                     .withProperties(properties).withBridge(bridgeUID).withRepresentationProperty(UNIQUE_ID)
251                     .withLabel(sensor.getName()).build();
252
253             thingDiscovered(discoveryResult);
254         } else {
255             logger.debug("discovered unsupported sensor of type '{}' and model '{}' with id {}", sensor.getType(),
256                     modelId, sensor.getId());
257         }
258     }
259
260     public void removeSensorDiscovery(FullSensor sensor) {
261         ThingUID thingUID = getThingUID(sensor);
262
263         if (thingUID != null) {
264             thingRemoved(thingUID);
265         }
266     }
267
268     public void addGroupDiscovery(FullGroup group) {
269         // Ignore the Hue Entertainment Areas
270         if ("Entertainment".equalsIgnoreCase(group.getType())) {
271             return;
272         }
273
274         ThingUID localBridgeUID = bridgeUID;
275         if (localBridgeUID != null) {
276             ThingUID thingUID = new ThingUID(THING_TYPE_GROUP, localBridgeUID, group.getId());
277
278             Map<String, Object> properties = new HashMap<>();
279             properties.put(GROUP_ID, group.getId());
280
281             String name;
282             if ("0".equals(group.getId())) {
283                 name = "@text/discovery.group.all_lights.label";
284             } else if ("Room".equals(group.getType())) {
285                 name = group.getName();
286             } else {
287                 name = String.format("%s (%s)", group.getName(), group.getType());
288             }
289             DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(THING_TYPE_GROUP)
290                     .withProperties(properties).withBridge(localBridgeUID).withRepresentationProperty(GROUP_ID)
291                     .withLabel(name).build();
292
293             thingDiscovered(discoveryResult);
294         }
295     }
296
297     public void removeGroupDiscovery(FullGroup group) {
298         ThingUID localBridgeUID = bridgeUID;
299         if (localBridgeUID != null) {
300             ThingUID thingUID = new ThingUID(THING_TYPE_GROUP, localBridgeUID, group.getId());
301             thingRemoved(thingUID);
302         }
303     }
304 }