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.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;
51 * Every bridge will add its discovered sensors to this discovery service to make them
52 * available to the framework.
54 * @author David Graeff - Initial contribution
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);
64 private @Nullable DeconzBridgeHandler handler;
65 private @Nullable ScheduledFuture<?> scanningJob;
66 private @Nullable ThingUID bridgeUID;
68 public ThingDiscoveryService() {
69 super(SUPPORTED_THING_TYPES_UIDS, 30);
73 protected void startScan() {
74 final DeconzBridgeHandler handler = this.handler;
75 if (handler != null) {
76 handler.requestFullState(false);
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);
89 protected void stopBackgroundDiscovery() {
90 final ScheduledFuture<?> scanningJob = this.scanningJob;
91 if (scanningJob != null) {
92 scanningJob.cancel(true);
93 this.scanningJob = null;
98 * Add a group to the discovery inbox.
100 * @param groupId The id of the light
101 * @param group The group description
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.");
110 ThingTypeUID thingTypeUID;
111 GroupType groupType = group.type;
113 if (groupType == null) {
114 logger.warn("No group type reported for group {} ({})", group.modelid, group.name);
118 Map<String, Object> properties = new HashMap<>();
119 properties.put(CONFIG_ID, groupId);
123 thingTypeUID = THING_TYPE_LIGHTGROUP;
127 "Found group: {} ({}), type {} but no thing type defined for that type. This should be reported.",
128 group.id, group.name, group.type);
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);
139 * Add a light device to the discovery inbox.
141 * @param lightId The id of the light
142 * @param light The light description
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.");
151 ThingTypeUID thingTypeUID;
152 LightType lightType = light.type;
154 if (lightType == null) {
155 logger.warn("No light type reported for light {} ({})", light.modelid, light.name);
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);
166 Integer ctmax = light.ctmax;
167 Integer ctmin = light.ctmin;
168 if (ctmax != null && ctmin != null) {
169 properties.put(PROPERTY_CT_MAX, Integer.toString(Util.constrainToRange(ctmax, ZCL_CT_MIN, ZCL_CT_MAX)));
170 properties.put(PROPERTY_CT_MIN, Integer.toString(Util.constrainToRange(ctmin, ZCL_CT_MIN, ZCL_CT_MAX)));
175 case ON_OFF_PLUGIN_UNIT:
177 thingTypeUID = THING_TYPE_ONOFF_LIGHT;
180 case DIMMABLE_PLUGIN_UNIT:
181 thingTypeUID = THING_TYPE_DIMMABLE_LIGHT;
183 case COLOR_TEMPERATURE_LIGHT:
184 thingTypeUID = THING_TYPE_COLOR_TEMPERATURE_LIGHT;
186 case COLOR_DIMMABLE_LIGHT:
188 thingTypeUID = THING_TYPE_COLOR_LIGHT;
190 case EXTENDED_COLOR_LIGHT:
191 thingTypeUID = THING_TYPE_EXTENDED_COLOR_LIGHT;
193 case WINDOW_COVERING_DEVICE:
194 thingTypeUID = THING_TYPE_WINDOW_COVERING;
197 thingTypeUID = THING_TYPE_WARNING_DEVICE;
200 thingTypeUID = THING_TYPE_DOORLOCK;
202 case CONFIGURATION_TOOL:
203 // ignore configuration tool device
207 "Found light: {} ({}), type {} but no thing type defined for that type. This should be reported.",
208 light.modelid, light.name, light.type);
212 ThingUID uid = new ThingUID(thingTypeUID, bridgeUID, light.uniqueid.replaceAll("[^a-z0-9\\[\\]]", ""));
213 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID)
214 .withLabel(light.name + " (" + light.manufacturername + ")").withProperties(properties)
215 .withRepresentationProperty(UNIQUE_ID).build();
216 thingDiscovered(discoveryResult);
220 * Add a sensor device to the discovery inbox.
222 * @param sensorID The id of the sensor
223 * @param sensor The sensor description
225 private void addSensor(String sensorID, SensorMessage sensor) {
226 final ThingUID bridgeUID = this.bridgeUID;
227 if (bridgeUID == null) {
228 logger.warn("Received a message from non-existent bridge. This most likely is a bug.");
231 ThingTypeUID thingTypeUID;
232 if (sensor.type.contains("Daylight")) { // deCONZ specific: Software simulated daylight sensor
233 thingTypeUID = THING_TYPE_DAYLIGHT_SENSOR;
234 } else if (sensor.type.contains("Power")) { // ZHAPower, CLIPPower
235 thingTypeUID = THING_TYPE_POWER_SENSOR;
236 } else if (sensor.type.contains("ZHAConsumption")) { // ZHAConsumption
237 thingTypeUID = THING_TYPE_CONSUMPTION_SENSOR;
238 } else if (sensor.type.contains("Presence")) { // ZHAPresence, CLIPPrensence
239 thingTypeUID = THING_TYPE_PRESENCE_SENSOR;
240 } else if (sensor.type.contains("Switch")) { // ZHASwitch
241 if (sensor.modelid.contains("RGBW")) {
242 thingTypeUID = THING_TYPE_COLOR_CONTROL;
244 thingTypeUID = THING_TYPE_SWITCH;
246 } else if (sensor.type.contains("LightLevel")) { // ZHALightLevel
247 thingTypeUID = THING_TYPE_LIGHT_SENSOR;
248 } else if (sensor.type.contains("ZHATemperature")) { // ZHATemperature
249 thingTypeUID = THING_TYPE_TEMPERATURE_SENSOR;
250 } else if (sensor.type.contains("ZHAHumidity")) { // ZHAHumidity
251 thingTypeUID = THING_TYPE_HUMIDITY_SENSOR;
252 } else if (sensor.type.contains("ZHAPressure")) { // ZHAPressure
253 thingTypeUID = THING_TYPE_PRESSURE_SENSOR;
254 } else if (sensor.type.contains("ZHAOpenClose")) { // ZHAOpenClose
255 thingTypeUID = THING_TYPE_OPENCLOSE_SENSOR;
256 } else if (sensor.type.contains("ZHAWater")) { // ZHAWater
257 thingTypeUID = THING_TYPE_WATERLEAKAGE_SENSOR;
258 } else if (sensor.type.contains("ZHAFire")) {
259 thingTypeUID = THING_TYPE_FIRE_SENSOR; // ZHAFire
260 } else if (sensor.type.contains("ZHAAlarm")) {
261 thingTypeUID = THING_TYPE_ALARM_SENSOR; // ZHAAlarm
262 } else if (sensor.type.contains("ZHAVibration")) {
263 thingTypeUID = THING_TYPE_VIBRATION_SENSOR; // ZHAVibration
264 } else if (sensor.type.contains("ZHABattery")) {
265 thingTypeUID = THING_TYPE_BATTERY_SENSOR; // ZHABattery
266 } else if (sensor.type.contains("ZHAThermostat")) {
267 thingTypeUID = THING_TYPE_THERMOSTAT; // ZHAThermostat
269 logger.debug("Unknown type {}", sensor.type);
273 ThingUID uid = new ThingUID(thingTypeUID, bridgeUID, sensor.uniqueid.replaceAll("[^a-z0-9\\[\\]]", ""));
275 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID)
276 .withLabel(sensor.name + " (" + sensor.manufacturername + ")").withProperty(CONFIG_ID, sensorID)
277 .withProperty(UNIQUE_ID, sensor.uniqueid).withRepresentationProperty(UNIQUE_ID).build();
278 thingDiscovered(discoveryResult);
282 public void setThingHandler(@Nullable ThingHandler handler) {
283 if (handler instanceof DeconzBridgeHandler) {
284 this.handler = (DeconzBridgeHandler) handler;
285 ((DeconzBridgeHandler) handler).setDiscoveryService(this);
286 this.bridgeUID = handler.getThing().getUID();
291 public @Nullable ThingHandler getThingHandler() {
296 public void activate() {
297 super.activate(null);
301 public void deactivate() {
306 * Call this method when a full bridge state request has been performed and either the fullState
307 * are known or a failure happened.
309 * @param fullState The fullState or null.
311 public void stateRequestFinished(final @Nullable BridgeFullState fullState) {
313 removeOlderResults(getTimestampOfLastScan());
314 if (fullState != null) {
315 fullState.sensors.forEach(this::addSensor);
316 fullState.lights.forEach(this::addLight);
317 fullState.groups.forEach(this::addGroup);