]> git.basschouten.com Git - openhab-addons.git/blob
99a203e14d9f53246bf25e96dc4ca00353a41b9e
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.api.dto.clip1.FullGroup;
29 import org.openhab.binding.hue.internal.api.dto.clip1.FullHueObject;
30 import org.openhab.binding.hue.internal.api.dto.clip1.FullLight;
31 import org.openhab.binding.hue.internal.api.dto.clip1.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.AbstractThingHandlerDiscoveryService;
43 import org.openhab.core.config.discovery.DiscoveryResult;
44 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
45 import org.openhab.core.i18n.LocaleProvider;
46 import org.openhab.core.i18n.TranslationProvider;
47 import org.openhab.core.thing.Thing;
48 import org.openhab.core.thing.ThingTypeUID;
49 import org.openhab.core.thing.ThingUID;
50 import org.osgi.service.component.annotations.Component;
51 import org.osgi.service.component.annotations.Reference;
52 import org.osgi.service.component.annotations.ServiceScope;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56 /**
57  * The {@link HueDeviceDiscoveryService} tracks for Hue lights, sensors and groups which are connected
58  * to a paired Hue Bridge. The default search time for Hue is 60 seconds.
59  *
60  * @author Kai Kreuzer - Initial contribution
61  * @author Andre Fuechsel - changed search timeout, changed discovery result creation to support generic thing types;
62  *         added representationProperty to discovery result
63  * @author Thomas Höfer - Added representation
64  * @author Denis Dudnik - switched to internally integrated source of Jue library
65  * @author Samuel Leisering - Added support for sensor API
66  * @author Christoph Weitkamp - Added support for sensor API
67  * @author Meng Yiqi - Added support for CLIP sensor
68  * @author Laurent Garnier - Added support for groups
69  */
70 @Component(scope = ServiceScope.PROTOTYPE, service = HueDeviceDiscoveryService.class)
71 @NonNullByDefault
72 public class HueDeviceDiscoveryService extends AbstractThingHandlerDiscoveryService<HueBridgeHandler> {
73     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = 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.toUnmodifiableSet());
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 ThingUID bridgeUID;
105
106     public HueDeviceDiscoveryService() {
107         super(HueBridgeHandler.class, SUPPORTED_THING_TYPES, SEARCH_TIME);
108     }
109
110     @Reference(unbind = "-")
111     public void bindTranslationProvider(TranslationProvider translationProvider) {
112         this.i18nProvider = translationProvider;
113     }
114
115     @Reference(unbind = "-")
116     public void bindLocaleProvider(LocaleProvider localeProvider) {
117         this.localeProvider = localeProvider;
118     }
119
120     @Override
121     public void initialize() {
122         bridgeUID = thingHandler.getThing().getUID();
123         thingHandler.registerDiscoveryListener(this);
124         super.initialize();
125     }
126
127     @Override
128     public void dispose() {
129         super.dispose();
130         removeOlderResults(new Date().getTime(), bridgeUID);
131         thingHandler.unregisterDiscoveryListener();
132     }
133
134     @Override
135     public Set<ThingTypeUID> getSupportedThingTypes() {
136         return SUPPORTED_THING_TYPES;
137     }
138
139     @Override
140     public void startScan() {
141         List<FullLight> lights = thingHandler.getFullLights();
142         for (FullLight l : lights) {
143             addLightDiscovery(l);
144         }
145         List<FullSensor> sensors = thingHandler.getFullSensors();
146         for (FullSensor s : sensors) {
147             addSensorDiscovery(s);
148         }
149         List<FullGroup> groups = thingHandler.getFullGroups();
150         for (FullGroup g : groups) {
151             addGroupDiscovery(g);
152         }
153         // search for unpaired lights
154         thingHandler.startSearch();
155     }
156
157     @Override
158     protected synchronized void stopScan() {
159         super.stopScan();
160         removeOlderResults(getTimestampOfLastScan(), thingHandler.getThing().getUID());
161     }
162
163     public void addLightDiscovery(FullLight light) {
164         ThingUID thingUID = getThingUID(light);
165         ThingTypeUID thingTypeUID = getThingTypeUID(light);
166
167         String modelId = light.getNormalizedModelID();
168
169         if (thingUID != null && thingTypeUID != null) {
170             Map<String, Object> properties = new HashMap<>();
171             properties.put(LIGHT_ID, light.getId());
172             if (modelId != null) {
173                 properties.put(Thing.PROPERTY_MODEL_ID, modelId);
174             }
175             String uniqueID = light.getUniqueID();
176             if (uniqueID != null) {
177                 properties.put(UNIQUE_ID, uniqueID);
178             }
179
180             DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID)
181                     .withProperties(properties).withBridge(bridgeUID).withRepresentationProperty(UNIQUE_ID)
182                     .withLabel(light.getName()).build();
183
184             thingDiscovered(discoveryResult);
185         } else {
186             logger.debug("discovered unsupported light of type '{}' and model '{}' with id {}", light.getType(),
187                     modelId, light.getId());
188         }
189     }
190
191     public void removeLightDiscovery(FullLight light) {
192         ThingUID thingUID = getThingUID(light);
193
194         if (thingUID != null) {
195             thingRemoved(thingUID);
196         }
197     }
198
199     private @Nullable ThingUID getThingUID(FullHueObject hueObject) {
200         ThingUID localBridgeUID = bridgeUID;
201         if (localBridgeUID != null) {
202             ThingTypeUID thingTypeUID = getThingTypeUID(hueObject);
203
204             if (thingTypeUID != null && getSupportedThingTypes().contains(thingTypeUID)) {
205                 return new ThingUID(thingTypeUID, localBridgeUID, hueObject.getId());
206             }
207         }
208         return null;
209     }
210
211     private @Nullable ThingTypeUID getThingTypeUID(FullHueObject hueObject) {
212         String thingTypeId = TYPE_TO_ZIGBEE_ID_MAP
213                 .get(hueObject.getType().replaceAll(NORMALIZE_ID_REGEX, "_").toLowerCase());
214         return thingTypeId != null ? new ThingTypeUID(BINDING_ID, thingTypeId) : null;
215     }
216
217     public void addSensorDiscovery(FullSensor sensor) {
218         ThingUID thingUID = getThingUID(sensor);
219         ThingTypeUID thingTypeUID = getThingTypeUID(sensor);
220
221         String modelId = sensor.getNormalizedModelID();
222         if (thingUID != null && thingTypeUID != null) {
223             Map<String, Object> properties = new HashMap<>();
224             properties.put(SENSOR_ID, sensor.getId());
225             if (modelId != null) {
226                 properties.put(Thing.PROPERTY_MODEL_ID, modelId);
227             }
228             String uniqueID = sensor.getUniqueID();
229             if (uniqueID != null) {
230                 properties.put(UNIQUE_ID, uniqueID);
231             }
232
233             DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID)
234                     .withProperties(properties).withBridge(bridgeUID).withRepresentationProperty(UNIQUE_ID)
235                     .withLabel(sensor.getName()).build();
236
237             thingDiscovered(discoveryResult);
238         } else {
239             logger.debug("discovered unsupported sensor of type '{}' and model '{}' with id {}", sensor.getType(),
240                     modelId, sensor.getId());
241         }
242     }
243
244     public void removeSensorDiscovery(FullSensor sensor) {
245         ThingUID thingUID = getThingUID(sensor);
246
247         if (thingUID != null) {
248             thingRemoved(thingUID);
249         }
250     }
251
252     public void addGroupDiscovery(FullGroup group) {
253         // Ignore the Hue Entertainment Areas
254         if ("Entertainment".equalsIgnoreCase(group.getType())) {
255             return;
256         }
257
258         ThingUID localBridgeUID = bridgeUID;
259         if (localBridgeUID != null) {
260             ThingUID thingUID = new ThingUID(THING_TYPE_GROUP, localBridgeUID, group.getId());
261
262             Map<String, Object> properties = new HashMap<>();
263             properties.put(GROUP_ID, group.getId());
264
265             String name;
266             if ("0".equals(group.getId())) {
267                 name = "@text/discovery.group.all-lights.label";
268             } else if ("Room".equals(group.getType())) {
269                 name = group.getName();
270             } else {
271                 name = String.format("%s (%s)", group.getName(), group.getType());
272             }
273             DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(THING_TYPE_GROUP)
274                     .withProperties(properties).withBridge(localBridgeUID).withRepresentationProperty(GROUP_ID)
275                     .withLabel(name).build();
276
277             thingDiscovered(discoveryResult);
278         }
279     }
280
281     public void removeGroupDiscovery(FullGroup group) {
282         ThingUID localBridgeUID = bridgeUID;
283         if (localBridgeUID != null) {
284             ThingUID thingUID = new ThingUID(THING_TYPE_GROUP, localBridgeUID, group.getId());
285             thingRemoved(thingUID);
286         }
287     }
288 }