[wemo] Improve GENA subscription reliability and error handling (#12148)

* Consolidate service subscriptions in base class.
* Remove unsynchronized and unneeded cache of subscriptions.
* Do not unregister participant when removing subscription.
* Fix status wrongly set to ONLINE when exception is thrown.
* Refactor error handling for WemoHttpCall.
* Adjust log level for communication errors.
* Add automatic subscription renewal.
* Fix more ONLINE/OFFLINE status transition issues.
* Adjust log level when getWemoURL fails because device is offline.
* Remove redundant logging.

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
This commit is contained in:
Jacob Laursen 2022-02-05 18:56:58 +01:00 committed by GitHub
parent 882e52647d
commit 8ebd4e9047
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 763 additions and 1129 deletions

View File

@ -133,104 +133,100 @@ public class WemoLinkDiscoveryService extends AbstractDiscoveryService implement
String endDeviceRequest = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); String endDeviceRequest = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
if (endDeviceRequest != null) { logger.trace("endDeviceRequest answered '{}'", endDeviceRequest);
logger.trace("endDeviceRequest answered '{}'", endDeviceRequest);
try { try {
String stringParser = substringBetween(endDeviceRequest, "<DeviceLists>", "</DeviceLists>"); String stringParser = substringBetween(endDeviceRequest, "<DeviceLists>", "</DeviceLists>");
stringParser = unescapeXml(stringParser); stringParser = unescapeXml(stringParser);
// check if there are already paired devices with WeMo Link // check if there are already paired devices with WeMo Link
if ("0".equals(stringParser)) { if ("0".equals(stringParser)) {
logger.debug("There are no devices connected with WeMo Link. Exit discovery"); logger.debug("There are no devices connected with WeMo Link. Exit discovery");
return; 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);
}
}
} catch (Exception e) {
logger.error("Failed to parse endDevices for bridge '{}'",
wemoBridgeHandler.getThing().getUID(), e);
} }
// 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.warn("Failed to parse endDevices for bridge '{}'", wemoBridgeHandler.getThing().getUID(), e);
} }
} }
} catch (Exception 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);
} }
} }

View File

