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
54 public class TradfriDiscoveryService extends AbstractDiscoveryService
55 implements DeviceUpdateListener, DiscoveryService, ThingHandlerService {
56 private final Logger logger = LoggerFactory.getLogger(TradfriDiscoveryService.class);
58 private @Nullable TradfriGatewayHandler handler;
60 private static final String REMOTE_CONTROLLER_MODEL = "TRADFRI remote control";
62 private static final Set<String> COLOR_TEMP_MODELS = Collections
63 .unmodifiableSet(Stream
64 .of("TRADFRI bulb E27 WS opal 980lm", "TRADFRI bulb E27 WS clear 950lm",
65 "TRADFRI bulb GU10 WS 400lm", "TRADFRI bulb E14 WS opal 400lm", "FLOALT panel WS 30x30",
66 "FLOALT panel WS 60x60", "FLOALT panel WS 30x90", "TRADFRI bulb E12 WS opal 400lm")
67 .collect(Collectors.toSet()));
69 private static final String[] COLOR_MODEL_IDENTIFIER_HINTS = new String[] { "CWS", " C/WS " };
71 public TradfriDiscoveryService() {
72 super(SUPPORTED_DEVICE_TYPES_UIDS, 10, true);
76 protected void startScan() {
81 protected synchronized void stopScan() {
83 removeOlderResults(getTimestampOfLastScan());
87 public void setThingHandler(@Nullable ThingHandler handler) {
88 if (handler instanceof TradfriGatewayHandler) {
89 this.handler = (TradfriGatewayHandler) handler;
94 public @Nullable ThingHandler getThingHandler() {
99 public void activate() {
100 handler.registerDeviceUpdateListener(this);
104 public void deactivate() {
105 removeOlderResults(new Date().getTime());
106 handler.unregisterDeviceUpdateListener(this);
110 public void onUpdate(@Nullable String instanceId, @Nullable JsonObject data) {
111 ThingUID bridge = handler.getThing().getUID();
113 if (data != null && data.has(INSTANCE_ID)) {
114 int id = data.get(INSTANCE_ID).getAsInt();
115 String type = data.get(TYPE).getAsString();
116 JsonObject deviceInfo = data.get(DEVICE).getAsJsonObject();
117 String model = deviceInfo.get(DEVICE_MODEL).getAsString();
118 ThingUID thingId = null;
120 if (TYPE_LIGHT.equals(type) && data.has(LIGHT)) {
121 JsonObject state = data.get(LIGHT).getAsJsonArray().get(0).getAsJsonObject();
123 // Color temperature light:
124 // We do not always receive a COLOR attribute, even the light supports it - but the gateway does not
125 // seem to have this information, if the bulb is unreachable. We therefore also check against
126 // concrete model names.
128 // As the protocol does not distinguishes between color and full-color lights,
129 // we check if the "CWS" or "CW/S" identifier is given in the model name
130 ThingTypeUID thingType = null;
131 if (model != null && Arrays.stream(COLOR_MODEL_IDENTIFIER_HINTS).anyMatch(model::contains)) {
132 thingType = THING_TYPE_COLOR_LIGHT;
134 if (thingType == null && //
135 (state.has(COLOR) || (model != null && COLOR_TEMP_MODELS.contains(model)))) {
136 thingType = THING_TYPE_COLOR_TEMP_LIGHT;
138 if (thingType == null) {
139 thingType = THING_TYPE_DIMMABLE_LIGHT;
141 thingId = new ThingUID(thingType, bridge, Integer.toString(id));
142 } else if (TYPE_BLINDS.equals(type) && data.has(BLINDS)) {
144 thingId = new ThingUID(THING_TYPE_BLINDS, bridge, Integer.toString(id));
145 } else if (TYPE_PLUG.equals(type) && data.has(PLUG)) {
147 thingId = new ThingUID(THING_TYPE_ONOFF_PLUG, bridge, Integer.toString(id));
148 } else if (TYPE_SWITCH.equals(type) && data.has(SWITCH)) {
149 // Remote control and wireless dimmer
150 // As protocol does not distinguishes between remote control and wireless dimmer,
151 // we check for the whole model name
152 ThingTypeUID thingType = (model != null && REMOTE_CONTROLLER_MODEL.equals(model))
153 ? THING_TYPE_REMOTE_CONTROL
155 thingId = new ThingUID(thingType, bridge, Integer.toString(id));
156 } else if (TYPE_REMOTE.equals(type)) {
157 thingId = new ThingUID(THING_TYPE_OPEN_CLOSE_REMOTE_CONTROL, bridge, Integer.toString(id));
158 } else if (TYPE_SENSOR.equals(type) && data.has(SENSOR)) {
160 thingId = new ThingUID(THING_TYPE_MOTION_SENSOR, bridge, Integer.toString(id));
163 if (thingId == null) {
164 // we didn't identify any device, so let's quit
165 logger.debug("Ignoring unknown device on TRADFRI gateway:\n\ttype : {}\n\tmodel: {}\n\tinfo : {}",
166 type, model, deviceInfo.getAsString());
170 String label = data.get(NAME).getAsString();
172 Map<String, Object> properties = new HashMap<>(1);
173 properties.put("id", id);
175 properties.put(PROPERTY_MODEL_ID, model);
177 if (deviceInfo.get(DEVICE_VENDOR) != null) {
178 properties.put(PROPERTY_VENDOR, deviceInfo.get(DEVICE_VENDOR).getAsString());
180 if (deviceInfo.get(DEVICE_FIRMWARE) != null) {
181 properties.put(PROPERTY_FIRMWARE_VERSION, deviceInfo.get(DEVICE_FIRMWARE).getAsString());
184 logger.debug("Adding device {} to inbox", thingId);
185 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingId).withBridge(bridge)
186 .withLabel(label).withProperties(properties).withRepresentationProperty("id").build();
187 thingDiscovered(discoveryResult);
189 } catch (JsonSyntaxException e) {
190 logger.debug("JSON error during discovery: {}", e.getMessage());