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