@ -13,6 +13,11 @@
package org.openhab.binding.wemo.internal.handler; package org.openhab.binding.wemo.internal.handler;
import java.net.URL; 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.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; 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.Thing;
import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command; import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* {@link WemoBaseThingHandler} provides a base implementation for the * {@link WemoBaseThingHandler} provides a base implementation for the
* concrete WeMo handlers for each thing type. * concrete WeMo handlers.
* *
* @author Jacob Laursen - Initial contribution * @author Jacob Laursen - Initial contribution
*/ */
@NonNullByDefault @NonNullByDefault
public abstract class WemoBaseThingHandler extends BaseThingHandler implements UpnpIOParticipant { 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 @Nullable UpnpIOService service;
protected WemoHttpCall wemoHttpCaller; protected WemoHttpCall wemoHttpCaller;
protected String host = ""; protected String host = "";
private Map<String, Instant> subscriptions = new ConcurrentHashMap<String, Instant>();
private @Nullable ScheduledFuture<?> subscriptionRenewalJob;
public WemoBaseThingHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) { public WemoBaseThingHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
super(thing); super(thing);
this.service = upnpIOService; this.service = upnpIOService;
@ -46,7 +61,22 @@ public abstract class WemoBaseThingHandler extends BaseThingHandler implements U
@Override @Override
public void initialize() { 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 @Override
@ -66,7 +96,13 @@ public abstract class WemoBaseThingHandler extends BaseThingHandler implements U
@Override @Override
public void onServiceSubscribed(@Nullable String service, boolean succeeded) { 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 @Override
@ -76,10 +112,115 @@ public abstract class WemoBaseThingHandler extends BaseThingHandler implements U
protected boolean isUpnpDeviceRegistered() { protected boolean isUpnpDeviceRegistered() {
UpnpIOService service = this.service; UpnpIOService service = this.service;
if (service != null) { return service != null && service.isRegistered(this);
return service.isRegistered(this); }
protected void addSubscription(String serviceId) {
if (subscriptions.containsKey(serviceId)) {
logger.debug("{} already subscribed to {}", getUDN(), serviceId);
return;
} }
return false; if (subscriptions.isEmpty()) {
logger.debug("Adding first GENA subscription for {}, scheduling renewal job", getUDN());
scheduleSubscriptionRenewalJob();
}
subscriptions.put(serviceId, Instant.ofEpochSecond(0));
UpnpIOService service = this.service;
if (service == null) {
return;
}
if (!service.isRegistered(this)) {
logger.debug("Registering UPnP participant for {}", getUDN());
service.registerParticipant(this);
}
if (!service.isRegistered(this)) {
logger.debug("Trying to add GENA subscription {} for {}, but service is not registered", serviceId,
getUDN());
return;
}
logger.debug("Adding GENA subscription {} for {}", serviceId, getUDN());
service.addSubscription(this, serviceId, WemoBindingConstants.SUBSCRIPTION_DURATION_SECONDS);
}
protected void removeSubscription(String serviceId) {
UpnpIOService service = this.service;
if (service == null) {
return;
}
subscriptions.remove(serviceId);
if (subscriptions.isEmpty()) {
logger.debug("Removing last GENA subscription for {}, cancelling renewal job", getUDN());
cancelSubscriptionRenewalJob();
}
if (!service.isRegistered(this)) {
logger.debug("Trying to remove GENA subscription {} for {}, but service is not registered", serviceId,
getUDN());
return;
}
logger.debug("Unsubscribing {} from service {}", getUDN(), serviceId);
service.removeSubscription(this, serviceId);
}
private void scheduleSubscriptionRenewalJob() {
cancelSubscriptionRenewalJob();
this.subscriptionRenewalJob = scheduler.scheduleWithFixedDelay(this::renewSubscriptions,
SUBSCRIPTION_RENEWAL_INITIAL_DELAY_SECONDS, SUBSCRIPTION_RENEWAL_INTERVAL_SECONDS, TimeUnit.SECONDS);
}
private void cancelSubscriptionRenewalJob() {
ScheduledFuture<?> subscriptionRenewalJob = this.subscriptionRenewalJob;
if (subscriptionRenewalJob != null) {
subscriptionRenewalJob.cancel(true);
}
this.subscriptionRenewalJob = null;
}
private void renewSubscriptions() {
if (subscriptions.isEmpty()) {
return;
}
UpnpIOService service = this.service;
if (service == null) {
return;
}
if (!service.isRegistered(this)) {
service.registerParticipant(this);
}
if (!service.isRegistered(this)) {
logger.debug("Trying to renew GENA subscriptions for {}, but service is not registered", getUDN());
return;
}
logger.debug("Renewing GENA subscriptions for {}", getUDN());
subscriptions.forEach((serviceId, lastRenewed) -> {
if (lastRenewed.isBefore(Instant.now().minusSeconds(
WemoBindingConstants.SUBSCRIPTION_DURATION_SECONDS - SUBSCRIPTION_RENEWAL_INTERVAL_SECONDS))) {
logger.debug("Subscription for service {} with timestamp {} has expired, renewing", serviceId,
lastRenewed);
service.removeSubscription(this, serviceId);
service.addSubscription(this, serviceId, WemoBindingConstants.SUBSCRIPTION_DURATION_SECONDS);
}
});
}
private void removeSubscriptions() {
if (subscriptions.isEmpty()) {
return;
}
UpnpIOService service = this.service;
if (service == null) {
return;
}
if (!service.isRegistered(this)) {
logger.debug("Trying to remove GENA subscriptions for {}, but service is not registered",
getThing().getUID());
return;
}
logger.debug("Removing GENA subscriptions for {}", getUDN());
subscriptions.forEach((serviceId, lastRenewed) -> {
logger.debug("Removing subscription for service {}", serviceId);
service.removeSubscription(this, serviceId);
});
subscriptions.clear();
} }
protected String getHost() { protected String getHost() {

View File

@ -19,8 +19,6 @@ import java.io.StringReader;
import java.time.Instant; import java.time.Instant;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.concurrent.ScheduledFuture; 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); 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 final Object jobLock = new Object();
private Map<String, Boolean> subscriptionState = new HashMap<>();
private @Nullable ScheduledFuture<?> pollingJob; private @Nullable ScheduledFuture<?> pollingJob;
public WemoCoffeeHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) { public WemoCoffeeHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
@ -82,14 +77,12 @@ public class WemoCoffeeHandler extends WemoBaseThingHandler {
@Override @Override
public void initialize() { public void initialize() {
super.initialize();
Configuration configuration = getConfig(); Configuration configuration = getConfig();
if (configuration.get(UDN) != null) { if (configuration.get(UDN) != null) {
logger.debug("Initializing WemoCoffeeHandler for UDN '{}'", configuration.get(UDN)); logger.debug("Initializing WemoCoffeeHandler for UDN '{}'", configuration.get(UDN));
UpnpIOService localService = service; addSubscription(DEVICEEVENT);
if (localService != null) {
localService.registerParticipant(this);
}
host = getHost(); host = getHost();
pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS, pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS,
TimeUnit.SECONDS); TimeUnit.SECONDS);
@ -103,13 +96,13 @@ public class WemoCoffeeHandler extends WemoBaseThingHandler {
@Override @Override
public void dispose() { public void dispose() {
logger.debug("WeMoCoffeeHandler disposed."); logger.debug("WemoCoffeeHandler disposed.");
ScheduledFuture<?> job = this.pollingJob; ScheduledFuture<?> job = this.pollingJob;
if (job != null && !job.isCancelled()) { if (job != null && !job.isCancelled()) {
job.cancel(true); job.cancel(true);
} }
this.pollingJob = null; this.pollingJob = null;
removeSubscription(); super.dispose();
} }
private void poll() { private void poll() {
@ -127,14 +120,9 @@ public class WemoCoffeeHandler extends WemoBaseThingHandler {
logger.debug("UPnP device {} not yet registered", getUDN()); logger.debug("UPnP device {} not yet registered", getUDN());
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING, updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING,
"@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]"); "@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
synchronized (upnpLock) {
subscriptionState = new HashMap<>();
}
return; return;
} }
updateStatus(ThingStatus.ONLINE);
updateWemoState(); updateWemoState();
addSubscription();
} catch (Exception e) { } catch (Exception e) {
logger.debug("Exception during poll: {}", e.getMessage(), 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) { public void handleCommand(ChannelUID channelUID, Command command) {
String localHost = getHost(); String localHost = getHost();
if (localHost.isEmpty()) { 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()); getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip"); "@text/config-status.error.missing-ip");
@ -153,7 +141,7 @@ public class WemoCoffeeHandler extends WemoBaseThingHandler {
} }
String wemoURL = getWemoURL(localHost, BASICACTION); String wemoURL = getWemoURL(localHost, BASICACTION);
if (wemoURL == null) { 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()); getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url"); "@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>" + "&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>"; + "</u:SetAttributes>" + "</s:Body>" + "</s:Envelope>";
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
if (wemoCallResponse != null) { updateState(CHANNEL_STATE, OnOffType.ON);
updateState(CHANNEL_STATE, OnOffType.ON); State newMode = new StringType("Brewing");
State newMode = new StringType("Brewing"); updateState(CHANNEL_COFFEEMODE, newMode);
updateState(CHANNEL_COFFEEMODE, newMode); updateStatus(ThingStatus.ONLINE);
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());
}
}
} catch (Exception e) { } 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()); e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
} }
} }
// if command.equals(OnOffType.OFF) we do nothing because WeMo Coffee Maker cannot be switched // if command.equals(OnOffType.OFF) we do nothing because WeMo Coffee Maker cannot be switched
// off // off remotely
// remotely
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 @Override
public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) { 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. // 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. * The {@link updateWemoState} polls the actual state of a WeMo CoffeeMaker.
*/ */
protected void updateWemoState() { protected void updateWemoState() {
String localHost = getHost(); String localHost = getHost();
if (localHost.isEmpty()) { 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, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip"); "@text/config-status.error.missing-ip");
return; return;
@ -282,7 +208,7 @@ public class WemoCoffeeHandler extends WemoBaseThingHandler {
String actionService = DEVICEACTION; String actionService = DEVICEACTION;
String wemoURL = getWemoURL(host, actionService); String wemoURL = getWemoURL(host, actionService);
if (wemoURL == null) { 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, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url"); "@text/config-status.error.missing-url");
return; return;
@ -292,147 +218,140 @@ public class WemoCoffeeHandler extends WemoBaseThingHandler {
String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\""; String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
String content = createStateRequestContent(action, actionService); String content = createStateRequestContent(action, actionService);
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
if (wemoCallResponse != null) { try {
if (logger.isTraceEnabled()) { String stringParser = substringBetween(wemoCallResponse, "<attributeList>", "</attributeList>");
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. // Due to Belkins bad response formatting, we need to run this twice.
stringParser = unescapeXml(stringParser); stringParser = unescapeXml(stringParser);
stringParser = unescapeXml(stringParser); stringParser = unescapeXml(stringParser);
logger.trace("CoffeeMaker response '{}' for device '{}' received", stringParser, logger.trace("CoffeeMaker response '{}' for device '{}' received", stringParser, getThing().getUID());
getThing().getUID());
stringParser = "<data>" + stringParser + "</data>"; stringParser = "<data>" + stringParser + "</data>";
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
// see // see
// https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html // 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-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-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.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
dbf.setXIncludeAware(false); dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false); dbf.setExpandEntityReferences(false);
DocumentBuilder db = dbf.newDocumentBuilder(); DocumentBuilder db = dbf.newDocumentBuilder();
InputSource is = new InputSource(); InputSource is = new InputSource();
is.setCharacterStream(new StringReader(stringParser)); is.setCharacterStream(new StringReader(stringParser));
Document doc = db.parse(is); Document doc = db.parse(is);
NodeList nodes = doc.getElementsByTagName("attribute"); NodeList nodes = doc.getElementsByTagName("attribute");
// iterate the attributes // iterate the attributes
for (int i = 0; i < nodes.getLength(); i++) { for (int i = 0; i < nodes.getLength(); i++) {
Element element = (Element) nodes.item(i); Element element = (Element) nodes.item(i);
NodeList deviceIndex = element.getElementsByTagName("name"); NodeList deviceIndex = element.getElementsByTagName("name");
Element line = (Element) deviceIndex.item(0); Element line = (Element) deviceIndex.item(0);
String attributeName = getCharacterDataFromElement(line); String attributeName = getCharacterDataFromElement(line);
logger.trace("attributeName: {}", attributeName); logger.trace("attributeName: {}", attributeName);
NodeList deviceID = element.getElementsByTagName("value"); NodeList deviceID = element.getElementsByTagName("value");
line = (Element) deviceID.item(0); line = (Element) deviceID.item(0);
String attributeValue = getCharacterDataFromElement(line); String attributeValue = getCharacterDataFromElement(line);
logger.trace("attributeValue: {}", attributeValue); logger.trace("attributeValue: {}", attributeValue);
switch (attributeName) { switch (attributeName) {
case "Mode": case "Mode":
State newMode = new StringType("Brewing"); State newMode = new StringType("Brewing");
State newAttributeValue; State newAttributeValue;
switch (attributeValue) { switch (attributeValue) {
case "0": case "0":
updateState(CHANNEL_STATE, OnOffType.ON); updateState(CHANNEL_STATE, OnOffType.ON);
newMode = new StringType("Refill"); newMode = new StringType("Refill");
updateState(CHANNEL_COFFEEMODE, newMode); updateState(CHANNEL_COFFEEMODE, newMode);
break; break;
case "1": case "1":
updateState(CHANNEL_STATE, OnOffType.OFF); updateState(CHANNEL_STATE, OnOffType.OFF);
newMode = new StringType("PlaceCarafe"); newMode = new StringType("PlaceCarafe");
updateState(CHANNEL_COFFEEMODE, newMode); updateState(CHANNEL_COFFEEMODE, newMode);
break; break;
case "2": case "2":
updateState(CHANNEL_STATE, OnOffType.OFF); updateState(CHANNEL_STATE, OnOffType.OFF);
newMode = new StringType("RefillWater"); newMode = new StringType("RefillWater");
updateState(CHANNEL_COFFEEMODE, newMode); updateState(CHANNEL_COFFEEMODE, newMode);
break; break;
case "3": case "3":
updateState(CHANNEL_STATE, OnOffType.OFF); updateState(CHANNEL_STATE, OnOffType.OFF);
newMode = new StringType("Ready"); newMode = new StringType("Ready");
updateState(CHANNEL_COFFEEMODE, newMode); updateState(CHANNEL_COFFEEMODE, newMode);
break; break;
case "4": case "4":
updateState(CHANNEL_STATE, OnOffType.ON); updateState(CHANNEL_STATE, OnOffType.ON);
newMode = new StringType("Brewing"); newMode = new StringType("Brewing");
updateState(CHANNEL_COFFEEMODE, newMode); updateState(CHANNEL_COFFEEMODE, newMode);
break; break;
case "5": case "5":
updateState(CHANNEL_STATE, OnOffType.OFF); updateState(CHANNEL_STATE, OnOffType.OFF);
newMode = new StringType("Brewed"); newMode = new StringType("Brewed");
updateState(CHANNEL_COFFEEMODE, newMode); updateState(CHANNEL_COFFEEMODE, newMode);
break; break;
case "6": case "6":
updateState(CHANNEL_STATE, OnOffType.OFF); updateState(CHANNEL_STATE, OnOffType.OFF);
newMode = new StringType("CleaningBrewing"); newMode = new StringType("CleaningBrewing");
updateState(CHANNEL_COFFEEMODE, newMode); updateState(CHANNEL_COFFEEMODE, newMode);
break; break;
case "7": case "7":
updateState(CHANNEL_STATE, OnOffType.OFF); updateState(CHANNEL_STATE, OnOffType.OFF);
newMode = new StringType("CleaningSoaking"); newMode = new StringType("CleaningSoaking");
updateState(CHANNEL_COFFEEMODE, newMode); updateState(CHANNEL_COFFEEMODE, newMode);
break; break;
case "8": case "8":
updateState(CHANNEL_STATE, OnOffType.OFF); updateState(CHANNEL_STATE, OnOffType.OFF);
newMode = new StringType("BrewFailCarafeRemoved"); newMode = new StringType("BrewFailCarafeRemoved");
updateState(CHANNEL_COFFEEMODE, newMode); updateState(CHANNEL_COFFEEMODE, newMode);
break; break;
} }
break; break;
case "ModeTime": case "ModeTime":
newAttributeValue = new DecimalType(attributeValue); newAttributeValue = new DecimalType(attributeValue);
updateState(CHANNEL_MODETIME, newAttributeValue); updateState(CHANNEL_MODETIME, newAttributeValue);
break; break;
case "TimeRemaining": case "TimeRemaining":
newAttributeValue = new DecimalType(attributeValue); newAttributeValue = new DecimalType(attributeValue);
updateState(CHANNEL_TIMEREMAINING, newAttributeValue); updateState(CHANNEL_TIMEREMAINING, newAttributeValue);
break; break;
case "WaterLevelReached": case "WaterLevelReached":
newAttributeValue = new DecimalType(attributeValue); newAttributeValue = new DecimalType(attributeValue);
updateState(CHANNEL_WATERLEVELREACHED, newAttributeValue); updateState(CHANNEL_WATERLEVELREACHED, newAttributeValue);
break; break;
case "CleanAdvise": case "CleanAdvise":
newAttributeValue = "0".equals(attributeValue) ? OnOffType.OFF : OnOffType.ON; newAttributeValue = "0".equals(attributeValue) ? OnOffType.OFF : OnOffType.ON;
updateState(CHANNEL_CLEANADVISE, newAttributeValue); updateState(CHANNEL_CLEANADVISE, newAttributeValue);
break; break;
case "FilterAdvise": case "FilterAdvise":
newAttributeValue = "0".equals(attributeValue) ? OnOffType.OFF : OnOffType.ON; newAttributeValue = "0".equals(attributeValue) ? OnOffType.OFF : OnOffType.ON;
updateState(CHANNEL_FILTERADVISE, newAttributeValue); updateState(CHANNEL_FILTERADVISE, newAttributeValue);
break; break;
case "Brewed": case "Brewed":
newAttributeValue = getDateTimeState(attributeValue); newAttributeValue = getDateTimeState(attributeValue);
if (newAttributeValue != null) { if (newAttributeValue != null) {
updateState(CHANNEL_BREWED, newAttributeValue); updateState(CHANNEL_BREWED, newAttributeValue);
} }
break; break;
case "LastCleaned": case "LastCleaned":
newAttributeValue = getDateTimeState(attributeValue); newAttributeValue = getDateTimeState(attributeValue);
if (newAttributeValue != null) { if (newAttributeValue != null) {
updateState(CHANNEL_LASTCLEANED, newAttributeValue); updateState(CHANNEL_LASTCLEANED, newAttributeValue);
} }
break; 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) { } 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 { try {
value = Long.parseLong(attributeValue); value = Long.parseLong(attributeValue);
} catch (NumberFormatException e) { } 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()); getThing().getUID());
return null; return null;
} }

View 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.WemoBindingConstants.*;
import static org.openhab.binding.wemo.internal.WemoUtil.*; import static org.openhab.binding.wemo.internal.WemoUtil.*;
import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; 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); 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 Object jobLock = new Object();
private final Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<>()); private final Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<>());
private Map<String, Boolean> subscriptionState = new HashMap<>();
private @Nullable ScheduledFuture<?> pollingJob; private @Nullable ScheduledFuture<?> pollingJob;
public WemoCrockpotHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) { public WemoCrockpotHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
@ -70,14 +68,12 @@ public class WemoCrockpotHandler extends WemoBaseThingHandler {
@Override @Override
public void initialize() { public void initialize() {
super.initialize();
Configuration configuration = getConfig(); Configuration configuration = getConfig();
if (configuration.get(UDN) != null) { if (configuration.get(UDN) != null) {
logger.debug("Initializing WemoCrockpotHandler for UDN '{}'", configuration.get(UDN)); logger.debug("Initializing WemoCrockpotHandler for UDN '{}'", configuration.get(UDN));
UpnpIOService localService = service; addSubscription(BASICEVENT);
if (localService != null) {
localService.registerParticipant(this);
}
host = getHost(); host = getHost();
pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS, pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS,
TimeUnit.SECONDS); TimeUnit.SECONDS);
@ -97,7 +93,7 @@ public class WemoCrockpotHandler extends WemoBaseThingHandler {
job.cancel(true); job.cancel(true);
} }
this.pollingJob = null; this.pollingJob = null;
removeSubscription(); super.dispose();
} }
private void poll() { private void poll() {
@ -114,14 +110,9 @@ public class WemoCrockpotHandler extends WemoBaseThingHandler {
logger.debug("UPnP device {} not yet registered", getUDN()); logger.debug("UPnP device {} not yet registered", getUDN());
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING, updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING,
"@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]"); "@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
synchronized (upnpLock) {
subscriptionState = new HashMap<>();
}
return; return;
} }
updateStatus(ThingStatus.ONLINE);
updateWemoState(); updateWemoState();
addSubscription();
} catch (Exception e) { } catch (Exception e) {
logger.debug("Exception during poll: {}", e.getMessage(), 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) { public void handleCommand(ChannelUID channelUID, Command command) {
String localHost = getHost(); String localHost = getHost();
if (localHost.isEmpty()) { 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()); getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip"); "@text/config-status.error.missing-ip");
@ -140,7 +131,7 @@ public class WemoCrockpotHandler extends WemoBaseThingHandler {
} }
String wemoURL = getWemoURL(localHost, BASICACTION); String wemoURL = getWemoURL(localHost, BASICACTION);
if (wemoURL == null) { 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()); getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url"); "@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>" + "<s:Body>" + "<u:SetCrockpotState xmlns:u=\"urn:Belkin:service:basicevent:1\">" + "<mode>"
+ mode + "</mode>" + "<time>" + time + "</time>" + "</u:SetCrockpotState>" + "</s:Body>" + mode + "</mode>" + "<time>" + time + "</time>" + "</u:SetCrockpotState>" + "</s:Body>"
+ "</s:Envelope>"; + "</s:Envelope>";
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
if (wemoCallResponse != null && logger.isTraceEnabled()) { updateStatus(ThingStatus.ONLINE);
logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID()); } catch (IOException e) {
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) {
logger.debug("Failed to send command '{}' for device '{}':", command, getThing().getUID(), e); logger.debug("Failed to send command '{}' for device '{}':", command, getThing().getUID(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); 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 * The {@link updateWemoState} polls the actual state of a WeMo device and
* calls {@link onValueReceived} to update the statemap and channels.. * calls {@link onValueReceived} to update the statemap and channels..
@ -261,7 +194,7 @@ public class WemoCrockpotHandler extends WemoBaseThingHandler {
protected void updateWemoState() { protected void updateWemoState() {
String localHost = getHost(); String localHost = getHost();
if (localHost.isEmpty()) { 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, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip"); "@text/config-status.error.missing-ip");
return; return;
@ -269,7 +202,7 @@ public class WemoCrockpotHandler extends WemoBaseThingHandler {
String actionService = BASICEVENT; String actionService = BASICEVENT;
String wemoURL = getWemoURL(localHost, actionService); String wemoURL = getWemoURL(localHost, actionService);
if (wemoURL == null) { 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, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url"); "@text/config-status.error.missing-url");
return; return;
@ -279,46 +212,38 @@ public class WemoCrockpotHandler extends WemoBaseThingHandler {
String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\""; String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
String content = createStateRequestContent(action, actionService); String content = createStateRequestContent(action, actionService);
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
if (wemoCallResponse != null) { String mode = substringBetween(wemoCallResponse, "<mode>", "</mode>");
if (logger.isTraceEnabled()) { String time = substringBetween(wemoCallResponse, "<time>", "</time>");
logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID()); String coockedTime = substringBetween(wemoCallResponse, "<coockedTime>", "</coockedTime>");
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 newMode = new StringType(mode);
State newCoockedTime = DecimalType.valueOf(coockedTime); State newCoockedTime = DecimalType.valueOf(coockedTime);
switch (mode) { switch (mode) {
case "0": case "0":
newMode = new StringType("OFF"); newMode = new StringType("OFF");
break; break;
case "50": case "50":
newMode = new StringType("WARM"); newMode = new StringType("WARM");
State warmTime = DecimalType.valueOf(time); State warmTime = DecimalType.valueOf(time);
updateState(CHANNEL_WARMCOOKTIME, warmTime); updateState(CHANNEL_WARMCOOKTIME, warmTime);
break; break;
case "51": case "51":
newMode = new StringType("LOW"); newMode = new StringType("LOW");
State lowTime = DecimalType.valueOf(time); State lowTime = DecimalType.valueOf(time);
updateState(CHANNEL_LOWCOOKTIME, lowTime); updateState(CHANNEL_LOWCOOKTIME, lowTime);
break; break;
case "52": case "52":
newMode = new StringType("HIGH"); newMode = new StringType("HIGH");
State highTime = DecimalType.valueOf(time); State highTime = DecimalType.valueOf(time);
updateState(CHANNEL_HIGHCOOKTIME, highTime); updateState(CHANNEL_HIGHCOOKTIME, highTime);
break; break;
}
updateState(CHANNEL_COOKMODE, newMode);
updateState(CHANNEL_COOKEDTIME, newCoockedTime);
} }
} 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); logger.debug("Failed to get actual state for device '{}': {}", getThing().getUID(), e.getMessage(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
} }
updateStatus(ThingStatus.ONLINE);
} }
} }

View File

@ -59,13 +59,10 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_DIMMER); 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 Object jobLock = new Object();
private final Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<>()); private final Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<>());
private Map<String, Boolean> subscriptionState = new HashMap<>();
private @Nullable ScheduledFuture<?> pollingJob; private @Nullable ScheduledFuture<?> pollingJob;
private int currentBrightness; private int currentBrightness;
@ -84,14 +81,12 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
@Override @Override
public void initialize() { public void initialize() {
super.initialize();
Configuration configuration = getConfig(); Configuration configuration = getConfig();
if (configuration.get(UDN) != null) { if (configuration.get(UDN) != null) {
logger.debug("Initializing WemoDimmerHandler for UDN '{}'", configuration.get(UDN)); logger.debug("Initializing WemoDimmerHandler for UDN '{}'", configuration.get(UDN));
UpnpIOService localService = service; addSubscription(BASICEVENT);
if (localService != null) {
localService.registerParticipant(this);
}
host = getHost(); host = getHost();
pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS, pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS,
TimeUnit.SECONDS); TimeUnit.SECONDS);
@ -112,7 +107,7 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
job.cancel(true); job.cancel(true);
} }
this.pollingJob = null; this.pollingJob = null;
removeSubscription(); super.dispose();
} }
private void poll() { private void poll() {
@ -129,14 +124,9 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
logger.debug("UPnP device {} not yet registered", getUDN()); logger.debug("UPnP device {} not yet registered", getUDN());
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING, updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING,
"@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]"); "@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
synchronized (upnpLock) {
subscriptionState = new HashMap<>();
}
return; return;
} }
updateStatus(ThingStatus.ONLINE);
updateWemoState(); updateWemoState();
addSubscription();
} catch (Exception e) { } catch (Exception e) {
logger.debug("Exception during poll: {}", e.getMessage(), 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 @Override
public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) { public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) {
logger.debug("Received pair '{}':'{}' (service '{}') for thing '{}'", 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 * The {@link updateWemoState} polls the actual state of a WeMo device and
* calls {@link onValueReceived} to update the statemap and channels.. * calls {@link onValueReceived} to update the statemap and channels..
@ -474,14 +420,14 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
protected void updateWemoState() { protected void updateWemoState() {
String localHost = getHost(); String localHost = getHost();
if (localHost.isEmpty()) { 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, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip"); "@text/config-status.error.missing-ip");
return; return;
} }
String wemoURL = getWemoURL(localHost, BASICACTION); String wemoURL = getWemoURL(localHost, BASICACTION);
if (wemoURL == null) { 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, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url"); "@text/config-status.error.missing-url");
return; return;
@ -494,24 +440,16 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
String content = createStateRequestContent(action, actionService); String content = createStateRequestContent(action, actionService);
try { try {
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
if (wemoCallResponse != null) { value = substringBetween(wemoCallResponse, "<BinaryState>", "</BinaryState>");
if (logger.isTraceEnabled()) { variable = "BinaryState";
logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID()); this.onValueReceived(variable, value, actionService + "1");
logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID()); value = substringBetween(wemoCallResponse, "<brightness>", "</brightness>");
logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID()); variable = "brightness";
logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID()); this.onValueReceived(variable, value, actionService + "1");
} value = substringBetween(wemoCallResponse, "<fader>", "</fader>");
value = substringBetween(wemoCallResponse, "<BinaryState>", "</BinaryState>"); variable = "fader";
variable = "BinaryState"; this.onValueReceived(variable, value, actionService + "1");
this.onValueReceived(variable, value, actionService + "1"); updateStatus(ThingStatus.ONLINE);
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) { } catch (Exception e) {
logger.debug("Failed to get actual state for device '{}': {}", getThing().getUID(), e.getMessage()); logger.debug("Failed to get actual state for device '{}': {}", getThing().getUID(), e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
@ -523,28 +461,19 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
content = createStateRequestContent(action, actionService); content = createStateRequestContent(action, actionService);
try { try {
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
if (wemoCallResponse != null) { value = substringBetween(wemoCallResponse, "<startTime>", "</startTime>");
if (logger.isTraceEnabled()) { variable = "startTime";
logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID()); this.onValueReceived(variable, value, actionService + "1");
logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID()); value = substringBetween(wemoCallResponse, "<endTime>", "</endTime>");
logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID()); variable = "endTime";
logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID()); this.onValueReceived(variable, value, actionService + "1");
} value = substringBetween(wemoCallResponse, "<nightMode>", "</nightMode>");
value = substringBetween(wemoCallResponse, "<startTime>", "</startTime>"); variable = "nightMode";
variable = "startTime"; this.onValueReceived(variable, value, actionService + "1");
this.onValueReceived(variable, value, actionService + "1"); value = substringBetween(wemoCallResponse, "<nightModeBrightness>", "</nightModeBrightness>");
value = substringBetween(wemoCallResponse, "<endTime>", "</endTime>"); variable = "nightModeBrightness";
variable = "endTime"; this.onValueReceived(variable, value, actionService + "1");
this.onValueReceived(variable, value, actionService + "1"); updateStatus(ThingStatus.ONLINE);
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) { } catch (Exception e) {
logger.debug("Failed to get actual NightMode state for device '{}': {}", getThing().getUID(), logger.debug("Failed to get actual NightMode state for device '{}': {}", getThing().getUID(),
e.getMessage()); e.getMessage());
@ -557,27 +486,26 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
try { try {
value = Long.parseLong(attributeValue); value = Long.parseLong(attributeValue);
} catch (NumberFormatException e) { } 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()); getThing().getUID());
return null; return null;
} }
ZonedDateTime zoned = ZonedDateTime.ofInstant(Instant.ofEpochSecond(value), TimeZone.getDefault().toZoneId()); ZonedDateTime zoned = ZonedDateTime.ofInstant(Instant.ofEpochSecond(value), TimeZone.getDefault().toZoneId());
State dateTimeState = new DateTimeType(zoned); State dateTimeState = new DateTimeType(zoned);
logger.trace("New attribute brewed '{}' received", dateTimeState);
return dateTimeState; return dateTimeState;
} }
public void setBinaryState(String action, String argument, String value) { public void setBinaryState(String action, String argument, String value) {
String localHost = getHost(); String localHost = getHost();
if (localHost.isEmpty()) { 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, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip"); "@text/config-status.error.missing-ip");
return; return;
} }
String wemoURL = getWemoURL(localHost, BASICACTION); String wemoURL = getWemoURL(localHost, BASICACTION);
if (wemoURL == null) { 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, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url"); "@text/config-status.error.missing-url");
return; return;
@ -589,13 +517,8 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
+ "<s:Body>" + "<u:" + action + " xmlns:u=\"urn:Belkin:service:basicevent:1\">" + "<" + argument + "<s:Body>" + "<u:" + action + " xmlns:u=\"urn:Belkin:service:basicevent:1\">" + "<" + argument
+ ">" + value + "</" + argument + ">" + "</u:" + action + ">" + "</s:Body>" + "</s:Envelope>"; + ">" + value + "</" + argument + ">" + "</u:" + action + ">" + "</s:Body>" + "</s:Envelope>";
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
if (wemoCallResponse != null && logger.isTraceEnabled()) { updateStatus(ThingStatus.ONLINE);
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 (Exception e) { } catch (Exception e) {
logger.debug("Failed to set binaryState '{}' for device '{}': {}", value, getThing().getUID(), logger.debug("Failed to set binaryState '{}' for device '{}': {}", value, getThing().getUID(),
e.getMessage()); e.getMessage());
@ -606,14 +529,14 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
public void setTimerStart(String action, String argument, String value) { public void setTimerStart(String action, String argument, String value) {
String localHost = getHost(); String localHost = getHost();
if (localHost.isEmpty()) { 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, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip"); "@text/config-status.error.missing-ip");
return; return;
} }
String wemoURL = getWemoURL(localHost, BASICACTION); String wemoURL = getWemoURL(localHost, BASICACTION);
if (wemoURL == null) { 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, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url"); "@text/config-status.error.missing-url");
return; 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: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 + "<s:Body>" + "<u:SetBinaryState xmlns:u=\"urn:Belkin:service:basicevent:1\">" + value
+ "</u:SetBinaryState>" + "</s:Body>" + "</s:Envelope>"; + "</u:SetBinaryState>" + "</s:Body>" + "</s:Envelope>";
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
if (wemoCallResponse != null && logger.isTraceEnabled()) { updateStatus(ThingStatus.ONLINE);
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 (Exception e) { } catch (Exception e) {
logger.debug("Failed to set timerStart '{}' for device '{}': {}", value, getThing().getUID(), logger.debug("Failed to set timerStart '{}' for device '{}': {}", value, getThing().getUID(),
e.getMessage()); e.getMessage());

View 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.WemoBindingConstants.*;
import static org.openhab.binding.wemo.internal.WemoUtil.*; 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.ScheduledFuture;
import java.util.concurrent.TimeUnit; 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.Thing;
import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.types.Command; import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType; import org.openhab.core.types.RefreshType;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -51,11 +48,8 @@ public abstract class WemoHandler extends WemoBaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(WemoHandler.class); private final Logger logger = LoggerFactory.getLogger(WemoHandler.class);
private final Object upnpLock = new Object();
private final Object jobLock = new Object(); private final Object jobLock = new Object();
private Map<String, Boolean> subscriptionState = new HashMap<>();
private @Nullable ScheduledFuture<?> pollingJob; private @Nullable ScheduledFuture<?> pollingJob;
public WemoHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) { public WemoHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
@ -66,13 +60,14 @@ public abstract class WemoHandler extends WemoBaseThingHandler {
@Override @Override
public void initialize() { public void initialize() {
super.initialize();
Configuration configuration = getConfig(); Configuration configuration = getConfig();
if (configuration.get(UDN) != null) { if (configuration.get(UDN) != null) {
logger.debug("Initializing WemoHandler for UDN '{}'", configuration.get(UDN)); logger.debug("Initializing WemoHandler for UDN '{}'", configuration.get(UDN));
UpnpIOService localService = service; addSubscription(BASICEVENT);
if (localService != null) { if (THING_TYPE_INSIGHT.equals(thing.getThingTypeUID())) {
localService.registerParticipant(this); addSubscription(INSIGHTEVENT);
} }
host = getHost(); host = getHost();
pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS, pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS,
@ -94,7 +89,7 @@ public abstract class WemoHandler extends WemoBaseThingHandler {
job.cancel(true); job.cancel(true);
} }
this.pollingJob = null; this.pollingJob = null;
removeSubscription(); super.dispose();
} }
private void poll() { private void poll() {
@ -111,14 +106,9 @@ public abstract class WemoHandler extends WemoBaseThingHandler {
logger.debug("UPnP device {} not yet registered", getUDN()); logger.debug("UPnP device {} not yet registered", getUDN());
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING, updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING,
"@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]"); "@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
synchronized (upnpLock) {
subscriptionState = new HashMap<>();
}
return; return;
} }
updateStatus(ThingStatus.ONLINE);
updateWemoState(); updateWemoState();
addSubscription();
} catch (Exception e) { } catch (Exception e) {
logger.debug("Exception during poll: {}", e.getMessage(), 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) { public void handleCommand(ChannelUID channelUID, Command command) {
String localHost = getHost(); String localHost = getHost();
if (localHost.isEmpty()) { 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()); getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip"); "@text/config-status.error.missing-ip");
@ -137,7 +127,7 @@ public abstract class WemoHandler extends WemoBaseThingHandler {
} }
String wemoURL = getWemoURL(localHost, BASICACTION); String wemoURL = getWemoURL(localHost, BASICACTION);
if (wemoURL == null) { 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()); getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url"); "@text/config-status.error.missing-url");
@ -155,92 +145,13 @@ public abstract class WemoHandler extends WemoBaseThingHandler {
boolean binaryState = OnOffType.ON.equals(command) ? true : false; boolean binaryState = OnOffType.ON.equals(command) ? true : false;
String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\""; String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\"";
String content = createBinaryStateContent(binaryState); String content = createBinaryStateContent(binaryState);
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
if (wemoCallResponse != null && logger.isTraceEnabled()) { updateStatus(ThingStatus.ONLINE);
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 (Exception e) { } 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()); e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); 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 actionService = BASICACTION;
String localhost = getHost(); String localhost = getHost();
if (localhost.isEmpty()) { 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, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip"); "@text/config-status.error.missing-ip");
return; return;
@ -269,7 +180,7 @@ public abstract class WemoHandler extends WemoBaseThingHandler {
} }
String wemoURL = getWemoURL(localhost, actionService); String wemoURL = getWemoURL(localhost, actionService);
if (wemoURL == null) { 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, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url"); "@text/config-status.error.missing-url");
return; return;
@ -278,25 +189,17 @@ public abstract class WemoHandler extends WemoBaseThingHandler {
String content = createStateRequestContent(action, actionService); String content = createStateRequestContent(action, actionService);
try { try {
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
if (wemoCallResponse != null) { if ("InsightParams".equals(variable)) {
if (logger.isTraceEnabled()) { value = substringBetween(wemoCallResponse, "<InsightParams>", "</InsightParams>");
logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID()); } else {
logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID()); value = substringBetween(wemoCallResponse, "<BinaryState>", "</BinaryState>");
logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID()); }
logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID()); if (value.length() != 0) {
} logger.trace("New state '{}' for device '{}' received", value, getThing().getUID());
if ("InsightParams".equals(variable)) { this.onValueReceived(variable, value, actionService + "1");
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) { } 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());
} }
} }
} }

View File

@ -68,13 +68,10 @@ public class WemoHolmesHandler extends WemoBaseThingHandler {
private static final int FILTER_LIFE_DAYS = 330; private static final int FILTER_LIFE_DAYS = 330;
private static final int FILTER_LIFE_MINS = FILTER_LIFE_DAYS * 24 * 60; 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 Object jobLock = new Object();
private final Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<>()); private final Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<>());
private Map<String, Boolean> subscriptionState = new HashMap<>();
private @Nullable ScheduledFuture<?> pollingJob; private @Nullable ScheduledFuture<?> pollingJob;
public WemoHolmesHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) { public WemoHolmesHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
@ -85,14 +82,12 @@ public class WemoHolmesHandler extends WemoBaseThingHandler {
@Override @Override
public void initialize() { public void initialize() {
super.initialize();
Configuration configuration = getConfig(); Configuration configuration = getConfig();
if (configuration.get(UDN) != null) { if (configuration.get(UDN) != null) {
logger.debug("Initializing WemoHolmesHandler for UDN '{}'", configuration.get(UDN)); logger.debug("Initializing WemoHolmesHandler for UDN '{}'", configuration.get(UDN));
UpnpIOService localService = service; addSubscription(BASICEVENT);
if (localService != null) {
localService.registerParticipant(this);
}
host = getHost(); host = getHost();
pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS, pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS,
TimeUnit.SECONDS); TimeUnit.SECONDS);
@ -113,7 +108,7 @@ public class WemoHolmesHandler extends WemoBaseThingHandler {
job.cancel(true); job.cancel(true);
} }
this.pollingJob = null; this.pollingJob = null;
removeSubscription(); super.dispose();
} }
private void poll() { private void poll() {
@ -130,14 +125,9 @@ public class WemoHolmesHandler extends WemoBaseThingHandler {
logger.debug("UPnP device {} not yet registered", getUDN()); logger.debug("UPnP device {} not yet registered", getUDN());
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING, updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING,
"@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]"); "@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
synchronized (upnpLock) {
subscriptionState = new HashMap<>();
}
return; return;
} }
updateStatus(ThingStatus.ONLINE);
updateWemoState(); updateWemoState();
addSubscription();
} catch (Exception e) { } catch (Exception e) {
logger.debug("Exception during poll: {}", e.getMessage(), 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) { public void handleCommand(ChannelUID channelUID, Command command) {
String localHost = getHost(); String localHost = getHost();
if (localHost.isEmpty()) { 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()); getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip"); "@text/config-status.error.missing-ip");
@ -156,7 +146,7 @@ public class WemoHolmesHandler extends WemoBaseThingHandler {
} }
String wemoURL = getWemoURL(localHost, DEVICEACTION); String wemoURL = getWemoURL(localHost, DEVICEACTION);
if (wemoURL == null) { 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()); getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url"); "@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 + "<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>" + "&lt;/value&gt;&lt;/attribute&gt;</attributeList>" + "</u:SetAttributes>" + "</s:Body>"
+ "</s:Envelope>"; + "</s:Envelope>";
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
if (wemoCallResponse != null && logger.isTraceEnabled()) { updateStatus(ThingStatus.ONLINE);
logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID()); } catch (IOException e) {
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) {
logger.debug("Failed to send command '{}' for device '{}':", command, getThing().getUID(), e); logger.debug("Failed to send command '{}' for device '{}':", command, getThing().getUID(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); 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 @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 * The {@link updateWemoState} polls the actual state of a WeMo device and
* calls {@link onValueReceived} to update the statemap and channels.. * calls {@link onValueReceived} to update the statemap and channels..
@ -354,7 +286,7 @@ public class WemoHolmesHandler extends WemoBaseThingHandler {
protected void updateWemoState() { protected void updateWemoState() {
String localHost = getHost(); String localHost = getHost();
if (localHost.isEmpty()) { 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, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip"); "@text/config-status.error.missing-ip");
return; return;
@ -362,7 +294,7 @@ public class WemoHolmesHandler extends WemoBaseThingHandler {
String actionService = DEVICEACTION; String actionService = DEVICEACTION;
String wemoURL = getWemoURL(localHost, actionService); String wemoURL = getWemoURL(localHost, actionService);
if (wemoURL == null) { 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, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url"); "@text/config-status.error.missing-url");
return; return;
@ -372,153 +304,49 @@ public class WemoHolmesHandler extends WemoBaseThingHandler {
String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\""; String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
String content = createStateRequestContent(action, actionService); String content = createStateRequestContent(action, actionService);
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
if (wemoCallResponse != null) { String stringParser = substringBetween(wemoCallResponse, "<attributeList>", "</attributeList>");
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);
// Due to Belkins bad response formatting, we need to run this twice. logger.trace("AirPurifier response '{}' for device '{}' received", stringParser, getThing().getUID());
stringParser = unescapeXml(stringParser);
stringParser = unescapeXml(stringParser);
logger.trace("AirPurifier response '{}' for device '{}' received", stringParser, getThing().getUID()); stringParser = "<data>" + stringParser + "</data>";
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));
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); Document doc = db.parse(is);
// see NodeList nodes = doc.getElementsByTagName("attribute");
// 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); // iterate the attributes
NodeList nodes = doc.getElementsByTagName("attribute"); for (int i = 0; i < nodes.getLength(); i++) {
Element element = (Element) nodes.item(i);
// iterate the attributes NodeList deviceIndex = element.getElementsByTagName("name");
for (int i = 0; i < nodes.getLength(); i++) { Element line = (Element) deviceIndex.item(0);
Element element = (Element) nodes.item(i); String attributeName = getCharacterDataFromElement(line);
logger.trace("attributeName: {}", attributeName);
NodeList deviceIndex = element.getElementsByTagName("name"); NodeList deviceID = element.getElementsByTagName("value");
Element line = (Element) deviceIndex.item(0); line = (Element) deviceID.item(0);
String attributeName = getCharacterDataFromElement(line); String attributeValue = getCharacterDataFromElement(line);
logger.trace("attributeName: {}", attributeName); logger.trace("attributeValue: {}", attributeValue);
NodeList deviceID = element.getElementsByTagName("value"); State newMode = new StringType();
line = (Element) deviceID.item(0); switch (attributeName) {
String attributeValue = getCharacterDataFromElement(line); case "Mode":
logger.trace("attributeValue: {}", attributeValue); if ("purifier".equals(getThing().getThingTypeUID().getId())) {
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":
switch (attributeValue) { switch (attributeValue) {
case "0": case "0":
newMode = new StringType("OFF"); newMode = new StringType("OFF");
@ -537,54 +365,149 @@ public class WemoHolmesHandler extends WemoBaseThingHandler {
break; break;
} }
updateState(CHANNEL_PURIFIERMODE, newMode); updateState(CHANNEL_PURIFIERMODE, newMode);
break; } else {
case "DesiredHumidity":
switch (attributeValue) { switch (attributeValue) {
case "0": case "0":
newMode = new PercentType("45"); newMode = new StringType("OFF");
break; break;
case "1": case "1":
newMode = new PercentType("50"); newMode = new StringType("FROSTPROTECT");
break; break;
case "2": case "2":
newMode = new PercentType("55"); newMode = new StringType("HIGH");
break; break;
case "3": case "3":
newMode = new PercentType("60"); newMode = new StringType("LOW");
break; break;
case "4": case "4":
newMode = new PercentType("100"); newMode = new StringType("ECO");
break; break;
} }
updateState(CHANNEL_DESIREDHUMIDITY, newMode); updateState(CHANNEL_HEATERMODE, newMode);
break; }
case "CurrentHumidity": break;
newMode = new StringType(attributeValue); case "Ionizer":
updateState(CHANNEL_CURRENTHUMIDITY, newMode); switch (attributeValue) {
break; case "0":
case "Temperature": newMode = OnOffType.OFF;
newMode = new StringType(attributeValue); break;
updateState(CHANNEL_CURRENTTEMP, newMode); case "1":
break; newMode = OnOffType.ON;
case "SetTemperature": break;
newMode = new StringType(attributeValue); }
updateState(CHANNEL_TARGETTEMP, newMode); updateState(CHANNEL_IONIZER, newMode);
break; break;
case "AutoOffTime": case "AirQuality":
newMode = new StringType(attributeValue); switch (attributeValue) {
updateState(CHANNEL_AUTOOFFTIME, newMode); case "0":
break; newMode = new StringType("POOR");
case "TimeRemaining": break;
newMode = new StringType(attributeValue); case "1":
updateState(CHANNEL_HEATINGREMAINING, newMode); newMode = new StringType("MODERATE");
break; 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) { } catch (RuntimeException | ParserConfigurationException | SAXException | IOException e) {
logger.debug("Failed to get actual state for device '{}':", getThing().getUID(), e); logger.debug("Failed to get actual state for device '{}':", getThing().getUID(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
} }
updateStatus(ThingStatus.ONLINE);
} }
} }

View File

@ -84,7 +84,7 @@ public class WemoInsightHandler extends WemoHandler {
try { try {
lastChangedAt = Long.parseLong(splitInsightParams[1]) * 1000; // convert s to ms lastChangedAt = Long.parseLong(splitInsightParams[1]) * 1000; // convert s to ms
} catch (NumberFormatException e) { } 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()); splitInsightParams[1], getThing().getUID());
} }
ZonedDateTime zoned = ZonedDateTime.ofInstant(Instant.ofEpochMilli(lastChangedAt), ZonedDateTime zoned = ZonedDateTime.ofInstant(Instant.ofEpochMilli(lastChangedAt),

View 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.WemoBindingConstants.*;
import static org.openhab.binding.wemo.internal.WemoUtil.*; 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.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -52,9 +50,6 @@ public class WemoLightHandler extends WemoBaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(WemoLightHandler.class); 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 final Object jobLock = new Object();
private @Nullable WemoBridgeHandler wemoBridgeHandler; private @Nullable WemoBridgeHandler wemoBridgeHandler;
@ -68,8 +63,6 @@ public class WemoLightHandler extends WemoBaseThingHandler {
*/ */
private static final int DIM_STEPSIZE = 5; private static final int DIM_STEPSIZE = 5;
protected static final String SUBSCRIPTION = "bridge1";
/** /**
* The default refresh initial delay in Seconds. * The default refresh initial delay in Seconds.
*/ */
@ -85,15 +78,13 @@ public class WemoLightHandler extends WemoBaseThingHandler {
@Override @Override
public void initialize() { public void initialize() {
super.initialize();
// initialize() is only called if the required parameter 'deviceID' is available // initialize() is only called if the required parameter 'deviceID' is available
wemoLightID = (String) getConfig().get(DEVICE_ID); wemoLightID = (String) getConfig().get(DEVICE_ID);
final Bridge bridge = getBridge(); final Bridge bridge = getBridge();
if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE) { if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE) {
UpnpIOService localService = service; addSubscription(BRIDGEEVENT);
if (localService != null) {
localService.registerParticipant(this);
}
host = getHost(); host = getHost();
pollingJob = scheduler.scheduleWithFixedDelay(this::poll, DEFAULT_REFRESH_INITIAL_DELAY, pollingJob = scheduler.scheduleWithFixedDelay(this::poll, DEFAULT_REFRESH_INITIAL_DELAY,
DEFAULT_REFRESH_INTERVAL_SECONDS, TimeUnit.SECONDS); DEFAULT_REFRESH_INTERVAL_SECONDS, TimeUnit.SECONDS);
@ -119,20 +110,20 @@ public class WemoLightHandler extends WemoBaseThingHandler {
@Override @Override
public void dispose() { public void dispose() {
logger.debug("WeMoLightHandler disposed."); logger.debug("WemoLightHandler disposed.");
ScheduledFuture<?> job = this.pollingJob; ScheduledFuture<?> job = this.pollingJob;
if (job != null && !job.isCancelled()) { if (job != null && !job.isCancelled()) {
job.cancel(true); job.cancel(true);
} }
this.pollingJob = null; this.pollingJob = null;
removeSubscription(); super.dispose();
} }
private synchronized @Nullable WemoBridgeHandler getWemoBridgeHandler() { private synchronized @Nullable WemoBridgeHandler getWemoBridgeHandler() {
Bridge bridge = getBridge(); Bridge bridge = getBridge();
if (bridge == null) { if (bridge == null) {
logger.error("Required bridge not defined for device {}.", wemoLightID); logger.warn("Required bridge not defined for device {}.", wemoLightID);
return null; return null;
} }
ThingHandler handler = bridge.getHandler(); ThingHandler handler = bridge.getHandler();
@ -159,14 +150,9 @@ public class WemoLightHandler extends WemoBaseThingHandler {
logger.debug("UPnP device {} not yet registered", getUDN()); logger.debug("UPnP device {} not yet registered", getUDN());
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING, updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING,
"@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]"); "@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
synchronized (upnpLock) {
subscriptionState = new HashMap<>();
}
return; return;
} }
updateStatus(ThingStatus.ONLINE);
getDeviceState(); getDeviceState();
addSubscription();
} catch (Exception e) { } catch (Exception e) {
logger.debug("Exception during poll: {}", e.getMessage(), 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) { public void handleCommand(ChannelUID channelUID, Command command) {
String localHost = getHost(); String localHost = getHost();
if (localHost.isEmpty()) { 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()); getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip"); "@text/config-status.error.missing-ip");
@ -185,7 +171,7 @@ public class WemoLightHandler extends WemoBaseThingHandler {
} }
String wemoURL = getWemoURL(localHost, BASICACTION); String wemoURL = getWemoURL(localHost, BASICACTION);
if (wemoURL == null) { 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()); getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url"); "@text/config-status.error.missing-url");
@ -277,25 +263,18 @@ public class WemoLightHandler extends WemoBaseThingHandler {
+ "&lt;/CapabilityValue&gt;&lt;/DeviceStatus&gt;" + "</DeviceStatusList>" + "&lt;/CapabilityValue&gt;&lt;/DeviceStatus&gt;" + "</DeviceStatusList>"
+ "</u:SetDeviceStatus>" + "</s:Body>" + "</s:Envelope>"; + "</u:SetDeviceStatus>" + "</s:Body>" + "</s:Envelope>";
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
if (wemoCallResponse != null) { if ("10008".equals(capability)) {
if (logger.isTraceEnabled()) { OnOffType binaryState = null;
logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID()); binaryState = "0".equals(value) ? OnOffType.OFF : OnOffType.ON;
logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, updateState(CHANNEL_STATE, binaryState);
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);
}
} }
updateStatus(ThingStatus.ONLINE);
} }
} catch (Exception e) { } 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() { public void getDeviceState() {
String localHost = getHost(); String localHost = getHost();
if (localHost.isEmpty()) { 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, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip"); "@text/config-status.error.missing-ip");
return; return;
@ -325,7 +304,7 @@ public class WemoLightHandler extends WemoBaseThingHandler {
logger.debug("Request actual state for LightID '{}'", wemoLightID); logger.debug("Request actual state for LightID '{}'", wemoLightID);
String wemoURL = getWemoURL(localHost, BRIDGEACTION); String wemoURL = getWemoURL(localHost, BRIDGEACTION);
if (wemoURL == null) { 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, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url"); "@text/config-status.error.missing-url");
return; return;
@ -338,43 +317,33 @@ public class WemoLightHandler extends WemoBaseThingHandler {
+ wemoLightID + "</DeviceIDs>" + "</u:GetDeviceStatus>" + "</s:Body>" + "</s:Envelope>"; + wemoLightID + "</DeviceIDs>" + "</u:GetDeviceStatus>" + "</s:Body>" + "</s:Envelope>";
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
if (wemoCallResponse != null) { wemoCallResponse = unescapeXml(wemoCallResponse);
if (logger.isTraceEnabled()) { String response = substringBetween(wemoCallResponse, "<CapabilityValue>", "</CapabilityValue>");
logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID()); logger.trace("wemoNewLightState = {}", response);
logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID()); String[] splitResponse = response.split(",");
logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID()); if (splitResponse[0] != null) {
logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID()); OnOffType binaryState = null;
} binaryState = "0".equals(splitResponse[0]) ? OnOffType.OFF : OnOffType.ON;
wemoCallResponse = unescapeXml(wemoCallResponse); updateState(CHANNEL_STATE, binaryState);
String response = substringBetween(wemoCallResponse, "<CapabilityValue>", "</CapabilityValue>"); }
logger.trace("wemoNewLightState = {}", response); if (splitResponse[1] != null) {
String[] splitResponse = response.split(","); String splitBrightness[] = splitResponse[1].split(":");
if (splitResponse[0] != null) { if (splitBrightness[0] != null) {
OnOffType binaryState = null; int newBrightnessValue = Integer.valueOf(splitBrightness[0]);
binaryState = "0".equals(splitResponse[0]) ? OnOffType.OFF : OnOffType.ON; int newBrightness = Math.round(newBrightnessValue * 100 / 255);
updateState(CHANNEL_STATE, binaryState); logger.trace("newBrightness = {}", newBrightness);
} State newBrightnessState = new PercentType(newBrightness);
if (splitResponse[1] != null) { updateState(CHANNEL_BRIGHTNESS, newBrightnessState);
String splitBrightness[] = splitResponse[1].split(":"); currentBrightness = newBrightness;
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) { } 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 @Override
public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) { public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) {
logger.trace("Received pair '{}':'{}' (service '{}') for thing '{}'", logger.trace("Received pair '{}':'{}' (service '{}') for thing '{}'",
@ -399,44 +368,4 @@ public class WemoLightHandler extends WemoBaseThingHandler {
break; 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);
}
}
}
}
} }

View File

@ -70,14 +70,11 @@ public class WemoMakerHandler extends WemoBaseThingHandler {
@Override @Override
public void initialize() { public void initialize() {
super.initialize();
Configuration configuration = getConfig(); Configuration configuration = getConfig();
if (configuration.get(UDN) != null) { if (configuration.get(UDN) != null) {
logger.debug("Initializing WemoMakerHandler for UDN '{}'", configuration.get(UDN)); logger.debug("Initializing WemoMakerHandler for UDN '{}'", configuration.get(UDN));
UpnpIOService localService = service;
if (localService != null) {
localService.registerParticipant(this);
}
host = getHost(); host = getHost();
pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS, pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS,
TimeUnit.SECONDS); TimeUnit.SECONDS);
@ -91,17 +88,14 @@ public class WemoMakerHandler extends WemoBaseThingHandler {
@Override @Override
public void dispose() { public void dispose() {
logger.debug("WeMoMakerHandler disposed."); logger.debug("WemoMakerHandler disposed.");
ScheduledFuture<?> job = this.pollingJob; ScheduledFuture<?> job = this.pollingJob;
if (job != null && !job.isCancelled()) { if (job != null && !job.isCancelled()) {
job.cancel(true); job.cancel(true);
} }
this.pollingJob = null; this.pollingJob = null;
UpnpIOService localService = service; super.dispose();
if (localService != null) {
localService.unregisterParticipant(this);
}
} }
private void poll() { private void poll() {
@ -120,7 +114,6 @@ public class WemoMakerHandler extends WemoBaseThingHandler {
"@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]"); "@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
return; return;
} }
updateStatus(ThingStatus.ONLINE);
updateWemoState(); updateWemoState();
} catch (Exception e) { } catch (Exception e) {
logger.debug("Exception during poll: {}", e.getMessage(), 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) { public void handleCommand(ChannelUID channelUID, Command command) {
String localHost = getHost(); String localHost = getHost();
if (localHost.isEmpty()) { 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()); getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip"); "@text/config-status.error.missing-ip");
@ -140,7 +133,7 @@ public class WemoMakerHandler extends WemoBaseThingHandler {
} }
String wemoURL = getWemoURL(localHost, BASICACTION); String wemoURL = getWemoURL(localHost, BASICACTION);
if (wemoURL == null) { 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()); getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url"); "@text/config-status.error.missing-url");
@ -158,16 +151,11 @@ public class WemoMakerHandler extends WemoBaseThingHandler {
boolean binaryState = OnOffType.ON.equals(command) ? true : false; boolean binaryState = OnOffType.ON.equals(command) ? true : false;
String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\""; String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\"";
String content = createBinaryStateContent(binaryState); String content = createBinaryStateContent(binaryState);
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
if (wemoCallResponse != null && logger.isTraceEnabled()) { updateStatus(ThingStatus.ONLINE);
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 (Exception e) { } 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() { protected void updateWemoState() {
String localHost = getHost(); String localHost = getHost();
if (localHost.isEmpty()) { 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, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip"); "@text/config-status.error.missing-ip");
return; return;
@ -187,7 +175,7 @@ public class WemoMakerHandler extends WemoBaseThingHandler {
String actionService = DEVICEACTION; String actionService = DEVICEACTION;
String wemoURL = getWemoURL(localHost, actionService); String wemoURL = getWemoURL(localHost, actionService);
if (wemoURL == null) { 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, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url"); "@text/config-status.error.missing-url");
return; return;
@ -197,75 +185,69 @@ public class WemoMakerHandler extends WemoBaseThingHandler {
String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\""; String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
String content = createStateRequestContent(action, actionService); String content = createStateRequestContent(action, actionService);
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content); String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
if (wemoCallResponse != null) { try {
if (logger.isTraceEnabled()) { String stringParser = substringBetween(wemoCallResponse, "<attributeList>", "</attributeList>");
logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID()); logger.trace("Escaped Maker response for device '{}' :", getThing().getUID());
logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID()); logger.trace("'{}'", stringParser);
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. // Due to Belkins bad response formatting, we need to run this twice.
stringParser = unescapeXml(stringParser); stringParser = unescapeXml(stringParser);
stringParser = unescapeXml(stringParser); stringParser = unescapeXml(stringParser);
logger.trace("Maker response '{}' for device '{}' received", stringParser, getThing().getUID()); logger.trace("Maker response '{}' for device '{}' received", stringParser, getThing().getUID());
stringParser = "<data>" + stringParser + "</data>"; stringParser = "<data>" + stringParser + "</data>";
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
// see // see
// https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html // 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-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-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.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
dbf.setXIncludeAware(false); dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false); dbf.setExpandEntityReferences(false);
DocumentBuilder db = dbf.newDocumentBuilder(); DocumentBuilder db = dbf.newDocumentBuilder();
InputSource is = new InputSource(); InputSource is = new InputSource();
is.setCharacterStream(new StringReader(stringParser)); is.setCharacterStream(new StringReader(stringParser));
Document doc = db.parse(is); Document doc = db.parse(is);
NodeList nodes = doc.getElementsByTagName("attribute"); NodeList nodes = doc.getElementsByTagName("attribute");
// iterate the attributes // iterate the attributes
for (int i = 0; i < nodes.getLength(); i++) { for (int i = 0; i < nodes.getLength(); i++) {
Element element = (Element) nodes.item(i); Element element = (Element) nodes.item(i);
NodeList deviceIndex = element.getElementsByTagName("name"); NodeList deviceIndex = element.getElementsByTagName("name");
Element line = (Element) deviceIndex.item(0); Element line = (Element) deviceIndex.item(0);
String attributeName = getCharacterDataFromElement(line); String attributeName = getCharacterDataFromElement(line);
logger.trace("attributeName: {}", attributeName); logger.trace("attributeName: {}", attributeName);
NodeList deviceID = element.getElementsByTagName("value"); NodeList deviceID = element.getElementsByTagName("value");
line = (Element) deviceID.item(0); line = (Element) deviceID.item(0);
String attributeValue = getCharacterDataFromElement(line); String attributeValue = getCharacterDataFromElement(line);
logger.trace("attributeValue: {}", attributeValue); logger.trace("attributeValue: {}", attributeValue);
switch (attributeName) { switch (attributeName) {
case "Switch": case "Switch":
State relayState = "0".equals(attributeValue) ? OnOffType.OFF : OnOffType.ON; State relayState = "0".equals(attributeValue) ? OnOffType.OFF : OnOffType.ON;
logger.debug("New relayState '{}' for device '{}' received", relayState, logger.debug("New relayState '{}' for device '{}' received", relayState,
getThing().getUID()); getThing().getUID());
updateState(CHANNEL_RELAY, relayState); updateState(CHANNEL_RELAY, relayState);
break; break;
case "Sensor": case "Sensor":
State sensorState = "1".equals(attributeValue) ? OnOffType.OFF : OnOffType.ON; State sensorState = "1".equals(attributeValue) ? OnOffType.OFF : OnOffType.ON;
logger.debug("New sensorState '{}' for device '{}' received", sensorState, logger.debug("New sensorState '{}' for device '{}' received", sensorState,
getThing().getUID()); getThing().getUID());
updateState(CHANNEL_SENSOR, sensorState); updateState(CHANNEL_SENSOR, sensorState);
break; 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) { } 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());
} }
} }
} }

View File

@ -19,7 +19,6 @@ import java.nio.charset.StandardCharsets;
import java.util.Properties; import java.util.Properties;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.wemo.internal.WemoBindingConstants; import org.openhab.binding.wemo.internal.WemoBindingConstants;
import org.openhab.core.io.net.http.HttpUtil; import org.openhab.core.io.net.http.HttpUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -35,20 +34,18 @@ public class WemoHttpCall {
private final Logger logger = LoggerFactory.getLogger(WemoHttpCall.class); private final Logger logger = LoggerFactory.getLogger(WemoHttpCall.class);
public @Nullable String executeCall(String wemoURL, String soapHeader, String content) { public String executeCall(String wemoURL, String soapHeader, String content) throws IOException {
try { Properties wemoHeaders = new Properties();
Properties wemoHeaders = new Properties(); wemoHeaders.setProperty("CONTENT-TYPE", WemoBindingConstants.HTTP_CALL_CONTENT_HEADER);
wemoHeaders.setProperty("CONTENT-TYPE", WemoBindingConstants.HTTP_CALL_CONTENT_HEADER); wemoHeaders.put("SOAPACTION", soapHeader);
wemoHeaders.put("SOAPACTION", soapHeader);
InputStream wemoContent = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)); InputStream wemoContent = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
String wemoCallResponse = HttpUtil.executeUrl("POST", wemoURL, wemoHeaders, wemoContent, null, 2000); logger.trace("Performing HTTP call for URL: '{}', header: '{}', request body: '{}'", wemoURL, soapHeader,
return wemoCallResponse; content);
} catch (IOException e) { String responseBody = HttpUtil.executeUrl("POST", wemoURL, wemoHeaders, wemoContent, null, 2000);
// throw new IllegalStateException("Could not call WeMo", e); logger.trace("HTTP response body: '{}'", responseBody);
logger.debug("Could not make HTTP call to WeMo");
return null; return responseBody;
}
} }
} }

View File

@ -50,7 +50,7 @@ public class WemoLinkDiscoveryServiceOSGiTest extends GenericWemoLightOSGiTestPa
@Test @Test
public void assertSupportedThingIsDiscovered() public void assertSupportedThingIsDiscovered()
throws MalformedURLException, URISyntaxException, ValidationException { throws MalformedURLException, URISyntaxException, ValidationException, IOException {
String model = WemoBindingConstants.THING_TYPE_MZ100.getId(); String model = WemoBindingConstants.THING_TYPE_MZ100.getId();
addUpnpDevice(SERVICE_ID, SERVICE_NUMBER, model); addUpnpDevice(SERVICE_ID, SERVICE_NUMBER, model);

View File

@ -69,7 +69,7 @@ public class WemoHandlerOSGiTest extends GenericWemoOSGiTest {
@Test @Test
public void assertThatThingHandlesOnOffCommandCorrectly() public void assertThatThingHandlesOnOffCommandCorrectly()
throws MalformedURLException, URISyntaxException, ValidationException { throws MalformedURLException, URISyntaxException, ValidationException, IOException {
Command command = OnOffType.OFF; Command command = OnOffType.OFF;
Thing thing = createThing(THING_TYPE_UID, DEFAULT_TEST_CHANNEL, DEFAULT_TEST_CHANNEL_TYPE); Thing thing = createThing(THING_TYPE_UID, DEFAULT_TEST_CHANNEL, DEFAULT_TEST_CHANNEL_TYPE);
@ -105,7 +105,7 @@ public class WemoHandlerOSGiTest extends GenericWemoOSGiTest {
@Test @Test
public void assertThatThingHandlesREFRESHCommandCorrectly() public void assertThatThingHandlesREFRESHCommandCorrectly()
throws MalformedURLException, URISyntaxException, ValidationException { throws MalformedURLException, URISyntaxException, ValidationException, IOException {
Command command = RefreshType.REFRESH; Command command = RefreshType.REFRESH;
Thing thing = createThing(THING_TYPE_UID, DEFAULT_TEST_CHANNEL, DEFAULT_TEST_CHANNEL_TYPE); Thing thing = createThing(THING_TYPE_UID, DEFAULT_TEST_CHANNEL, DEFAULT_TEST_CHANNEL_TYPE);

View File

@ -65,7 +65,7 @@ public class WemoLightHandlerOSGiTest extends GenericWemoLightOSGiTestParent {
@Test @Test
public void handleONcommandForBRIGHTNESSchannel() public void handleONcommandForBRIGHTNESSchannel()
throws MalformedURLException, URISyntaxException, ValidationException { throws MalformedURLException, URISyntaxException, ValidationException, IOException {
Command command = OnOffType.ON; Command command = OnOffType.ON;
String channelID = WemoBindingConstants.CHANNEL_BRIGHTNESS; String channelID = WemoBindingConstants.CHANNEL_BRIGHTNESS;
@ -80,7 +80,7 @@ public class WemoLightHandlerOSGiTest extends GenericWemoLightOSGiTestParent {
@Test @Test
public void handlePercentCommandForBRIGHTNESSChannel() public void handlePercentCommandForBRIGHTNESSChannel()
throws MalformedURLException, URISyntaxException, ValidationException { throws MalformedURLException, URISyntaxException, ValidationException, IOException {
// Set brightness value to 20 Percent // Set brightness value to 20 Percent
Command command = new PercentType(20); Command command = new PercentType(20);
String channelID = WemoBindingConstants.CHANNEL_BRIGHTNESS; String channelID = WemoBindingConstants.CHANNEL_BRIGHTNESS;
@ -95,7 +95,7 @@ public class WemoLightHandlerOSGiTest extends GenericWemoLightOSGiTestParent {
@Test @Test
public void handleIncreaseCommandForBRIGHTNESSchannel() public void handleIncreaseCommandForBRIGHTNESSchannel()
throws MalformedURLException, URISyntaxException, ValidationException { throws MalformedURLException, URISyntaxException, ValidationException, IOException {
// The value is increased by 5 Percents by default // The value is increased by 5 Percents by default
Command command = IncreaseDecreaseType.INCREASE; Command command = IncreaseDecreaseType.INCREASE;
String channelID = WemoBindingConstants.CHANNEL_BRIGHTNESS; String channelID = WemoBindingConstants.CHANNEL_BRIGHTNESS;
@ -110,7 +110,7 @@ public class WemoLightHandlerOSGiTest extends GenericWemoLightOSGiTestParent {
@Test @Test
public void handleDecreaseCommandForBRIGHTNESSchannel() public void handleDecreaseCommandForBRIGHTNESSchannel()
throws MalformedURLException, URISyntaxException, ValidationException { throws MalformedURLException, URISyntaxException, ValidationException, IOException {
// The value can not be decreased below 0 // The value can not be decreased below 0
Command command = IncreaseDecreaseType.DECREASE; Command command = IncreaseDecreaseType.DECREASE;
String channelID = WemoBindingConstants.CHANNEL_BRIGHTNESS; String channelID = WemoBindingConstants.CHANNEL_BRIGHTNESS;
@ -123,7 +123,8 @@ public class WemoLightHandlerOSGiTest extends GenericWemoLightOSGiTestParent {
} }
@Test @Test
public void handleOnCommandForSTATEChannel() throws MalformedURLException, URISyntaxException, ValidationException { public void handleOnCommandForSTATEChannel()
throws MalformedURLException, URISyntaxException, ValidationException, IOException {
Command command = OnOffType.ON; Command command = OnOffType.ON;
String channelID = WemoBindingConstants.CHANNEL_STATE; String channelID = WemoBindingConstants.CHANNEL_STATE;
@ -137,7 +138,7 @@ public class WemoLightHandlerOSGiTest extends GenericWemoLightOSGiTestParent {
@Test @Test
public void handleREFRESHCommandForChannelSTATE() public void handleREFRESHCommandForChannelSTATE()
throws MalformedURLException, URISyntaxException, ValidationException { throws MalformedURLException, URISyntaxException, ValidationException, IOException {
Command command = RefreshType.REFRESH; Command command = RefreshType.REFRESH;
String channelID = WemoBindingConstants.CHANNEL_STATE; 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, 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 bridge = createBridge(BRIDGE_TYPE_UID);
Thing thing = createThing(THING_TYPE_UID, DEFAULT_TEST_CHANNEL, DEFAULT_TEST_CHANNEL_TYPE); Thing thing = createThing(THING_TYPE_UID, DEFAULT_TEST_CHANNEL, DEFAULT_TEST_CHANNEL_TYPE);

View File

@ -70,7 +70,7 @@ public class WemoMakerHandlerOSGiTest extends GenericWemoOSGiTest {
@Test @Test
public void assertThatThingHandlesOnOffCommandCorrectly() public void assertThatThingHandlesOnOffCommandCorrectly()
throws MalformedURLException, URISyntaxException, ValidationException { throws MalformedURLException, URISyntaxException, ValidationException, IOException {
Command command = OnOffType.OFF; Command command = OnOffType.OFF;
Thing thing = createThing(THING_TYPE_UID, DEFAULT_TEST_CHANNEL, DEFAULT_TEST_CHANNEL_TYPE); Thing thing = createThing(THING_TYPE_UID, DEFAULT_TEST_CHANNEL, DEFAULT_TEST_CHANNEL_TYPE);
@ -106,7 +106,7 @@ public class WemoMakerHandlerOSGiTest extends GenericWemoOSGiTest {
@Test @Test
public void assertThatThingHandlesREFRESHCommand() public void assertThatThingHandlesREFRESHCommand()
throws MalformedURLException, URISyntaxException, ValidationException { throws MalformedURLException, URISyntaxException, ValidationException, IOException {
Command command = RefreshType.REFRESH; Command command = RefreshType.REFRESH;
Thing thing = createThing(THING_TYPE_UID, DEFAULT_TEST_CHANNEL, DEFAULT_TEST_CHANNEL_TYPE); Thing thing = createThing(THING_TYPE_UID, DEFAULT_TEST_CHANNEL, DEFAULT_TEST_CHANNEL_TYPE);