]> git.basschouten.com Git - openhab-addons.git/blob
e95fdd0e7331465cf03771c8aa93a605837fa020
[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.deconz.internal.discovery;
14
15 import static org.openhab.binding.deconz.internal.BindingConstants.*;
16
17 import java.util.HashMap;
18 import java.util.Map;
19 import java.util.Set;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
22 import java.util.stream.Collectors;
23 import java.util.stream.Stream;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.deconz.internal.Util;
28 import org.openhab.binding.deconz.internal.dto.BridgeFullState;
29 import org.openhab.binding.deconz.internal.dto.GroupMessage;
30 import org.openhab.binding.deconz.internal.dto.LightMessage;
31 import org.openhab.binding.deconz.internal.dto.SensorMessage;
32 import org.openhab.binding.deconz.internal.handler.DeconzBridgeHandler;
33 import org.openhab.binding.deconz.internal.handler.LightThingHandler;
34 import org.openhab.binding.deconz.internal.handler.SensorThermostatThingHandler;
35 import org.openhab.binding.deconz.internal.handler.SensorThingHandler;
36 import org.openhab.binding.deconz.internal.types.GroupType;
37 import org.openhab.binding.deconz.internal.types.LightType;
38 import org.openhab.core.config.discovery.AbstractDiscoveryService;
39 import org.openhab.core.config.discovery.DiscoveryResult;
40 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
41 import org.openhab.core.config.discovery.DiscoveryService;
42 import org.openhab.core.thing.Thing;
43 import org.openhab.core.thing.ThingTypeUID;
44 import org.openhab.core.thing.ThingUID;
45 import org.openhab.core.thing.binding.ThingHandler;
46 import org.openhab.core.thing.binding.ThingHandlerService;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49
50 /**
51  * Every bridge will add its discovered sensors to this discovery service to make them
52  * available to the framework.
53  *
54  * @author David Graeff - Initial contribution
55  */
56 @NonNullByDefault
57 public class ThingDiscoveryService extends AbstractDiscoveryService implements DiscoveryService, ThingHandlerService {
58     private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
59             .of(LightThingHandler.SUPPORTED_THING_TYPE_UIDS, SensorThingHandler.SUPPORTED_THING_TYPES,
60                     SensorThermostatThingHandler.SUPPORTED_THING_TYPES)
61             .flatMap(Set::stream).collect(Collectors.toSet());
62     private final Logger logger = LoggerFactory.getLogger(ThingDiscoveryService.class);
63
64     private @Nullable DeconzBridgeHandler handler;
65     private @Nullable ScheduledFuture<?> scanningJob;
66     private @Nullable ThingUID bridgeUID;
67
68     public ThingDiscoveryService() {
69         super(SUPPORTED_THING_TYPES_UIDS, 30);
70     }
71
72     @Override
73     protected void startScan() {
74         final DeconzBridgeHandler handler = this.handler;
75         if (handler != null) {
76             handler.requestFullState(false);
77         }
78     }
79
80     @Override
81     protected void startBackgroundDiscovery() {
82         final ScheduledFuture<?> scanningJob = this.scanningJob;
83         if (scanningJob == null || scanningJob.isCancelled()) {
84             this.scanningJob = scheduler.scheduleWithFixedDelay(this::startScan, 0, 5, TimeUnit.MINUTES);
85         }
86     }
87
88     @Override
89     protected void stopBackgroundDiscovery() {
90         final ScheduledFuture<?> scanningJob = this.scanningJob;
91         if (scanningJob != null) {
92             scanningJob.cancel(true);
93             this.scanningJob = null;
94         }
95     }
96
97     /**
98      * Add a group to the discovery inbox.
99      *
100      * @param groupId The id of the light
101      * @param group The group description
102      */
103     private void addGroup(String groupId, GroupMessage group) {
104         final ThingUID bridgeUID = this.bridgeUID;
105         if (bridgeUID == null) {
106             logger.warn("Received a message from non-existent bridge. This most likely is a bug.");
107             return;
108         }
109
110         ThingTypeUID thingTypeUID;
111         GroupType groupType = group.type;
112
113         if (groupType == null) {
114             logger.warn("No group type reported for group {} ({})", group.modelid, group.name);
115             return;
116         }
117
118         Map<String, Object> properties = new HashMap<>();
119         properties.put(CONFIG_ID, groupId);
120
121         switch (groupType) {
122             case LIGHT_GROUP:
123                 thingTypeUID = THING_TYPE_LIGHTGROUP;
124                 break;
125             default:
126                 logger.debug(
127                         "Found group: {} ({}), type {} but no thing type defined for that type. This should be reported.",
128                         group.id, group.name, group.type);
129                 return;
130         }
131
132         ThingUID uid = new ThingUID(thingTypeUID, bridgeUID, group.id);
133         DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID).withLabel(group.name)
134                 .withProperties(properties).withRepresentationProperty(CONFIG_ID).build();
135         thingDiscovered(discoveryResult);
136     }
137
138     /**
139      * Add a light device to the discovery inbox.
140      *
141      * @param lightId The id of the light
142      * @param light The light description
143      */
144     private void addLight(String lightId, LightMessage light) {
145         final ThingUID bridgeUID = this.bridgeUID;
146         if (bridgeUID == null) {
147             logger.warn("Received a message from non-existent bridge. This most likely is a bug.");
148             return;
149         }
150
151         ThingTypeUID thingTypeUID;
152         LightType lightType = light.type;
153
154         if (lightType == null) {
155             logger.warn("No light type reported for light {} ({})", light.modelid, light.name);
156             return;
157         }
158
159         Map<String, Object> properties = new HashMap<>();
160         properties.put(CONFIG_ID, lightId);
161         properties.put(UNIQUE_ID, light.uniqueid);
162         properties.put(Thing.PROPERTY_FIRMWARE_VERSION, light.swversion);
163         properties.put(Thing.PROPERTY_VENDOR, light.manufacturername);
164         properties.put(Thing.PROPERTY_MODEL_ID, light.modelid);
165
166         Integer ctmax = light.ctmax;
167         Integer ctmin = light.ctmin;
168         if (ctmax != null && ctmin != null) {
169             properties.put(PROPERTY_CT_MAX, Integer.toString(Util.constrainToRange(ctmax, ZCL_CT_MIN, ZCL_CT_MAX)));
170             properties.put(PROPERTY_CT_MIN, Integer.toString(Util.constrainToRange(ctmin, ZCL_CT_MIN, ZCL_CT_MAX)));
171         }
172
173         switch (lightType) {
174             case ON_OFF_LIGHT:
175             case ON_OFF_PLUGIN_UNIT:
176             case SMART_PLUG:
177                 thingTypeUID = THING_TYPE_ONOFF_LIGHT;
178                 break;
179             case DIMMABLE_LIGHT:
180             case DIMMABLE_PLUGIN_UNIT:
181                 thingTypeUID = THING_TYPE_DIMMABLE_LIGHT;
182                 break;
183             case COLOR_TEMPERATURE_LIGHT:
184                 thingTypeUID = THING_TYPE_COLOR_TEMPERATURE_LIGHT;
185                 break;
186             case COLOR_DIMMABLE_LIGHT:
187             case COLOR_LIGHT:
188                 thingTypeUID = THING_TYPE_COLOR_LIGHT;
189                 break;
190             case EXTENDED_COLOR_LIGHT:
191                 thingTypeUID = THING_TYPE_EXTENDED_COLOR_LIGHT;
192                 break;
193             case WINDOW_COVERING_DEVICE:
194                 thingTypeUID = THING_TYPE_WINDOW_COVERING;
195                 break;
196             case WARNING_DEVICE:
197                 thingTypeUID = THING_TYPE_WARNING_DEVICE;
198                 break;
199             case DOORLOCK:
200                 thingTypeUID = THING_TYPE_DOORLOCK;
201                 break;
202             case CONFIGURATION_TOOL:
203                 // ignore configuration tool device
204                 return;
205             default:
206                 logger.debug(
207                         "Found light: {} ({}), type {} but no thing type defined for that type. This should be reported.",
208                         light.modelid, light.name, light.type);
209                 return;
210         }
211
212         ThingUID uid = new ThingUID(thingTypeUID, bridgeUID, light.uniqueid.replaceAll("[^a-z0-9\\[\\]]", ""));
213         DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID)
214                 .withLabel(light.name + " (" + light.manufacturername + ")").withProperties(properties)
215                 .withRepresentationProperty(UNIQUE_ID).build();
216         thingDiscovered(discoveryResult);
217     }
218
219     /**
220      * Add a sensor device to the discovery inbox.
221      *
222      * @param sensorID The id of the sensor
223      * @param sensor The sensor description
224      */
225     private void addSensor(String sensorID, SensorMessage sensor) {
226         final ThingUID bridgeUID = this.bridgeUID;
227         if (bridgeUID == null) {
228             logger.warn("Received a message from non-existent bridge. This most likely is a bug.");
229             return;
230         }
231         ThingTypeUID thingTypeUID;
232         if (sensor.type.contains("Daylight")) { // deCONZ specific: Software simulated daylight sensor
233             thingTypeUID = THING_TYPE_DAYLIGHT_SENSOR;
234         } else if (sensor.type.contains("Power")) { // ZHAPower, CLIPPower
235             thingTypeUID = THING_TYPE_POWER_SENSOR;
236         } else if (sensor.type.contains("ZHAConsumption")) { // ZHAConsumption
237             thingTypeUID = THING_TYPE_CONSUMPTION_SENSOR;
238         } else if (sensor.type.contains("Presence")) { // ZHAPresence, CLIPPrensence
239             thingTypeUID = THING_TYPE_PRESENCE_SENSOR;
240         } else if (sensor.type.contains("Switch")) { // ZHASwitch
241             if (sensor.modelid.contains("RGBW")) {
242                 thingTypeUID = THING_TYPE_COLOR_CONTROL;
243             } else {
244                 thingTypeUID = THING_TYPE_SWITCH;
245             }
246         } else if (sensor.type.contains("LightLevel")) { // ZHALightLevel
247             thingTypeUID = THING_TYPE_LIGHT_SENSOR;
248         } else if (sensor.type.contains("ZHATemperature")) { // ZHATemperature
249             thingTypeUID = THING_TYPE_TEMPERATURE_SENSOR;
250         } else if (sensor.type.contains("ZHAHumidity")) { // ZHAHumidity
251             thingTypeUID = THING_TYPE_HUMIDITY_SENSOR;
252         } else if (sensor.type.contains("ZHAPressure")) { // ZHAPressure
253             thingTypeUID = THING_TYPE_PRESSURE_SENSOR;
254         } else if (sensor.type.contains("ZHAOpenClose")) { // ZHAOpenClose
255             thingTypeUID = THING_TYPE_OPENCLOSE_SENSOR;
256         } else if (sensor.type.contains("ZHAWater")) { // ZHAWater
257             thingTypeUID = THING_TYPE_WATERLEAKAGE_SENSOR;
258         } else if (sensor.type.contains("ZHAFire")) {
259             thingTypeUID = THING_TYPE_FIRE_SENSOR; // ZHAFire
260         } else if (sensor.type.contains("ZHAAlarm")) {
261             thingTypeUID = THING_TYPE_ALARM_SENSOR; // ZHAAlarm
262         } else if (sensor.type.contains("ZHAVibration")) {
263             thingTypeUID = THING_TYPE_VIBRATION_SENSOR; // ZHAVibration
264         } else if (sensor.type.contains("ZHABattery")) {
265             thingTypeUID = THING_TYPE_BATTERY_SENSOR; // ZHABattery
266         } else if (sensor.type.contains("ZHAThermostat")) {
267             thingTypeUID = THING_TYPE_THERMOSTAT; // ZHAThermostat
268         } else {
269             logger.debug("Unknown type {}", sensor.type);
270             return;
271         }
272
273         ThingUID uid = new ThingUID(thingTypeUID, bridgeUID, sensor.uniqueid.replaceAll("[^a-z0-9\\[\\]]", ""));
274
275         DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID)
276                 .withLabel(sensor.name + " (" + sensor.manufacturername + ")").withProperty(CONFIG_ID, sensorID)
277                 .withProperty(UNIQUE_ID, sensor.uniqueid).withRepresentationProperty(UNIQUE_ID).build();
278         thingDiscovered(discoveryResult);
279     }
280
281     @Override
282     public void setThingHandler(@Nullable ThingHandler handler) {
283         if (handler instanceof DeconzBridgeHandler) {
284             this.handler = (DeconzBridgeHandler) handler;
285             ((DeconzBridgeHandler) handler).setDiscoveryService(this);
286             this.bridgeUID = handler.getThing().getUID();
287         }
288     }
289
290     @Override
291     public @Nullable ThingHandler getThingHandler() {
292         return handler;
293     }
294
295     @Override
296     public void activate() {
297         super.activate(null);
298     }
299
300     @Override
301     public void deactivate() {
302         super.deactivate();
303     }
304
305     /**
306      * Call this method when a full bridge state request has been performed and either the fullState
307      * are known or a failure happened.
308      *
309      * @param fullState The fullState or null.
310      */
311     public void stateRequestFinished(final @Nullable BridgeFullState fullState) {
312         stopScan();
313         removeOlderResults(getTimestampOfLastScan());
314         if (fullState != null) {
315             fullState.sensors.forEach(this::addSensor);
316             fullState.lights.forEach(this::addLight);
317             fullState.groups.forEach(this::addGroup);
318         }
319     }
320 }