]> git.basschouten.com Git - openhab-addons.git/blob
0b910f50334bdd03edfdc642e26e1abfaff6f2f2
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.Date;
18 import java.util.HashMap;
19 import java.util.Map;
20 import java.util.Set;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
23 import java.util.stream.Collectors;
24 import java.util.stream.Stream;
25
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.deconz.internal.Util;
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     public void startScan() {
74         final DeconzBridgeHandler handler = this.handler;
75         if (handler != null) {
76             handler.getBridgeFullState().thenAccept(fullState -> {
77                 stopScan();
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 synchronized void stopScan() {
90         removeOlderResults(getTimestampOfLastScan());
91         super.stopScan();
92     }
93
94     @Override
95     protected void startBackgroundDiscovery() {
96         final ScheduledFuture<?> scanningJob = this.scanningJob;
97         if (scanningJob == null || scanningJob.isCancelled()) {
98             this.scanningJob = scheduler.scheduleWithFixedDelay(this::startScan, 0, 5, TimeUnit.MINUTES);
99         }
100     }
101
102     @Override
103     protected void stopBackgroundDiscovery() {
104         final ScheduledFuture<?> scanningJob = this.scanningJob;
105         if (scanningJob != null) {
106             scanningJob.cancel(true);
107             this.scanningJob = null;
108         }
109     }
110
111     /**
112      * Add a group to the discovery inbox.
113      *
114      * @param groupId The id of the light
115      * @param group The group description
116      */
117     private void addGroup(String groupId, GroupMessage group) {
118         final ThingUID bridgeUID = this.bridgeUID;
119         if (bridgeUID == null) {
120             logger.warn("Received a message from non-existent bridge. This most likely is a bug.");
121             return;
122         }
123
124         ThingTypeUID thingTypeUID;
125         GroupType groupType = group.type;
126
127         if (groupType == null) {
128             logger.warn("No group type reported for group {} ({})", group.modelid, group.name);
129             return;
130         }
131
132         Map<String, Object> properties = new HashMap<>();
133         properties.put(CONFIG_ID, groupId);
134
135         switch (groupType) {
136             case LIGHT_GROUP -> thingTypeUID = THING_TYPE_LIGHTGROUP;
137             case LUMINAIRE, LIGHT_SOURCE, ROOM -> {
138                 logger.debug("Group {} ({}), type {} ignored.", group.id, group.name, group.type);
139                 return;
140             }
141             default -> {
142                 logger.debug(
143                         "Found group: {} ({}), type {} but no thing type defined for that type. This should be reported.",
144                         group.id, group.name, group.type);
145                 return;
146             }
147         }
148
149         ThingUID uid = new ThingUID(thingTypeUID, bridgeUID, group.id);
150         DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID).withLabel(group.name)
151                 .withProperties(properties).withRepresentationProperty(CONFIG_ID).build();
152         thingDiscovered(discoveryResult);
153     }
154
155     /**
156      * Add a light device to the discovery inbox.
157      *
158      * @param lightId The id of the light
159      * @param light The light description
160      */
161     private void addLight(String lightId, LightMessage light) {
162         final ThingUID bridgeUID = this.bridgeUID;
163         if (bridgeUID == null) {
164             logger.warn("Received a message from non-existent bridge. This most likely is a bug.");
165             return;
166         }
167
168         ThingTypeUID thingTypeUID;
169         LightType lightType = light.type;
170
171         if (lightType == null) {
172             logger.warn("No light type reported for light {} ({})", light.modelid, light.name);
173             return;
174         }
175
176         Map<String, Object> properties = new HashMap<>();
177         properties.put(CONFIG_ID, lightId);
178         properties.put(UNIQUE_ID, light.uniqueid);
179         properties.put(Thing.PROPERTY_FIRMWARE_VERSION, light.swversion);
180         properties.put(Thing.PROPERTY_VENDOR, light.manufacturername);
181         properties.put(Thing.PROPERTY_MODEL_ID, light.modelid);
182
183         Integer ctmax = light.ctmax;
184         Integer ctmin = light.ctmin;
185         if (ctmax != null && ctmin != null) {
186             properties.put(PROPERTY_CT_MAX, Integer.toString(Util.constrainToRange(ctmax, ZCL_CT_MIN, ZCL_CT_MAX)));
187             properties.put(PROPERTY_CT_MIN, Integer.toString(Util.constrainToRange(ctmin, ZCL_CT_MIN, ZCL_CT_MAX)));
188         }
189
190         switch (lightType) {
191             case ON_OFF_LIGHT, ON_OFF_PLUGIN_UNIT, SMART_PLUG -> thingTypeUID = THING_TYPE_ONOFF_LIGHT;
192             case DIMMABLE_LIGHT, DIMMABLE_PLUGIN_UNIT -> thingTypeUID = THING_TYPE_DIMMABLE_LIGHT;
193             case COLOR_TEMPERATURE_LIGHT -> thingTypeUID = THING_TYPE_COLOR_TEMPERATURE_LIGHT;
194             case COLOR_DIMMABLE_LIGHT, COLOR_LIGHT -> thingTypeUID = THING_TYPE_COLOR_LIGHT;
195             case EXTENDED_COLOR_LIGHT -> thingTypeUID = THING_TYPE_EXTENDED_COLOR_LIGHT;
196             case WINDOW_COVERING_DEVICE, WINDOW_COVERING_CONTROLLER -> thingTypeUID = THING_TYPE_WINDOW_COVERING;
197             case WARNING_DEVICE -> thingTypeUID = THING_TYPE_WARNING_DEVICE;
198             case DOORLOCK -> thingTypeUID = THING_TYPE_DOORLOCK;
199             case CONFIGURATION_TOOL -> {
200                 // ignore configuration tool device
201                 return;
202             }
203             default -> {
204                 logger.debug(
205                         "Found light: {} ({}), type {} but no thing type defined for that type. This should be reported.",
206                         light.modelid, light.name, light.type);
207                 return;
208             }
209         }
210
211         ThingUID uid = new ThingUID(thingTypeUID, bridgeUID, light.uniqueid.replaceAll("[^a-z0-9\\[\\]]", ""));
212         DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID)
213                 .withLabel(light.name + " (" + light.manufacturername + ")").withProperties(properties)
214                 .withRepresentationProperty(UNIQUE_ID).build();
215         thingDiscovered(discoveryResult);
216     }
217
218     /**
219      * Add a sensor device to the discovery inbox.
220      *
221      * @param sensorID The id of the sensor
222      * @param sensor The sensor description
223      */
224     private void addSensor(String sensorID, SensorMessage sensor) {
225         final ThingUID bridgeUID = this.bridgeUID;
226         if (bridgeUID == null) {
227             logger.warn("Received a message from non-existent bridge. This most likely is a bug.");
228             return;
229         }
230         ThingTypeUID thingTypeUID;
231
232         Map<String, Object> properties = new HashMap<>();
233         properties.put(CONFIG_ID, sensorID);
234         properties.put(UNIQUE_ID, sensor.uniqueid);
235         properties.put(Thing.PROPERTY_FIRMWARE_VERSION, sensor.swversion);
236         properties.put(Thing.PROPERTY_VENDOR, sensor.manufacturername);
237         properties.put(Thing.PROPERTY_MODEL_ID, sensor.modelid);
238
239         if (sensor.type.contains("Daylight")) { // deCONZ specific: Software simulated daylight sensor
240             thingTypeUID = THING_TYPE_DAYLIGHT_SENSOR;
241         } else if (sensor.type.contains("Power")) { // ZHAPower, CLIPPower
242             thingTypeUID = THING_TYPE_POWER_SENSOR;
243         } else if (sensor.type.contains("ZHAConsumption")) { // ZHAConsumption
244             thingTypeUID = THING_TYPE_CONSUMPTION_SENSOR;
245         } else if (sensor.type.contains("Presence")) { // ZHAPresence, CLIPPrensence
246             thingTypeUID = THING_TYPE_PRESENCE_SENSOR;
247         } else if (sensor.type.contains("Switch")) { // ZHASwitch
248             if (sensor.modelid.contains("RGBW")) {
249                 thingTypeUID = THING_TYPE_COLOR_CONTROL;
250             } else {
251                 thingTypeUID = THING_TYPE_SWITCH;
252             }
253         } else if (sensor.type.contains("LightLevel")) { // ZHALightLevel
254             thingTypeUID = THING_TYPE_LIGHT_SENSOR;
255         } else if (sensor.type.contains("ZHAAirQuality")) { // ZHAAirQuality
256             thingTypeUID = THING_TYPE_AIRQUALITY_SENSOR;
257         } else if (sensor.type.contains("ZHATemperature")) { // ZHATemperature
258             thingTypeUID = THING_TYPE_TEMPERATURE_SENSOR;
259         } else if (sensor.type.contains("ZHAHumidity")) { // ZHAHumidity
260             thingTypeUID = THING_TYPE_HUMIDITY_SENSOR;
261         } else if (sensor.type.contains("ZHAPressure")) { // ZHAPressure
262             thingTypeUID = THING_TYPE_PRESSURE_SENSOR;
263         } else if (sensor.type.contains("ZHAOpenClose")) { // ZHAOpenClose
264             thingTypeUID = THING_TYPE_OPENCLOSE_SENSOR;
265         } else if (sensor.type.contains("ZHAWater")) { // ZHAWater
266             thingTypeUID = THING_TYPE_WATERLEAKAGE_SENSOR;
267         } else if (sensor.type.contains("ZHAFire")) {
268             thingTypeUID = THING_TYPE_FIRE_SENSOR; // ZHAFire
269         } else if (sensor.type.contains("ZHAAlarm")) {
270             thingTypeUID = THING_TYPE_ALARM_SENSOR; // ZHAAlarm
271         } else if (sensor.type.contains("ZHAVibration")) {
272             thingTypeUID = THING_TYPE_VIBRATION_SENSOR; // ZHAVibration
273         } else if (sensor.type.contains("ZHABattery")) {
274             thingTypeUID = THING_TYPE_BATTERY_SENSOR; // ZHABattery
275         } else if (sensor.type.contains("ZHAMoisture")) {
276             thingTypeUID = THING_TYPE_MOISTURE_SENSOR; // ZHAMoisture
277         } else if (sensor.type.contains("ZHAThermostat")) {
278             thingTypeUID = THING_TYPE_THERMOSTAT; // ZHAThermostat
279         } else {
280             logger.debug("Unknown type {}", sensor.type);
281             return;
282         }
283
284         ThingUID uid = new ThingUID(thingTypeUID, bridgeUID, sensor.uniqueid.replaceAll("[^a-z0-9\\[\\]]", ""));
285
286         DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID)
287                 .withLabel(sensor.name + " (" + sensor.manufacturername + ")").withProperties(properties)
288                 .withRepresentationProperty(UNIQUE_ID).build();
289         thingDiscovered(discoveryResult);
290     }
291
292     @Override
293     public void setThingHandler(@Nullable ThingHandler handler) {
294         if (handler instanceof DeconzBridgeHandler bridgeHandler) {
295             this.handler = bridgeHandler;
296             this.bridgeUID = handler.getThing().getUID();
297         }
298     }
299
300     @Override
301     public @Nullable ThingHandler getThingHandler() {
302         return handler;
303     }
304
305     @Override
306     public void activate() {
307         super.activate(null);
308     }
309
310     @Override
311     public void deactivate() {
312         removeOlderResults(new Date().getTime());
313         super.deactivate();
314     }
315 }