2 * Copyright (c) 2010-2024 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.AbstractThingHandlerDiscoveryService;
32 import org.openhab.core.config.discovery.DiscoveryResult;
33 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
34 import org.openhab.core.thing.ThingTypeUID;
35 import org.openhab.core.thing.ThingUID;
36 import org.osgi.service.component.annotations.Component;
37 import org.osgi.service.component.annotations.ServiceScope;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
41 import com.google.gson.JsonObject;
42 import com.google.gson.JsonSyntaxException;
45 * This class identifies devices that are available on the gateway and adds discovery results for them.
47 * @author Kai Kreuzer - Initial contribution
48 * @author Christoph Weitkamp - Added support for remote controller and motion sensor devices (read-only battery level)
49 * @author Andre Fuechsel - fixed the results removal
50 * @author Manuel Raffel - Added support for blinds
51 * @author Vivien Boistuaud - Added support for Air Purifiers
53 @Component(scope = ServiceScope.PROTOTYPE, service = TradfriDiscoveryService.class)
55 public class TradfriDiscoveryService extends AbstractThingHandlerDiscoveryService<TradfriGatewayHandler>
56 implements DeviceUpdateListener {
57 private final Logger logger = LoggerFactory.getLogger(TradfriDiscoveryService.class);
59 private static final String REMOTE_CONTROLLER_MODEL = "TRADFRI remote control";
61 private static final Set<String> COLOR_TEMP_MODELS = Collections
62 .unmodifiableSet(Stream
63 .of("TRADFRI bulb E27 WS opal 980lm", "TRADFRI bulb E27 WS clear 950lm",
64 "TRADFRI bulb GU10 WS 400lm", "TRADFRI bulb E14 WS opal 400lm", "FLOALT panel WS 30x30",
65 "FLOALT panel WS 60x60", "FLOALT panel WS 30x90", "TRADFRI bulb E12 WS opal 400lm")
66 .collect(Collectors.toSet()));
68 private static final String[] COLOR_MODEL_IDENTIFIER_HINTS = new String[] { "CWS", " C/WS " };
70 public TradfriDiscoveryService() {
71 super(TradfriGatewayHandler.class, SUPPORTED_DEVICE_TYPES_UIDS, 10, true);
75 protected void startScan() {
76 thingHandler.startScan();
80 protected synchronized void stopScan() {
82 removeOlderResults(getTimestampOfLastScan());
86 public void initialize() {
87 thingHandler.registerDeviceUpdateListener(this);
92 public void dispose() {
94 removeOlderResults(new Date().getTime());
95 thingHandler.unregisterDeviceUpdateListener(this);
99 public void onUpdate(@Nullable String instanceId, @Nullable JsonObject data) {
100 ThingUID bridge = thingHandler.getThing().getUID();
102 if (data != null && data.has(INSTANCE_ID)) {
103 int id = data.get(INSTANCE_ID).getAsInt();
104 String type = data.get(TYPE).getAsString();
105 JsonObject deviceInfo = data.get(DEVICE).getAsJsonObject();
106 String model = deviceInfo.get(DEVICE_MODEL).getAsString();
107 ThingUID thingId = null;
109 if (TYPE_LIGHT.equals(type) && data.has(LIGHT)) {
110 JsonObject state = data.get(LIGHT).getAsJsonArray().get(0).getAsJsonObject();
112 // Color temperature light:
113 // We do not always receive a COLOR attribute, even the light supports it - but the gateway does not
114 // seem to have this information, if the bulb is unreachable. We therefore also check against
115 // concrete model names.
117 // As the protocol does not distinguishes between color and full-color lights,
118 // we check if the "CWS" or "CW/S" identifier is given in the model name
119 ThingTypeUID thingType = null;
120 if (model != null && Arrays.stream(COLOR_MODEL_IDENTIFIER_HINTS).anyMatch(model::contains)) {
121 thingType = THING_TYPE_COLOR_LIGHT;
123 if (thingType == null && //
124 (state.has(COLOR) || (model != null && COLOR_TEMP_MODELS.contains(model)))) {
125 thingType = THING_TYPE_COLOR_TEMP_LIGHT;
127 if (thingType == null) {
128 thingType = THING_TYPE_DIMMABLE_LIGHT;
130 thingId = new ThingUID(thingType, bridge, Integer.toString(id));
131 } else if (TYPE_BLINDS.equals(type) && data.has(BLINDS)) {
133 thingId = new ThingUID(THING_TYPE_BLINDS, bridge, Integer.toString(id));
134 } else if (TYPE_PLUG.equals(type) && data.has(PLUG)) {
136 thingId = new ThingUID(THING_TYPE_ONOFF_PLUG, bridge, Integer.toString(id));
137 } else if (TYPE_SWITCH.equals(type) && data.has(SWITCH)) {
138 // Remote control and wireless dimmer
139 // As protocol does not distinguishes between remote control and wireless dimmer,
140 // we check for the whole model name
141 ThingTypeUID thingType = (model != null && REMOTE_CONTROLLER_MODEL.equals(model))
142 ? THING_TYPE_REMOTE_CONTROL
144 thingId = new ThingUID(thingType, bridge, Integer.toString(id));
145 } else if (TYPE_REMOTE.equals(type)) {
146 thingId = new ThingUID(THING_TYPE_OPEN_CLOSE_REMOTE_CONTROL, bridge, Integer.toString(id));
147 } else if (TYPE_SENSOR.equals(type) && data.has(SENSOR)) {
149 thingId = new ThingUID(THING_TYPE_MOTION_SENSOR, bridge, Integer.toString(id));
150 } else if (TYPE_AIR_PURIFIER.equals(type) && data.has(AIR_PURIFIER)) {
151 thingId = new ThingUID(THING_TYPE_AIR_PURIFIER, bridge, Integer.toString(id));
154 if (thingId == null) {
155 // we didn't identify any device, so let's quit
156 logger.debug("Ignoring unknown device on TRADFRI gateway:\n\ttype : {}\n\tmodel: {}\n\tinfo : {}",
157 type, model, deviceInfo.getAsString());
161 String label = data.get(NAME).getAsString();
163 Map<String, Object> properties = new HashMap<>(1);
164 properties.put("id", id);
166 properties.put(PROPERTY_MODEL_ID, model);
168 if (deviceInfo.get(DEVICE_VENDOR) != null) {
169 properties.put(PROPERTY_VENDOR, deviceInfo.get(DEVICE_VENDOR).getAsString());
171 if (deviceInfo.get(DEVICE_FIRMWARE) != null) {
172 properties.put(PROPERTY_FIRMWARE_VERSION, deviceInfo.get(DEVICE_FIRMWARE).getAsString());
175 logger.debug("Adding device {} to inbox", thingId);
176 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingId).withBridge(bridge)
177 .withLabel(label).withProperties(properties).withRepresentationProperty("id").build();
178 thingDiscovered(discoveryResult);
180 } catch (JsonSyntaxException e) {
181 logger.debug("JSON error during discovery: {}", e.getMessage());