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