]> git.basschouten.com Git - openhab-addons.git/blob
5cfbfeb876b8f88f781399a5319c38e66fb77a91
[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         if (light.ctmax != null && light.ctmin != null) {
167             properties.put(PROPERTY_CT_MAX,
168                     Integer.toString(Util.constrainToRange(light.ctmax, ZCL_CT_MIN, ZCL_CT_MAX)));
169             properties.put(PROPERTY_CT_MIN,
170                     Integer.toString(Util.constrainToRange(light.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 CONFIGURATION_TOOL:
200                 // ignore configuration tool device
201                 return;
202             default:
203                 logger.debug(
204                         "Found light: {} ({}), type {} but no thing type defined for that type. This should be reported.",
205                         light.modelid, light.name, light.type);
206                 return;
207         }
208
209         ThingUID uid = new ThingUID(thingTypeUID, bridgeUID, light.uniqueid.replaceAll("[^a-z0-9\\[\\]]", ""));
210         DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID)
211                 .withLabel(light.name + " (" + light.manufacturername + ")").withProperties(properties)
212                 .withRepresentationProperty(UNIQUE_ID).build();
213         thingDiscovered(discoveryResult);
214     }
215
216     /**
217      * Add a sensor device to the discovery inbox.
218      *
219      * @param sensorID The id of the sensor
220      * @param sensor The sensor description
221      */
222     private void addSensor(String sensorID, SensorMessage sensor) {
223         final ThingUID bridgeUID = this.bridgeUID;
224         if (bridgeUID == null) {
225             logger.warn("Received a message from non-existent bridge. This most likely is a bug.");
226             return;
227         }
228         ThingTypeUID thingTypeUID;
229         if (sensor.type.contains("Daylight")) { // deCONZ specific: Software simulated daylight sensor
230             thingTypeUID = THING_TYPE_DAYLIGHT_SENSOR;
231         } else if (sensor.type.contains("Power")) { // ZHAPower, CLIPPower
232             thingTypeUID = THING_TYPE_POWER_SENSOR;
233         } else if (sensor.type.contains("ZHAConsumption")) { // ZHAConsumption
234             thingTypeUID = THING_TYPE_CONSUMPTION_SENSOR;
235         } else if (sensor.type.contains("Presence")) { // ZHAPresence, CLIPPrensence
236             thingTypeUID = THING_TYPE_PRESENCE_SENSOR;
237         } else if (sensor.type.contains("Switch")) { // ZHASwitch
238             if (sensor.modelid.contains("RGBW")) {
239                 thingTypeUID = THING_TYPE_COLOR_CONTROL;
240             } else {
241                 thingTypeUID = THING_TYPE_SWITCH;
242             }
243         } else if (sensor.type.contains("LightLevel")) { // ZHALightLevel
244             thingTypeUID = THING_TYPE_LIGHT_SENSOR;
245         } else if (sensor.type.contains("ZHATemperature")) { // ZHATemperature
246             thingTypeUID = THING_TYPE_TEMPERATURE_SENSOR;
247         } else if (sensor.type.contains("ZHAHumidity")) { // ZHAHumidity
248             thingTypeUID = THING_TYPE_HUMIDITY_SENSOR;
249         } else if (sensor.type.contains("ZHAPressure")) { // ZHAPressure
250             thingTypeUID = THING_TYPE_PRESSURE_SENSOR;
251         } else if (sensor.type.contains("ZHAOpenClose")) { // ZHAOpenClose
252             thingTypeUID = THING_TYPE_OPENCLOSE_SENSOR;
253         } else if (sensor.type.contains("ZHAWater")) { // ZHAWater
254             thingTypeUID = THING_TYPE_WATERLEAKAGE_SENSOR;
255         } else if (sensor.type.contains("ZHAFire")) {
256             thingTypeUID = THING_TYPE_FIRE_SENSOR; // ZHAFire
257         } else if (sensor.type.contains("ZHAAlarm")) {
258             thingTypeUID = THING_TYPE_ALARM_SENSOR; // ZHAAlarm
259         } else if (sensor.type.contains("ZHAVibration")) {
260             thingTypeUID = THING_TYPE_VIBRATION_SENSOR; // ZHAVibration
261         } else if (sensor.type.contains("ZHABattery")) {
262             thingTypeUID = THING_TYPE_BATTERY_SENSOR; // ZHABattery
263         } else if (sensor.type.contains("ZHAThermostat")) {
264             thingTypeUID = THING_TYPE_THERMOSTAT; // ZHAThermostat
265         } else {
266             logger.debug("Unknown type {}", sensor.type);
267             return;
268         }
269
270         ThingUID uid = new ThingUID(thingTypeUID, bridgeUID, sensor.uniqueid.replaceAll("[^a-z0-9\\[\\]]", ""));
271
272         DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID)
273                 .withLabel(sensor.name + " (" + sensor.manufacturername + ")").withProperty(CONFIG_ID, sensorID)
274                 .withProperty(UNIQUE_ID, sensor.uniqueid).withRepresentationProperty(UNIQUE_ID).build();
275         thingDiscovered(discoveryResult);
276     }
277
278     @Override
279     public void setThingHandler(@Nullable ThingHandler handler) {
280         if (handler instanceof DeconzBridgeHandler) {
281             this.handler = (DeconzBridgeHandler) handler;
282             ((DeconzBridgeHandler) handler).setDiscoveryService(this);
283             this.bridgeUID = handler.getThing().getUID();
284         }
285     }
286
287     @Override
288     public @Nullable ThingHandler getThingHandler() {
289         return handler;
290     }
291
292     @Override
293     public void activate() {
294         super.activate(null);
295     }
296
297     @Override
298     public void deactivate() {
299         super.deactivate();
300     }
301
302     /**
303      * Call this method when a full bridge state request has been performed and either the fullState
304      * are known or a failure happened.
305      *
306      * @param fullState The fullState or null.
307      */
308     public void stateRequestFinished(final @Nullable BridgeFullState fullState) {
309         stopScan();
310         removeOlderResults(getTimestampOfLastScan());
311         if (fullState != null) {
312             fullState.sensors.forEach(this::addSensor);
313             fullState.lights.forEach(this::addLight);
314             fullState.groups.forEach(this::addGroup);
315         }
316     }
317 }