]> git.basschouten.com Git - openhab-addons.git/blob
18d9881536e0583d312758b48ba1a05f4bd03c45
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.tradfri.internal.discovery;
14
15 import static org.openhab.binding.tradfri.internal.TradfriBindingConstants.*;
16 import static org.openhab.core.thing.Thing.*;
17
18 import java.util.Arrays;
19 import java.util.Collections;
20 import java.util.Date;
21 import java.util.HashMap;
22 import java.util.Map;
23 import java.util.Set;
24 import java.util.stream.Collectors;
25 import java.util.stream.Stream;
26
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;
40
41 import com.google.gson.JsonObject;
42 import com.google.gson.JsonSyntaxException;
43
44 /**
45  * This class identifies devices that are available on the gateway and adds discovery results for them.
46  *
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
52  */
53 @Component(scope = ServiceScope.PROTOTYPE, service = TradfriDiscoveryService.class)
54 @NonNullByDefault
55 public class TradfriDiscoveryService extends AbstractThingHandlerDiscoveryService<TradfriGatewayHandler>
56         implements DeviceUpdateListener {
57     private final Logger logger = LoggerFactory.getLogger(TradfriDiscoveryService.class);
58
59     private static final String REMOTE_CONTROLLER_MODEL = "TRADFRI remote control";
60
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()));
67
68     private static final String[] COLOR_MODEL_IDENTIFIER_HINTS = new String[] { "CWS", " C/WS " };
69
70     public TradfriDiscoveryService() {
71         super(TradfriGatewayHandler.class, SUPPORTED_DEVICE_TYPES_UIDS, 10, true);
72     }
73
74     @Override
75     protected void startScan() {
76         thingHandler.startScan();
77     }
78
79     @Override
80     protected synchronized void stopScan() {
81         super.stopScan();
82         removeOlderResults(getTimestampOfLastScan());
83     }
84
85     @Override
86     public void initialize() {
87         thingHandler.registerDeviceUpdateListener(this);
88         super.initialize();
89     }
90
91     @Override
92     public void dispose() {
93         super.dispose();
94         removeOlderResults(new Date().getTime());
95         thingHandler.unregisterDeviceUpdateListener(this);
96     }
97
98     @Override
99     public void onUpdate(@Nullable String instanceId, @Nullable JsonObject data) {
100         ThingUID bridge = thingHandler.getThing().getUID();
101         try {
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;
108
109                 if (TYPE_LIGHT.equals(type) && data.has(LIGHT)) {
110                     JsonObject state = data.get(LIGHT).getAsJsonArray().get(0).getAsJsonObject();
111
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.
116                     // Color light:
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;
122                     }
123                     if (thingType == null && //
124                             (state.has(COLOR) || (model != null && COLOR_TEMP_MODELS.contains(model)))) {
125                         thingType = THING_TYPE_COLOR_TEMP_LIGHT;
126                     }
127                     if (thingType == null) {
128                         thingType = THING_TYPE_DIMMABLE_LIGHT;
129                     }
130                     thingId = new ThingUID(thingType, bridge, Integer.toString(id));
131                 } else if (TYPE_BLINDS.equals(type) && data.has(BLINDS)) {
132                     // Blinds
133                     thingId = new ThingUID(THING_TYPE_BLINDS, bridge, Integer.toString(id));
134                 } else if (TYPE_PLUG.equals(type) && data.has(PLUG)) {
135                     // Smart 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
143                             : THING_TYPE_DIMMER;
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)) {
148                     // Motion 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));
152                 }
153
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());
158                     return;
159                 }
160
161                 String label = data.get(NAME).getAsString();
162
163                 Map<String, Object> properties = new HashMap<>(1);
164                 properties.put("id", id);
165                 if (model != null) {
166                     properties.put(PROPERTY_MODEL_ID, model);
167                 }
168                 if (deviceInfo.get(DEVICE_VENDOR) != null) {
169                     properties.put(PROPERTY_VENDOR, deviceInfo.get(DEVICE_VENDOR).getAsString());
170                 }
171                 if (deviceInfo.get(DEVICE_FIRMWARE) != null) {
172                     properties.put(PROPERTY_FIRMWARE_VERSION, deviceInfo.get(DEVICE_FIRMWARE).getAsString());
173                 }
174
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);
179             }
180         } catch (JsonSyntaxException e) {
181             logger.debug("JSON error during discovery: {}", e.getMessage());
182         }
183     }
184 }