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