]> git.basschouten.com Git - openhab-addons.git/commitdiff
[wemo] Improve GENA subscription reliability and error handling (#12148)
authorJacob Laursen <jacob-github@vindvejr.dk>
Sat, 5 Feb 2022 17:56:58 +0000 (18:56 +0100)
committerGitHub <noreply@github.com>
Sat, 5 Feb 2022 17:56:58 +0000 (18:56 +0100)
* 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 <jacob-github@vindvejr.dk>
15 files changed:
bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/discovery/WemoLinkDiscoveryService.java
bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoBaseThingHandler.java
bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoCoffeeHandler.java
bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoCrockpotHandler.java
bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoDimmerHandler.java
bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoHandler.java
bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoHolmesHandler.java
bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoInsightHandler.java
bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoLightHandler.java
bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoMakerHandler.java
bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/http/WemoHttpCall.java
itests/org.openhab.binding.wemo.tests/src/main/java/org/openhab/binding/wemo/internal/discovery/test/WemoLinkDiscoveryServiceOSGiTest.java
itests/org.openhab.binding.wemo.tests/src/main/java/org/openhab/binding/wemo/internal/handler/test/WemoHandlerOSGiTest.java
itests/org.openhab.binding.wemo.tests/src/main/java/org/openhab/binding/wemo/internal/handler/test/WemoLightHandlerOSGiTest.java
itests/org.openhab.binding.wemo.tests/src/main/java/org/openhab/binding/wemo/internal/handler/test/WemoMakerHandlerOSGiTest.java

index f58820f485b8d87b64b0af8d86a47664029fda5c..7a3ee2f38d156f012b9fe87c1febcb04f7d7fee4 100644 (file)
@@ -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, "<DeviceLists>", "</DeviceLists>");
+                try {
+                    String stringParser = substringBetween(endDeviceRequest, "<DeviceLists>", "</DeviceLists>");
 
-                        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;
-                        }
+                    // 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 <DeviceList>
-                        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<String, Object> 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);
+                    // Build parser for received <DeviceList>
+                    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<String, Object> 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);
+
                     }
+                } 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);
         }
     }
 
index 81b734a8f4d616bd6463e8c392342648f9a76fc5..577dbcac4226b25164022352f5dfdb1fc8546888 100644 (file)
 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<String, Instant> subscriptions = new ConcurrentHashMap<String, Instant>();
+    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;
+        }
+        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;
         }
-        return false;
+        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() {
index e9e6205e05c4939e2daf5859769ad0ae2555f297..1f3d01f3b4e3d9adf7cdec7689476555b2c8c2e4 100644 (file)
@@ -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<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_COFFEE);
 
-    private final Object upnpLock = new Object();
     private final Object jobLock = new Object();
 
-    private Map<String, Boolean> 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 {
                                 + "&lt;attribute&gt;&lt;name&gt;Cleaning&lt;/name&gt;&lt;value&gt;NULL&lt;/value&gt;&lt;/attribute&gt;</attributeList>"
                                 + "</u:SetAttributes>" + "</s:Body>" + "</s:Envelope>";
 
-                        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, "<attributeList>", "</attributeList>");
-
-                    // 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());
-
-                    stringParser = "<data>" + stringParser + "</data>";
-
-                    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");
-
-                    // 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 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 (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;
-                        }
+            try {
+                String stringParser = substringBetween(wemoCallResponse, "<attributeList>", "</attributeList>");
+
+                // 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());
+
+                stringParser = "<data>" + stringParser + "</data>";
+
+                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");
+
+                // 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 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 (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;
         }
index 73d7edd564a0cc574b6ddbcd198e676804c90e79..7abd6f56da5559b3012526633acc04a4a9913b81 100644 (file)
@@ -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<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_CROCKPOT);
 
-    private final Object upnpLock = new Object();
     private final Object jobLock = new Object();
 
     private final Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<>());
 
