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