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