-    private Map<String, Boolean> 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 {
                         + "<s:Body>" + "<u:SetCrockpotState xmlns:u=\"urn:Belkin:service:basicevent:1\">" + "<mode>"
                         + mode + "</mode>" + "<time>" + time + "</time>" + "</u:SetCrockpotState>" + "</s:Body>"
                         + "</s:Envelope>";
-                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, "<mode>", "</mode>");
-                String time = substringBetween(wemoCallResponse, "<time>", "</time>");
-                String coockedTime = substringBetween(wemoCallResponse, "<coockedTime>", "</coockedTime>");
-
-                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);
+            String mode = substringBetween(wemoCallResponse, "<mode>", "</mode>");
+            String time = substringBetween(wemoCallResponse, "<time>", "</time>");
+            String coockedTime = substringBetween(wemoCallResponse, "<coockedTime>", "</coockedTime>");
+
+            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);
     }
 }
index eaaba0fd89ef9102c1a82b3c482ecf261b525c24..69ac9b5a5f2b3ecf40fcacdf35e3913d7ba1f132 100644 (file)
@@ -59,13 +59,10 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
 
     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_DIMMER);
 
-    private final Object upnpLock = new Object();
     private final Object jobLock = new Object();
 
     private final Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<>());
 
-    private Map<String, Boolean> 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, "<BinaryState>", "</BinaryState>");
-                variable = "BinaryState";
-                this.onValueReceived(variable, value, actionService + "1");
-                value = substringBetween(wemoCallResponse, "<brightness>", "</brightness>");
-                variable = "brightness";
-                this.onValueReceived(variable, value, actionService + "1");
-                value = substringBetween(wemoCallResponse, "<fader>", "</fader>");
-                variable = "fader";
-                this.onValueReceived(variable, value, actionService + "1");
-                updateStatus(ThingStatus.ONLINE);
-            }
+            value = substringBetween(wemoCallResponse, "<BinaryState>", "</BinaryState>");
+            variable = "BinaryState";
+            this.onValueReceived(variable, value, actionService + "1");
+            value = substringBetween(wemoCallResponse, "<brightness>", "</brightness>");
+            variable = "brightness";
+            this.onValueReceived(variable, value, actionService + "1");
+            value = substringBetween(wemoCallResponse, "<fader>", "</fader>");
+            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, "<startTime>", "</startTime>");
-                variable = "startTime";
-                this.onValueReceived(variable, value, actionService + "1");
-                value = substringBetween(wemoCallResponse, "<endTime>", "</endTime>");
-                variable = "endTime";
-                this.onValueReceived(variable, value, actionService + "1");
-                value = substringBetween(wemoCallResponse, "<nightMode>", "</nightMode>");
-                variable = "nightMode";
-                this.onValueReceived(variable, value, actionService + "1");
-                value = substringBetween(wemoCallResponse, "<nightModeBrightness>", "</nightModeBrightness>");
-                variable = "nightModeBrightness";
-                this.onValueReceived(variable, value, actionService + "1");
-                updateStatus(ThingStatus.ONLINE);
-
-            }
+            value = substringBetween(wemoCallResponse, "<startTime>", "</startTime>");
+            variable = "startTime";
+            this.onValueReceived(variable, value, actionService + "1");
+            value = substringBetween(wemoCallResponse, "<endTime>", "</endTime>");
+            variable = "endTime";
+            this.onValueReceived(variable, value, actionService + "1");
+            value = substringBetween(wemoCallResponse, "<nightMode>", "</nightMode>");
+            variable = "nightMode";
+            this.onValueReceived(variable, value, actionService + "1");
+            value = substringBetween(wemoCallResponse, "<nightModeBrightness>", "</nightModeBrightness>");
+            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 {
                     + "<s:Body>" + "<u:" + action + " xmlns:u=\"urn:Belkin:service:basicevent:1\">" + "<" + argument
                     + ">" + value + "</" + argument + ">" + "</u:" + action + ">" + "</s:Body>" + "</s:Envelope>";
 
-            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 {
                     + "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
                     + "<s:Body>" + "<u:SetBinaryState xmlns:u=\"urn:Belkin:service:basicevent:1\">" + value
                     + "</u:SetBinaryState>" + "</s:Body>" + "</s:Envelope>";
-            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());
index 4b16e08a744330600886ba63c9b7c6bcb648decf..8c66336888d0bdffe984c047c9cd1c68ae893304 100644 (file)
@@ -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<String, Boolean> 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, "<InsightParams>", "</InsightParams>");
-                } else {
-                    value = substringBetween(wemoCallResponse, "<BinaryState>", "</BinaryState>");
-                }
-                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, "<InsightParams>", "</InsightParams>");
+            } else {
+                value = substringBetween(wemoCallResponse, "<BinaryState>", "</BinaryState>");
+            }
+            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());
         }
     }
 }
