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