]> git.basschouten.com Git - openhab-addons.git/blob
6982bc98399740a409eed736db7374950feb8e10
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.smartthings.internal.discovery;
14
15 import java.util.ArrayList;
16 import java.util.HashMap;
17 import java.util.List;
18 import java.util.Map;
19 import java.util.concurrent.ExecutionException;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
22 import java.util.concurrent.TimeoutException;
23 import java.util.regex.Pattern;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.smartthings.internal.SmartthingsBindingConstants;
28 import org.openhab.binding.smartthings.internal.SmartthingsHandlerFactory;
29 import org.openhab.binding.smartthings.internal.dto.SmartthingsDeviceData;
30 import org.openhab.core.config.discovery.AbstractDiscoveryService;
31 import org.openhab.core.config.discovery.DiscoveryResult;
32 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
33 import org.openhab.core.config.discovery.DiscoveryService;
34 import org.openhab.core.thing.ThingUID;
35 import org.openhab.core.thing.binding.ThingHandlerFactory;
36 import org.osgi.service.component.annotations.Component;
37 import org.osgi.service.component.annotations.Reference;
38 import org.osgi.service.event.Event;
39 import org.osgi.service.event.EventHandler;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 import com.google.gson.Gson;
44
45 /**
46  * Smartthings Discovery service
47  *
48  * @author Bob Raker - Initial contribution
49  */
50 @NonNullByDefault
51 @Component(service = { DiscoveryService.class,
52         EventHandler.class }, configurationPid = "discovery.smartthings", property = "event.topics=org/openhab/binding/smartthings/discovery")
53 public class SmartthingsDiscoveryService extends AbstractDiscoveryService implements EventHandler {
54     private static final int DISCOVERY_TIMEOUT_SEC = 30;
55     private static final int INITIAL_DELAY_SEC = 10; // Delay 10 sec to give time for bridge and things to be created
56     private static final int SCAN_INTERVAL_SEC = 600;
57
58     private final Pattern findIllegalChars = Pattern.compile("[^A-Za-z0-9_-]");
59
60     private final Logger logger = LoggerFactory.getLogger(SmartthingsDiscoveryService.class);
61
62     private final Gson gson;
63
64     private @Nullable SmartthingsHandlerFactory smartthingsHandlerFactory;
65
66     private @Nullable ScheduledFuture<?> scanningJob;
67
68     /*
69      * default constructor
70      */
71     public SmartthingsDiscoveryService() {
72         super(SmartthingsBindingConstants.SUPPORTED_THING_TYPES_UIDS, DISCOVERY_TIMEOUT_SEC);
73         gson = new Gson();
74     }
75
76     @Reference
77     protected void setThingHandlerFactory(ThingHandlerFactory handlerFactory) {
78         if (handlerFactory instanceof SmartthingsHandlerFactory) {
79             smartthingsHandlerFactory = (SmartthingsHandlerFactory) handlerFactory;
80         }
81     }
82
83     protected void unsetThingHandlerFactory(ThingHandlerFactory handlerFactory) {
84         // Make sure it is this handleFactory that should be unset
85         if (handlerFactory == smartthingsHandlerFactory) {
86             this.smartthingsHandlerFactory = null;
87         }
88     }
89
90     /**
91      * Called from the UI when starting a search.
92      */
93     @Override
94     public void startScan() {
95         sendSmartthingsDiscoveryRequest();
96     }
97
98     /**
99      * Stops a running scan.
100      */
101     @Override
102     protected synchronized void stopScan() {
103         super.stopScan();
104         removeOlderResults(getTimestampOfLastScan());
105     }
106
107     /**
108      * Starts background scanning for attached devices.
109      */
110     @Override
111     protected void startBackgroundDiscovery() {
112         if (scanningJob == null) {
113             this.scanningJob = scheduler.scheduleWithFixedDelay(this::sendSmartthingsDiscoveryRequest,
114                     INITIAL_DELAY_SEC, SCAN_INTERVAL_SEC, TimeUnit.SECONDS);
115             logger.debug("Discovery background scanning job started");
116         }
117     }
118
119     /**
120      * Stops background scanning for attached devices.
121      */
122     @Override
123     protected void stopBackgroundDiscovery() {
124         final ScheduledFuture<?> currentScanningJob = scanningJob;
125         if (currentScanningJob != null) {
126             currentScanningJob.cancel(false);
127             scanningJob = null;
128         }
129     }
130
131     /**
132      * Start the discovery process by sending a discovery request to the Smartthings Hub
133      */
134     private void sendSmartthingsDiscoveryRequest() {
135         final SmartthingsHandlerFactory currentSmartthingsHandlerFactory = smartthingsHandlerFactory;
136         if (currentSmartthingsHandlerFactory != null) {
137             try {
138                 String discoveryMsg = "{\"discovery\": \"yes\"}";
139                 currentSmartthingsHandlerFactory.sendDeviceCommand("/discovery", 5, discoveryMsg);
140                 // Smartthings will not return a response to this message but will send it's response message
141                 // which will get picked up by the SmartthingBridgeHandler.receivedPushMessage handler
142             } catch (InterruptedException | TimeoutException | ExecutionException e) {
143                 logger.warn("Attempt to send command to the Smartthings hub failed with: {}", e.getMessage());
144             }
145         }
146     }
147
148     /**
149      * Handle discovery data returned from the Smartthings hub.
150      * The data is delivered into the SmartthingServlet. From there it is sent here via the Event service
151      */
152     @Override
153     public void handleEvent(@Nullable Event event) {
154         if (event == null) {
155             logger.info("SmartthingsDiscoveryService.handleEvent: event is uexpectedly null");
156             return;
157         }
158         String topic = event.getTopic();
159         String data = (String) event.getProperty("data");
160         if (data == null) {
161             logger.debug("Event received on topic: {} but the data field is null", topic);
162             return;
163         } else {
164             logger.trace("Event received on topic: {}", topic);
165         }
166
167         // The data returned from the Smartthings hub is a list of strings where each
168         // element is the data for one device. That device string is another json object
169         List<String> devices = new ArrayList<String>();
170         devices = gson.fromJson(data, devices.getClass());
171         for (String device : devices) {
172             SmartthingsDeviceData deviceData = gson.fromJson(device, SmartthingsDeviceData.class);
173             createDevice(deviceData);
174         }
175     }
176
177     /**
178      * Create a device with the data from the Smartthings hub
179      *
180      * @param deviceData Device data from the hub
181      */
182     private void createDevice(SmartthingsDeviceData deviceData) {
183         logger.trace("Discovery: Creating device: ThingType {} with name {}", deviceData.capability, deviceData.name);
184
185         // Build the UID as a string smartthings:{ThingType}:{BridgeName}:{DeviceName}
186         String name = deviceData.name; // Note: this is necessary for null analysis to work
187         if (name == null) {
188             logger.info(
189                     "Unexpectedly received data for a device with no name. Check the Smartthings hub devices and make sure every device has a name");
190             return;
191         }
192         String deviceNameNoSpaces = name.replaceAll("\\s", "_");
193         String smartthingsDeviceName = findIllegalChars.matcher(deviceNameNoSpaces).replaceAll("");
194         final SmartthingsHandlerFactory currentSmartthingsHandlerFactory = smartthingsHandlerFactory;
195         if (currentSmartthingsHandlerFactory == null) {
196             logger.info(
197                     "SmartthingsDiscoveryService: smartthingshandlerfactory is unexpectedly null, could not create device {}",
198                     deviceData);
199             return;
200         }
201         ThingUID bridgeUid = currentSmartthingsHandlerFactory.getBridgeHandler().getThing().getUID();
202         String bridgeId = bridgeUid.getId();
203         String uidStr = String.format("smartthings:%s:%s:%s", deviceData.capability, bridgeId, smartthingsDeviceName);
204
205         Map<String, Object> properties = new HashMap<>();
206         properties.put("smartthingsName", name);
207         properties.put("deviceId", deviceData.id);
208
209         DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(new ThingUID(uidStr)).withProperties(properties)
210                 .withRepresentationProperty("deviceId").withBridge(bridgeUid).withLabel(name).build();
211
212         thingDiscovered(discoveryResult);
213     }
214 }