index 5742e4a5f66fc819e94e0e796517cb60094270ed..cc96df98e32bccc93b3eea6bb7acbbe2fe64344c 100644 (file)
@@ -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<String, String> stateMap = Collections.synchronizedMap(new HashMap<>());
 
-    private Map<String, Boolean> 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 {
                     + "<attributeList>&lt;attribute&gt;&lt;name&gt;" + attribute + "&lt;/name&gt;&lt;value&gt;" + value
                     + "&lt;/value&gt;&lt;/attribute&gt;</attributeList>" + "</u:SetAttributes>" + "</s:Body>"
                     + "</s:Envelope>";
-            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, "<attributeList>", "</attributeList>");
-
-                // 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());
-
-                stringParser = "<data>" + stringParser + "</data>";
-
-                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");
-
-                // 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 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":
+            String stringParser = substringBetween(wemoCallResponse, "<attributeList>", "</attributeList>");
+
+            // 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());
+
+            stringParser = "<data>" + stringParser + "</data>";
+
+            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");
+
+            // 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 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");
@@ -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);
     }
 }
index 4383233f091e85bc0a68a43da6b463f4074b0112..02ca4ef6ebc19f0ebef95840c1059eda209d7fd1 100644 (file)
@@ -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),
index 94c5df71461222396c4d7a5d8f2764b1d62b5e46..745e33db631adf5642a89ded25cce017700b2241 100644 (file)
@@ -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<String, Boolean> 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 {
                             + "&lt;/CapabilityValue&gt;&lt;/DeviceStatus&gt;" + "</DeviceStatusList>"
                             + "</u:SetDeviceStatus>" + "</s:Body>" + "</s:Envelope>";
 
-                    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 + "</DeviceIDs>" + "</u:GetDeviceStatus>" + "</s:Body>" + "</s:Envelope>";
 
             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, "<CapabilityValue>", "</CapabilityValue>");
-                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, "<CapabilityValue>", "</CapabilityValue>");
+            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);
-                }
-            }
-        }
-    }
 }
index 2bfb66deda81448d9335817b5d9f6908da5b7a45..ada6c3b9903c3f8c41491b252cd456029c47950f 100644 (file)
@@ -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, "<attributeList>", "</attributeList>");
-                    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());
-
-                    stringParser = "<data>" + stringParser + "</data>";
-
-                    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");
-
-                    // 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 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;
-                        }
+            try {
+                String stringParser = substringBetween(wemoCallResponse, "<attributeList>", "</attributeList>");
+                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());
+
+                stringParser = "<data>" + stringParser + "</data>";
+
+                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");
+
+                // 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 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;
                     }
-                } 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());
         }
     }
 }
index fa1e4c5d239a1e2461b6c758a734a2a6af2676a1..30314d7cff014a4429ebf2a909d216d0eb6534ea 100644 (file)
@@ -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);
-
-            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;
-        }
+    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));
+
+        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;
     }
 }
index 68cab0533c56adec778856a492b91eb01dcca0f6..cc2fc7c93f6f09f136a960cb1948202e7b75ae7b 100644 (file)
@@ -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);
 
index 70429419ef1cdf0eb7449ce7ad8e155ee470455b..c8f3ad0d19d2d3c11cf3ba056dc8a495f255238d 100644 (file)
@@ -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);
index cb03331bcc3ce68574cf0ff8a26cde0f6c579d9a..8442b83cc286e6a51cb99baac2eaa213f670af97 100644 (file)
@@ -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);
index 1ab8cb64278462d7cbb7e5329dee0b73ae67c167..fa882614066bcaf8ad38649e2faa39a2f7d45471 100644 (file)
@@ -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);