From 8ebd4e9047ed441364ae6ea79e78c9adec3bf629 Mon Sep 17 00:00:00 2001 From: Jacob Laursen Date: Sat, 5 Feb 2022 18:56:58 +0100 Subject: [PATCH] [wemo] Improve GENA subscription reliability and error handling (#12148) * Consolidate service subscriptions in base class. * Remove unsynchronized and unneeded cache of subscriptions. * Do not unregister participant when removing subscription. * Fix status wrongly set to ONLINE when exception is thrown. * Refactor error handling for WemoHttpCall. * Adjust log level for communication errors. * Add automatic subscription renewal. * Fix more ONLINE/OFFLINE status transition issues. * Adjust log level when getWemoURL fails because device is offline. * Remove redundant logging. Signed-off-by: Jacob Laursen --- .../discovery/WemoLinkDiscoveryService.java | 178 ++++---- .../handler/WemoBaseThingHandler.java | 153 ++++++- .../internal/handler/WemoCoffeeHandler.java | 353 ++++++--------- .../internal/handler/WemoCrockpotHandler.java | 153 ++----- .../internal/handler/WemoDimmerHandler.java | 156 ++----- .../wemo/internal/handler/WemoHandler.java | 139 +----- .../internal/handler/WemoHolmesHandler.java | 417 +++++++----------- .../internal/handler/WemoInsightHandler.java | 2 +- .../internal/handler/WemoLightHandler.java | 149 ++----- .../internal/handler/WemoMakerHandler.java | 142 +++--- .../wemo/internal/http/WemoHttpCall.java | 25 +- .../WemoLinkDiscoveryServiceOSGiTest.java | 2 +- .../handler/test/WemoHandlerOSGiTest.java | 4 +- .../test/WemoLightHandlerOSGiTest.java | 15 +- .../test/WemoMakerHandlerOSGiTest.java | 4 +- 15 files changed, 763 insertions(+), 1129 deletions(-) diff --git a/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/discovery/WemoLinkDiscoveryService.java b/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/discovery/WemoLinkDiscoveryService.java index f58820f48..7a3ee2f38 100644 --- a/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/discovery/WemoLinkDiscoveryService.java +++ b/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/discovery/WemoLinkDiscoveryService.java @@ -133,104 +133,100 @@ public class WemoLinkDiscoveryService extends AbstractDiscoveryService implement String endDeviceRequest = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); - if (endDeviceRequest != null) { - logger.trace("endDeviceRequest answered '{}'", endDeviceRequest); + logger.trace("endDeviceRequest answered '{}'", endDeviceRequest); - try { - String stringParser = substringBetween(endDeviceRequest, "", ""); + try { + String stringParser = substringBetween(endDeviceRequest, "", ""); - stringParser = unescapeXml(stringParser); + stringParser = unescapeXml(stringParser); - // check if there are already paired devices with WeMo Link - if ("0".equals(stringParser)) { - logger.debug("There are no devices connected with WeMo Link. Exit discovery"); - return; - } - - // Build parser for received - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - // see - // https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html - dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); - dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); - dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); - dbf.setXIncludeAware(false); - dbf.setExpandEntityReferences(false); - DocumentBuilder db = dbf.newDocumentBuilder(); - InputSource is = new InputSource(); - is.setCharacterStream(new StringReader(stringParser)); - - Document doc = db.parse(is); - NodeList nodes = doc.getElementsByTagName("DeviceInfo"); - - // iterate the devices - for (int i = 0; i < nodes.getLength(); i++) { - Element element = (Element) nodes.item(i); - - NodeList deviceIndex = element.getElementsByTagName("DeviceIndex"); - Element line = (Element) deviceIndex.item(0); - logger.trace("DeviceIndex: {}", getCharacterDataFromElement(line)); - - NodeList deviceID = element.getElementsByTagName("DeviceID"); - line = (Element) deviceID.item(0); - String endDeviceID = getCharacterDataFromElement(line); - logger.trace("DeviceID: {}", endDeviceID); - - NodeList friendlyName = element.getElementsByTagName("FriendlyName"); - line = (Element) friendlyName.item(0); - String endDeviceName = getCharacterDataFromElement(line); - logger.trace("FriendlyName: {}", endDeviceName); - - NodeList vendor = element.getElementsByTagName("Manufacturer"); - line = (Element) vendor.item(0); - String endDeviceVendor = getCharacterDataFromElement(line); - logger.trace("Manufacturer: {}", endDeviceVendor); - - NodeList model = element.getElementsByTagName("ModelCode"); - line = (Element) model.item(0); - String endDeviceModelID = getCharacterDataFromElement(line); - endDeviceModelID = endDeviceModelID.replaceAll(NORMALIZE_ID_REGEX, "_"); - - logger.trace("ModelCode: {}", endDeviceModelID); - - if (SUPPORTED_THING_TYPES.contains(new ThingTypeUID(BINDING_ID, endDeviceModelID))) { - logger.debug("Discovered a WeMo LED Light thing with ID '{}'", endDeviceID); - - ThingUID bridgeUID = wemoBridgeHandler.getThing().getUID(); - ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, endDeviceModelID); - - if (thingTypeUID.equals(THING_TYPE_MZ100)) { - String thingLightId = endDeviceID; - ThingUID thingUID = new ThingUID(thingTypeUID, bridgeUID, thingLightId); - - Map properties = new HashMap<>(1); - properties.put(DEVICE_ID, endDeviceID); - - DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID) - .withProperties(properties) - .withBridge(wemoBridgeHandler.getThing().getUID()).withLabel(endDeviceName) - .build(); - - thingDiscovered(discoveryResult); - } - } else { - logger.debug("Discovered an unsupported device :"); - logger.debug("DeviceIndex : {}", getCharacterDataFromElement(line)); - logger.debug("DeviceID : {}", endDeviceID); - logger.debug("FriendlyName: {}", endDeviceName); - logger.debug("Manufacturer: {}", endDeviceVendor); - logger.debug("ModelCode : {}", endDeviceModelID); - } - - } - } catch (Exception e) { - logger.error("Failed to parse endDevices for bridge '{}'", - wemoBridgeHandler.getThing().getUID(), e); + // check if there are already paired devices with WeMo Link + if ("0".equals(stringParser)) { + logger.debug("There are no devices connected with WeMo Link. Exit discovery"); + return; } + + // Build parser for received + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + // see + // https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); + dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + dbf.setXIncludeAware(false); + dbf.setExpandEntityReferences(false); + DocumentBuilder db = dbf.newDocumentBuilder(); + InputSource is = new InputSource(); + is.setCharacterStream(new StringReader(stringParser)); + + Document doc = db.parse(is); + NodeList nodes = doc.getElementsByTagName("DeviceInfo"); + + // iterate the devices + for (int i = 0; i < nodes.getLength(); i++) { + Element element = (Element) nodes.item(i); + + NodeList deviceIndex = element.getElementsByTagName("DeviceIndex"); + Element line = (Element) deviceIndex.item(0); + logger.trace("DeviceIndex: {}", getCharacterDataFromElement(line)); + + NodeList deviceID = element.getElementsByTagName("DeviceID"); + line = (Element) deviceID.item(0); + String endDeviceID = getCharacterDataFromElement(line); + logger.trace("DeviceID: {}", endDeviceID); + + NodeList friendlyName = element.getElementsByTagName("FriendlyName"); + line = (Element) friendlyName.item(0); + String endDeviceName = getCharacterDataFromElement(line); + logger.trace("FriendlyName: {}", endDeviceName); + + NodeList vendor = element.getElementsByTagName("Manufacturer"); + line = (Element) vendor.item(0); + String endDeviceVendor = getCharacterDataFromElement(line); + logger.trace("Manufacturer: {}", endDeviceVendor); + + NodeList model = element.getElementsByTagName("ModelCode"); + line = (Element) model.item(0); + String endDeviceModelID = getCharacterDataFromElement(line); + endDeviceModelID = endDeviceModelID.replaceAll(NORMALIZE_ID_REGEX, "_"); + + logger.trace("ModelCode: {}", endDeviceModelID); + + if (SUPPORTED_THING_TYPES.contains(new ThingTypeUID(BINDING_ID, endDeviceModelID))) { + logger.debug("Discovered a WeMo LED Light thing with ID '{}'", endDeviceID); + + ThingUID bridgeUID = wemoBridgeHandler.getThing().getUID(); + ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, endDeviceModelID); + + if (thingTypeUID.equals(THING_TYPE_MZ100)) { + String thingLightId = endDeviceID; + ThingUID thingUID = new ThingUID(thingTypeUID, bridgeUID, thingLightId); + + Map properties = new HashMap<>(1); + properties.put(DEVICE_ID, endDeviceID); + + DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID) + .withProperties(properties).withBridge(wemoBridgeHandler.getThing().getUID()) + .withLabel(endDeviceName).build(); + + thingDiscovered(discoveryResult); + } + } else { + logger.debug("Discovered an unsupported device :"); + logger.debug("DeviceIndex : {}", getCharacterDataFromElement(line)); + logger.debug("DeviceID : {}", endDeviceID); + logger.debug("FriendlyName: {}", endDeviceName); + logger.debug("Manufacturer: {}", endDeviceVendor); + logger.debug("ModelCode : {}", endDeviceModelID); + } + + } + } catch (Exception e) { + logger.warn("Failed to parse endDevices for bridge '{}'", wemoBridgeHandler.getThing().getUID(), e); } } } catch (Exception e) { - logger.error("Failed to get endDevices for bridge '{}'", wemoBridgeHandler.getThing().getUID(), e); + logger.warn("Failed to get endDevices for bridge '{}'", wemoBridgeHandler.getThing().getUID(), e); } } diff --git a/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoBaseThingHandler.java b/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoBaseThingHandler.java index 81b734a8f..577dbcac4 100644 --- a/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoBaseThingHandler.java +++ b/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoBaseThingHandler.java @@ -13,6 +13,11 @@ package org.openhab.binding.wemo.internal.handler; import java.net.URL; +import java.time.Instant; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -24,20 +29,30 @@ import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * {@link WemoBaseThingHandler} provides a base implementation for the - * concrete WeMo handlers for each thing type. + * concrete WeMo handlers. * * @author Jacob Laursen - Initial contribution */ @NonNullByDefault public abstract class WemoBaseThingHandler extends BaseThingHandler implements UpnpIOParticipant { + private static final int SUBSCRIPTION_RENEWAL_INITIAL_DELAY_SECONDS = 15; + private static final int SUBSCRIPTION_RENEWAL_INTERVAL_SECONDS = 60; + + private final Logger logger = LoggerFactory.getLogger(WemoBaseThingHandler.class); + protected @Nullable UpnpIOService service; protected WemoHttpCall wemoHttpCaller; protected String host = ""; + private Map subscriptions = new ConcurrentHashMap(); + private @Nullable ScheduledFuture subscriptionRenewalJob; + public WemoBaseThingHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) { super(thing); this.service = upnpIOService; @@ -46,7 +61,22 @@ public abstract class WemoBaseThingHandler extends BaseThingHandler implements U @Override public void initialize() { - // can be overridden by subclasses + UpnpIOService service = this.service; + if (service != null) { + logger.debug("Registering UPnP participant for {}", getThing().getUID()); + service.registerParticipant(this); + } + } + + @Override + public void dispose() { + removeSubscriptions(); + UpnpIOService service = this.service; + if (service != null) { + logger.debug("Unregistering UPnP participant for {}", getThing().getUID()); + service.unregisterParticipant(this); + } + cancelSubscriptionRenewalJob(); } @Override @@ -66,7 +96,13 @@ public abstract class WemoBaseThingHandler extends BaseThingHandler implements U @Override public void onServiceSubscribed(@Nullable String service, boolean succeeded) { - // can be overridden by subclasses + if (service == null) { + return; + } + logger.debug("Subscription to service {} for {} {}", service, getUDN(), succeeded ? "succeeded" : "failed"); + if (succeeded) { + subscriptions.put(service, Instant.now()); + } } @Override @@ -76,10 +112,115 @@ public abstract class WemoBaseThingHandler extends BaseThingHandler implements U protected boolean isUpnpDeviceRegistered() { UpnpIOService service = this.service; - if (service != null) { - return service.isRegistered(this); + return service != null && service.isRegistered(this); + } + + protected void addSubscription(String serviceId) { + if (subscriptions.containsKey(serviceId)) { + logger.debug("{} already subscribed to {}", getUDN(), serviceId); + return; } - return false; + if (subscriptions.isEmpty()) { + logger.debug("Adding first GENA subscription for {}, scheduling renewal job", getUDN()); + scheduleSubscriptionRenewalJob(); + } + subscriptions.put(serviceId, Instant.ofEpochSecond(0)); + UpnpIOService service = this.service; + if (service == null) { + return; + } + if (!service.isRegistered(this)) { + logger.debug("Registering UPnP participant for {}", getUDN()); + service.registerParticipant(this); + } + if (!service.isRegistered(this)) { + logger.debug("Trying to add GENA subscription {} for {}, but service is not registered", serviceId, + getUDN()); + return; + } + logger.debug("Adding GENA subscription {} for {}", serviceId, getUDN()); + service.addSubscription(this, serviceId, WemoBindingConstants.SUBSCRIPTION_DURATION_SECONDS); + } + + protected void removeSubscription(String serviceId) { + UpnpIOService service = this.service; + if (service == null) { + return; + } + subscriptions.remove(serviceId); + if (subscriptions.isEmpty()) { + logger.debug("Removing last GENA subscription for {}, cancelling renewal job", getUDN()); + cancelSubscriptionRenewalJob(); + } + if (!service.isRegistered(this)) { + logger.debug("Trying to remove GENA subscription {} for {}, but service is not registered", serviceId, + getUDN()); + return; + } + logger.debug("Unsubscribing {} from service {}", getUDN(), serviceId); + service.removeSubscription(this, serviceId); + } + + private void scheduleSubscriptionRenewalJob() { + cancelSubscriptionRenewalJob(); + this.subscriptionRenewalJob = scheduler.scheduleWithFixedDelay(this::renewSubscriptions, + SUBSCRIPTION_RENEWAL_INITIAL_DELAY_SECONDS, SUBSCRIPTION_RENEWAL_INTERVAL_SECONDS, TimeUnit.SECONDS); + } + + private void cancelSubscriptionRenewalJob() { + ScheduledFuture subscriptionRenewalJob = this.subscriptionRenewalJob; + if (subscriptionRenewalJob != null) { + subscriptionRenewalJob.cancel(true); + } + this.subscriptionRenewalJob = null; + } + + private void renewSubscriptions() { + if (subscriptions.isEmpty()) { + return; + } + UpnpIOService service = this.service; + if (service == null) { + return; + } + if (!service.isRegistered(this)) { + service.registerParticipant(this); + } + if (!service.isRegistered(this)) { + logger.debug("Trying to renew GENA subscriptions for {}, but service is not registered", getUDN()); + return; + } + logger.debug("Renewing GENA subscriptions for {}", getUDN()); + subscriptions.forEach((serviceId, lastRenewed) -> { + if (lastRenewed.isBefore(Instant.now().minusSeconds( + WemoBindingConstants.SUBSCRIPTION_DURATION_SECONDS - SUBSCRIPTION_RENEWAL_INTERVAL_SECONDS))) { + logger.debug("Subscription for service {} with timestamp {} has expired, renewing", serviceId, + lastRenewed); + service.removeSubscription(this, serviceId); + service.addSubscription(this, serviceId, WemoBindingConstants.SUBSCRIPTION_DURATION_SECONDS); + } + }); + } + + private void removeSubscriptions() { + if (subscriptions.isEmpty()) { + return; + } + UpnpIOService service = this.service; + if (service == null) { + return; + } + if (!service.isRegistered(this)) { + logger.debug("Trying to remove GENA subscriptions for {}, but service is not registered", + getThing().getUID()); + return; + } + logger.debug("Removing GENA subscriptions for {}", getUDN()); + subscriptions.forEach((serviceId, lastRenewed) -> { + logger.debug("Removing subscription for service {}", serviceId); + service.removeSubscription(this, serviceId); + }); + subscriptions.clear(); } protected String getHost() { diff --git a/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoCoffeeHandler.java b/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoCoffeeHandler.java index e9e6205e0..1f3d01f3b 100644 --- a/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoCoffeeHandler.java +++ b/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoCoffeeHandler.java @@ -19,8 +19,6 @@ import java.io.StringReader; import java.time.Instant; import java.time.ZonedDateTime; import java.util.Collections; -import java.util.HashMap; -import java.util.Map; import java.util.Set; import java.util.TimeZone; import java.util.concurrent.ScheduledFuture; @@ -67,11 +65,8 @@ public class WemoCoffeeHandler extends WemoBaseThingHandler { public static final Set SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_COFFEE); - private final Object upnpLock = new Object(); private final Object jobLock = new Object(); - private Map subscriptionState = new HashMap<>(); - private @Nullable ScheduledFuture pollingJob; public WemoCoffeeHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) { @@ -82,14 +77,12 @@ public class WemoCoffeeHandler extends WemoBaseThingHandler { @Override public void initialize() { + super.initialize(); Configuration configuration = getConfig(); if (configuration.get(UDN) != null) { logger.debug("Initializing WemoCoffeeHandler for UDN '{}'", configuration.get(UDN)); - UpnpIOService localService = service; - if (localService != null) { - localService.registerParticipant(this); - } + addSubscription(DEVICEEVENT); host = getHost(); pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS, TimeUnit.SECONDS); @@ -103,13 +96,13 @@ public class WemoCoffeeHandler extends WemoBaseThingHandler { @Override public void dispose() { - logger.debug("WeMoCoffeeHandler disposed."); + logger.debug("WemoCoffeeHandler disposed."); ScheduledFuture job = this.pollingJob; if (job != null && !job.isCancelled()) { job.cancel(true); } this.pollingJob = null; - removeSubscription(); + super.dispose(); } private void poll() { @@ -127,14 +120,9 @@ public class WemoCoffeeHandler extends WemoBaseThingHandler { logger.debug("UPnP device {} not yet registered", getUDN()); updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING, "@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]"); - synchronized (upnpLock) { - subscriptionState = new HashMap<>(); - } return; } - updateStatus(ThingStatus.ONLINE); updateWemoState(); - addSubscription(); } catch (Exception e) { logger.debug("Exception during poll: {}", e.getMessage(), e); } @@ -145,7 +133,7 @@ public class WemoCoffeeHandler extends WemoBaseThingHandler { public void handleCommand(ChannelUID channelUID, Command command) { String localHost = getHost(); if (localHost.isEmpty()) { - logger.error("Failed to send command '{}' for device '{}': IP address missing", command, + logger.warn("Failed to send command '{}' for device '{}': IP address missing", command, getThing().getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/config-status.error.missing-ip"); @@ -153,7 +141,7 @@ public class WemoCoffeeHandler extends WemoBaseThingHandler { } String wemoURL = getWemoURL(localHost, BASICACTION); if (wemoURL == null) { - logger.error("Failed to send command '{}' for device '{}': URL cannot be created", command, + logger.debug("Failed to send command '{}' for device '{}': URL cannot be created", command, getThing().getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/config-status.error.missing-url"); @@ -184,97 +172,35 @@ public class WemoCoffeeHandler extends WemoBaseThingHandler { + "<attribute><name>Cleaning</name><value>NULL</value></attribute>" + "" + "" + ""; - String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); - if (wemoCallResponse != null) { - updateState(CHANNEL_STATE, OnOffType.ON); - State newMode = new StringType("Brewing"); - updateState(CHANNEL_COFFEEMODE, newMode); - if (logger.isTraceEnabled()) { - logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID()); - logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, - getThing().getUID()); - logger.trace("wemoCall with content '{}' for device '{}'", content, - getThing().getUID()); - logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, - getThing().getUID()); - } - } + wemoHttpCaller.executeCall(wemoURL, soapHeader, content); + updateState(CHANNEL_STATE, OnOffType.ON); + State newMode = new StringType("Brewing"); + updateState(CHANNEL_COFFEEMODE, newMode); + updateStatus(ThingStatus.ONLINE); } catch (Exception e) { - logger.error("Failed to send command '{}' for device '{}': {}", command, getThing().getUID(), + logger.warn("Failed to send command '{}' for device '{}': {}", command, getThing().getUID(), e.getMessage()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); } } // if command.equals(OnOffType.OFF) we do nothing because WeMo Coffee Maker cannot be switched - // off - // remotely - updateStatus(ThingStatus.ONLINE); + // off remotely } } } - @Override - public void onServiceSubscribed(@Nullable String service, boolean succeeded) { - if (service != null) { - logger.debug("WeMo {}: Subscription to service {} {}", getUDN(), service, - succeeded ? "succeeded" : "failed"); - subscriptionState.put(service, succeeded); - } - } - @Override public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) { // We can subscribe to GENA events, but there is no usefull response right now. } - private synchronized void addSubscription() { - synchronized (upnpLock) { - UpnpIOService localService = service; - if (localService != null) { - if (localService.isRegistered(this)) { - logger.debug("Checking WeMo GENA subscription for '{}'", getThing().getUID()); - - String subscription = DEVICEEVENT; - if (subscriptionState.get(subscription) == null) { - logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(), - subscription); - localService.addSubscription(this, subscription, SUBSCRIPTION_DURATION_SECONDS); - subscriptionState.put(subscription, true); - } - } else { - logger.debug( - "Setting up WeMo GENA subscription for '{}' FAILED - service.isRegistered(this) is FALSE", - getThing().getUID()); - } - } - } - } - - private synchronized void removeSubscription() { - logger.debug("Removing WeMo GENA subscription for '{}'", getThing().getUID()); - synchronized (upnpLock) { - UpnpIOService localService = service; - if (localService != null) { - if (localService.isRegistered(this)) { - String subscription = DEVICEEVENT; - if (subscriptionState.get(subscription) != null) { - logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription); - localService.removeSubscription(this, subscription); - } - subscriptionState = new HashMap<>(); - localService.unregisterParticipant(this); - } - } - } - } - /** * The {@link updateWemoState} polls the actual state of a WeMo CoffeeMaker. */ protected void updateWemoState() { String localHost = getHost(); if (localHost.isEmpty()) { - logger.error("Failed to get actual state for device '{}': IP address missing", getThing().getUID()); + logger.warn("Failed to get actual state for device '{}': IP address missing", getThing().getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/config-status.error.missing-ip"); return; @@ -282,7 +208,7 @@ public class WemoCoffeeHandler extends WemoBaseThingHandler { String actionService = DEVICEACTION; String wemoURL = getWemoURL(host, actionService); if (wemoURL == null) { - logger.error("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID()); + logger.debug("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/config-status.error.missing-url"); return; @@ -292,147 +218,140 @@ public class WemoCoffeeHandler extends WemoBaseThingHandler { String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\""; String content = createStateRequestContent(action, actionService); String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); - if (wemoCallResponse != null) { - if (logger.isTraceEnabled()) { - logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID()); - logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID()); - logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID()); - logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID()); - } - try { - String stringParser = substringBetween(wemoCallResponse, "", ""); + try { + String stringParser = substringBetween(wemoCallResponse, "", ""); - // Due to Belkins bad response formatting, we need to run this twice. - stringParser = unescapeXml(stringParser); - stringParser = unescapeXml(stringParser); + // Due to Belkins bad response formatting, we need to run this twice. + stringParser = unescapeXml(stringParser); + stringParser = unescapeXml(stringParser); - logger.trace("CoffeeMaker response '{}' for device '{}' received", stringParser, - getThing().getUID()); + logger.trace("CoffeeMaker response '{}' for device '{}' received", stringParser, getThing().getUID()); - stringParser = "" + stringParser + ""; + stringParser = "" + stringParser + ""; - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - // see - // https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html - dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); - dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); - dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); - dbf.setXIncludeAware(false); - dbf.setExpandEntityReferences(false); - DocumentBuilder db = dbf.newDocumentBuilder(); - InputSource is = new InputSource(); - is.setCharacterStream(new StringReader(stringParser)); + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + // see + // https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); + dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + dbf.setXIncludeAware(false); + dbf.setExpandEntityReferences(false); + DocumentBuilder db = dbf.newDocumentBuilder(); + InputSource is = new InputSource(); + is.setCharacterStream(new StringReader(stringParser)); - Document doc = db.parse(is); - NodeList nodes = doc.getElementsByTagName("attribute"); + Document doc = db.parse(is); + NodeList nodes = doc.getElementsByTagName("attribute"); - // iterate the attributes - for (int i = 0; i < nodes.getLength(); i++) { - Element element = (Element) nodes.item(i); + // iterate the attributes + for (int i = 0; i < nodes.getLength(); i++) { + Element element = (Element) nodes.item(i); - NodeList deviceIndex = element.getElementsByTagName("name"); - Element line = (Element) deviceIndex.item(0); - String attributeName = getCharacterDataFromElement(line); - logger.trace("attributeName: {}", attributeName); + NodeList deviceIndex = element.getElementsByTagName("name"); + Element line = (Element) deviceIndex.item(0); + String attributeName = getCharacterDataFromElement(line); + logger.trace("attributeName: {}", attributeName); - NodeList deviceID = element.getElementsByTagName("value"); - line = (Element) deviceID.item(0); - String attributeValue = getCharacterDataFromElement(line); - logger.trace("attributeValue: {}", attributeValue); + NodeList deviceID = element.getElementsByTagName("value"); + line = (Element) deviceID.item(0); + String attributeValue = getCharacterDataFromElement(line); + logger.trace("attributeValue: {}", attributeValue); - switch (attributeName) { - case "Mode": - State newMode = new StringType("Brewing"); - State newAttributeValue; + switch (attributeName) { + case "Mode": + State newMode = new StringType("Brewing"); + State newAttributeValue; - switch (attributeValue) { - case "0": - updateState(CHANNEL_STATE, OnOffType.ON); - newMode = new StringType("Refill"); - updateState(CHANNEL_COFFEEMODE, newMode); - break; - case "1": - updateState(CHANNEL_STATE, OnOffType.OFF); - newMode = new StringType("PlaceCarafe"); - updateState(CHANNEL_COFFEEMODE, newMode); - break; - case "2": - updateState(CHANNEL_STATE, OnOffType.OFF); - newMode = new StringType("RefillWater"); - updateState(CHANNEL_COFFEEMODE, newMode); - break; - case "3": - updateState(CHANNEL_STATE, OnOffType.OFF); - newMode = new StringType("Ready"); - updateState(CHANNEL_COFFEEMODE, newMode); - break; - case "4": - updateState(CHANNEL_STATE, OnOffType.ON); - newMode = new StringType("Brewing"); - updateState(CHANNEL_COFFEEMODE, newMode); - break; - case "5": - updateState(CHANNEL_STATE, OnOffType.OFF); - newMode = new StringType("Brewed"); - updateState(CHANNEL_COFFEEMODE, newMode); - break; - case "6": - updateState(CHANNEL_STATE, OnOffType.OFF); - newMode = new StringType("CleaningBrewing"); - updateState(CHANNEL_COFFEEMODE, newMode); - break; - case "7": - updateState(CHANNEL_STATE, OnOffType.OFF); - newMode = new StringType("CleaningSoaking"); - updateState(CHANNEL_COFFEEMODE, newMode); - break; - case "8": - updateState(CHANNEL_STATE, OnOffType.OFF); - newMode = new StringType("BrewFailCarafeRemoved"); - updateState(CHANNEL_COFFEEMODE, newMode); - break; - } - break; - case "ModeTime": - newAttributeValue = new DecimalType(attributeValue); - updateState(CHANNEL_MODETIME, newAttributeValue); - break; - case "TimeRemaining": - newAttributeValue = new DecimalType(attributeValue); - updateState(CHANNEL_TIMEREMAINING, newAttributeValue); - break; - case "WaterLevelReached": - newAttributeValue = new DecimalType(attributeValue); - updateState(CHANNEL_WATERLEVELREACHED, newAttributeValue); - break; - case "CleanAdvise": - newAttributeValue = "0".equals(attributeValue) ? OnOffType.OFF : OnOffType.ON; - updateState(CHANNEL_CLEANADVISE, newAttributeValue); - break; - case "FilterAdvise": - newAttributeValue = "0".equals(attributeValue) ? OnOffType.OFF : OnOffType.ON; - updateState(CHANNEL_FILTERADVISE, newAttributeValue); - break; - case "Brewed": - newAttributeValue = getDateTimeState(attributeValue); - if (newAttributeValue != null) { - updateState(CHANNEL_BREWED, newAttributeValue); - } - break; - case "LastCleaned": - newAttributeValue = getDateTimeState(attributeValue); - if (newAttributeValue != null) { - updateState(CHANNEL_LASTCLEANED, newAttributeValue); - } - break; - } + switch (attributeValue) { + case "0": + updateState(CHANNEL_STATE, OnOffType.ON); + newMode = new StringType("Refill"); + updateState(CHANNEL_COFFEEMODE, newMode); + break; + case "1": + updateState(CHANNEL_STATE, OnOffType.OFF); + newMode = new StringType("PlaceCarafe"); + updateState(CHANNEL_COFFEEMODE, newMode); + break; + case "2": + updateState(CHANNEL_STATE, OnOffType.OFF); + newMode = new StringType("RefillWater"); + updateState(CHANNEL_COFFEEMODE, newMode); + break; + case "3": + updateState(CHANNEL_STATE, OnOffType.OFF); + newMode = new StringType("Ready"); + updateState(CHANNEL_COFFEEMODE, newMode); + break; + case "4": + updateState(CHANNEL_STATE, OnOffType.ON); + newMode = new StringType("Brewing"); + updateState(CHANNEL_COFFEEMODE, newMode); + break; + case "5": + updateState(CHANNEL_STATE, OnOffType.OFF); + newMode = new StringType("Brewed"); + updateState(CHANNEL_COFFEEMODE, newMode); + break; + case "6": + updateState(CHANNEL_STATE, OnOffType.OFF); + newMode = new StringType("CleaningBrewing"); + updateState(CHANNEL_COFFEEMODE, newMode); + break; + case "7": + updateState(CHANNEL_STATE, OnOffType.OFF); + newMode = new StringType("CleaningSoaking"); + updateState(CHANNEL_COFFEEMODE, newMode); + break; + case "8": + updateState(CHANNEL_STATE, OnOffType.OFF); + newMode = new StringType("BrewFailCarafeRemoved"); + updateState(CHANNEL_COFFEEMODE, newMode); + break; + } + break; + case "ModeTime": + newAttributeValue = new DecimalType(attributeValue); + updateState(CHANNEL_MODETIME, newAttributeValue); + break; + case "TimeRemaining": + newAttributeValue = new DecimalType(attributeValue); + updateState(CHANNEL_TIMEREMAINING, newAttributeValue); + break; + case "WaterLevelReached": + newAttributeValue = new DecimalType(attributeValue); + updateState(CHANNEL_WATERLEVELREACHED, newAttributeValue); + break; + case "CleanAdvise": + newAttributeValue = "0".equals(attributeValue) ? OnOffType.OFF : OnOffType.ON; + updateState(CHANNEL_CLEANADVISE, newAttributeValue); + break; + case "FilterAdvise": + newAttributeValue = "0".equals(attributeValue) ? OnOffType.OFF : OnOffType.ON; + updateState(CHANNEL_FILTERADVISE, newAttributeValue); + break; + case "Brewed": + newAttributeValue = getDateTimeState(attributeValue); + if (newAttributeValue != null) { + updateState(CHANNEL_BREWED, newAttributeValue); + } + break; + case "LastCleaned": + newAttributeValue = getDateTimeState(attributeValue); + if (newAttributeValue != null) { + updateState(CHANNEL_LASTCLEANED, newAttributeValue); + } + break; } - } catch (Exception e) { - logger.error("Failed to parse attributeList for WeMo CoffeMaker '{}'", this.getThing().getUID(), e); } + updateStatus(ThingStatus.ONLINE); + } catch (Exception e) { + logger.warn("Failed to parse attributeList for WeMo CoffeMaker '{}'", this.getThing().getUID(), e); } } catch (Exception e) { - logger.error("Failed to get attributes for device '{}'", getThing().getUID(), e); + logger.warn("Failed to get attributes for device '{}'", getThing().getUID(), e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } } @@ -441,7 +360,7 @@ public class WemoCoffeeHandler extends WemoBaseThingHandler { try { value = Long.parseLong(attributeValue); } catch (NumberFormatException e) { - logger.error("Unable to parse attributeValue '{}' for device '{}'; expected long", attributeValue, + logger.warn("Unable to parse attributeValue '{}' for device '{}'; expected long", attributeValue, getThing().getUID()); return null; } diff --git a/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoCrockpotHandler.java b/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoCrockpotHandler.java index 73d7edd56..7abd6f56d 100644 --- a/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoCrockpotHandler.java +++ b/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoCrockpotHandler.java @@ -15,6 +15,7 @@ package org.openhab.binding.wemo.internal.handler; import static org.openhab.binding.wemo.internal.WemoBindingConstants.*; import static org.openhab.binding.wemo.internal.WemoUtil.*; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -53,13 +54,10 @@ public class WemoCrockpotHandler extends WemoBaseThingHandler { public static final Set SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_CROCKPOT); - private final Object upnpLock = new Object(); private final Object jobLock = new Object(); private final Map stateMap = Collections.synchronizedMap(new HashMap<>()); - private Map subscriptionState = new HashMap<>(); - private @Nullable ScheduledFuture pollingJob; public WemoCrockpotHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) { @@ -70,14 +68,12 @@ public class WemoCrockpotHandler extends WemoBaseThingHandler { @Override public void initialize() { + super.initialize(); Configuration configuration = getConfig(); if (configuration.get(UDN) != null) { logger.debug("Initializing WemoCrockpotHandler for UDN '{}'", configuration.get(UDN)); - UpnpIOService localService = service; - if (localService != null) { - localService.registerParticipant(this); - } + addSubscription(BASICEVENT); host = getHost(); pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS, TimeUnit.SECONDS); @@ -97,7 +93,7 @@ public class WemoCrockpotHandler extends WemoBaseThingHandler { job.cancel(true); } this.pollingJob = null; - removeSubscription(); + super.dispose(); } private void poll() { @@ -114,14 +110,9 @@ public class WemoCrockpotHandler extends WemoBaseThingHandler { logger.debug("UPnP device {} not yet registered", getUDN()); updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING, "@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]"); - synchronized (upnpLock) { - subscriptionState = new HashMap<>(); - } return; } - updateStatus(ThingStatus.ONLINE); updateWemoState(); - addSubscription(); } catch (Exception e) { logger.debug("Exception during poll: {}", e.getMessage(), e); } @@ -132,7 +123,7 @@ public class WemoCrockpotHandler extends WemoBaseThingHandler { public void handleCommand(ChannelUID channelUID, Command command) { String localHost = getHost(); if (localHost.isEmpty()) { - logger.error("Failed to send command '{}' for device '{}': IP address missing", command, + logger.warn("Failed to send command '{}' for device '{}': IP address missing", command, getThing().getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/config-status.error.missing-ip"); @@ -140,7 +131,7 @@ public class WemoCrockpotHandler extends WemoBaseThingHandler { } String wemoURL = getWemoURL(localHost, BASICACTION); if (wemoURL == null) { - logger.error("Failed to send command '{}' for device '{}': URL cannot be created", command, + logger.debug("Failed to send command '{}' for device '{}': URL cannot be created", command, getThing().getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/config-status.error.missing-url"); @@ -175,27 +166,12 @@ public class WemoCrockpotHandler extends WemoBaseThingHandler { + "" + "" + "" + mode + "" + "" + "" + "" + ""; - String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); - if (wemoCallResponse != null && logger.isTraceEnabled()) { - logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID()); - logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID()); - logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID()); - logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID()); - } - } catch (RuntimeException e) { + wemoHttpCaller.executeCall(wemoURL, soapHeader, content); + updateStatus(ThingStatus.ONLINE); + } catch (IOException e) { logger.debug("Failed to send command '{}' for device '{}':", command, getThing().getUID(), e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } - updateStatus(ThingStatus.ONLINE); - } - } - - @Override - public void onServiceSubscribed(@Nullable String service, boolean succeeded) { - if (service != null) { - logger.debug("WeMo {}: Subscription to service {} {}", getUDN(), service, - succeeded ? "succeeded" : "failed"); - subscriptionState.put(service, succeeded); } } @@ -210,49 +186,6 @@ public class WemoCrockpotHandler extends WemoBaseThingHandler { } } - private synchronized void addSubscription() { - synchronized (upnpLock) { - UpnpIOService localService = service; - if (localService != null) { - if (localService.isRegistered(this)) { - logger.debug("Checking WeMo GENA subscription for '{}'", getThing().getUID()); - - String subscription = BASICEVENT; - - if (subscriptionState.get(subscription) == null) { - logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(), - subscription); - localService.addSubscription(this, subscription, SUBSCRIPTION_DURATION_SECONDS); - subscriptionState.put(subscription, true); - } - } else { - logger.debug( - "Setting up WeMo GENA subscription for '{}' FAILED - service.isRegistered(this) is FALSE", - getThing().getUID()); - } - } - } - } - - private synchronized void removeSubscription() { - synchronized (upnpLock) { - UpnpIOService localService = service; - if (localService != null) { - if (localService.isRegistered(this)) { - logger.debug("Removing WeMo GENA subscription for '{}'", getThing().getUID()); - String subscription = BASICEVENT; - - if (subscriptionState.get(subscription) != null) { - logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription); - localService.removeSubscription(this, subscription); - } - subscriptionState.remove(subscription); - localService.unregisterParticipant(this); - } - } - } - } - /** * The {@link updateWemoState} polls the actual state of a WeMo device and * calls {@link onValueReceived} to update the statemap and channels.. @@ -261,7 +194,7 @@ public class WemoCrockpotHandler extends WemoBaseThingHandler { protected void updateWemoState() { String localHost = getHost(); if (localHost.isEmpty()) { - logger.error("Failed to get actual state for device '{}': IP address missing", getThing().getUID()); + logger.warn("Failed to get actual state for device '{}': IP address missing", getThing().getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/config-status.error.missing-ip"); return; @@ -269,7 +202,7 @@ public class WemoCrockpotHandler extends WemoBaseThingHandler { String actionService = BASICEVENT; String wemoURL = getWemoURL(localHost, actionService); if (wemoURL == null) { - logger.error("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID()); + logger.warn("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/config-status.error.missing-url"); return; @@ -279,46 +212,38 @@ public class WemoCrockpotHandler extends WemoBaseThingHandler { String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\""; String content = createStateRequestContent(action, actionService); String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); - if (wemoCallResponse != null) { - if (logger.isTraceEnabled()) { - logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID()); - logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID()); - logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID()); - logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID()); - } - String mode = substringBetween(wemoCallResponse, "", ""); - String time = substringBetween(wemoCallResponse, ""); - String coockedTime = substringBetween(wemoCallResponse, "", ""); + String mode = substringBetween(wemoCallResponse, "", ""); + String time = substringBetween(wemoCallResponse, ""); + String coockedTime = substringBetween(wemoCallResponse, "", ""); - State newMode = new StringType(mode); - State newCoockedTime = DecimalType.valueOf(coockedTime); - switch (mode) { - case "0": - newMode = new StringType("OFF"); - break; - case "50": - newMode = new StringType("WARM"); - State warmTime = DecimalType.valueOf(time); - updateState(CHANNEL_WARMCOOKTIME, warmTime); - break; - case "51": - newMode = new StringType("LOW"); - State lowTime = DecimalType.valueOf(time); - updateState(CHANNEL_LOWCOOKTIME, lowTime); - break; - case "52": - newMode = new StringType("HIGH"); - State highTime = DecimalType.valueOf(time); - updateState(CHANNEL_HIGHCOOKTIME, highTime); - break; - } - updateState(CHANNEL_COOKMODE, newMode); - updateState(CHANNEL_COOKEDTIME, newCoockedTime); + State newMode = new StringType(mode); + State newCoockedTime = DecimalType.valueOf(coockedTime); + switch (mode) { + case "0": + newMode = new StringType("OFF"); + break; + case "50": + newMode = new StringType("WARM"); + State warmTime = DecimalType.valueOf(time); + updateState(CHANNEL_WARMCOOKTIME, warmTime); + break; + case "51": + newMode = new StringType("LOW"); + State lowTime = DecimalType.valueOf(time); + updateState(CHANNEL_LOWCOOKTIME, lowTime); + break; + case "52": + newMode = new StringType("HIGH"); + State highTime = DecimalType.valueOf(time); + updateState(CHANNEL_HIGHCOOKTIME, highTime); + break; } - } catch (RuntimeException e) { + updateState(CHANNEL_COOKMODE, newMode); + updateState(CHANNEL_COOKEDTIME, newCoockedTime); + updateStatus(ThingStatus.ONLINE); + } catch (IOException e) { logger.debug("Failed to get actual state for device '{}': {}", getThing().getUID(), e.getMessage(), e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } - updateStatus(ThingStatus.ONLINE); } } diff --git a/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoDimmerHandler.java b/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoDimmerHandler.java index eaaba0fd8..69ac9b5a5 100644 --- a/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoDimmerHandler.java +++ b/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoDimmerHandler.java @@ -59,13 +59,10 @@ public class WemoDimmerHandler extends WemoBaseThingHandler { public static final Set SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_DIMMER); - private final Object upnpLock = new Object(); private final Object jobLock = new Object(); private final Map stateMap = Collections.synchronizedMap(new HashMap<>()); - private Map subscriptionState = new HashMap<>(); - private @Nullable ScheduledFuture pollingJob; private int currentBrightness; @@ -84,14 +81,12 @@ public class WemoDimmerHandler extends WemoBaseThingHandler { @Override public void initialize() { + super.initialize(); Configuration configuration = getConfig(); if (configuration.get(UDN) != null) { logger.debug("Initializing WemoDimmerHandler for UDN '{}'", configuration.get(UDN)); - UpnpIOService localService = service; - if (localService != null) { - localService.registerParticipant(this); - } + addSubscription(BASICEVENT); host = getHost(); pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS, TimeUnit.SECONDS); @@ -112,7 +107,7 @@ public class WemoDimmerHandler extends WemoBaseThingHandler { job.cancel(true); } this.pollingJob = null; - removeSubscription(); + super.dispose(); } private void poll() { @@ -129,14 +124,9 @@ public class WemoDimmerHandler extends WemoBaseThingHandler { logger.debug("UPnP device {} not yet registered", getUDN()); updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING, "@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]"); - synchronized (upnpLock) { - subscriptionState = new HashMap<>(); - } return; } - updateStatus(ThingStatus.ONLINE); updateWemoState(); - addSubscription(); } catch (Exception e) { logger.debug("Exception during poll: {}", e.getMessage(), e); } @@ -333,15 +323,6 @@ public class WemoDimmerHandler extends WemoBaseThingHandler { } } - @Override - public void onServiceSubscribed(@Nullable String service, boolean succeeded) { - if (service != null) { - logger.debug("WeMo {}: Subscription to service {} {}", getUDN(), service, - succeeded ? "succeeded" : "failed"); - subscriptionState.put(service, succeeded); - } - } - @Override public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) { logger.debug("Received pair '{}':'{}' (service '{}') for thing '{}'", @@ -431,41 +412,6 @@ public class WemoDimmerHandler extends WemoBaseThingHandler { } } - private synchronized void addSubscription() { - UpnpIOService localService = service; - if (localService != null) { - if (localService.isRegistered(this)) { - logger.debug("Checking WeMo GENA subscription for '{}'", getThing().getUID()); - String subscription = BASICEVENT; - if (subscriptionState.get(subscription) == null) { - logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(), - subscription); - localService.addSubscription(this, subscription, SUBSCRIPTION_DURATION_SECONDS); - subscriptionState.put(subscription, true); - } - } else { - logger.debug("Setting up WeMo GENA subscription for '{}' FAILED - service.isRegistered(this) is FALSE", - getThing().getUID()); - } - } - } - - private synchronized void removeSubscription() { - logger.debug("Removing WeMo GENA subscription for '{}'", getThing().getUID()); - UpnpIOService localService = service; - if (localService != null) { - if (localService.isRegistered(this)) { - String subscription = BASICEVENT; - if (subscriptionState.get(subscription) != null) { - logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription); - localService.removeSubscription(this, subscription); - } - subscriptionState = new HashMap<>(); - localService.unregisterParticipant(this); - } - } - } - /** * The {@link updateWemoState} polls the actual state of a WeMo device and * calls {@link onValueReceived} to update the statemap and channels.. @@ -474,14 +420,14 @@ public class WemoDimmerHandler extends WemoBaseThingHandler { protected void updateWemoState() { String localHost = getHost(); if (localHost.isEmpty()) { - logger.error("Failed to get actual state for device '{}': IP address missing", getThing().getUID()); + logger.warn("Failed to get actual state for device '{}': IP address missing", getThing().getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/config-status.error.missing-ip"); return; } String wemoURL = getWemoURL(localHost, BASICACTION); if (wemoURL == null) { - logger.error("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID()); + logger.debug("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/config-status.error.missing-url"); return; @@ -494,24 +440,16 @@ public class WemoDimmerHandler extends WemoBaseThingHandler { String content = createStateRequestContent(action, actionService); try { String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); - if (wemoCallResponse != null) { - if (logger.isTraceEnabled()) { - logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID()); - logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID()); - logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID()); - logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID()); - } - value = substringBetween(wemoCallResponse, "", ""); - variable = "BinaryState"; - this.onValueReceived(variable, value, actionService + "1"); - value = substringBetween(wemoCallResponse, "", ""); - variable = "brightness"; - this.onValueReceived(variable, value, actionService + "1"); - value = substringBetween(wemoCallResponse, "", ""); - variable = "fader"; - this.onValueReceived(variable, value, actionService + "1"); - updateStatus(ThingStatus.ONLINE); - } + value = substringBetween(wemoCallResponse, "", ""); + variable = "BinaryState"; + this.onValueReceived(variable, value, actionService + "1"); + value = substringBetween(wemoCallResponse, "", ""); + variable = "brightness"; + this.onValueReceived(variable, value, actionService + "1"); + value = substringBetween(wemoCallResponse, "", ""); + variable = "fader"; + this.onValueReceived(variable, value, actionService + "1"); + updateStatus(ThingStatus.ONLINE); } catch (Exception e) { logger.debug("Failed to get actual state for device '{}': {}", getThing().getUID(), e.getMessage()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); @@ -523,28 +461,19 @@ public class WemoDimmerHandler extends WemoBaseThingHandler { content = createStateRequestContent(action, actionService); try { String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); - if (wemoCallResponse != null) { - if (logger.isTraceEnabled()) { - logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID()); - logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID()); - logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID()); - logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID()); - } - value = substringBetween(wemoCallResponse, "", ""); - variable = "startTime"; - this.onValueReceived(variable, value, actionService + "1"); - value = substringBetween(wemoCallResponse, "", ""); - variable = "endTime"; - this.onValueReceived(variable, value, actionService + "1"); - value = substringBetween(wemoCallResponse, "", ""); - variable = "nightMode"; - this.onValueReceived(variable, value, actionService + "1"); - value = substringBetween(wemoCallResponse, "", ""); - variable = "nightModeBrightness"; - this.onValueReceived(variable, value, actionService + "1"); - updateStatus(ThingStatus.ONLINE); - - } + value = substringBetween(wemoCallResponse, "", ""); + variable = "startTime"; + this.onValueReceived(variable, value, actionService + "1"); + value = substringBetween(wemoCallResponse, "", ""); + variable = "endTime"; + this.onValueReceived(variable, value, actionService + "1"); + value = substringBetween(wemoCallResponse, "", ""); + variable = "nightMode"; + this.onValueReceived(variable, value, actionService + "1"); + value = substringBetween(wemoCallResponse, "", ""); + variable = "nightModeBrightness"; + this.onValueReceived(variable, value, actionService + "1"); + updateStatus(ThingStatus.ONLINE); } catch (Exception e) { logger.debug("Failed to get actual NightMode state for device '{}': {}", getThing().getUID(), e.getMessage()); @@ -557,27 +486,26 @@ public class WemoDimmerHandler extends WemoBaseThingHandler { try { value = Long.parseLong(attributeValue); } catch (NumberFormatException e) { - logger.error("Unable to parse attributeValue '{}' for device '{}'; expected long", attributeValue, + logger.warn("Unable to parse attributeValue '{}' for device '{}'; expected long", attributeValue, getThing().getUID()); return null; } ZonedDateTime zoned = ZonedDateTime.ofInstant(Instant.ofEpochSecond(value), TimeZone.getDefault().toZoneId()); State dateTimeState = new DateTimeType(zoned); - logger.trace("New attribute brewed '{}' received", dateTimeState); return dateTimeState; } public void setBinaryState(String action, String argument, String value) { String localHost = getHost(); if (localHost.isEmpty()) { - logger.error("Failed to set binary state for device '{}': IP address missing", getThing().getUID()); + logger.warn("Failed to set binary state for device '{}': IP address missing", getThing().getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/config-status.error.missing-ip"); return; } String wemoURL = getWemoURL(localHost, BASICACTION); if (wemoURL == null) { - logger.error("Failed to set binary state for device '{}': URL cannot be created", getThing().getUID()); + logger.debug("Failed to set binary state for device '{}': URL cannot be created", getThing().getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/config-status.error.missing-url"); return; @@ -589,13 +517,8 @@ public class WemoDimmerHandler extends WemoBaseThingHandler { + "" + "" + "<" + argument + ">" + value + "" + "" + "" + ""; - String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); - if (wemoCallResponse != null && logger.isTraceEnabled()) { - logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID()); - logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID()); - logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID()); - logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID()); - } + wemoHttpCaller.executeCall(wemoURL, soapHeader, content); + updateStatus(ThingStatus.ONLINE); } catch (Exception e) { logger.debug("Failed to set binaryState '{}' for device '{}': {}", value, getThing().getUID(), e.getMessage()); @@ -606,14 +529,14 @@ public class WemoDimmerHandler extends WemoBaseThingHandler { public void setTimerStart(String action, String argument, String value) { String localHost = getHost(); if (localHost.isEmpty()) { - logger.error("Failed to set timerStart for device '{}': IP address missing", getThing().getUID()); + logger.warn("Failed to set timerStart for device '{}': IP address missing", getThing().getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/config-status.error.missing-ip"); return; } String wemoURL = getWemoURL(localHost, BASICACTION); if (wemoURL == null) { - logger.error("Failed to set timerStart for device '{}': URL cannot be created", getThing().getUID()); + logger.warn("Failed to set timerStart for device '{}': URL cannot be created", getThing().getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/config-status.error.missing-url"); return; @@ -624,13 +547,8 @@ public class WemoDimmerHandler extends WemoBaseThingHandler { + "" + "" + "" + value + "" + "" + ""; - String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); - if (wemoCallResponse != null && logger.isTraceEnabled()) { - logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID()); - logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID()); - logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID()); - logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID()); - } + wemoHttpCaller.executeCall(wemoURL, soapHeader, content); + updateStatus(ThingStatus.ONLINE); } catch (Exception e) { logger.debug("Failed to set timerStart '{}' for device '{}': {}", value, getThing().getUID(), e.getMessage()); diff --git a/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoHandler.java b/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoHandler.java index 4b16e08a7..8c6633688 100644 --- a/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoHandler.java +++ b/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoHandler.java @@ -15,8 +15,6 @@ package org.openhab.binding.wemo.internal.handler; import static org.openhab.binding.wemo.internal.WemoBindingConstants.*; import static org.openhab.binding.wemo.internal.WemoUtil.*; -import java.util.HashMap; -import java.util.Map; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -30,7 +28,6 @@ import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatusDetail; -import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; import org.slf4j.Logger; @@ -51,11 +48,8 @@ public abstract class WemoHandler extends WemoBaseThingHandler { private final Logger logger = LoggerFactory.getLogger(WemoHandler.class); - private final Object upnpLock = new Object(); private final Object jobLock = new Object(); - private Map subscriptionState = new HashMap<>(); - private @Nullable ScheduledFuture pollingJob; public WemoHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) { @@ -66,13 +60,14 @@ public abstract class WemoHandler extends WemoBaseThingHandler { @Override public void initialize() { + super.initialize(); Configuration configuration = getConfig(); if (configuration.get(UDN) != null) { logger.debug("Initializing WemoHandler for UDN '{}'", configuration.get(UDN)); - UpnpIOService localService = service; - if (localService != null) { - localService.registerParticipant(this); + addSubscription(BASICEVENT); + if (THING_TYPE_INSIGHT.equals(thing.getThingTypeUID())) { + addSubscription(INSIGHTEVENT); } host = getHost(); pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS, @@ -94,7 +89,7 @@ public abstract class WemoHandler extends WemoBaseThingHandler { job.cancel(true); } this.pollingJob = null; - removeSubscription(); + super.dispose(); } private void poll() { @@ -111,14 +106,9 @@ public abstract class WemoHandler extends WemoBaseThingHandler { logger.debug("UPnP device {} not yet registered", getUDN()); updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING, "@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]"); - synchronized (upnpLock) { - subscriptionState = new HashMap<>(); - } return; } - updateStatus(ThingStatus.ONLINE); updateWemoState(); - addSubscription(); } catch (Exception e) { logger.debug("Exception during poll: {}", e.getMessage(), e); } @@ -129,7 +119,7 @@ public abstract class WemoHandler extends WemoBaseThingHandler { public void handleCommand(ChannelUID channelUID, Command command) { String localHost = getHost(); if (localHost.isEmpty()) { - logger.error("Failed to send command '{}' for device '{}': IP address missing", command, + logger.warn("Failed to send command '{}' for device '{}': IP address missing", command, getThing().getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/config-status.error.missing-ip"); @@ -137,7 +127,7 @@ public abstract class WemoHandler extends WemoBaseThingHandler { } String wemoURL = getWemoURL(localHost, BASICACTION); if (wemoURL == null) { - logger.error("Failed to send command '{}' for device '{}': URL cannot be created", command, + logger.debug("Failed to send command '{}' for device '{}': URL cannot be created", command, getThing().getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/config-status.error.missing-url"); @@ -155,92 +145,13 @@ public abstract class WemoHandler extends WemoBaseThingHandler { boolean binaryState = OnOffType.ON.equals(command) ? true : false; String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\""; String content = createBinaryStateContent(binaryState); - String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); - if (wemoCallResponse != null && logger.isTraceEnabled()) { - logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID()); - logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID()); - logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID()); - logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, - getThing().getUID()); - } + wemoHttpCaller.executeCall(wemoURL, soapHeader, content); + updateStatus(ThingStatus.ONLINE); } catch (Exception e) { - logger.error("Failed to send command '{}' for device '{}': {}", command, getThing().getUID(), + logger.warn("Failed to send command '{}' for device '{}': {}", command, getThing().getUID(), e.getMessage()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); } - updateStatus(ThingStatus.ONLINE); - } - } - } - - @Override - public void onServiceSubscribed(@Nullable String service, boolean succeeded) { - if (service != null) { - logger.debug("WeMo {}: Subscription to service {} {}", getUDN(), service, - succeeded ? "succeeded" : "failed"); - subscriptionState.put(service, succeeded); - } - } - - private synchronized void addSubscription() { - synchronized (upnpLock) { - UpnpIOService localService = service; - if (localService != null) { - if (localService.isRegistered(this)) { - logger.debug("Checking WeMo GENA subscription for '{}'", getThing().getUID()); - - ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - String subscription = BASICEVENT; - - if (subscriptionState.get(subscription) == null) { - logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(), - subscription); - localService.addSubscription(this, subscription, SUBSCRIPTION_DURATION_SECONDS); - subscriptionState.put(subscription, true); - } - - if (THING_TYPE_INSIGHT.equals(thingTypeUID)) { - subscription = INSIGHTEVENT; - if (subscriptionState.get(subscription) == null) { - logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(), - subscription); - localService.addSubscription(this, subscription, SUBSCRIPTION_DURATION_SECONDS); - subscriptionState.put(subscription, true); - } - } - } else { - logger.debug( - "Setting up WeMo GENA subscription for '{}' FAILED - service.isRegistered(this) is FALSE", - getThing().getUID()); - } - } - } - } - - private synchronized void removeSubscription() { - synchronized (upnpLock) { - UpnpIOService localService = service; - if (localService != null) { - if (localService.isRegistered(this)) { - logger.debug("Removing WeMo GENA subscription for '{}'", getThing().getUID()); - ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - String subscription = BASICEVENT; - - if (subscriptionState.get(subscription) != null) { - logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription); - localService.removeSubscription(this, subscription); - } - - if (THING_TYPE_INSIGHT.equals(thingTypeUID)) { - subscription = INSIGHTEVENT; - if (subscriptionState.get(subscription) != null) { - logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription); - localService.removeSubscription(this, subscription); - } - } - subscriptionState = new HashMap<>(); - localService.unregisterParticipant(this); - } } } } @@ -254,7 +165,7 @@ public abstract class WemoHandler extends WemoBaseThingHandler { String actionService = BASICACTION; String localhost = getHost(); if (localhost.isEmpty()) { - logger.error("Failed to get actual state for device '{}': IP address missing", getThing().getUID()); + logger.warn("Failed to get actual state for device '{}': IP address missing", getThing().getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/config-status.error.missing-ip"); return; @@ -269,7 +180,7 @@ public abstract class WemoHandler extends WemoBaseThingHandler { } String wemoURL = getWemoURL(localhost, actionService); if (wemoURL == null) { - logger.error("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID()); + logger.debug("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/config-status.error.missing-url"); return; @@ -278,25 +189,17 @@ public abstract class WemoHandler extends WemoBaseThingHandler { String content = createStateRequestContent(action, actionService); try { String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); - if (wemoCallResponse != null) { - if (logger.isTraceEnabled()) { - logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID()); - logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID()); - logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID()); - logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID()); - } - if ("InsightParams".equals(variable)) { - value = substringBetween(wemoCallResponse, "", ""); - } else { - value = substringBetween(wemoCallResponse, "", ""); - } - if (value.length() != 0) { - logger.trace("New state '{}' for device '{}' received", value, getThing().getUID()); - this.onValueReceived(variable, value, actionService + "1"); - } + if ("InsightParams".equals(variable)) { + value = substringBetween(wemoCallResponse, "", ""); + } else { + value = substringBetween(wemoCallResponse, "", ""); + } + if (value.length() != 0) { + logger.trace("New state '{}' for device '{}' received", value, getThing().getUID()); + this.onValueReceived(variable, value, actionService + "1"); } } catch (Exception e) { - logger.error("Failed to get actual state for device '{}': {}", getThing().getUID(), e.getMessage()); + logger.warn("Failed to get actual state for device '{}': {}", getThing().getUID(), e.getMessage()); } } } diff --git a/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoHolmesHandler.java b/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoHolmesHandler.java index 5742e4a5f..cc96df98e 100644 --- a/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoHolmesHandler.java +++ b/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoHolmesHandler.java @@ -68,13 +68,10 @@ public class WemoHolmesHandler extends WemoBaseThingHandler { private static final int FILTER_LIFE_DAYS = 330; private static final int FILTER_LIFE_MINS = FILTER_LIFE_DAYS * 24 * 60; - private final Object upnpLock = new Object(); private final Object jobLock = new Object(); private final Map stateMap = Collections.synchronizedMap(new HashMap<>()); - private Map subscriptionState = new HashMap<>(); - private @Nullable ScheduledFuture pollingJob; public WemoHolmesHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) { @@ -85,14 +82,12 @@ public class WemoHolmesHandler extends WemoBaseThingHandler { @Override public void initialize() { + super.initialize(); Configuration configuration = getConfig(); if (configuration.get(UDN) != null) { logger.debug("Initializing WemoHolmesHandler for UDN '{}'", configuration.get(UDN)); - UpnpIOService localService = service; - if (localService != null) { - localService.registerParticipant(this); - } + addSubscription(BASICEVENT); host = getHost(); pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS, TimeUnit.SECONDS); @@ -113,7 +108,7 @@ public class WemoHolmesHandler extends WemoBaseThingHandler { job.cancel(true); } this.pollingJob = null; - removeSubscription(); + super.dispose(); } private void poll() { @@ -130,14 +125,9 @@ public class WemoHolmesHandler extends WemoBaseThingHandler { logger.debug("UPnP device {} not yet registered", getUDN()); updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING, "@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]"); - synchronized (upnpLock) { - subscriptionState = new HashMap<>(); - } return; } - updateStatus(ThingStatus.ONLINE); updateWemoState(); - addSubscription(); } catch (Exception e) { logger.debug("Exception during poll: {}", e.getMessage(), e); } @@ -148,7 +138,7 @@ public class WemoHolmesHandler extends WemoBaseThingHandler { public void handleCommand(ChannelUID channelUID, Command command) { String localHost = getHost(); if (localHost.isEmpty()) { - logger.error("Failed to send command '{}' for device '{}': IP address missing", command, + logger.warn("Failed to send command '{}' for device '{}': IP address missing", command, getThing().getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/config-status.error.missing-ip"); @@ -156,7 +146,7 @@ public class WemoHolmesHandler extends WemoBaseThingHandler { } String wemoURL = getWemoURL(localHost, DEVICEACTION); if (wemoURL == null) { - logger.error("Failed to send command '{}' for device '{}': URL cannot be created", command, + logger.debug("Failed to send command '{}' for device '{}': URL cannot be created", command, getThing().getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/config-status.error.missing-url"); @@ -269,27 +259,12 @@ public class WemoHolmesHandler extends WemoBaseThingHandler { + "<attribute><name>" + attribute + "</name><value>" + value + "</value></attribute>" + "" + "" + ""; - String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); - if (wemoCallResponse != null && logger.isTraceEnabled()) { - logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID()); - logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID()); - logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID()); - logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID()); - } - } catch (RuntimeException e) { + wemoHttpCaller.executeCall(wemoURL, soapHeader, content); + updateStatus(ThingStatus.ONLINE); + } catch (IOException e) { logger.debug("Failed to send command '{}' for device '{}':", command, getThing().getUID(), e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } - updateStatus(ThingStatus.ONLINE); - } - - @Override - public void onServiceSubscribed(@Nullable String service, boolean succeeded) { - if (service != null) { - logger.debug("WeMo {}: Subscription to service {} {}", getUDN(), service, - succeeded ? "succeeded" : "failed"); - subscriptionState.put(service, succeeded); - } } @Override @@ -303,49 +278,6 @@ public class WemoHolmesHandler extends WemoBaseThingHandler { } } - private synchronized void addSubscription() { - synchronized (upnpLock) { - UpnpIOService localService = service; - if (localService != null) { - if (localService.isRegistered(this)) { - logger.debug("Checking WeMo GENA subscription for '{}'", getThing().getUID()); - - String subscription = BASICEVENT; - - if (subscriptionState.get(subscription) == null) { - logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(), - subscription); - localService.addSubscription(this, subscription, SUBSCRIPTION_DURATION_SECONDS); - subscriptionState.put(subscription, true); - } - } else { - logger.debug( - "Setting up WeMo GENA subscription for '{}' FAILED - service.isRegistered(this) is FALSE", - getThing().getUID()); - } - } - } - } - - private synchronized void removeSubscription() { - synchronized (upnpLock) { - UpnpIOService localService = service; - if (localService != null) { - if (localService.isRegistered(this)) { - logger.debug("Removing WeMo GENA subscription for '{}'", getThing().getUID()); - String subscription = BASICEVENT; - - if (subscriptionState.get(subscription) != null) { - logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription); - localService.removeSubscription(this, subscription); - } - subscriptionState.remove(subscription); - localService.unregisterParticipant(this); - } - } - } - } - /** * The {@link updateWemoState} polls the actual state of a WeMo device and * calls {@link onValueReceived} to update the statemap and channels.. @@ -354,7 +286,7 @@ public class WemoHolmesHandler extends WemoBaseThingHandler { protected void updateWemoState() { String localHost = getHost(); if (localHost.isEmpty()) { - logger.error("Failed to get actual state for device '{}': IP address missing", getThing().getUID()); + logger.warn("Failed to get actual state for device '{}': IP address missing", getThing().getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/config-status.error.missing-ip"); return; @@ -362,7 +294,7 @@ public class WemoHolmesHandler extends WemoBaseThingHandler { String actionService = DEVICEACTION; String wemoURL = getWemoURL(localHost, actionService); if (wemoURL == null) { - logger.error("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID()); + logger.debug("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/config-status.error.missing-url"); return; @@ -372,153 +304,49 @@ public class WemoHolmesHandler extends WemoBaseThingHandler { String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\""; String content = createStateRequestContent(action, actionService); String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); - if (wemoCallResponse != null) { - if (logger.isTraceEnabled()) { - logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID()); - logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID()); - logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID()); - logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID()); - } + String stringParser = substringBetween(wemoCallResponse, "", ""); - String stringParser = substringBetween(wemoCallResponse, "", ""); + // Due to Belkins bad response formatting, we need to run this twice. + stringParser = unescapeXml(stringParser); + stringParser = unescapeXml(stringParser); - // Due to Belkins bad response formatting, we need to run this twice. - stringParser = unescapeXml(stringParser); - stringParser = unescapeXml(stringParser); + logger.trace("AirPurifier response '{}' for device '{}' received", stringParser, getThing().getUID()); - logger.trace("AirPurifier response '{}' for device '{}' received", stringParser, getThing().getUID()); + stringParser = "" + stringParser + ""; - stringParser = "" + stringParser + ""; + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + // see + // https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); + dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + dbf.setXIncludeAware(false); + dbf.setExpandEntityReferences(false); + DocumentBuilder db = dbf.newDocumentBuilder(); + InputSource is = new InputSource(); + is.setCharacterStream(new StringReader(stringParser)); - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - // see - // https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html - dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); - dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); - dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); - dbf.setXIncludeAware(false); - dbf.setExpandEntityReferences(false); - DocumentBuilder db = dbf.newDocumentBuilder(); - InputSource is = new InputSource(); - is.setCharacterStream(new StringReader(stringParser)); + Document doc = db.parse(is); + NodeList nodes = doc.getElementsByTagName("attribute"); - Document doc = db.parse(is); - NodeList nodes = doc.getElementsByTagName("attribute"); + // iterate the attributes + for (int i = 0; i < nodes.getLength(); i++) { + Element element = (Element) nodes.item(i); - // iterate the attributes - for (int i = 0; i < nodes.getLength(); i++) { - Element element = (Element) nodes.item(i); + NodeList deviceIndex = element.getElementsByTagName("name"); + Element line = (Element) deviceIndex.item(0); + String attributeName = getCharacterDataFromElement(line); + logger.trace("attributeName: {}", attributeName); - NodeList deviceIndex = element.getElementsByTagName("name"); - Element line = (Element) deviceIndex.item(0); - String attributeName = getCharacterDataFromElement(line); - logger.trace("attributeName: {}", attributeName); + NodeList deviceID = element.getElementsByTagName("value"); + line = (Element) deviceID.item(0); + String attributeValue = getCharacterDataFromElement(line); + logger.trace("attributeValue: {}", attributeValue); - NodeList deviceID = element.getElementsByTagName("value"); - line = (Element) deviceID.item(0); - String attributeValue = getCharacterDataFromElement(line); - logger.trace("attributeValue: {}", attributeValue); - - State newMode = new StringType(); - switch (attributeName) { - case "Mode": - if ("purifier".equals(getThing().getThingTypeUID().getId())) { - switch (attributeValue) { - case "0": - newMode = new StringType("OFF"); - break; - case "1": - newMode = new StringType("LOW"); - break; - case "2": - newMode = new StringType("MED"); - break; - case "3": - newMode = new StringType("HIGH"); - break; - case "4": - newMode = new StringType("AUTO"); - break; - } - updateState(CHANNEL_PURIFIERMODE, newMode); - } else { - switch (attributeValue) { - case "0": - newMode = new StringType("OFF"); - break; - case "1": - newMode = new StringType("FROSTPROTECT"); - break; - case "2": - newMode = new StringType("HIGH"); - break; - case "3": - newMode = new StringType("LOW"); - break; - case "4": - newMode = new StringType("ECO"); - break; - } - updateState(CHANNEL_HEATERMODE, newMode); - } - break; - case "Ionizer": - switch (attributeValue) { - case "0": - newMode = OnOffType.OFF; - break; - case "1": - newMode = OnOffType.ON; - break; - } - updateState(CHANNEL_IONIZER, newMode); - break; - case "AirQuality": - switch (attributeValue) { - case "0": - newMode = new StringType("POOR"); - break; - case "1": - newMode = new StringType("MODERATE"); - break; - case "2": - newMode = new StringType("GOOD"); - break; - } - updateState(CHANNEL_AIRQUALITY, newMode); - break; - case "FilterLife": - int filterLife = Integer.valueOf(attributeValue); - if ("purifier".equals(getThing().getThingTypeUID().getId())) { - filterLife = Math.round((filterLife / FILTER_LIFE_MINS) * 100); - } else { - filterLife = Math.round((filterLife / 60480) * 100); - } - updateState(CHANNEL_FILTERLIFE, new PercentType(String.valueOf(filterLife))); - break; - case "ExpiredFilterTime": - switch (attributeValue) { - case "0": - newMode = OnOffType.OFF; - break; - case "1": - newMode = OnOffType.ON; - break; - } - updateState(CHANNEL_EXPIREDFILTERTIME, newMode); - break; - case "FilterPresent": - switch (attributeValue) { - case "0": - newMode = OnOffType.OFF; - break; - case "1": - newMode = OnOffType.ON; - break; - } - updateState(CHANNEL_FILTERPRESENT, newMode); - break; - case "FANMode": + State newMode = new StringType(); + switch (attributeName) { + case "Mode": + if ("purifier".equals(getThing().getThingTypeUID().getId())) { switch (attributeValue) { case "0": newMode = new StringType("OFF"); @@ -537,54 +365,149 @@ public class WemoHolmesHandler extends WemoBaseThingHandler { break; } updateState(CHANNEL_PURIFIERMODE, newMode); - break; - case "DesiredHumidity": + } else { switch (attributeValue) { case "0": - newMode = new PercentType("45"); + newMode = new StringType("OFF"); break; case "1": - newMode = new PercentType("50"); + newMode = new StringType("FROSTPROTECT"); break; case "2": - newMode = new PercentType("55"); + newMode = new StringType("HIGH"); break; case "3": - newMode = new PercentType("60"); + newMode = new StringType("LOW"); break; case "4": - newMode = new PercentType("100"); + newMode = new StringType("ECO"); break; } - updateState(CHANNEL_DESIREDHUMIDITY, newMode); - break; - case "CurrentHumidity": - newMode = new StringType(attributeValue); - updateState(CHANNEL_CURRENTHUMIDITY, newMode); - break; - case "Temperature": - newMode = new StringType(attributeValue); - updateState(CHANNEL_CURRENTTEMP, newMode); - break; - case "SetTemperature": - newMode = new StringType(attributeValue); - updateState(CHANNEL_TARGETTEMP, newMode); - break; - case "AutoOffTime": - newMode = new StringType(attributeValue); - updateState(CHANNEL_AUTOOFFTIME, newMode); - break; - case "TimeRemaining": - newMode = new StringType(attributeValue); - updateState(CHANNEL_HEATINGREMAINING, newMode); - break; - } + updateState(CHANNEL_HEATERMODE, newMode); + } + break; + case "Ionizer": + switch (attributeValue) { + case "0": + newMode = OnOffType.OFF; + break; + case "1": + newMode = OnOffType.ON; + break; + } + updateState(CHANNEL_IONIZER, newMode); + break; + case "AirQuality": + switch (attributeValue) { + case "0": + newMode = new StringType("POOR"); + break; + case "1": + newMode = new StringType("MODERATE"); + break; + case "2": + newMode = new StringType("GOOD"); + break; + } + updateState(CHANNEL_AIRQUALITY, newMode); + break; + case "FilterLife": + int filterLife = Integer.valueOf(attributeValue); + if ("purifier".equals(getThing().getThingTypeUID().getId())) { + filterLife = Math.round((filterLife / FILTER_LIFE_MINS) * 100); + } else { + filterLife = Math.round((filterLife / 60480) * 100); + } + updateState(CHANNEL_FILTERLIFE, new PercentType(String.valueOf(filterLife))); + break; + case "ExpiredFilterTime": + switch (attributeValue) { + case "0": + newMode = OnOffType.OFF; + break; + case "1": + newMode = OnOffType.ON; + break; + } + updateState(CHANNEL_EXPIREDFILTERTIME, newMode); + break; + case "FilterPresent": + switch (attributeValue) { + case "0": + newMode = OnOffType.OFF; + break; + case "1": + newMode = OnOffType.ON; + break; + } + updateState(CHANNEL_FILTERPRESENT, newMode); + break; + case "FANMode": + switch (attributeValue) { + case "0": + newMode = new StringType("OFF"); + break; + case "1": + newMode = new StringType("LOW"); + break; + case "2": + newMode = new StringType("MED"); + break; + case "3": + newMode = new StringType("HIGH"); + break; + case "4": + newMode = new StringType("AUTO"); + break; + } + updateState(CHANNEL_PURIFIERMODE, newMode); + break; + case "DesiredHumidity": + switch (attributeValue) { + case "0": + newMode = new PercentType("45"); + break; + case "1": + newMode = new PercentType("50"); + break; + case "2": + newMode = new PercentType("55"); + break; + case "3": + newMode = new PercentType("60"); + break; + case "4": + newMode = new PercentType("100"); + break; + } + updateState(CHANNEL_DESIREDHUMIDITY, newMode); + break; + case "CurrentHumidity": + newMode = new StringType(attributeValue); + updateState(CHANNEL_CURRENTHUMIDITY, newMode); + break; + case "Temperature": + newMode = new StringType(attributeValue); + updateState(CHANNEL_CURRENTTEMP, newMode); + break; + case "SetTemperature": + newMode = new StringType(attributeValue); + updateState(CHANNEL_TARGETTEMP, newMode); + break; + case "AutoOffTime": + newMode = new StringType(attributeValue); + updateState(CHANNEL_AUTOOFFTIME, newMode); + break; + case "TimeRemaining": + newMode = new StringType(attributeValue); + updateState(CHANNEL_HEATINGREMAINING, newMode); + break; } } + updateStatus(ThingStatus.ONLINE); } catch (RuntimeException | ParserConfigurationException | SAXException | IOException e) { logger.debug("Failed to get actual state for device '{}':", getThing().getUID(), e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } - updateStatus(ThingStatus.ONLINE); } } diff --git a/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoInsightHandler.java b/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoInsightHandler.java index 4383233f0..02ca4ef6e 100644 --- a/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoInsightHandler.java +++ b/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoInsightHandler.java @@ -84,7 +84,7 @@ public class WemoInsightHandler extends WemoHandler { try { lastChangedAt = Long.parseLong(splitInsightParams[1]) * 1000; // convert s to ms } catch (NumberFormatException e) { - logger.error("Unable to parse lastChangedAt value '{}' for device '{}'; expected long", + logger.warn("Unable to parse lastChangedAt value '{}' for device '{}'; expected long", splitInsightParams[1], getThing().getUID()); } ZonedDateTime zoned = ZonedDateTime.ofInstant(Instant.ofEpochMilli(lastChangedAt), diff --git a/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoLightHandler.java b/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoLightHandler.java index 94c5df714..745e33db6 100644 --- a/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoLightHandler.java +++ b/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoLightHandler.java @@ -15,8 +15,6 @@ package org.openhab.binding.wemo.internal.handler; import static org.openhab.binding.wemo.internal.WemoBindingConstants.*; import static org.openhab.binding.wemo.internal.WemoUtil.*; -import java.util.HashMap; -import java.util.Map; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -52,9 +50,6 @@ public class WemoLightHandler extends WemoBaseThingHandler { private final Logger logger = LoggerFactory.getLogger(WemoLightHandler.class); - private Map subscriptionState = new HashMap<>(); - - private final Object upnpLock = new Object(); private final Object jobLock = new Object(); private @Nullable WemoBridgeHandler wemoBridgeHandler; @@ -68,8 +63,6 @@ public class WemoLightHandler extends WemoBaseThingHandler { */ private static final int DIM_STEPSIZE = 5; - protected static final String SUBSCRIPTION = "bridge1"; - /** * The default refresh initial delay in Seconds. */ @@ -85,15 +78,13 @@ public class WemoLightHandler extends WemoBaseThingHandler { @Override public void initialize() { + super.initialize(); // initialize() is only called if the required parameter 'deviceID' is available wemoLightID = (String) getConfig().get(DEVICE_ID); final Bridge bridge = getBridge(); if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE) { - UpnpIOService localService = service; - if (localService != null) { - localService.registerParticipant(this); - } + addSubscription(BRIDGEEVENT); host = getHost(); pollingJob = scheduler.scheduleWithFixedDelay(this::poll, DEFAULT_REFRESH_INITIAL_DELAY, DEFAULT_REFRESH_INTERVAL_SECONDS, TimeUnit.SECONDS); @@ -119,20 +110,20 @@ public class WemoLightHandler extends WemoBaseThingHandler { @Override public void dispose() { - logger.debug("WeMoLightHandler disposed."); + logger.debug("WemoLightHandler disposed."); ScheduledFuture job = this.pollingJob; if (job != null && !job.isCancelled()) { job.cancel(true); } this.pollingJob = null; - removeSubscription(); + super.dispose(); } private synchronized @Nullable WemoBridgeHandler getWemoBridgeHandler() { Bridge bridge = getBridge(); if (bridge == null) { - logger.error("Required bridge not defined for device {}.", wemoLightID); + logger.warn("Required bridge not defined for device {}.", wemoLightID); return null; } ThingHandler handler = bridge.getHandler(); @@ -159,14 +150,9 @@ public class WemoLightHandler extends WemoBaseThingHandler { logger.debug("UPnP device {} not yet registered", getUDN()); updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING, "@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]"); - synchronized (upnpLock) { - subscriptionState = new HashMap<>(); - } return; } - updateStatus(ThingStatus.ONLINE); getDeviceState(); - addSubscription(); } catch (Exception e) { logger.debug("Exception during poll: {}", e.getMessage(), e); } @@ -177,7 +163,7 @@ public class WemoLightHandler extends WemoBaseThingHandler { public void handleCommand(ChannelUID channelUID, Command command) { String localHost = getHost(); if (localHost.isEmpty()) { - logger.error("Failed to send command '{}' for device '{}': IP address missing", command, + logger.warn("Failed to send command '{}' for device '{}': IP address missing", command, getThing().getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/config-status.error.missing-ip"); @@ -185,7 +171,7 @@ public class WemoLightHandler extends WemoBaseThingHandler { } String wemoURL = getWemoURL(localHost, BASICACTION); if (wemoURL == null) { - logger.error("Failed to send command '{}' for device '{}': URL cannot be created", command, + logger.debug("Failed to send command '{}' for device '{}': URL cannot be created", command, getThing().getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/config-status.error.missing-url"); @@ -277,25 +263,18 @@ public class WemoLightHandler extends WemoBaseThingHandler { + "</CapabilityValue></DeviceStatus>" + "" + "" + "" + ""; - String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); - if (wemoCallResponse != null) { - if (logger.isTraceEnabled()) { - logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID()); - logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, - getThing().getUID()); - logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID()); - logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, - getThing().getUID()); - } - if ("10008".equals(capability)) { - OnOffType binaryState = null; - binaryState = "0".equals(value) ? OnOffType.OFF : OnOffType.ON; - updateState(CHANNEL_STATE, binaryState); - } + wemoHttpCaller.executeCall(wemoURL, soapHeader, content); + if ("10008".equals(capability)) { + OnOffType binaryState = null; + binaryState = "0".equals(value) ? OnOffType.OFF : OnOffType.ON; + updateState(CHANNEL_STATE, binaryState); } + updateStatus(ThingStatus.ONLINE); } } catch (Exception e) { - throw new IllegalStateException("Could not send command to WeMo Bridge", e); + logger.warn("Failed to send command '{}' for device '{}': {}", command, getThing().getUID(), + e.getMessage()); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); } } } @@ -317,7 +296,7 @@ public class WemoLightHandler extends WemoBaseThingHandler { public void getDeviceState() { String localHost = getHost(); if (localHost.isEmpty()) { - logger.error("Failed to get actual state for device '{}': IP address missing", getThing().getUID()); + logger.warn("Failed to get actual state for device '{}': IP address missing", getThing().getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/config-status.error.missing-ip"); return; @@ -325,7 +304,7 @@ public class WemoLightHandler extends WemoBaseThingHandler { logger.debug("Request actual state for LightID '{}'", wemoLightID); String wemoURL = getWemoURL(localHost, BRIDGEACTION); if (wemoURL == null) { - logger.error("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID()); + logger.debug("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/config-status.error.missing-url"); return; @@ -338,43 +317,33 @@ public class WemoLightHandler extends WemoBaseThingHandler { + wemoLightID + "" + "" + "" + ""; String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); - if (wemoCallResponse != null) { - if (logger.isTraceEnabled()) { - logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID()); - logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID()); - logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID()); - logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID()); - } - wemoCallResponse = unescapeXml(wemoCallResponse); - String response = substringBetween(wemoCallResponse, "", ""); - logger.trace("wemoNewLightState = {}", response); - String[] splitResponse = response.split(","); - if (splitResponse[0] != null) { - OnOffType binaryState = null; - binaryState = "0".equals(splitResponse[0]) ? OnOffType.OFF : OnOffType.ON; - updateState(CHANNEL_STATE, binaryState); - } - if (splitResponse[1] != null) { - String splitBrightness[] = splitResponse[1].split(":"); - if (splitBrightness[0] != null) { - int newBrightnessValue = Integer.valueOf(splitBrightness[0]); - int newBrightness = Math.round(newBrightnessValue * 100 / 255); - logger.trace("newBrightness = {}", newBrightness); - State newBrightnessState = new PercentType(newBrightness); - updateState(CHANNEL_BRIGHTNESS, newBrightnessState); - currentBrightness = newBrightness; - } + wemoCallResponse = unescapeXml(wemoCallResponse); + String response = substringBetween(wemoCallResponse, "", ""); + logger.trace("wemoNewLightState = {}", response); + String[] splitResponse = response.split(","); + if (splitResponse[0] != null) { + OnOffType binaryState = null; + binaryState = "0".equals(splitResponse[0]) ? OnOffType.OFF : OnOffType.ON; + updateState(CHANNEL_STATE, binaryState); + } + if (splitResponse[1] != null) { + String splitBrightness[] = splitResponse[1].split(":"); + if (splitBrightness[0] != null) { + int newBrightnessValue = Integer.valueOf(splitBrightness[0]); + int newBrightness = Math.round(newBrightnessValue * 100 / 255); + logger.trace("newBrightness = {}", newBrightness); + State newBrightnessState = new PercentType(newBrightness); + updateState(CHANNEL_BRIGHTNESS, newBrightnessState); + currentBrightness = newBrightness; } } + updateStatus(ThingStatus.ONLINE); } catch (Exception e) { - throw new IllegalStateException("Could not retrieve new Wemo light state", e); + logger.debug("Could not retrieve new Wemo light state for '{}':", getThing().getUID(), e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); } } - @Override - public void onServiceSubscribed(@Nullable String service, boolean succeeded) { - } - @Override public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) { logger.trace("Received pair '{}':'{}' (service '{}') for thing '{}'", @@ -399,44 +368,4 @@ public class WemoLightHandler extends WemoBaseThingHandler { break; } } - - private synchronized void addSubscription() { - synchronized (upnpLock) { - UpnpIOService localService = service; - if (localService != null) { - if (localService.isRegistered(this)) { - logger.debug("Checking WeMo GENA subscription for '{}'", getThing().getUID()); - - if (subscriptionState.get(SUBSCRIPTION) == null) { - logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(), - SUBSCRIPTION); - localService.addSubscription(this, SUBSCRIPTION, SUBSCRIPTION_DURATION_SECONDS); - subscriptionState.put(SUBSCRIPTION, true); - } - } else { - logger.debug( - "Setting up WeMo GENA subscription for '{}' FAILED - service.isRegistered(this) is FALSE", - getThing().getUID()); - } - } - } - } - - private synchronized void removeSubscription() { - synchronized (upnpLock) { - UpnpIOService localService = service; - if (localService != null) { - if (localService.isRegistered(this)) { - logger.debug("Removing WeMo GENA subscription for '{}'", getThing().getUID()); - - if (subscriptionState.get(SUBSCRIPTION) != null) { - logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), SUBSCRIPTION); - localService.removeSubscription(this, SUBSCRIPTION); - } - subscriptionState = new HashMap<>(); - localService.unregisterParticipant(this); - } - } - } - } } diff --git a/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoMakerHandler.java b/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoMakerHandler.java index 2bfb66ded..ada6c3b99 100644 --- a/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoMakerHandler.java +++ b/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoMakerHandler.java @@ -70,14 +70,11 @@ public class WemoMakerHandler extends WemoBaseThingHandler { @Override public void initialize() { + super.initialize(); Configuration configuration = getConfig(); if (configuration.get(UDN) != null) { logger.debug("Initializing WemoMakerHandler for UDN '{}'", configuration.get(UDN)); - UpnpIOService localService = service; - if (localService != null) { - localService.registerParticipant(this); - } host = getHost(); pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS, TimeUnit.SECONDS); @@ -91,17 +88,14 @@ public class WemoMakerHandler extends WemoBaseThingHandler { @Override public void dispose() { - logger.debug("WeMoMakerHandler disposed."); + logger.debug("WemoMakerHandler disposed."); ScheduledFuture job = this.pollingJob; if (job != null && !job.isCancelled()) { job.cancel(true); } this.pollingJob = null; - UpnpIOService localService = service; - if (localService != null) { - localService.unregisterParticipant(this); - } + super.dispose(); } private void poll() { @@ -120,7 +114,6 @@ public class WemoMakerHandler extends WemoBaseThingHandler { "@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]"); return; } - updateStatus(ThingStatus.ONLINE); updateWemoState(); } catch (Exception e) { logger.debug("Exception during poll: {}", e.getMessage(), e); @@ -132,7 +125,7 @@ public class WemoMakerHandler extends WemoBaseThingHandler { public void handleCommand(ChannelUID channelUID, Command command) { String localHost = getHost(); if (localHost.isEmpty()) { - logger.error("Failed to send command '{}' for device '{}': IP address missing", command, + logger.warn("Failed to send command '{}' for device '{}': IP address missing", command, getThing().getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/config-status.error.missing-ip"); @@ -140,7 +133,7 @@ public class WemoMakerHandler extends WemoBaseThingHandler { } String wemoURL = getWemoURL(localHost, BASICACTION); if (wemoURL == null) { - logger.error("Failed to send command '{}' for device '{}': URL cannot be created", command, + logger.debug("Failed to send command '{}' for device '{}': URL cannot be created", command, getThing().getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/config-status.error.missing-url"); @@ -158,16 +151,11 @@ public class WemoMakerHandler extends WemoBaseThingHandler { boolean binaryState = OnOffType.ON.equals(command) ? true : false; String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\""; String content = createBinaryStateContent(binaryState); - String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); - if (wemoCallResponse != null && logger.isTraceEnabled()) { - logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID()); - logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID()); - logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID()); - logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, - getThing().getUID()); - } + wemoHttpCaller.executeCall(wemoURL, soapHeader, content); + updateStatus(ThingStatus.ONLINE); } catch (Exception e) { - logger.error("Failed to send command '{}' for device '{}' ", command, getThing().getUID(), e); + logger.warn("Failed to send command '{}' for device '{}' ", command, getThing().getUID(), e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); } } } @@ -179,7 +167,7 @@ public class WemoMakerHandler extends WemoBaseThingHandler { protected void updateWemoState() { String localHost = getHost(); if (localHost.isEmpty()) { - logger.error("Failed to get actual state for device '{}': IP address missing", getThing().getUID()); + logger.warn("Failed to get actual state for device '{}': IP address missing", getThing().getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/config-status.error.missing-ip"); return; @@ -187,7 +175,7 @@ public class WemoMakerHandler extends WemoBaseThingHandler { String actionService = DEVICEACTION; String wemoURL = getWemoURL(localHost, actionService); if (wemoURL == null) { - logger.error("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID()); + logger.debug("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/config-status.error.missing-url"); return; @@ -197,75 +185,69 @@ public class WemoMakerHandler extends WemoBaseThingHandler { String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\""; String content = createStateRequestContent(action, actionService); String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); - if (wemoCallResponse != null) { - if (logger.isTraceEnabled()) { - logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID()); - logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID()); - logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID()); - logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID()); - } - try { - String stringParser = substringBetween(wemoCallResponse, "", ""); - logger.trace("Escaped Maker response for device '{}' :", getThing().getUID()); - logger.trace("'{}'", stringParser); + try { + String stringParser = substringBetween(wemoCallResponse, "", ""); + logger.trace("Escaped Maker response for device '{}' :", getThing().getUID()); + logger.trace("'{}'", stringParser); - // Due to Belkins bad response formatting, we need to run this twice. - stringParser = unescapeXml(stringParser); - stringParser = unescapeXml(stringParser); - logger.trace("Maker response '{}' for device '{}' received", stringParser, getThing().getUID()); + // Due to Belkins bad response formatting, we need to run this twice. + stringParser = unescapeXml(stringParser); + stringParser = unescapeXml(stringParser); + logger.trace("Maker response '{}' for device '{}' received", stringParser, getThing().getUID()); - stringParser = "" + stringParser + ""; + stringParser = "" + stringParser + ""; - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - // see - // https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html - dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); - dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); - dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); - dbf.setXIncludeAware(false); - dbf.setExpandEntityReferences(false); - DocumentBuilder db = dbf.newDocumentBuilder(); - InputSource is = new InputSource(); - is.setCharacterStream(new StringReader(stringParser)); + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + // see + // https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); + dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + dbf.setXIncludeAware(false); + dbf.setExpandEntityReferences(false); + DocumentBuilder db = dbf.newDocumentBuilder(); + InputSource is = new InputSource(); + is.setCharacterStream(new StringReader(stringParser)); - Document doc = db.parse(is); - NodeList nodes = doc.getElementsByTagName("attribute"); + Document doc = db.parse(is); + NodeList nodes = doc.getElementsByTagName("attribute"); - // iterate the attributes - for (int i = 0; i < nodes.getLength(); i++) { - Element element = (Element) nodes.item(i); + // iterate the attributes + for (int i = 0; i < nodes.getLength(); i++) { + Element element = (Element) nodes.item(i); - NodeList deviceIndex = element.getElementsByTagName("name"); - Element line = (Element) deviceIndex.item(0); - String attributeName = getCharacterDataFromElement(line); - logger.trace("attributeName: {}", attributeName); + NodeList deviceIndex = element.getElementsByTagName("name"); + Element line = (Element) deviceIndex.item(0); + String attributeName = getCharacterDataFromElement(line); + logger.trace("attributeName: {}", attributeName); - NodeList deviceID = element.getElementsByTagName("value"); - line = (Element) deviceID.item(0); - String attributeValue = getCharacterDataFromElement(line); - logger.trace("attributeValue: {}", attributeValue); + NodeList deviceID = element.getElementsByTagName("value"); + line = (Element) deviceID.item(0); + String attributeValue = getCharacterDataFromElement(line); + logger.trace("attributeValue: {}", attributeValue); - switch (attributeName) { - case "Switch": - State relayState = "0".equals(attributeValue) ? OnOffType.OFF : OnOffType.ON; - logger.debug("New relayState '{}' for device '{}' received", relayState, - getThing().getUID()); - updateState(CHANNEL_RELAY, relayState); - break; - case "Sensor": - State sensorState = "1".equals(attributeValue) ? OnOffType.OFF : OnOffType.ON; - logger.debug("New sensorState '{}' for device '{}' received", sensorState, - getThing().getUID()); - updateState(CHANNEL_SENSOR, sensorState); - break; - } + switch (attributeName) { + case "Switch": + State relayState = "0".equals(attributeValue) ? OnOffType.OFF : OnOffType.ON; + logger.debug("New relayState '{}' for device '{}' received", relayState, + getThing().getUID()); + updateState(CHANNEL_RELAY, relayState); + break; + case "Sensor": + State sensorState = "1".equals(attributeValue) ? OnOffType.OFF : OnOffType.ON; + logger.debug("New sensorState '{}' for device '{}' received", sensorState, + getThing().getUID()); + updateState(CHANNEL_SENSOR, sensorState); + break; } - } catch (Exception e) { - logger.error("Failed to parse attributeList for WeMo Maker '{}'", this.getThing().getUID(), e); } + updateStatus(ThingStatus.ONLINE); + } catch (Exception e) { + logger.warn("Failed to parse attributeList for WeMo Maker '{}'", this.getThing().getUID(), e); } } catch (Exception e) { - logger.error("Failed to get attributes for device '{}'", getThing().getUID(), e); + logger.warn("Failed to get attributes for device '{}'", getThing().getUID(), e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } } } diff --git a/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/http/WemoHttpCall.java b/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/http/WemoHttpCall.java index fa1e4c5d2..30314d7cf 100644 --- a/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/http/WemoHttpCall.java +++ b/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/http/WemoHttpCall.java @@ -19,7 +19,6 @@ import java.nio.charset.StandardCharsets; import java.util.Properties; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.wemo.internal.WemoBindingConstants; import org.openhab.core.io.net.http.HttpUtil; import org.slf4j.Logger; @@ -35,20 +34,18 @@ public class WemoHttpCall { private final Logger logger = LoggerFactory.getLogger(WemoHttpCall.class); - public @Nullable String executeCall(String wemoURL, String soapHeader, String content) { - try { - Properties wemoHeaders = new Properties(); - wemoHeaders.setProperty("CONTENT-TYPE", WemoBindingConstants.HTTP_CALL_CONTENT_HEADER); - wemoHeaders.put("SOAPACTION", soapHeader); + public String executeCall(String wemoURL, String soapHeader, String content) throws IOException { + Properties wemoHeaders = new Properties(); + wemoHeaders.setProperty("CONTENT-TYPE", WemoBindingConstants.HTTP_CALL_CONTENT_HEADER); + wemoHeaders.put("SOAPACTION", soapHeader); - InputStream wemoContent = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)); + InputStream wemoContent = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)); - String wemoCallResponse = HttpUtil.executeUrl("POST", wemoURL, wemoHeaders, wemoContent, null, 2000); - return wemoCallResponse; - } catch (IOException e) { - // throw new IllegalStateException("Could not call WeMo", e); - logger.debug("Could not make HTTP call to WeMo"); - return null; - } + logger.trace("Performing HTTP call for URL: '{}', header: '{}', request body: '{}'", wemoURL, soapHeader, + content); + String responseBody = HttpUtil.executeUrl("POST", wemoURL, wemoHeaders, wemoContent, null, 2000); + logger.trace("HTTP response body: '{}'", responseBody); + + return responseBody; } } diff --git a/itests/org.openhab.binding.wemo.tests/src/main/java/org/openhab/binding/wemo/internal/discovery/test/WemoLinkDiscoveryServiceOSGiTest.java b/itests/org.openhab.binding.wemo.tests/src/main/java/org/openhab/binding/wemo/internal/discovery/test/WemoLinkDiscoveryServiceOSGiTest.java index 68cab0533..cc2fc7c93 100644 --- a/itests/org.openhab.binding.wemo.tests/src/main/java/org/openhab/binding/wemo/internal/discovery/test/WemoLinkDiscoveryServiceOSGiTest.java +++ b/itests/org.openhab.binding.wemo.tests/src/main/java/org/openhab/binding/wemo/internal/discovery/test/WemoLinkDiscoveryServiceOSGiTest.java @@ -50,7 +50,7 @@ public class WemoLinkDiscoveryServiceOSGiTest extends GenericWemoLightOSGiTestPa @Test public void assertSupportedThingIsDiscovered() - throws MalformedURLException, URISyntaxException, ValidationException { + throws MalformedURLException, URISyntaxException, ValidationException, IOException { String model = WemoBindingConstants.THING_TYPE_MZ100.getId(); addUpnpDevice(SERVICE_ID, SERVICE_NUMBER, model); diff --git a/itests/org.openhab.binding.wemo.tests/src/main/java/org/openhab/binding/wemo/internal/handler/test/WemoHandlerOSGiTest.java b/itests/org.openhab.binding.wemo.tests/src/main/java/org/openhab/binding/wemo/internal/handler/test/WemoHandlerOSGiTest.java index 70429419e..c8f3ad0d1 100644 --- a/itests/org.openhab.binding.wemo.tests/src/main/java/org/openhab/binding/wemo/internal/handler/test/WemoHandlerOSGiTest.java +++ b/itests/org.openhab.binding.wemo.tests/src/main/java/org/openhab/binding/wemo/internal/handler/test/WemoHandlerOSGiTest.java @@ -69,7 +69,7 @@ public class WemoHandlerOSGiTest extends GenericWemoOSGiTest { @Test public void assertThatThingHandlesOnOffCommandCorrectly() - throws MalformedURLException, URISyntaxException, ValidationException { + throws MalformedURLException, URISyntaxException, ValidationException, IOException { Command command = OnOffType.OFF; Thing thing = createThing(THING_TYPE_UID, DEFAULT_TEST_CHANNEL, DEFAULT_TEST_CHANNEL_TYPE); @@ -105,7 +105,7 @@ public class WemoHandlerOSGiTest extends GenericWemoOSGiTest { @Test public void assertThatThingHandlesREFRESHCommandCorrectly() - throws MalformedURLException, URISyntaxException, ValidationException { + throws MalformedURLException, URISyntaxException, ValidationException, IOException { Command command = RefreshType.REFRESH; Thing thing = createThing(THING_TYPE_UID, DEFAULT_TEST_CHANNEL, DEFAULT_TEST_CHANNEL_TYPE); diff --git a/itests/org.openhab.binding.wemo.tests/src/main/java/org/openhab/binding/wemo/internal/handler/test/WemoLightHandlerOSGiTest.java b/itests/org.openhab.binding.wemo.tests/src/main/java/org/openhab/binding/wemo/internal/handler/test/WemoLightHandlerOSGiTest.java index cb03331bc..8442b83cc 100644 --- a/itests/org.openhab.binding.wemo.tests/src/main/java/org/openhab/binding/wemo/internal/handler/test/WemoLightHandlerOSGiTest.java +++ b/itests/org.openhab.binding.wemo.tests/src/main/java/org/openhab/binding/wemo/internal/handler/test/WemoLightHandlerOSGiTest.java @@ -65,7 +65,7 @@ public class WemoLightHandlerOSGiTest extends GenericWemoLightOSGiTestParent { @Test public void handleONcommandForBRIGHTNESSchannel() - throws MalformedURLException, URISyntaxException, ValidationException { + throws MalformedURLException, URISyntaxException, ValidationException, IOException { Command command = OnOffType.ON; String channelID = WemoBindingConstants.CHANNEL_BRIGHTNESS; @@ -80,7 +80,7 @@ public class WemoLightHandlerOSGiTest extends GenericWemoLightOSGiTestParent { @Test public void handlePercentCommandForBRIGHTNESSChannel() - throws MalformedURLException, URISyntaxException, ValidationException { + throws MalformedURLException, URISyntaxException, ValidationException, IOException { // Set brightness value to 20 Percent Command command = new PercentType(20); String channelID = WemoBindingConstants.CHANNEL_BRIGHTNESS; @@ -95,7 +95,7 @@ public class WemoLightHandlerOSGiTest extends GenericWemoLightOSGiTestParent { @Test public void handleIncreaseCommandForBRIGHTNESSchannel() - throws MalformedURLException, URISyntaxException, ValidationException { + throws MalformedURLException, URISyntaxException, ValidationException, IOException { // The value is increased by 5 Percents by default Command command = IncreaseDecreaseType.INCREASE; String channelID = WemoBindingConstants.CHANNEL_BRIGHTNESS; @@ -110,7 +110,7 @@ public class WemoLightHandlerOSGiTest extends GenericWemoLightOSGiTestParent { @Test public void handleDecreaseCommandForBRIGHTNESSchannel() - throws MalformedURLException, URISyntaxException, ValidationException { + throws MalformedURLException, URISyntaxException, ValidationException, IOException { // The value can not be decreased below 0 Command command = IncreaseDecreaseType.DECREASE; String channelID = WemoBindingConstants.CHANNEL_BRIGHTNESS; @@ -123,7 +123,8 @@ public class WemoLightHandlerOSGiTest extends GenericWemoLightOSGiTestParent { } @Test - public void handleOnCommandForSTATEChannel() throws MalformedURLException, URISyntaxException, ValidationException { + public void handleOnCommandForSTATEChannel() + throws MalformedURLException, URISyntaxException, ValidationException, IOException { Command command = OnOffType.ON; String channelID = WemoBindingConstants.CHANNEL_STATE; @@ -137,7 +138,7 @@ public class WemoLightHandlerOSGiTest extends GenericWemoLightOSGiTestParent { @Test public void handleREFRESHCommandForChannelSTATE() - throws MalformedURLException, URISyntaxException, ValidationException { + throws MalformedURLException, URISyntaxException, ValidationException, IOException { Command command = RefreshType.REFRESH; String channelID = WemoBindingConstants.CHANNEL_STATE; @@ -149,7 +150,7 @@ public class WemoLightHandlerOSGiTest extends GenericWemoLightOSGiTestParent { } private void assertRequestForCommand(String channelID, Command command, String action, String value, - String capitability) throws MalformedURLException, URISyntaxException, ValidationException { + String capitability) throws MalformedURLException, URISyntaxException, ValidationException, IOException { Thing bridge = createBridge(BRIDGE_TYPE_UID); Thing thing = createThing(THING_TYPE_UID, DEFAULT_TEST_CHANNEL, DEFAULT_TEST_CHANNEL_TYPE); diff --git a/itests/org.openhab.binding.wemo.tests/src/main/java/org/openhab/binding/wemo/internal/handler/test/WemoMakerHandlerOSGiTest.java b/itests/org.openhab.binding.wemo.tests/src/main/java/org/openhab/binding/wemo/internal/handler/test/WemoMakerHandlerOSGiTest.java index 1ab8cb642..fa8826140 100644 --- a/itests/org.openhab.binding.wemo.tests/src/main/java/org/openhab/binding/wemo/internal/handler/test/WemoMakerHandlerOSGiTest.java +++ b/itests/org.openhab.binding.wemo.tests/src/main/java/org/openhab/binding/wemo/internal/handler/test/WemoMakerHandlerOSGiTest.java @@ -70,7 +70,7 @@ public class WemoMakerHandlerOSGiTest extends GenericWemoOSGiTest { @Test public void assertThatThingHandlesOnOffCommandCorrectly() - throws MalformedURLException, URISyntaxException, ValidationException { + throws MalformedURLException, URISyntaxException, ValidationException, IOException { Command command = OnOffType.OFF; Thing thing = createThing(THING_TYPE_UID, DEFAULT_TEST_CHANNEL, DEFAULT_TEST_CHANNEL_TYPE); @@ -106,7 +106,7 @@ public class WemoMakerHandlerOSGiTest extends GenericWemoOSGiTest { @Test public void assertThatThingHandlesREFRESHCommand() - throws MalformedURLException, URISyntaxException, ValidationException { + throws MalformedURLException, URISyntaxException, ValidationException, IOException { Command command = RefreshType.REFRESH; Thing thing = createThing(THING_TYPE_UID, DEFAULT_TEST_CHANNEL, DEFAULT_TEST_CHANNEL_TYPE);