]> git.basschouten.com Git - openhab-addons.git/blob
6fe7f8dc051d8247dec5a9502670eb6add99dcdf
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.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;
41
42 import com.google.gson.JsonObject;
43 import com.google.gson.JsonSyntaxException;
44
45 /**
46  * This class identifies devices that are available on the gateway and adds discovery results for them.
47  *
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
53  */
54 @NonNullByDefault
55 public class TradfriDiscoveryService extends AbstractDiscoveryService
56         implements DeviceUpdateListener, DiscoveryService, ThingHandlerService {
57     private final Logger logger = LoggerFactory.getLogger(TradfriDiscoveryService.class);
58
59     private @Nullable TradfriGatewayHandler handler;
60
61     private static final String REMOTE_CONTROLLER_MODEL = "TRADFRI remote control";
62
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()));
69
70     private static final String[] COLOR_MODEL_IDENTIFIER_HINTS = new String[] { "CWS", " C/WS " };
71
72     public TradfriDiscoveryService() {
73         super(SUPPORTED_DEVICE_TYPES_UIDS, 10, true);
74     }
75
76     @Override
77     protected void startScan() {
78         handler.startScan();
79     }
80
81     @Override
82     protected synchronized void stopScan() {
83         super.stopScan();
84         removeOlderResults(getTimestampOfLastScan());
85     }
86
87     @Override
88     public void setThingHandler(@Nullable ThingHandler handler) {
89         if (handler instanceof TradfriGatewayHandler gatewayHandler) {
90             this.handler = gatewayHandler;
91         }
92     }
93
94     @Override
95     public @Nullable ThingHandler getThingHandler() {
96         return handler;
97     }
98
99     @Override
100     public void activate() {
101         handler.registerDeviceUpdateListener(this);
102     }
103
104     @Override
105     public void deactivate() {
106         removeOlderResults(new Date().getTime());
107         handler.unregisterDeviceUpdateListener(this);
108     }
109
110     @Override
111     public void onUpdate(@Nullable String instanceId, @Nullable JsonObject data) {
112         ThingUID bridge = handler.getThing().getUID();
113         try {
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;
120
121                 if (TYPE_LIGHT.equals(type) && data.has(LIGHT)) {
122                     JsonObject state = data.get(LIGHT).getAsJsonArray().get(0).getAsJsonObject();
123
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.
128                     // Color light:
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;
134                     }
135                     if (thingType == null && //
136                             (state.has(COLOR) || (model != null && COLOR_TEMP_MODELS.contains(model)))) {
137                         thingType = THING_TYPE_COLOR_TEMP_LIGHT;
138                     }
139                     if (thingType == null) {
140                         thingType = THING_TYPE_DIMMABLE_LIGHT;
141                     }
142                     thingId = new ThingUID(thingType, bridge, Integer.toString(id));
143                 } else if (TYPE_BLINDS.equals(type) && data.has(BLINDS)) {
144                     // Blinds
145                     thingId = new ThingUID(THING_TYPE_BLINDS, bridge, Integer.toString(id));
146                 } else if (TYPE_PLUG.equals(type) && data.has(PLUG)) {
147                     // Smart 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
155                             : THING_TYPE_DIMMER;
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)) {
160                     // Motion 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));
164                 }
165
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());
170                     return;
171                 }
172
173                 String label = data.get(NAME).getAsString();
174
175                 Map<String, Object> properties = new HashMap<>(1);
176                 properties.put("id", id);
177                 if (model != null) {
178                     properties.put(PROPERTY_MODEL_ID, model);
179                 }
180                 if (deviceInfo.get(DEVICE_VENDOR) != null) {
181                     properties.put(PROPERTY_VENDOR, deviceInfo.get(DEVICE_VENDOR).getAsString());
182                 }
183                 if (deviceInfo.get(DEVICE_FIRMWARE) != null) {
184                     properties.put(PROPERTY_FIRMWARE_VERSION, deviceInfo.get(DEVICE_FIRMWARE).getAsString());
185                 }
186
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);
191             }
192         } catch (JsonSyntaxException e) {
193             logger.debug("JSON error during discovery: {}", e.getMessage());
194         }
195     }
196 }