2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.deconz.internal.discovery;
15 import static org.openhab.binding.deconz.internal.BindingConstants.*;
17 import java.util.HashMap;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
22 import java.util.stream.Collectors;
23 import java.util.stream.Stream;
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;
49 * Every bridge will add its discovered sensors to this discovery service to make them
50 * available to the framework.
52 * @author David Graeff - Initial contribution
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);
62 private @Nullable DeconzBridgeHandler handler;
63 private @Nullable ScheduledFuture<?> scanningJob;
64 private @Nullable ThingUID bridgeUID;
66 public ThingDiscoveryService() {
67 super(SUPPORTED_THING_TYPES_UIDS, 30);
71 protected void startScan() {
72 final DeconzBridgeHandler handler = this.handler;
73 if (handler != null) {
74 handler.requestFullState(false);
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);
87 protected void stopBackgroundDiscovery() {
88 final ScheduledFuture<?> scanningJob = this.scanningJob;
89 if (scanningJob != null) {
90 scanningJob.cancel(true);
91 this.scanningJob = null;
96 * Add a sensor device to the discovery inbox.
98 * @param lightID The id of the light
99 * @param light The sensor description
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.");
108 ThingTypeUID thingTypeUID;
109 LightType lightType = light.type;
111 if (lightType == null) {
112 logger.warn("No light type reported for light {} ({})", light.modelid, light.name);
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);
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)));
132 case ON_OFF_PLUGIN_UNIT:
134 thingTypeUID = THING_TYPE_ONOFF_LIGHT;
137 case DIMMABLE_PLUGIN_UNIT:
138 thingTypeUID = THING_TYPE_DIMMABLE_LIGHT;
140 case COLOR_TEMPERATURE_LIGHT:
141 thingTypeUID = THING_TYPE_COLOR_TEMPERATURE_LIGHT;
143 case COLOR_DIMMABLE_LIGHT:
145 thingTypeUID = THING_TYPE_COLOR_LIGHT;
147 case EXTENDED_COLOR_LIGHT:
148 thingTypeUID = THING_TYPE_EXTENDED_COLOR_LIGHT;
150 case WINDOW_COVERING_DEVICE:
151 thingTypeUID = THING_TYPE_WINDOW_COVERING;
154 thingTypeUID = THING_TYPE_WARNING_DEVICE;
156 case CONFIGURATION_TOOL:
157 // ignore configuration tool device
161 "Found light: {} ({}), type {} but no thing type defined for that type. This should be reported.",
162 light.modelid, light.name, light.type);
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);
174 * Add a sensor device to the discovery inbox.
176 * @param sensorID The id of the sensor
177 * @param sensor The sensor description
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.");
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;
198 thingTypeUID = THING_TYPE_SWITCH;
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
223 logger.debug("Unknown type {}", sensor.type);
227 ThingUID uid = new ThingUID(thingTypeUID, bridgeUID, sensor.uniqueid.replaceAll("[^a-z0-9\\[\\]]", ""));
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);
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();
245 public @Nullable ThingHandler getThingHandler() {
250 public void activate() {
251 super.activate(null);
255 public void deactivate() {
260 * Call this method when a full bridge state request has been performed and either the fullState
261 * are known or a failure happened.
263 * @param fullState The fullState or null.
265 public void stateRequestFinished(final @Nullable BridgeFullState fullState) {
267 removeOlderResults(getTimestampOfLastScan());
268 if (fullState != null) {
269 fullState.sensors.forEach(this::addSensor);
270 fullState.lights.forEach(this::addLight);