2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.wemo.internal.handler;
15 import static org.openhab.binding.wemo.internal.WemoBindingConstants.*;
17 import java.io.StringReader;
18 import java.math.BigDecimal;
20 import java.util.Collections;
22 import java.util.concurrent.ScheduledFuture;
23 import java.util.concurrent.TimeUnit;
25 import javax.xml.parsers.DocumentBuilder;
26 import javax.xml.parsers.DocumentBuilderFactory;
28 import org.apache.commons.lang.StringEscapeUtils;
29 import org.apache.commons.lang.StringUtils;
30 import org.openhab.binding.wemo.internal.http.WemoHttpCall;
31 import org.openhab.core.config.core.Configuration;
32 import org.openhab.core.io.transport.upnp.UpnpIOParticipant;
33 import org.openhab.core.io.transport.upnp.UpnpIOService;
34 import org.openhab.core.library.types.OnOffType;
35 import org.openhab.core.thing.ChannelUID;
36 import org.openhab.core.thing.Thing;
37 import org.openhab.core.thing.ThingStatus;
38 import org.openhab.core.thing.ThingStatusDetail;
39 import org.openhab.core.thing.ThingTypeUID;
40 import org.openhab.core.types.Command;
41 import org.openhab.core.types.RefreshType;
42 import org.openhab.core.types.State;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45 import org.w3c.dom.CharacterData;
46 import org.w3c.dom.Document;
47 import org.w3c.dom.Element;
48 import org.w3c.dom.Node;
49 import org.w3c.dom.NodeList;
50 import org.xml.sax.InputSource;
53 * The {@link WemoMakerHandler} is responsible for handling commands, which are
54 * sent to one of the channels and to update their states.
56 * @author Hans-Jörg Merk - Initial contribution
59 public class WemoMakerHandler extends AbstractWemoHandler implements UpnpIOParticipant {
61 private final Logger logger = LoggerFactory.getLogger(WemoMakerHandler.class);
63 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_MAKER);
65 private UpnpIOService service;
68 * The default refresh interval in Seconds.
70 private final int DEFAULT_REFRESH_INTERVAL = 15;
72 private ScheduledFuture<?> refreshJob;
74 private final Runnable refreshRunnable = new Runnable() {
80 } catch (Exception e) {
81 logger.debug("Exception during poll", e);
82 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
87 public WemoMakerHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpcaller) {
90 this.wemoHttpCaller = wemoHttpcaller;
92 logger.debug("Creating a WemoMakerHandler for thing '{}'", getThing().getUID());
94 if (upnpIOService != null) {
95 this.service = upnpIOService;
97 logger.debug("upnpIOService not set.");
102 public void initialize() {
103 Configuration configuration = getConfig();
105 if (configuration.get("udn") != null) {
106 logger.debug("Initializing WemoMakerHandler for UDN '{}'", configuration.get("udn"));
108 updateStatus(ThingStatus.ONLINE);
110 logger.debug("Cannot initalize WemoMakerHandler. UDN not set.");
115 public void dispose() {
116 logger.debug("WeMoMakerHandler disposed.");
118 if (refreshJob != null && !refreshJob.isCancelled()) {
119 refreshJob.cancel(true);
125 public void handleCommand(ChannelUID channelUID, Command command) {
126 logger.trace("Command '{}' received for channel '{}'", command, channelUID);
128 if (command instanceof RefreshType) {
131 } catch (Exception e) {
132 logger.debug("Exception during poll", e);
134 } else if (channelUID.getId().equals(CHANNEL_RELAY)) {
135 if (command instanceof OnOffType) {
137 String binaryState = null;
139 if (command.equals(OnOffType.ON)) {
141 } else if (command.equals(OnOffType.OFF)) {
145 String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\"";
147 String content = "<?xml version=\"1.0\"?>"
148 + "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
149 + "<s:Body>" + "<u:SetBinaryState xmlns:u=\"urn:Belkin:service:basicevent:1\">"
150 + "<BinaryState>" + binaryState + "</BinaryState>" + "</u:SetBinaryState>" + "</s:Body>"
153 String wemoURL = getWemoURL("basicevent");
155 if (wemoURL != null) {
156 @SuppressWarnings("unused")
157 String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
159 } catch (Exception e) {
160 logger.error("Failed to send command '{}' for device '{}' ", command, getThing().getUID(), e);
166 @SuppressWarnings("unused")
167 private synchronized void onSubscription() {
170 @SuppressWarnings("unused")
171 private synchronized void removeSubscription() {
174 private synchronized void onUpdate() {
175 if (service.isRegistered(this)) {
176 if (refreshJob == null || refreshJob.isCancelled()) {
177 Configuration config = getThing().getConfiguration();
178 int refreshInterval = DEFAULT_REFRESH_INTERVAL;
179 Object refreshConfig = config.get("refresh");
180 if (refreshConfig != null) {
181 refreshInterval = ((BigDecimal) refreshConfig).intValue();
183 refreshJob = scheduler.scheduleWithFixedDelay(refreshRunnable, 0, refreshInterval, TimeUnit.SECONDS);
189 public String getUDN() {
190 return (String) this.getThing().getConfiguration().get(UDN);
194 * The {@link updateWemoState} polls the actual state of a WeMo Maker.
196 @SuppressWarnings("null")
197 protected void updateWemoState() {
198 String action = "GetAttributes";
199 String actionService = "deviceevent";
201 String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
202 String content = "<?xml version=\"1.0\"?>"
203 + "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
204 + "<s:Body>" + "<u:" + action + " xmlns:u=\"urn:Belkin:service:" + actionService + ":1\">" + "</u:"
205 + action + ">" + "</s:Body>" + "</s:Envelope>";
208 String wemoURL = getWemoURL(actionService);
209 if (wemoURL != null) {
210 String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
211 if (wemoCallResponse != null) {
213 String stringParser = StringUtils.substringBetween(wemoCallResponse, "<attributeList>",
216 // Due to Belkins bad response formatting, we need to run this twice.
217 stringParser = StringEscapeUtils.unescapeXml(stringParser);
218 stringParser = StringEscapeUtils.unescapeXml(stringParser);
220 logger.trace("Maker response '{}' for device '{}' received", stringParser, getThing().getUID());
222 stringParser = "<data>" + stringParser + "</data>";
224 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
225 DocumentBuilder db = dbf.newDocumentBuilder();
226 InputSource is = new InputSource();
227 is.setCharacterStream(new StringReader(stringParser));
229 Document doc = db.parse(is);
230 NodeList nodes = doc.getElementsByTagName("attribute");
232 // iterate the attributes
233 for (int i = 0; i < nodes.getLength(); i++) {
234 Element element = (Element) nodes.item(i);
236 NodeList deviceIndex = element.getElementsByTagName("name");
237 Element line = (Element) deviceIndex.item(0);
238 String attributeName = getCharacterDataFromElement(line);
239 logger.trace("attributeName: {}", attributeName);
241 NodeList deviceID = element.getElementsByTagName("value");
242 line = (Element) deviceID.item(0);
243 String attributeValue = getCharacterDataFromElement(line);
244 logger.trace("attributeValue: {}", attributeValue);
246 switch (attributeName) {
248 State relayState = attributeValue.equals("0") ? OnOffType.OFF : OnOffType.ON;
249 if (relayState != null) {
250 logger.debug("New relayState '{}' for device '{}' received", relayState,
251 getThing().getUID());
252 updateState(CHANNEL_RELAY, relayState);
256 State sensorState = attributeValue.equals("1") ? OnOffType.OFF : OnOffType.ON;
257 if (sensorState != null) {
258 logger.debug("New sensorState '{}' for device '{}' received", sensorState,
259 getThing().getUID());
260 updateState(CHANNEL_SENSOR, sensorState);
265 } catch (Exception e) {
266 logger.error("Failed to parse attributeList for WeMo Maker '{}'", this.getThing().getUID(), e);
270 } catch (Exception e) {
271 logger.error("Failed to get attributes for device '{}'", getThing().getUID(), e);
275 public String getWemoURL(String actionService) {
276 URL descriptorURL = service.getDescriptorURL(this);
277 String wemoURL = null;
278 if (descriptorURL != null) {
279 String deviceURL = StringUtils.substringBefore(descriptorURL.toString(), "/setup.xml");
280 wemoURL = deviceURL + "/upnp/control/" + actionService + "1";
286 public static String getCharacterDataFromElement(Element e) {
287 Node child = e.getFirstChild();
288 if (child instanceof CharacterData) {
289 CharacterData cd = (CharacterData) child;
296 public void onStatusChanged(boolean status) {
300 public void onServiceSubscribed(String service, boolean succeeded) {
304 public void onValueReceived(String variable, String value, String service) {