2 * Copyright (c) 2010-2023 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.tradfri.internal.discovery;
15 import static org.openhab.binding.tradfri.internal.TradfriBindingConstants.*;
16 import static org.openhab.core.thing.Thing.*;
18 import java.util.Arrays;
19 import java.util.Collections;
20 import java.util.Date;
21 import java.util.HashMap;
24 import java.util.stream.Collectors;
25 import java.util.stream.Stream;
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.openhab.binding.tradfri.internal.DeviceUpdateListener;
30 import org.openhab.binding.tradfri.internal.handler.TradfriGatewayHandler;
31 import org.openhab.core.config.discovery.AbstractDiscoveryService;
32 import org.openhab.core.config.discovery.DiscoveryResult;
33 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
34 import org.openhab.core.config.discovery.DiscoveryService;
35 import org.openhab.core.thing.ThingTypeUID;
36 import org.openhab.core.thing.ThingUID;
37 import org.openhab.core.thing.binding.ThingHandler;
38 import org.openhab.core.thing.binding.ThingHandlerService;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
42 import com.google.gson.JsonObject;
43 import com.google.gson.JsonSyntaxException;
46 * This class identifies devices that are available on the gateway and adds discovery results for them.
48 * @author Kai Kreuzer - Initial contribution
49 * @author Christoph Weitkamp - Added support for remote controller and motion sensor devices (read-only battery level)
50 * @author Andre Fuechsel - fixed the results removal
51 * @author Manuel Raffel - Added support for blinds
52 * @author Vivien Boistuaud - Added support for Air Purifiers
55 public class TradfriDiscoveryService extends AbstractDiscoveryService
56 implements DeviceUpdateListener, DiscoveryService, ThingHandlerService {
57 private final Logger logger = LoggerFactory.getLogger(TradfriDiscoveryService.class);
59 private @Nullable TradfriGatewayHandler handler;
61 private static final String REMOTE_CONTROLLER_MODEL = "TRADFRI remote control";
63 private static final Set<String> COLOR_TEMP_MODELS = Collections
64 .unmodifiableSet(Stream
65 .of("TRADFRI bulb E27 WS opal 980lm", "TRADFRI bulb E27 WS clear 950lm",
66 "TRADFRI bulb GU10 WS 400lm", "TRADFRI bulb E14 WS opal 400lm", "FLOALT panel WS 30x30",
67 "FLOALT panel WS 60x60", "FLOALT panel WS 30x90", "TRADFRI bulb E12 WS opal 400lm")
68 .collect(Collectors.toSet()));
70 private static final String[] COLOR_MODEL_IDENTIFIER_HINTS = new String[] { "CWS", " C/WS " };
72 public TradfriDiscoveryService() {
73 super(SUPPORTED_DEVICE_TYPES_UIDS, 10, true);
77 protected void startScan() {
82 protected synchronized void stopScan() {
84 removeOlderResults(getTimestampOfLastScan());
88 public void setThingHandler(@Nullable ThingHandler handler) {
89 if (handler instanceof TradfriGatewayHandler) {
90 this.handler = (TradfriGatewayHandler) handler;
95 public @Nullable ThingHandler getThingHandler() {
100 public void activate() {
101 handler.registerDeviceUpdateListener(this);
105 public void deactivate() {
106 removeOlderResults(new Date().getTime());
107 handler.unregisterDeviceUpdateListener(this);
111 public void onUpdate(@Nullable String instanceId, @Nullable JsonObject data) {
112 ThingUID bridge = handler.getThing().getUID();
114 if (data != null && data.has(INSTANCE_ID)) {
115 int id = data.get(INSTANCE_ID).getAsInt();
116 String type = data.get(TYPE).getAsString();
117 JsonObject deviceInfo = data.get(DEVICE).getAsJsonObject();
118 String model = deviceInfo.get(DEVICE_MODEL).getAsString();
119 ThingUID thingId = null;
121 if (TYPE_LIGHT.equals(type) && data.has(LIGHT)) {
122 JsonObject state = data.get(LIGHT).getAsJsonArray().get(0).getAsJsonObject();
124 // Color temperature light:
125 // We do not always receive a COLOR attribute, even the light supports it - but the gateway does not
126 // seem to have this information, if the bulb is unreachable. We therefore also check against
127 // concrete model names.
129 // As the protocol does not distinguishes between color and full-color lights,
130 // we check if the "CWS" or "CW/S" identifier is given in the model name
131 ThingTypeUID thingType = null;
132 if (model != null && Arrays.stream(COLOR_MODEL_IDENTIFIER_HINTS).anyMatch(model::contains)) {
133 thingType = THING_TYPE_COLOR_LIGHT;
135 if (thingType == null && //
136 (state.has(COLOR) || (model != null && COLOR_TEMP_MODELS.contains(model)))) {
137 thingType = THING_TYPE_COLOR_TEMP_LIGHT;
139 if (thingType == null) {
140 thingType = THING_TYPE_DIMMABLE_LIGHT;
142 thingId = new ThingUID(thingType, bridge, Integer.toString(id));
143 } else if (TYPE_BLINDS.equals(type) && data.has(BLINDS)) {
145 thingId = new ThingUID(THING_TYPE_BLINDS, bridge, Integer.toString(id));
146 } else if (TYPE_PLUG.equals(type) && data.has(PLUG)) {
148 thingId = new ThingUID(THING_TYPE_ONOFF_PLUG, bridge, Integer.toString(id));
149 } else if (TYPE_SWITCH.equals(type) && data.has(SWITCH)) {
150 // Remote control and wireless dimmer
151 // As protocol does not distinguishes between remote control and wireless dimmer,
152 // we check for the whole model name
153 ThingTypeUID thingType = (model != null && REMOTE_CONTROLLER_MODEL.equals(model))
154 ? THING_TYPE_REMOTE_CONTROL
156 thingId = new ThingUID(thingType, bridge, Integer.toString(id));
157 } else if (TYPE_REMOTE.equals(type)) {
158 thingId = new ThingUID(THING_TYPE_OPEN_CLOSE_REMOTE_CONTROL, bridge, Integer.toString(id));
159 } else if (TYPE_SENSOR.equals(type) && data.has(SENSOR)) {
161 thingId = new ThingUID(THING_TYPE_MOTION_SENSOR, bridge, Integer.toString(id));
162 } else if (TYPE_AIR_PURIFIER.equals(type) && data.has(AIR_PURIFIER)) {
163 thingId = new ThingUID(THING_TYPE_AIR_PURIFIER, bridge, Integer.toString(id));
166 if (thingId == null) {
167 // we didn't identify any device, so let's quit
168 logger.debug("Ignoring unknown device on TRADFRI gateway:\n\ttype : {}\n\tmodel: {}\n\tinfo : {}",
169 type, model, deviceInfo.getAsString());
173 String label = data.get(NAME).getAsString();
175 Map<String, Object> properties = new HashMap<>(1);
176 properties.put("id", id);
178 properties.put(PROPERTY_MODEL_ID, model);
180 if (deviceInfo.get(DEVICE_VENDOR) != null) {
181 properties.put(PROPERTY_VENDOR, deviceInfo.get(DEVICE_VENDOR).getAsString());
183 if (deviceInfo.get(DEVICE_FIRMWARE) != null) {
184 properties.put(PROPERTY_FIRMWARE_VERSION, deviceInfo.get(DEVICE_FIRMWARE).getAsString());
187 logger.debug("Adding device {} to inbox", thingId);
188 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingId).withBridge(bridge)
189 .withLabel(label).withProperties(properties).withRepresentationProperty("id").build();
190 thingDiscovered(discoveryResult);
192 } catch (JsonSyntaxException e) {
193 logger.debug("JSON error during discovery: {}", e.getMessage());