]> git.basschouten.com Git - openhab-addons.git/blob
657fd815e703d113f726c0b50ee08922984e97e1
[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.wemo.internal.discovery;
14
15 import static org.openhab.binding.wemo.internal.WemoBindingConstants.*;
16
17 import java.io.StringReader;
18 import java.net.URL;
19 import java.util.Collections;
20 import java.util.HashMap;
21 import java.util.Map;
22 import java.util.Set;
23 import java.util.concurrent.ScheduledFuture;
24 import java.util.concurrent.TimeUnit;
25
26 import javax.xml.parsers.DocumentBuilder;
27 import javax.xml.parsers.DocumentBuilderFactory;
28
29 import org.apache.commons.lang.StringEscapeUtils;
30 import org.apache.commons.lang.StringUtils;
31 import org.openhab.binding.wemo.internal.handler.WemoBridgeHandler;
32 import org.openhab.binding.wemo.internal.http.WemoHttpCall;
33 import org.openhab.core.config.discovery.AbstractDiscoveryService;
34 import org.openhab.core.config.discovery.DiscoveryResult;
35 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
36 import org.openhab.core.io.transport.upnp.UpnpIOParticipant;
37 import org.openhab.core.io.transport.upnp.UpnpIOService;
38 import org.openhab.core.thing.ThingTypeUID;
39 import org.openhab.core.thing.ThingUID;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42 import org.w3c.dom.CharacterData;
43 import org.w3c.dom.Document;
44 import org.w3c.dom.Element;
45 import org.w3c.dom.Node;
46 import org.w3c.dom.NodeList;
47 import org.xml.sax.InputSource;
48
49 /**
50  * The {@link WemoLinkDiscoveryService} is responsible for discovering new and
51  * removed WeMo devices connected to the WeMo Link Bridge.
52  *
53  * @author Hans-Jörg Merk - Initial contribution
54  *
55  */
56 public class WemoLinkDiscoveryService extends AbstractDiscoveryService implements UpnpIOParticipant {
57
58     private final Logger logger = LoggerFactory.getLogger(WemoLinkDiscoveryService.class);
59
60     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_MZ100);
61
62     public static final String NORMALIZE_ID_REGEX = "[^a-zA-Z0-9_]";
63
64     /**
65      * Maximum time to search for devices in seconds.
66      */
67     private static final int SEARCH_TIME = 20;
68
69     /**
70      * Scan interval for scanning job in seconds.
71      */
72     private static final int SCAN_INTERVAL = 120;
73
74     /**
75      * The handler for WeMo Link bridge
76      */
77     private final WemoBridgeHandler wemoBridgeHandler;
78
79     /**
80      * Job which will do the background scanning
81      */
82     private final WemoLinkScan scanningRunnable;
83
84     /**
85      * Schedule for scanning
86      */
87     private ScheduledFuture<?> scanningJob;
88
89     /**
90      * The Upnp service
91      */
92     private UpnpIOService service;
93
94     private final WemoHttpCall wemoHttpCaller;
95
96     public WemoLinkDiscoveryService(WemoBridgeHandler wemoBridgeHandler, UpnpIOService upnpIOService,
97             WemoHttpCall wemoHttpCaller) {
98         super(SEARCH_TIME);
99         this.wemoBridgeHandler = wemoBridgeHandler;
100
101         this.wemoHttpCaller = wemoHttpCaller;
102
103         if (upnpIOService != null) {
104             this.service = upnpIOService;
105         } else {
106             logger.debug("upnpIOService not set.");
107         }
108
109         this.scanningRunnable = new WemoLinkScan();
110         if (wemoBridgeHandler == null) {
111             logger.warn("no bridge handler for scan given");
112         }
113         this.activate(null);
114     }
115
116     public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
117         return SUPPORTED_THING_TYPES;
118     }
119
120     @Override
121     public void startScan() {
122         logger.trace("Starting WeMoEndDevice discovery on WeMo Link {}", wemoBridgeHandler.getThing().getUID());
123         try {
124             String devUDN = "uuid:" + wemoBridgeHandler.getThing().getConfiguration().get(UDN).toString();
125             logger.trace("devUDN = '{}'", devUDN);
126
127             String soapHeader = "\"urn:Belkin:service:bridge:1#GetEndDevices\"";
128             String content = "<?xml version=\"1.0\"?>"
129                     + "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
130                     + "<s:Body>" + "<u:GetEndDevices xmlns:u=\"urn:Belkin:service:bridge:1\">" + "<DevUDN>" + devUDN
131                     + "</DevUDN><ReqListType>PAIRED_LIST</ReqListType>" + "</u:GetEndDevices>" + "</s:Body>"
132                     + "</s:Envelope>";
133
134             URL descriptorURL = service.getDescriptorURL(this);
135
136             if (descriptorURL != null) {
137                 String deviceURL = StringUtils.substringBefore(descriptorURL.toString(), "/setup.xml");
138                 String wemoURL = deviceURL + "/upnp/control/bridge1";
139
140                 String endDeviceRequest = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
141
142                 if (endDeviceRequest != null) {
143                     logger.trace("endDeviceRequest answered '{}'", endDeviceRequest);
144
145                     try {
146                         String stringParser = StringUtils.substringBetween(endDeviceRequest, "<DeviceLists>",
147                                 "</DeviceLists>");
148
149                         stringParser = StringEscapeUtils.unescapeXml(stringParser);
150
151                         // check if there are already paired devices with WeMo Link
152                         if ("0".equals(stringParser)) {
153                             logger.debug("There are no devices connected with WeMo Link. Exit discovery");
154                             return;
155                         }
156
157                         // Build parser for received <DeviceList>
158                         DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
159                         DocumentBuilder db = dbf.newDocumentBuilder();
160                         InputSource is = new InputSource();
161                         is.setCharacterStream(new StringReader(stringParser));
162
163                         Document doc = db.parse(is);
164                         NodeList nodes = doc.getElementsByTagName("DeviceInfo");
165
166                         // iterate the devices
167                         for (int i = 0; i < nodes.getLength(); i++) {
168                             Element element = (Element) nodes.item(i);
169
170                             NodeList deviceIndex = element.getElementsByTagName("DeviceIndex");
171                             Element line = (Element) deviceIndex.item(0);
172                             logger.trace("DeviceIndex: {}", getCharacterDataFromElement(line));
173
174                             NodeList deviceID = element.getElementsByTagName("DeviceID");
175                             line = (Element) deviceID.item(0);
176                             String endDeviceID = getCharacterDataFromElement(line);
177                             logger.trace("DeviceID: {}", endDeviceID);
178
179                             NodeList friendlyName = element.getElementsByTagName("FriendlyName");
180                             line = (Element) friendlyName.item(0);
181                             String endDeviceName = getCharacterDataFromElement(line);
182                             logger.trace("FriendlyName: {}", endDeviceName);
183
184                             NodeList vendor = element.getElementsByTagName("Manufacturer");
185                             line = (Element) vendor.item(0);
186                             String endDeviceVendor = getCharacterDataFromElement(line);
187                             logger.trace("Manufacturer: {}", endDeviceVendor);
188
189                             NodeList model = element.getElementsByTagName("ModelCode");
190                             line = (Element) model.item(0);
191                             String endDeviceModelID = getCharacterDataFromElement(line);
192                             endDeviceModelID = endDeviceModelID.replaceAll(NORMALIZE_ID_REGEX, "_");
193
194                             logger.trace("ModelCode: {}", endDeviceModelID);
195
196                             if (SUPPORTED_THING_TYPES.contains(new ThingTypeUID(BINDING_ID, endDeviceModelID))) {
197                                 logger.debug("Discovered a WeMo LED Light thing with ID '{}'", endDeviceID);
198
199                                 ThingUID bridgeUID = wemoBridgeHandler.getThing().getUID();
200                                 ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, endDeviceModelID);
201
202                                 if (thingTypeUID.equals(THING_TYPE_MZ100)) {
203                                     String thingLightId = endDeviceID;
204                                     ThingUID thingUID = new ThingUID(thingTypeUID, bridgeUID, thingLightId);
205
206                                     Map<String, Object> properties = new HashMap<>(1);
207                                     properties.put(DEVICE_ID, endDeviceID);
208
209                                     DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
210                                             .withProperties(properties)
211                                             .withBridge(wemoBridgeHandler.getThing().getUID()).withLabel(endDeviceName)
212                                             .build();
213
214                                     thingDiscovered(discoveryResult);
215                                 }
216                             } else {
217                                 logger.debug("Discovered an unsupported device :");
218                                 logger.debug("DeviceIndex : {}", getCharacterDataFromElement(line));
219                                 logger.debug("DeviceID    : {}", endDeviceID);
220                                 logger.debug("FriendlyName: {}", endDeviceName);
221                                 logger.debug("Manufacturer: {}", endDeviceVendor);
222                                 logger.debug("ModelCode   : {}", endDeviceModelID);
223                             }
224
225                         }
226                     } catch (Exception e) {
227                         logger.error("Failed to parse endDevices for bridge '{}'",
228                                 wemoBridgeHandler.getThing().getUID(), e);
229                     }
230                 }
231             }
232         } catch (Exception e) {
233             logger.error("Failed to get endDevices for bridge '{}'", wemoBridgeHandler.getThing().getUID(), e);
234         }
235     }
236
237     @Override
238     protected void startBackgroundDiscovery() {
239         logger.trace("Start WeMo device background discovery");
240
241         if (scanningJob == null || scanningJob.isCancelled()) {
242             this.scanningJob = scheduler.scheduleWithFixedDelay(this.scanningRunnable,
243                     LINK_DISCOVERY_SERVICE_INITIAL_DELAY, SCAN_INTERVAL, TimeUnit.SECONDS);
244         } else {
245             logger.trace("scanningJob active");
246         }
247     }
248
249     @Override
250     protected void stopBackgroundDiscovery() {
251         logger.debug("Stop WeMo device background discovery");
252
253         if (scanningJob != null && !scanningJob.isCancelled()) {
254             scanningJob.cancel(true);
255             scanningJob = null;
256         }
257     }
258
259     @Override
260     public String getUDN() {
261         return (String) this.wemoBridgeHandler.getThing().getConfiguration().get(UDN);
262     }
263
264     @Override
265     public void onServiceSubscribed(String service, boolean succeeded) {
266     }
267
268     @Override
269     public void onValueReceived(String variable, String value, String service) {
270     }
271
272     @Override
273     public void onStatusChanged(boolean status) {
274     }
275
276     public static String getCharacterDataFromElement(Element e) {
277         Node child = e.getFirstChild();
278         if (child instanceof CharacterData) {
279             CharacterData cd = (CharacterData) child;
280             return cd.getData();
281         }
282         return "?";
283     }
284
285     public class WemoLinkScan implements Runnable {
286         @Override
287         public void run() {
288             startScan();
289         }
290     }
291 }