* 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>
String endDeviceRequest = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
- if (endDeviceRequest != null) {
- logger.trace("endDeviceRequest answered '{}'", endDeviceRequest);
+ logger.trace("endDeviceRequest answered '{}'", endDeviceRequest);
- try {
- String stringParser = substringBetween(endDeviceRequest, "<DeviceLists>", "</DeviceLists>");
+ try {
+ String stringParser = substringBetween(endDeviceRequest, "<DeviceLists>", "</DeviceLists>");
- stringParser = unescapeXml(stringParser);
+ stringParser = unescapeXml(stringParser);
- // check if there are already paired devices with WeMo Link
- if ("0".equals(stringParser)) {
- logger.debug("There are no devices connected with WeMo Link. Exit discovery");
- return;
- }
+ // check if there are already paired devices with WeMo Link
+ if ("0".equals(stringParser)) {
+ logger.debug("There are no devices connected with WeMo Link. Exit discovery");
+ return;
+ }
- // Build parser for received <DeviceList>
- DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
- // see
- // https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
- dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
- dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
- dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
- dbf.setXIncludeAware(false);
- dbf.setExpandEntityReferences(false);
- DocumentBuilder db = dbf.newDocumentBuilder();
- InputSource is = new InputSource();
- is.setCharacterStream(new StringReader(stringParser));
-
- Document doc = db.parse(is);
- NodeList nodes = doc.getElementsByTagName("DeviceInfo");
-
- // iterate the devices
- for (int i = 0; i < nodes.getLength(); i++) {
- Element element = (Element) nodes.item(i);
-
- NodeList deviceIndex = element.getElementsByTagName("DeviceIndex");
- Element line = (Element) deviceIndex.item(0);
- logger.trace("DeviceIndex: {}", getCharacterDataFromElement(line));
-
- NodeList deviceID = element.getElementsByTagName("DeviceID");
- line = (Element) deviceID.item(0);
- String endDeviceID = getCharacterDataFromElement(line);
- logger.trace("DeviceID: {}", endDeviceID);
-
- NodeList friendlyName = element.getElementsByTagName("FriendlyName");
- line = (Element) friendlyName.item(0);
- String endDeviceName = getCharacterDataFromElement(line);
- logger.trace("FriendlyName: {}", endDeviceName);
-
- NodeList vendor = element.getElementsByTagName("Manufacturer");
- line = (Element) vendor.item(0);
- String endDeviceVendor = getCharacterDataFromElement(line);
- logger.trace("Manufacturer: {}", endDeviceVendor);
-
- NodeList model = element.getElementsByTagName("ModelCode");
- line = (Element) model.item(0);
- String endDeviceModelID = getCharacterDataFromElement(line);
- endDeviceModelID = endDeviceModelID.replaceAll(NORMALIZE_ID_REGEX, "_");
-
- logger.trace("ModelCode: {}", endDeviceModelID);
-
- if (SUPPORTED_THING_TYPES.contains(new ThingTypeUID(BINDING_ID, endDeviceModelID))) {
- logger.debug("Discovered a WeMo LED Light thing with ID '{}'", endDeviceID);
-
- ThingUID bridgeUID = wemoBridgeHandler.getThing().getUID();
- ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, endDeviceModelID);
-
- if (thingTypeUID.equals(THING_TYPE_MZ100)) {
- String thingLightId = endDeviceID;
- ThingUID thingUID = new ThingUID(thingTypeUID, bridgeUID, thingLightId);
-
- Map<String, Object> properties = new HashMap<>(1);
- properties.put(DEVICE_ID, endDeviceID);
-
- DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
- .withProperties(properties)
- .withBridge(wemoBridgeHandler.getThing().getUID()).withLabel(endDeviceName)
- .build();
-
- thingDiscovered(discoveryResult);
- }
- } else {
- logger.debug("Discovered an unsupported device :");
- logger.debug("DeviceIndex : {}", getCharacterDataFromElement(line));
- logger.debug("DeviceID : {}", endDeviceID);
- logger.debug("FriendlyName: {}", endDeviceName);
- logger.debug("Manufacturer: {}", endDeviceVendor);
- logger.debug("ModelCode : {}", endDeviceModelID);
+ // Build parser for received <DeviceList>
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+ // see
+ // https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
+ dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
+ dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+ dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
+ dbf.setXIncludeAware(false);
+ dbf.setExpandEntityReferences(false);
+ DocumentBuilder db = dbf.newDocumentBuilder();
+ InputSource is = new InputSource();
+ is.setCharacterStream(new StringReader(stringParser));
+
+ Document doc = db.parse(is);
+ NodeList nodes = doc.getElementsByTagName("DeviceInfo");
+
+ // iterate the devices
+ for (int i = 0; i < nodes.getLength(); i++) {
+ Element element = (Element) nodes.item(i);
+
+ NodeList deviceIndex = element.getElementsByTagName("DeviceIndex");
+ Element line = (Element) deviceIndex.item(0);
+ logger.trace("DeviceIndex: {}", getCharacterDataFromElement(line));
+
+ NodeList deviceID = element.getElementsByTagName("DeviceID");
+ line = (Element) deviceID.item(0);
+ String endDeviceID = getCharacterDataFromElement(line);
+ logger.trace("DeviceID: {}", endDeviceID);
+
+ NodeList friendlyName = element.getElementsByTagName("FriendlyName");
+ line = (Element) friendlyName.item(0);
+ String endDeviceName = getCharacterDataFromElement(line);
+ logger.trace("FriendlyName: {}", endDeviceName);
+
+ NodeList vendor = element.getElementsByTagName("Manufacturer");
+ line = (Element) vendor.item(0);
+ String endDeviceVendor = getCharacterDataFromElement(line);
+ logger.trace("Manufacturer: {}", endDeviceVendor);
+
+ NodeList model = element.getElementsByTagName("ModelCode");
+ line = (Element) model.item(0);
+ String endDeviceModelID = getCharacterDataFromElement(line);
+ endDeviceModelID = endDeviceModelID.replaceAll(NORMALIZE_ID_REGEX, "_");
+
+ logger.trace("ModelCode: {}", endDeviceModelID);
+
+ if (SUPPORTED_THING_TYPES.contains(new ThingTypeUID(BINDING_ID, endDeviceModelID))) {
+ logger.debug("Discovered a WeMo LED Light thing with ID '{}'", endDeviceID);
+
+ ThingUID bridgeUID = wemoBridgeHandler.getThing().getUID();
+ ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, endDeviceModelID);
+
+ if (thingTypeUID.equals(THING_TYPE_MZ100)) {
+ String thingLightId = endDeviceID;
+ ThingUID thingUID = new ThingUID(thingTypeUID, bridgeUID, thingLightId);
+
+ Map<String, Object> properties = new HashMap<>(1);
+ properties.put(DEVICE_ID, endDeviceID);
+
+ DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
+ .withProperties(properties).withBridge(wemoBridgeHandler.getThing().getUID())
+ .withLabel(endDeviceName).build();
+
+ thingDiscovered(discoveryResult);
}
-
+ } else {
+ logger.debug("Discovered an unsupported device :");
+ logger.debug("DeviceIndex : {}", getCharacterDataFromElement(line));
+ logger.debug("DeviceID : {}", endDeviceID);
+ logger.debug("FriendlyName: {}", endDeviceName);
+ logger.debug("Manufacturer: {}", endDeviceVendor);
+ logger.debug("ModelCode : {}", endDeviceModelID);
}
- } catch (Exception e) {
- logger.error("Failed to parse endDevices for bridge '{}'",
- wemoBridgeHandler.getThing().getUID(), e);
+
}
+ } catch (Exception e) {
+ logger.warn("Failed to parse endDevices for bridge '{}'", wemoBridgeHandler.getThing().getUID(), e);
}
}
} catch (Exception e) {
- logger.error("Failed to get endDevices for bridge '{}'", wemoBridgeHandler.getThing().getUID(), e);
+ logger.warn("Failed to get endDevices for bridge '{}'", wemoBridgeHandler.getThing().getUID(), e);
}
}
package org.openhab.binding.wemo.internal.handler;
import java.net.URL;
+import java.time.Instant;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* {@link WemoBaseThingHandler} provides a base implementation for the
- * concrete WeMo handlers for each thing type.
+ * concrete WeMo handlers.
*
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
public abstract class WemoBaseThingHandler extends BaseThingHandler implements UpnpIOParticipant {
+ private static final int SUBSCRIPTION_RENEWAL_INITIAL_DELAY_SECONDS = 15;
+ private static final int SUBSCRIPTION_RENEWAL_INTERVAL_SECONDS = 60;
+
+ private final Logger logger = LoggerFactory.getLogger(WemoBaseThingHandler.class);
+
protected @Nullable UpnpIOService service;
protected WemoHttpCall wemoHttpCaller;
protected String host = "";
+ private Map<String, Instant> subscriptions = new ConcurrentHashMap<String, Instant>();
+ private @Nullable ScheduledFuture<?> subscriptionRenewalJob;
+
public WemoBaseThingHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
super(thing);
this.service = upnpIOService;
@Override
public void initialize() {
- // can be overridden by subclasses
+ UpnpIOService service = this.service;
+ if (service != null) {
+ logger.debug("Registering UPnP participant for {}", getThing().getUID());
+ service.registerParticipant(this);
+ }
+ }
+
+ @Override
+ public void dispose() {
+ removeSubscriptions();
+ UpnpIOService service = this.service;
+ if (service != null) {
+ logger.debug("Unregistering UPnP participant for {}", getThing().getUID());
+ service.unregisterParticipant(this);
+ }
+ cancelSubscriptionRenewalJob();
}
@Override
@Override
public void onServiceSubscribed(@Nullable String service, boolean succeeded) {
- // can be overridden by subclasses
+ if (service == null) {
+ return;
+ }
+ logger.debug("Subscription to service {} for {} {}", service, getUDN(), succeeded ? "succeeded" : "failed");
+ if (succeeded) {
+ subscriptions.put(service, Instant.now());
+ }
}
@Override
protected boolean isUpnpDeviceRegistered() {
UpnpIOService service = this.service;
- if (service != null) {
- return service.isRegistered(this);
+ return service != null && service.isRegistered(this);
+ }
+
+ protected void addSubscription(String serviceId) {
+ if (subscriptions.containsKey(serviceId)) {
+ logger.debug("{} already subscribed to {}", getUDN(), serviceId);
+ return;
+ }
+ if (subscriptions.isEmpty()) {
+ logger.debug("Adding first GENA subscription for {}, scheduling renewal job", getUDN());
+ scheduleSubscriptionRenewalJob();
+ }
+ subscriptions.put(serviceId, Instant.ofEpochSecond(0));
+ UpnpIOService service = this.service;
+ if (service == null) {
+ return;
+ }
+ if (!service.isRegistered(this)) {
+ logger.debug("Registering UPnP participant for {}", getUDN());
+ service.registerParticipant(this);
+ }
+ if (!service.isRegistered(this)) {
+ logger.debug("Trying to add GENA subscription {} for {}, but service is not registered", serviceId,
+ getUDN());
+ return;
+ }
+ logger.debug("Adding GENA subscription {} for {}", serviceId, getUDN());
+ service.addSubscription(this, serviceId, WemoBindingConstants.SUBSCRIPTION_DURATION_SECONDS);
+ }
+
+ protected void removeSubscription(String serviceId) {
+ UpnpIOService service = this.service;
+ if (service == null) {
+ return;
+ }
+ subscriptions.remove(serviceId);
+ if (subscriptions.isEmpty()) {
+ logger.debug("Removing last GENA subscription for {}, cancelling renewal job", getUDN());
+ cancelSubscriptionRenewalJob();
+ }
+ if (!service.isRegistered(this)) {
+ logger.debug("Trying to remove GENA subscription {} for {}, but service is not registered", serviceId,
+ getUDN());
+ return;
+ }
+ logger.debug("Unsubscribing {} from service {}", getUDN(), serviceId);
+ service.removeSubscription(this, serviceId);
+ }
+
+ private void scheduleSubscriptionRenewalJob() {
+ cancelSubscriptionRenewalJob();
+ this.subscriptionRenewalJob = scheduler.scheduleWithFixedDelay(this::renewSubscriptions,
+ SUBSCRIPTION_RENEWAL_INITIAL_DELAY_SECONDS, SUBSCRIPTION_RENEWAL_INTERVAL_SECONDS, TimeUnit.SECONDS);
+ }
+
+ private void cancelSubscriptionRenewalJob() {
+ ScheduledFuture<?> subscriptionRenewalJob = this.subscriptionRenewalJob;
+ if (subscriptionRenewalJob != null) {
+ subscriptionRenewalJob.cancel(true);
+ }
+ this.subscriptionRenewalJob = null;
+ }
+
+ private void renewSubscriptions() {
+ if (subscriptions.isEmpty()) {
+ return;
+ }
+ UpnpIOService service = this.service;
+ if (service == null) {
+ return;
+ }
+ if (!service.isRegistered(this)) {
+ service.registerParticipant(this);
+ }
+ if (!service.isRegistered(this)) {
+ logger.debug("Trying to renew GENA subscriptions for {}, but service is not registered", getUDN());
+ return;
+ }
+ logger.debug("Renewing GENA subscriptions for {}", getUDN());
+ subscriptions.forEach((serviceId, lastRenewed) -> {
+ if (lastRenewed.isBefore(Instant.now().minusSeconds(
+ WemoBindingConstants.SUBSCRIPTION_DURATION_SECONDS - SUBSCRIPTION_RENEWAL_INTERVAL_SECONDS))) {
+ logger.debug("Subscription for service {} with timestamp {} has expired, renewing", serviceId,
+ lastRenewed);
+ service.removeSubscription(this, serviceId);
+ service.addSubscription(this, serviceId, WemoBindingConstants.SUBSCRIPTION_DURATION_SECONDS);
+ }
+ });
+ }
+
+ private void removeSubscriptions() {
+ if (subscriptions.isEmpty()) {
+ return;
+ }
+ UpnpIOService service = this.service;
+ if (service == null) {
+ return;
+ }
+ if (!service.isRegistered(this)) {
+ logger.debug("Trying to remove GENA subscriptions for {}, but service is not registered",
+ getThing().getUID());
+ return;
}
- return false;
+ logger.debug("Removing GENA subscriptions for {}", getUDN());
+ subscriptions.forEach((serviceId, lastRenewed) -> {
+ logger.debug("Removing subscription for service {}", serviceId);
+ service.removeSubscription(this, serviceId);
+ });
+ subscriptions.clear();
}
protected String getHost() {
import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.ScheduledFuture;
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_COFFEE);
- private final Object upnpLock = new Object();
private final Object jobLock = new Object();
- private Map<String, Boolean> subscriptionState = new HashMap<>();
-
private @Nullable ScheduledFuture<?> pollingJob;
public WemoCoffeeHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
@Override
public void initialize() {
+ super.initialize();
Configuration configuration = getConfig();
if (configuration.get(UDN) != null) {
logger.debug("Initializing WemoCoffeeHandler for UDN '{}'", configuration.get(UDN));
- UpnpIOService localService = service;
- if (localService != null) {
- localService.registerParticipant(this);
- }
+ addSubscription(DEVICEEVENT);
host = getHost();
pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS,
TimeUnit.SECONDS);
@Override
public void dispose() {
- logger.debug("WeMoCoffeeHandler disposed.");
+ logger.debug("WemoCoffeeHandler disposed.");
ScheduledFuture<?> job = this.pollingJob;
if (job != null && !job.isCancelled()) {
job.cancel(true);
}
this.pollingJob = null;
- removeSubscription();
+ super.dispose();
}
private void poll() {
logger.debug("UPnP device {} not yet registered", getUDN());
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING,
"@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
- synchronized (upnpLock) {
- subscriptionState = new HashMap<>();
- }
return;
}
- updateStatus(ThingStatus.ONLINE);
updateWemoState();
- addSubscription();
} catch (Exception e) {
logger.debug("Exception during poll: {}", e.getMessage(), e);
}
public void handleCommand(ChannelUID channelUID, Command command) {
String localHost = getHost();
if (localHost.isEmpty()) {
- logger.error("Failed to send command '{}' for device '{}': IP address missing", command,
+ logger.warn("Failed to send command '{}' for device '{}': IP address missing", command,
getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
}
String wemoURL = getWemoURL(localHost, BASICACTION);
if (wemoURL == null) {
- logger.error("Failed to send command '{}' for device '{}': URL cannot be created", command,
+ logger.debug("Failed to send command '{}' for device '{}': URL cannot be created", command,
getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url");
+ "<attribute><name>Cleaning</name><value>NULL</value></attribute></attributeList>"
+ "</u:SetAttributes>" + "</s:Body>" + "</s:Envelope>";
- String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
- if (wemoCallResponse != null) {
- updateState(CHANNEL_STATE, OnOffType.ON);
- State newMode = new StringType("Brewing");
- updateState(CHANNEL_COFFEEMODE, newMode);
- if (logger.isTraceEnabled()) {
- logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID());
- logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader,
- getThing().getUID());
- logger.trace("wemoCall with content '{}' for device '{}'", content,
- getThing().getUID());
- logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse,
- getThing().getUID());
- }
- }
+ wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
+ updateState(CHANNEL_STATE, OnOffType.ON);
+ State newMode = new StringType("Brewing");
+ updateState(CHANNEL_COFFEEMODE, newMode);
+ updateStatus(ThingStatus.ONLINE);
} catch (Exception e) {
- logger.error("Failed to send command '{}' for device '{}': {}", command, getThing().getUID(),
+ logger.warn("Failed to send command '{}' for device '{}': {}", command, getThing().getUID(),
e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
}
}
// if command.equals(OnOffType.OFF) we do nothing because WeMo Coffee Maker cannot be switched
- // off
- // remotely
- updateStatus(ThingStatus.ONLINE);
+ // off remotely
}
}
}
- @Override
- public void onServiceSubscribed(@Nullable String service, boolean succeeded) {
- if (service != null) {
- logger.debug("WeMo {}: Subscription to service {} {}", getUDN(), service,
- succeeded ? "succeeded" : "failed");
- subscriptionState.put(service, succeeded);
- }
- }
-
@Override
public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) {
// We can subscribe to GENA events, but there is no usefull response right now.
}
- private synchronized void addSubscription() {
- synchronized (upnpLock) {
- UpnpIOService localService = service;
- if (localService != null) {
- if (localService.isRegistered(this)) {
- logger.debug("Checking WeMo GENA subscription for '{}'", getThing().getUID());
-
- String subscription = DEVICEEVENT;
- if (subscriptionState.get(subscription) == null) {
- logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(),
- subscription);
- localService.addSubscription(this, subscription, SUBSCRIPTION_DURATION_SECONDS);
- subscriptionState.put(subscription, true);
- }
- } else {
- logger.debug(
- "Setting up WeMo GENA subscription for '{}' FAILED - service.isRegistered(this) is FALSE",
- getThing().getUID());
- }
- }
- }
- }
-
- private synchronized void removeSubscription() {
- logger.debug("Removing WeMo GENA subscription for '{}'", getThing().getUID());
- synchronized (upnpLock) {
- UpnpIOService localService = service;
- if (localService != null) {
- if (localService.isRegistered(this)) {
- String subscription = DEVICEEVENT;
- if (subscriptionState.get(subscription) != null) {
- logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription);
- localService.removeSubscription(this, subscription);
- }
- subscriptionState = new HashMap<>();
- localService.unregisterParticipant(this);
- }
- }
- }
- }
-
/**
* The {@link updateWemoState} polls the actual state of a WeMo CoffeeMaker.
*/
protected void updateWemoState() {
String localHost = getHost();
if (localHost.isEmpty()) {
- logger.error("Failed to get actual state for device '{}': IP address missing", getThing().getUID());
+ logger.warn("Failed to get actual state for device '{}': IP address missing", getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
return;
String actionService = DEVICEACTION;
String wemoURL = getWemoURL(host, actionService);
if (wemoURL == null) {
- logger.error("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID());
+ logger.debug("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url");
return;
String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
String content = createStateRequestContent(action, actionService);
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
- if (wemoCallResponse != null) {
- if (logger.isTraceEnabled()) {
- logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID());
- logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID());
- logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID());
- logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID());
- }
- try {
- String stringParser = substringBetween(wemoCallResponse, "<attributeList>", "</attributeList>");
-
- // Due to Belkins bad response formatting, we need to run this twice.
- stringParser = unescapeXml(stringParser);
- stringParser = unescapeXml(stringParser);
-
- logger.trace("CoffeeMaker response '{}' for device '{}' received", stringParser,
- getThing().getUID());
-
- stringParser = "<data>" + stringParser + "</data>";
-
- DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
- // see
- // https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
- dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
- dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
- dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
- dbf.setXIncludeAware(false);
- dbf.setExpandEntityReferences(false);
- DocumentBuilder db = dbf.newDocumentBuilder();
- InputSource is = new InputSource();
- is.setCharacterStream(new StringReader(stringParser));
-
- Document doc = db.parse(is);
- NodeList nodes = doc.getElementsByTagName("attribute");
-
- // iterate the attributes
- for (int i = 0; i < nodes.getLength(); i++) {
- Element element = (Element) nodes.item(i);
-
- NodeList deviceIndex = element.getElementsByTagName("name");
- Element line = (Element) deviceIndex.item(0);
- String attributeName = getCharacterDataFromElement(line);
- logger.trace("attributeName: {}", attributeName);
-
- NodeList deviceID = element.getElementsByTagName("value");
- line = (Element) deviceID.item(0);
- String attributeValue = getCharacterDataFromElement(line);
- logger.trace("attributeValue: {}", attributeValue);
-
- switch (attributeName) {
- case "Mode":
- State newMode = new StringType("Brewing");
- State newAttributeValue;
-
- switch (attributeValue) {
- case "0":
- updateState(CHANNEL_STATE, OnOffType.ON);
- newMode = new StringType("Refill");
- updateState(CHANNEL_COFFEEMODE, newMode);
- break;
- case "1":
- updateState(CHANNEL_STATE, OnOffType.OFF);
- newMode = new StringType("PlaceCarafe");
- updateState(CHANNEL_COFFEEMODE, newMode);
- break;
- case "2":
- updateState(CHANNEL_STATE, OnOffType.OFF);
- newMode = new StringType("RefillWater");
- updateState(CHANNEL_COFFEEMODE, newMode);
- break;
- case "3":
- updateState(CHANNEL_STATE, OnOffType.OFF);
- newMode = new StringType("Ready");
- updateState(CHANNEL_COFFEEMODE, newMode);
- break;
- case "4":
- updateState(CHANNEL_STATE, OnOffType.ON);
- newMode = new StringType("Brewing");
- updateState(CHANNEL_COFFEEMODE, newMode);
- break;
- case "5":
- updateState(CHANNEL_STATE, OnOffType.OFF);
- newMode = new StringType("Brewed");
- updateState(CHANNEL_COFFEEMODE, newMode);
- break;
- case "6":
- updateState(CHANNEL_STATE, OnOffType.OFF);
- newMode = new StringType("CleaningBrewing");
- updateState(CHANNEL_COFFEEMODE, newMode);
- break;
- case "7":
- updateState(CHANNEL_STATE, OnOffType.OFF);
- newMode = new StringType("CleaningSoaking");
- updateState(CHANNEL_COFFEEMODE, newMode);
- break;
- case "8":
- updateState(CHANNEL_STATE, OnOffType.OFF);
- newMode = new StringType("BrewFailCarafeRemoved");
- updateState(CHANNEL_COFFEEMODE, newMode);
- break;
- }
- break;
- case "ModeTime":
- newAttributeValue = new DecimalType(attributeValue);
- updateState(CHANNEL_MODETIME, newAttributeValue);
- break;
- case "TimeRemaining":
- newAttributeValue = new DecimalType(attributeValue);
- updateState(CHANNEL_TIMEREMAINING, newAttributeValue);
- break;
- case "WaterLevelReached":
- newAttributeValue = new DecimalType(attributeValue);
- updateState(CHANNEL_WATERLEVELREACHED, newAttributeValue);
- break;
- case "CleanAdvise":
- newAttributeValue = "0".equals(attributeValue) ? OnOffType.OFF : OnOffType.ON;
- updateState(CHANNEL_CLEANADVISE, newAttributeValue);
- break;
- case "FilterAdvise":
- newAttributeValue = "0".equals(attributeValue) ? OnOffType.OFF : OnOffType.ON;
- updateState(CHANNEL_FILTERADVISE, newAttributeValue);
- break;
- case "Brewed":
- newAttributeValue = getDateTimeState(attributeValue);
- if (newAttributeValue != null) {
- updateState(CHANNEL_BREWED, newAttributeValue);
- }
- break;
- case "LastCleaned":
- newAttributeValue = getDateTimeState(attributeValue);
- if (newAttributeValue != null) {
- updateState(CHANNEL_LASTCLEANED, newAttributeValue);
- }
- break;
- }
+ try {
+ String stringParser = substringBetween(wemoCallResponse, "<attributeList>", "</attributeList>");
+
+ // Due to Belkins bad response formatting, we need to run this twice.
+ stringParser = unescapeXml(stringParser);
+ stringParser = unescapeXml(stringParser);
+
+ logger.trace("CoffeeMaker response '{}' for device '{}' received", stringParser, getThing().getUID());
+
+ stringParser = "<data>" + stringParser + "</data>";
+
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+ // see
+ // https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
+ dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
+ dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+ dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
+ dbf.setXIncludeAware(false);
+ dbf.setExpandEntityReferences(false);
+ DocumentBuilder db = dbf.newDocumentBuilder();
+ InputSource is = new InputSource();
+ is.setCharacterStream(new StringReader(stringParser));
+
+ Document doc = db.parse(is);
+ NodeList nodes = doc.getElementsByTagName("attribute");
+
+ // iterate the attributes
+ for (int i = 0; i < nodes.getLength(); i++) {
+ Element element = (Element) nodes.item(i);
+
+ NodeList deviceIndex = element.getElementsByTagName("name");
+ Element line = (Element) deviceIndex.item(0);
+ String attributeName = getCharacterDataFromElement(line);
+ logger.trace("attributeName: {}", attributeName);
+
+ NodeList deviceID = element.getElementsByTagName("value");
+ line = (Element) deviceID.item(0);
+ String attributeValue = getCharacterDataFromElement(line);
+ logger.trace("attributeValue: {}", attributeValue);
+
+ switch (attributeName) {
+ case "Mode":
+ State newMode = new StringType("Brewing");
+ State newAttributeValue;
+
+ switch (attributeValue) {
+ case "0":
+ updateState(CHANNEL_STATE, OnOffType.ON);
+ newMode = new StringType("Refill");
+ updateState(CHANNEL_COFFEEMODE, newMode);
+ break;
+ case "1":
+ updateState(CHANNEL_STATE, OnOffType.OFF);
+ newMode = new StringType("PlaceCarafe");
+ updateState(CHANNEL_COFFEEMODE, newMode);
+ break;
+ case "2":
+ updateState(CHANNEL_STATE, OnOffType.OFF);
+ newMode = new StringType("RefillWater");
+ updateState(CHANNEL_COFFEEMODE, newMode);
+ break;
+ case "3":
+ updateState(CHANNEL_STATE, OnOffType.OFF);
+ newMode = new StringType("Ready");
+ updateState(CHANNEL_COFFEEMODE, newMode);
+ break;
+ case "4":
+ updateState(CHANNEL_STATE, OnOffType.ON);
+ newMode = new StringType("Brewing");
+ updateState(CHANNEL_COFFEEMODE, newMode);
+ break;
+ case "5":
+ updateState(CHANNEL_STATE, OnOffType.OFF);
+ newMode = new StringType("Brewed");
+ updateState(CHANNEL_COFFEEMODE, newMode);
+ break;
+ case "6":
+ updateState(CHANNEL_STATE, OnOffType.OFF);
+ newMode = new StringType("CleaningBrewing");
+ updateState(CHANNEL_COFFEEMODE, newMode);
+ break;
+ case "7":
+ updateState(CHANNEL_STATE, OnOffType.OFF);
+ newMode = new StringType("CleaningSoaking");
+ updateState(CHANNEL_COFFEEMODE, newMode);
+ break;
+ case "8":
+ updateState(CHANNEL_STATE, OnOffType.OFF);
+ newMode = new StringType("BrewFailCarafeRemoved");
+ updateState(CHANNEL_COFFEEMODE, newMode);
+ break;
+ }
+ break;
+ case "ModeTime":
+ newAttributeValue = new DecimalType(attributeValue);
+ updateState(CHANNEL_MODETIME, newAttributeValue);
+ break;
+ case "TimeRemaining":
+ newAttributeValue = new DecimalType(attributeValue);
+ updateState(CHANNEL_TIMEREMAINING, newAttributeValue);
+ break;
+ case "WaterLevelReached":
+ newAttributeValue = new DecimalType(attributeValue);
+ updateState(CHANNEL_WATERLEVELREACHED, newAttributeValue);
+ break;
+ case "CleanAdvise":
+ newAttributeValue = "0".equals(attributeValue) ? OnOffType.OFF : OnOffType.ON;
+ updateState(CHANNEL_CLEANADVISE, newAttributeValue);
+ break;
+ case "FilterAdvise":
+ newAttributeValue = "0".equals(attributeValue) ? OnOffType.OFF : OnOffType.ON;
+ updateState(CHANNEL_FILTERADVISE, newAttributeValue);
+ break;
+ case "Brewed":
+ newAttributeValue = getDateTimeState(attributeValue);
+ if (newAttributeValue != null) {
+ updateState(CHANNEL_BREWED, newAttributeValue);
+ }
+ break;
+ case "LastCleaned":
+ newAttributeValue = getDateTimeState(attributeValue);
+ if (newAttributeValue != null) {
+ updateState(CHANNEL_LASTCLEANED, newAttributeValue);
+ }
+ break;
}
- } catch (Exception e) {
- logger.error("Failed to parse attributeList for WeMo CoffeMaker '{}'", this.getThing().getUID(), e);
}
+ updateStatus(ThingStatus.ONLINE);
+ } catch (Exception e) {
+ logger.warn("Failed to parse attributeList for WeMo CoffeMaker '{}'", this.getThing().getUID(), e);
}
} catch (Exception e) {
- logger.error("Failed to get attributes for device '{}'", getThing().getUID(), e);
+ logger.warn("Failed to get attributes for device '{}'", getThing().getUID(), e);
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
try {
value = Long.parseLong(attributeValue);
} catch (NumberFormatException e) {
- logger.error("Unable to parse attributeValue '{}' for device '{}'; expected long", attributeValue,
+ logger.warn("Unable to parse attributeValue '{}' for device '{}'; expected long", attributeValue,
getThing().getUID());
return null;
}
import static org.openhab.binding.wemo.internal.WemoBindingConstants.*;
import static org.openhab.binding.wemo.internal.WemoUtil.*;
+import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_CROCKPOT);
- private final Object upnpLock = new Object();
private final Object jobLock = new Object();
private final Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<>());
- private Map<String, Boolean> subscriptionState = new HashMap<>();
-
private @Nullable ScheduledFuture<?> pollingJob;
public WemoCrockpotHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
@Override
public void initialize() {
+ super.initialize();
Configuration configuration = getConfig();
if (configuration.get(UDN) != null) {
logger.debug("Initializing WemoCrockpotHandler for UDN '{}'", configuration.get(UDN));
- UpnpIOService localService = service;
- if (localService != null) {
- localService.registerParticipant(this);
- }
+ addSubscription(BASICEVENT);
host = getHost();
pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS,
TimeUnit.SECONDS);
job.cancel(true);
}
this.pollingJob = null;
- removeSubscription();
+ super.dispose();
}
private void poll() {
logger.debug("UPnP device {} not yet registered", getUDN());
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING,
"@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
- synchronized (upnpLock) {
- subscriptionState = new HashMap<>();
- }
return;
}
- updateStatus(ThingStatus.ONLINE);
updateWemoState();
- addSubscription();
} catch (Exception e) {
logger.debug("Exception during poll: {}", e.getMessage(), e);
}
public void handleCommand(ChannelUID channelUID, Command command) {
String localHost = getHost();
if (localHost.isEmpty()) {
- logger.error("Failed to send command '{}' for device '{}': IP address missing", command,
+ logger.warn("Failed to send command '{}' for device '{}': IP address missing", command,
getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
}
String wemoURL = getWemoURL(localHost, BASICACTION);
if (wemoURL == null) {
- logger.error("Failed to send command '{}' for device '{}': URL cannot be created", command,
+ logger.debug("Failed to send command '{}' for device '{}': URL cannot be created", command,
getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url");
+ "<s:Body>" + "<u:SetCrockpotState xmlns:u=\"urn:Belkin:service:basicevent:1\">" + "<mode>"
+ mode + "</mode>" + "<time>" + time + "</time>" + "</u:SetCrockpotState>" + "</s:Body>"
+ "</s:Envelope>";
- String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
- if (wemoCallResponse != null && logger.isTraceEnabled()) {
- logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID());
- logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID());
- logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID());
- logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID());
- }
- } catch (RuntimeException e) {
+ wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
+ updateStatus(ThingStatus.ONLINE);
+ } catch (IOException e) {
logger.debug("Failed to send command '{}' for device '{}':", command, getThing().getUID(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
- updateStatus(ThingStatus.ONLINE);
- }
- }
-
- @Override
- public void onServiceSubscribed(@Nullable String service, boolean succeeded) {
- if (service != null) {
- logger.debug("WeMo {}: Subscription to service {} {}", getUDN(), service,
- succeeded ? "succeeded" : "failed");
- subscriptionState.put(service, succeeded);
}
}
}
}
- private synchronized void addSubscription() {
- synchronized (upnpLock) {
- UpnpIOService localService = service;
- if (localService != null) {
- if (localService.isRegistered(this)) {
- logger.debug("Checking WeMo GENA subscription for '{}'", getThing().getUID());
-
- String subscription = BASICEVENT;
-
- if (subscriptionState.get(subscription) == null) {
- logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(),
- subscription);
- localService.addSubscription(this, subscription, SUBSCRIPTION_DURATION_SECONDS);
- subscriptionState.put(subscription, true);
- }
- } else {
- logger.debug(
- "Setting up WeMo GENA subscription for '{}' FAILED - service.isRegistered(this) is FALSE",
- getThing().getUID());
- }
- }
- }
- }
-
- private synchronized void removeSubscription() {
- synchronized (upnpLock) {
- UpnpIOService localService = service;
- if (localService != null) {
- if (localService.isRegistered(this)) {
- logger.debug("Removing WeMo GENA subscription for '{}'", getThing().getUID());
- String subscription = BASICEVENT;
-
- if (subscriptionState.get(subscription) != null) {
- logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription);
- localService.removeSubscription(this, subscription);
- }
- subscriptionState.remove(subscription);
- localService.unregisterParticipant(this);
- }
- }
- }
- }
-
/**
* The {@link updateWemoState} polls the actual state of a WeMo device and
* calls {@link onValueReceived} to update the statemap and channels..
protected void updateWemoState() {
String localHost = getHost();
if (localHost.isEmpty()) {
- logger.error("Failed to get actual state for device '{}': IP address missing", getThing().getUID());
+ logger.warn("Failed to get actual state for device '{}': IP address missing", getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
return;
String actionService = BASICEVENT;
String wemoURL = getWemoURL(localHost, actionService);
if (wemoURL == null) {
- logger.error("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID());
+ logger.warn("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url");
return;
String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
String content = createStateRequestContent(action, actionService);
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
- if (wemoCallResponse != null) {
- if (logger.isTraceEnabled()) {
- logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID());
- logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID());
- logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID());
- logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID());
- }
- String mode = substringBetween(wemoCallResponse, "<mode>", "</mode>");
- String time = substringBetween(wemoCallResponse, "<time>", "</time>");
- String coockedTime = substringBetween(wemoCallResponse, "<coockedTime>", "</coockedTime>");
-
- State newMode = new StringType(mode);
- State newCoockedTime = DecimalType.valueOf(coockedTime);
- switch (mode) {
- case "0":
- newMode = new StringType("OFF");
- break;
- case "50":
- newMode = new StringType("WARM");
- State warmTime = DecimalType.valueOf(time);
- updateState(CHANNEL_WARMCOOKTIME, warmTime);
- break;
- case "51":
- newMode = new StringType("LOW");
- State lowTime = DecimalType.valueOf(time);
- updateState(CHANNEL_LOWCOOKTIME, lowTime);
- break;
- case "52":
- newMode = new StringType("HIGH");
- State highTime = DecimalType.valueOf(time);
- updateState(CHANNEL_HIGHCOOKTIME, highTime);
- break;
- }
- updateState(CHANNEL_COOKMODE, newMode);
- updateState(CHANNEL_COOKEDTIME, newCoockedTime);
+ String mode = substringBetween(wemoCallResponse, "<mode>", "</mode>");
+ String time = substringBetween(wemoCallResponse, "<time>", "</time>");
+ String coockedTime = substringBetween(wemoCallResponse, "<coockedTime>", "</coockedTime>");
+
+ State newMode = new StringType(mode);
+ State newCoockedTime = DecimalType.valueOf(coockedTime);
+ switch (mode) {
+ case "0":
+ newMode = new StringType("OFF");
+ break;
+ case "50":
+ newMode = new StringType("WARM");
+ State warmTime = DecimalType.valueOf(time);
+ updateState(CHANNEL_WARMCOOKTIME, warmTime);
+ break;
+ case "51":
+ newMode = new StringType("LOW");
+ State lowTime = DecimalType.valueOf(time);
+ updateState(CHANNEL_LOWCOOKTIME, lowTime);
+ break;
+ case "52":
+ newMode = new StringType("HIGH");
+ State highTime = DecimalType.valueOf(time);
+ updateState(CHANNEL_HIGHCOOKTIME, highTime);
+ break;
}
- } catch (RuntimeException e) {
+ updateState(CHANNEL_COOKMODE, newMode);
+ updateState(CHANNEL_COOKEDTIME, newCoockedTime);
+ updateStatus(ThingStatus.ONLINE);
+ } catch (IOException e) {
logger.debug("Failed to get actual state for device '{}': {}", getThing().getUID(), e.getMessage(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
- updateStatus(ThingStatus.ONLINE);
}
}
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_DIMMER);
- private final Object upnpLock = new Object();
private final Object jobLock = new Object();
private final Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<>());
- private Map<String, Boolean> subscriptionState = new HashMap<>();
-
private @Nullable ScheduledFuture<?> pollingJob;
private int currentBrightness;
@Override
public void initialize() {
+ super.initialize();
Configuration configuration = getConfig();
if (configuration.get(UDN) != null) {
logger.debug("Initializing WemoDimmerHandler for UDN '{}'", configuration.get(UDN));
- UpnpIOService localService = service;
- if (localService != null) {
- localService.registerParticipant(this);
- }
+ addSubscription(BASICEVENT);
host = getHost();
pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS,
TimeUnit.SECONDS);
job.cancel(true);
}
this.pollingJob = null;
- removeSubscription();
+ super.dispose();
}
private void poll() {
logger.debug("UPnP device {} not yet registered", getUDN());
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING,
"@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
- synchronized (upnpLock) {
- subscriptionState = new HashMap<>();
- }
return;
}
- updateStatus(ThingStatus.ONLINE);
updateWemoState();
- addSubscription();
} catch (Exception e) {
logger.debug("Exception during poll: {}", e.getMessage(), e);
}
}
}
- @Override
- public void onServiceSubscribed(@Nullable String service, boolean succeeded) {
- if (service != null) {
- logger.debug("WeMo {}: Subscription to service {} {}", getUDN(), service,
- succeeded ? "succeeded" : "failed");
- subscriptionState.put(service, succeeded);
- }
- }
-
@Override
public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) {
logger.debug("Received pair '{}':'{}' (service '{}') for thing '{}'",
}
}
- private synchronized void addSubscription() {
- UpnpIOService localService = service;
- if (localService != null) {
- if (localService.isRegistered(this)) {
- logger.debug("Checking WeMo GENA subscription for '{}'", getThing().getUID());
- String subscription = BASICEVENT;
- if (subscriptionState.get(subscription) == null) {
- logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(),
- subscription);
- localService.addSubscription(this, subscription, SUBSCRIPTION_DURATION_SECONDS);
- subscriptionState.put(subscription, true);
- }
- } else {
- logger.debug("Setting up WeMo GENA subscription for '{}' FAILED - service.isRegistered(this) is FALSE",
- getThing().getUID());
- }
- }
- }
-
- private synchronized void removeSubscription() {
- logger.debug("Removing WeMo GENA subscription for '{}'", getThing().getUID());
- UpnpIOService localService = service;
- if (localService != null) {
- if (localService.isRegistered(this)) {
- String subscription = BASICEVENT;
- if (subscriptionState.get(subscription) != null) {
- logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription);
- localService.removeSubscription(this, subscription);
- }
- subscriptionState = new HashMap<>();
- localService.unregisterParticipant(this);
- }
- }
- }
-
/**
* The {@link updateWemoState} polls the actual state of a WeMo device and
* calls {@link onValueReceived} to update the statemap and channels..
protected void updateWemoState() {
String localHost = getHost();
if (localHost.isEmpty()) {
- logger.error("Failed to get actual state for device '{}': IP address missing", getThing().getUID());
+ logger.warn("Failed to get actual state for device '{}': IP address missing", getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
return;
}
String wemoURL = getWemoURL(localHost, BASICACTION);
if (wemoURL == null) {
- logger.error("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID());
+ logger.debug("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url");
return;
String content = createStateRequestContent(action, actionService);
try {
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
- if (wemoCallResponse != null) {
- if (logger.isTraceEnabled()) {
- logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID());
- logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID());
- logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID());
- logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID());
- }
- value = substringBetween(wemoCallResponse, "<BinaryState>", "</BinaryState>");
- variable = "BinaryState";
- this.onValueReceived(variable, value, actionService + "1");
- value = substringBetween(wemoCallResponse, "<brightness>", "</brightness>");
- variable = "brightness";
- this.onValueReceived(variable, value, actionService + "1");
- value = substringBetween(wemoCallResponse, "<fader>", "</fader>");
- variable = "fader";
- this.onValueReceived(variable, value, actionService + "1");
- updateStatus(ThingStatus.ONLINE);
- }
+ value = substringBetween(wemoCallResponse, "<BinaryState>", "</BinaryState>");
+ variable = "BinaryState";
+ this.onValueReceived(variable, value, actionService + "1");
+ value = substringBetween(wemoCallResponse, "<brightness>", "</brightness>");
+ variable = "brightness";
+ this.onValueReceived(variable, value, actionService + "1");
+ value = substringBetween(wemoCallResponse, "<fader>", "</fader>");
+ variable = "fader";
+ this.onValueReceived(variable, value, actionService + "1");
+ updateStatus(ThingStatus.ONLINE);
} catch (Exception e) {
logger.debug("Failed to get actual state for device '{}': {}", getThing().getUID(), e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
content = createStateRequestContent(action, actionService);
try {
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
- if (wemoCallResponse != null) {
- if (logger.isTraceEnabled()) {
- logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID());
- logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID());
- logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID());
- logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID());
- }
- value = substringBetween(wemoCallResponse, "<startTime>", "</startTime>");
- variable = "startTime";
- this.onValueReceived(variable, value, actionService + "1");
- value = substringBetween(wemoCallResponse, "<endTime>", "</endTime>");
- variable = "endTime";
- this.onValueReceived(variable, value, actionService + "1");
- value = substringBetween(wemoCallResponse, "<nightMode>", "</nightMode>");
- variable = "nightMode";
- this.onValueReceived(variable, value, actionService + "1");
- value = substringBetween(wemoCallResponse, "<nightModeBrightness>", "</nightModeBrightness>");
- variable = "nightModeBrightness";
- this.onValueReceived(variable, value, actionService + "1");
- updateStatus(ThingStatus.ONLINE);
-
- }
+ value = substringBetween(wemoCallResponse, "<startTime>", "</startTime>");
+ variable = "startTime";
+ this.onValueReceived(variable, value, actionService + "1");
+ value = substringBetween(wemoCallResponse, "<endTime>", "</endTime>");
+ variable = "endTime";
+ this.onValueReceived(variable, value, actionService + "1");
+ value = substringBetween(wemoCallResponse, "<nightMode>", "</nightMode>");
+ variable = "nightMode";
+ this.onValueReceived(variable, value, actionService + "1");
+ value = substringBetween(wemoCallResponse, "<nightModeBrightness>", "</nightModeBrightness>");
+ variable = "nightModeBrightness";
+ this.onValueReceived(variable, value, actionService + "1");
+ updateStatus(ThingStatus.ONLINE);
} catch (Exception e) {
logger.debug("Failed to get actual NightMode state for device '{}': {}", getThing().getUID(),
e.getMessage());
try {
value = Long.parseLong(attributeValue);
} catch (NumberFormatException e) {
- logger.error("Unable to parse attributeValue '{}' for device '{}'; expected long", attributeValue,
+ logger.warn("Unable to parse attributeValue '{}' for device '{}'; expected long", attributeValue,
getThing().getUID());
return null;
}
ZonedDateTime zoned = ZonedDateTime.ofInstant(Instant.ofEpochSecond(value), TimeZone.getDefault().toZoneId());
State dateTimeState = new DateTimeType(zoned);
- logger.trace("New attribute brewed '{}' received", dateTimeState);
return dateTimeState;
}
public void setBinaryState(String action, String argument, String value) {
String localHost = getHost();
if (localHost.isEmpty()) {
- logger.error("Failed to set binary state for device '{}': IP address missing", getThing().getUID());
+ logger.warn("Failed to set binary state for device '{}': IP address missing", getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
return;
}
String wemoURL = getWemoURL(localHost, BASICACTION);
if (wemoURL == null) {
- logger.error("Failed to set binary state for device '{}': URL cannot be created", getThing().getUID());
+ logger.debug("Failed to set binary state for device '{}': URL cannot be created", getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url");
return;
+ "<s:Body>" + "<u:" + action + " xmlns:u=\"urn:Belkin:service:basicevent:1\">" + "<" + argument
+ ">" + value + "</" + argument + ">" + "</u:" + action + ">" + "</s:Body>" + "</s:Envelope>";
- String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
- if (wemoCallResponse != null && logger.isTraceEnabled()) {
- logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID());
- logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID());
- logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID());
- logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID());
- }
+ wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
+ updateStatus(ThingStatus.ONLINE);
} catch (Exception e) {
logger.debug("Failed to set binaryState '{}' for device '{}': {}", value, getThing().getUID(),
e.getMessage());
public void setTimerStart(String action, String argument, String value) {
String localHost = getHost();
if (localHost.isEmpty()) {
- logger.error("Failed to set timerStart for device '{}': IP address missing", getThing().getUID());
+ logger.warn("Failed to set timerStart for device '{}': IP address missing", getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
return;
}
String wemoURL = getWemoURL(localHost, BASICACTION);
if (wemoURL == null) {
- logger.error("Failed to set timerStart for device '{}': URL cannot be created", getThing().getUID());
+ logger.warn("Failed to set timerStart for device '{}': URL cannot be created", getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url");
return;
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
+ "<s:Body>" + "<u:SetBinaryState xmlns:u=\"urn:Belkin:service:basicevent:1\">" + value
+ "</u:SetBinaryState>" + "</s:Body>" + "</s:Envelope>";
- String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
- if (wemoCallResponse != null && logger.isTraceEnabled()) {
- logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID());
- logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID());
- logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID());
- logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID());
- }
+ wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
+ updateStatus(ThingStatus.ONLINE);
} catch (Exception e) {
logger.debug("Failed to set timerStart '{}' for device '{}': {}", value, getThing().getUID(),
e.getMessage());
import static org.openhab.binding.wemo.internal.WemoBindingConstants.*;
import static org.openhab.binding.wemo.internal.WemoUtil.*;
-import java.util.HashMap;
-import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
-import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
private final Logger logger = LoggerFactory.getLogger(WemoHandler.class);
- private final Object upnpLock = new Object();
private final Object jobLock = new Object();
- private Map<String, Boolean> subscriptionState = new HashMap<>();
-
private @Nullable ScheduledFuture<?> pollingJob;
public WemoHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
@Override
public void initialize() {
+ super.initialize();
Configuration configuration = getConfig();
if (configuration.get(UDN) != null) {
logger.debug("Initializing WemoHandler for UDN '{}'", configuration.get(UDN));
- UpnpIOService localService = service;
- if (localService != null) {
- localService.registerParticipant(this);
+ addSubscription(BASICEVENT);
+ if (THING_TYPE_INSIGHT.equals(thing.getThingTypeUID())) {
+ addSubscription(INSIGHTEVENT);
}
host = getHost();
pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS,
job.cancel(true);
}
this.pollingJob = null;
- removeSubscription();
+ super.dispose();
}
private void poll() {
logger.debug("UPnP device {} not yet registered", getUDN());
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING,
"@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
- synchronized (upnpLock) {
- subscriptionState = new HashMap<>();
- }
return;
}
- updateStatus(ThingStatus.ONLINE);
updateWemoState();
- addSubscription();
} catch (Exception e) {
logger.debug("Exception during poll: {}", e.getMessage(), e);
}
public void handleCommand(ChannelUID channelUID, Command command) {
String localHost = getHost();
if (localHost.isEmpty()) {
- logger.error("Failed to send command '{}' for device '{}': IP address missing", command,
+ logger.warn("Failed to send command '{}' for device '{}': IP address missing", command,
getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
}
String wemoURL = getWemoURL(localHost, BASICACTION);
if (wemoURL == null) {
- logger.error("Failed to send command '{}' for device '{}': URL cannot be created", command,
+ logger.debug("Failed to send command '{}' for device '{}': URL cannot be created", command,
getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url");
boolean binaryState = OnOffType.ON.equals(command) ? true : false;
String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\"";
String content = createBinaryStateContent(binaryState);
- String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
- if (wemoCallResponse != null && logger.isTraceEnabled()) {
- logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID());
- logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID());
- logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID());
- logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse,
- getThing().getUID());
- }
+ wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
+ updateStatus(ThingStatus.ONLINE);
} catch (Exception e) {
- logger.error("Failed to send command '{}' for device '{}': {}", command, getThing().getUID(),
+ logger.warn("Failed to send command '{}' for device '{}': {}", command, getThing().getUID(),
e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
}
- updateStatus(ThingStatus.ONLINE);
- }
- }
- }
-
- @Override
- public void onServiceSubscribed(@Nullable String service, boolean succeeded) {
- if (service != null) {
- logger.debug("WeMo {}: Subscription to service {} {}", getUDN(), service,
- succeeded ? "succeeded" : "failed");
- subscriptionState.put(service, succeeded);
- }
- }
-
- private synchronized void addSubscription() {
- synchronized (upnpLock) {
- UpnpIOService localService = service;
- if (localService != null) {
- if (localService.isRegistered(this)) {
- logger.debug("Checking WeMo GENA subscription for '{}'", getThing().getUID());
-
- ThingTypeUID thingTypeUID = thing.getThingTypeUID();
- String subscription = BASICEVENT;
-
- if (subscriptionState.get(subscription) == null) {
- logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(),
- subscription);
- localService.addSubscription(this, subscription, SUBSCRIPTION_DURATION_SECONDS);
- subscriptionState.put(subscription, true);
- }
-
- if (THING_TYPE_INSIGHT.equals(thingTypeUID)) {
- subscription = INSIGHTEVENT;
- if (subscriptionState.get(subscription) == null) {
- logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(),
- subscription);
- localService.addSubscription(this, subscription, SUBSCRIPTION_DURATION_SECONDS);
- subscriptionState.put(subscription, true);
- }
- }
- } else {
- logger.debug(
- "Setting up WeMo GENA subscription for '{}' FAILED - service.isRegistered(this) is FALSE",
- getThing().getUID());
- }
- }
- }
- }
-
- private synchronized void removeSubscription() {
- synchronized (upnpLock) {
- UpnpIOService localService = service;
- if (localService != null) {
- if (localService.isRegistered(this)) {
- logger.debug("Removing WeMo GENA subscription for '{}'", getThing().getUID());
- ThingTypeUID thingTypeUID = thing.getThingTypeUID();
- String subscription = BASICEVENT;
-
- if (subscriptionState.get(subscription) != null) {
- logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription);
- localService.removeSubscription(this, subscription);
- }
-
- if (THING_TYPE_INSIGHT.equals(thingTypeUID)) {
- subscription = INSIGHTEVENT;
- if (subscriptionState.get(subscription) != null) {
- logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription);
- localService.removeSubscription(this, subscription);
- }
- }
- subscriptionState = new HashMap<>();
- localService.unregisterParticipant(this);
- }
}
}
}
String actionService = BASICACTION;
String localhost = getHost();
if (localhost.isEmpty()) {
- logger.error("Failed to get actual state for device '{}': IP address missing", getThing().getUID());
+ logger.warn("Failed to get actual state for device '{}': IP address missing", getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
return;
}
String wemoURL = getWemoURL(localhost, actionService);
if (wemoURL == null) {
- logger.error("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID());
+ logger.debug("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url");
return;
String content = createStateRequestContent(action, actionService);
try {
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
- if (wemoCallResponse != null) {
- if (logger.isTraceEnabled()) {
- logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID());
- logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID());
- logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID());
- logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID());
- }
- if ("InsightParams".equals(variable)) {
- value = substringBetween(wemoCallResponse, "<InsightParams>", "</InsightParams>");
- } else {
- value = substringBetween(wemoCallResponse, "<BinaryState>", "</BinaryState>");
- }
- if (value.length() != 0) {
- logger.trace("New state '{}' for device '{}' received", value, getThing().getUID());
- this.onValueReceived(variable, value, actionService + "1");
- }
+ if ("InsightParams".equals(variable)) {
+ value = substringBetween(wemoCallResponse, "<InsightParams>", "</InsightParams>");
+ } else {
+ value = substringBetween(wemoCallResponse, "<BinaryState>", "</BinaryState>");
+ }
+ if (value.length() != 0) {
+ logger.trace("New state '{}' for device '{}' received", value, getThing().getUID());
+ this.onValueReceived(variable, value, actionService + "1");
}
} catch (Exception e) {
- logger.error("Failed to get actual state for device '{}': {}", getThing().getUID(), e.getMessage());
+ logger.warn("Failed to get actual state for device '{}': {}", getThing().getUID(), e.getMessage());
}
}
}
private static final int FILTER_LIFE_DAYS = 330;
private static final int FILTER_LIFE_MINS = FILTER_LIFE_DAYS * 24 * 60;
- private final Object upnpLock = new Object();
private final Object jobLock = new Object();
private final Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<>());
- private Map<String, Boolean> subscriptionState = new HashMap<>();
-
private @Nullable ScheduledFuture<?> pollingJob;
public WemoHolmesHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
@Override
public void initialize() {
+ super.initialize();
Configuration configuration = getConfig();
if (configuration.get(UDN) != null) {
logger.debug("Initializing WemoHolmesHandler for UDN '{}'", configuration.get(UDN));
- UpnpIOService localService = service;
- if (localService != null) {
- localService.registerParticipant(this);
- }
+ addSubscription(BASICEVENT);
host = getHost();
pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS,
TimeUnit.SECONDS);
job.cancel(true);
}
this.pollingJob = null;
- removeSubscription();
+ super.dispose();
}
private void poll() {
logger.debug("UPnP device {} not yet registered", getUDN());
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING,
"@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
- synchronized (upnpLock) {
- subscriptionState = new HashMap<>();
- }
return;
}
- updateStatus(ThingStatus.ONLINE);
updateWemoState();
- addSubscription();
} catch (Exception e) {
logger.debug("Exception during poll: {}", e.getMessage(), e);
}
public void handleCommand(ChannelUID channelUID, Command command) {
String localHost = getHost();
if (localHost.isEmpty()) {
- logger.error("Failed to send command '{}' for device '{}': IP address missing", command,
+ logger.warn("Failed to send command '{}' for device '{}': IP address missing", command,
getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
}
String wemoURL = getWemoURL(localHost, DEVICEACTION);
if (wemoURL == null) {
- logger.error("Failed to send command '{}' for device '{}': URL cannot be created", command,
+ logger.debug("Failed to send command '{}' for device '{}': URL cannot be created", command,
getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url");
+ "<attributeList><attribute><name>" + attribute + "</name><value>" + value
+ "</value></attribute></attributeList>" + "</u:SetAttributes>" + "</s:Body>"
+ "</s:Envelope>";
- String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
- if (wemoCallResponse != null && logger.isTraceEnabled()) {
- logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID());
- logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID());
- logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID());
- logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID());
- }
- } catch (RuntimeException e) {
+ wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
+ updateStatus(ThingStatus.ONLINE);
+ } catch (IOException e) {
logger.debug("Failed to send command '{}' for device '{}':", command, getThing().getUID(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
- updateStatus(ThingStatus.ONLINE);
- }
-
- @Override
- public void onServiceSubscribed(@Nullable String service, boolean succeeded) {
- if (service != null) {
- logger.debug("WeMo {}: Subscription to service {} {}", getUDN(), service,
- succeeded ? "succeeded" : "failed");
- subscriptionState.put(service, succeeded);
- }
}
@Override
}
}
- private synchronized void addSubscription() {
- synchronized (upnpLock) {
- UpnpIOService localService = service;
- if (localService != null) {
- if (localService.isRegistered(this)) {
- logger.debug("Checking WeMo GENA subscription for '{}'", getThing().getUID());
-
- String subscription = BASICEVENT;
-
- if (subscriptionState.get(subscription) == null) {
- logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(),
- subscription);
- localService.addSubscription(this, subscription, SUBSCRIPTION_DURATION_SECONDS);
- subscriptionState.put(subscription, true);
- }
- } else {
- logger.debug(
- "Setting up WeMo GENA subscription for '{}' FAILED - service.isRegistered(this) is FALSE",
- getThing().getUID());
- }
- }
- }
- }
-
- private synchronized void removeSubscription() {
- synchronized (upnpLock) {
- UpnpIOService localService = service;
- if (localService != null) {
- if (localService.isRegistered(this)) {
- logger.debug("Removing WeMo GENA subscription for '{}'", getThing().getUID());
- String subscription = BASICEVENT;
-
- if (subscriptionState.get(subscription) != null) {
- logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription);
- localService.removeSubscription(this, subscription);
- }
- subscriptionState.remove(subscription);
- localService.unregisterParticipant(this);
- }
- }
- }
- }
-
/**
* The {@link updateWemoState} polls the actual state of a WeMo device and
* calls {@link onValueReceived} to update the statemap and channels..
protected void updateWemoState() {
String localHost = getHost();
if (localHost.isEmpty()) {
- logger.error("Failed to get actual state for device '{}': IP address missing", getThing().getUID());
+ logger.warn("Failed to get actual state for device '{}': IP address missing", getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
return;
String actionService = DEVICEACTION;
String wemoURL = getWemoURL(localHost, actionService);
if (wemoURL == null) {
- logger.error("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID());
+ logger.debug("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url");
return;
String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
String content = createStateRequestContent(action, actionService);
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
- if (wemoCallResponse != null) {
- if (logger.isTraceEnabled()) {
- logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID());
- logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID());
- logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID());
- logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID());
- }
-
- String stringParser = substringBetween(wemoCallResponse, "<attributeList>", "</attributeList>");
-
- // Due to Belkins bad response formatting, we need to run this twice.
- stringParser = unescapeXml(stringParser);
- stringParser = unescapeXml(stringParser);
-
- logger.trace("AirPurifier response '{}' for device '{}' received", stringParser, getThing().getUID());
-
- stringParser = "<data>" + stringParser + "</data>";
-
- DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
- // see
- // https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
- dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
- dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
- dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
- dbf.setXIncludeAware(false);
- dbf.setExpandEntityReferences(false);
- DocumentBuilder db = dbf.newDocumentBuilder();
- InputSource is = new InputSource();
- is.setCharacterStream(new StringReader(stringParser));
-
- Document doc = db.parse(is);
- NodeList nodes = doc.getElementsByTagName("attribute");
-
- // iterate the attributes
- for (int i = 0; i < nodes.getLength(); i++) {
- Element element = (Element) nodes.item(i);
-
- NodeList deviceIndex = element.getElementsByTagName("name");
- Element line = (Element) deviceIndex.item(0);
- String attributeName = getCharacterDataFromElement(line);
- logger.trace("attributeName: {}", attributeName);
-
- NodeList deviceID = element.getElementsByTagName("value");
- line = (Element) deviceID.item(0);
- String attributeValue = getCharacterDataFromElement(line);
- logger.trace("attributeValue: {}", attributeValue);
-
- State newMode = new StringType();
- switch (attributeName) {
- case "Mode":
- if ("purifier".equals(getThing().getThingTypeUID().getId())) {
- switch (attributeValue) {
- case "0":
- newMode = new StringType("OFF");
- break;
- case "1":
- newMode = new StringType("LOW");
- break;
- case "2":
- newMode = new StringType("MED");
- break;
- case "3":
- newMode = new StringType("HIGH");
- break;
- case "4":
- newMode = new StringType("AUTO");
- break;
- }
- updateState(CHANNEL_PURIFIERMODE, newMode);
- } else {
- switch (attributeValue) {
- case "0":
- newMode = new StringType("OFF");
- break;
- case "1":
- newMode = new StringType("FROSTPROTECT");
- break;
- case "2":
- newMode = new StringType("HIGH");
- break;
- case "3":
- newMode = new StringType("LOW");
- break;
- case "4":
- newMode = new StringType("ECO");
- break;
- }
- updateState(CHANNEL_HEATERMODE, newMode);
- }
- break;
- case "Ionizer":
- switch (attributeValue) {
- case "0":
- newMode = OnOffType.OFF;
- break;
- case "1":
- newMode = OnOffType.ON;
- break;
- }
- updateState(CHANNEL_IONIZER, newMode);
- break;
- case "AirQuality":
- switch (attributeValue) {
- case "0":
- newMode = new StringType("POOR");
- break;
- case "1":
- newMode = new StringType("MODERATE");
- break;
- case "2":
- newMode = new StringType("GOOD");
- break;
- }
- updateState(CHANNEL_AIRQUALITY, newMode);
- break;
- case "FilterLife":
- int filterLife = Integer.valueOf(attributeValue);
- if ("purifier".equals(getThing().getThingTypeUID().getId())) {
- filterLife = Math.round((filterLife / FILTER_LIFE_MINS) * 100);
- } else {
- filterLife = Math.round((filterLife / 60480) * 100);
- }
- updateState(CHANNEL_FILTERLIFE, new PercentType(String.valueOf(filterLife)));
- break;
- case "ExpiredFilterTime":
- switch (attributeValue) {
- case "0":
- newMode = OnOffType.OFF;
- break;
- case "1":
- newMode = OnOffType.ON;
- break;
- }
- updateState(CHANNEL_EXPIREDFILTERTIME, newMode);
- break;
- case "FilterPresent":
- switch (attributeValue) {
- case "0":
- newMode = OnOffType.OFF;
- break;
- case "1":
- newMode = OnOffType.ON;
- break;
- }
- updateState(CHANNEL_FILTERPRESENT, newMode);
- break;
- case "FANMode":
+ String stringParser = substringBetween(wemoCallResponse, "<attributeList>", "</attributeList>");
+
+ // Due to Belkins bad response formatting, we need to run this twice.
+ stringParser = unescapeXml(stringParser);
+ stringParser = unescapeXml(stringParser);
+
+ logger.trace("AirPurifier response '{}' for device '{}' received", stringParser, getThing().getUID());
+
+ stringParser = "<data>" + stringParser + "</data>";
+
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+ // see
+ // https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
+ dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
+ dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+ dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
+ dbf.setXIncludeAware(false);
+ dbf.setExpandEntityReferences(false);
+ DocumentBuilder db = dbf.newDocumentBuilder();
+ InputSource is = new InputSource();
+ is.setCharacterStream(new StringReader(stringParser));
+
+ Document doc = db.parse(is);
+ NodeList nodes = doc.getElementsByTagName("attribute");
+
+ // iterate the attributes
+ for (int i = 0; i < nodes.getLength(); i++) {
+ Element element = (Element) nodes.item(i);
+
+ NodeList deviceIndex = element.getElementsByTagName("name");
+ Element line = (Element) deviceIndex.item(0);
+ String attributeName = getCharacterDataFromElement(line);
+ logger.trace("attributeName: {}", attributeName);
+
+ NodeList deviceID = element.getElementsByTagName("value");
+ line = (Element) deviceID.item(0);
+ String attributeValue = getCharacterDataFromElement(line);
+ logger.trace("attributeValue: {}", attributeValue);
+
+ State newMode = new StringType();
+ switch (attributeName) {
+ case "Mode":
+ if ("purifier".equals(getThing().getThingTypeUID().getId())) {
switch (attributeValue) {
case "0":
newMode = new StringType("OFF");
break;
}
updateState(CHANNEL_PURIFIERMODE, newMode);
- break;
- case "DesiredHumidity":
+ } else {
switch (attributeValue) {
case "0":
- newMode = new PercentType("45");
+ newMode = new StringType("OFF");
break;
case "1":
- newMode = new PercentType("50");
+ newMode = new StringType("FROSTPROTECT");
break;
case "2":
- newMode = new PercentType("55");
+ newMode = new StringType("HIGH");
break;
case "3":
- newMode = new PercentType("60");
+ newMode = new StringType("LOW");
break;
case "4":
- newMode = new PercentType("100");
+ newMode = new StringType("ECO");
break;
}
- updateState(CHANNEL_DESIREDHUMIDITY, newMode);
- break;
- case "CurrentHumidity":
- newMode = new StringType(attributeValue);
- updateState(CHANNEL_CURRENTHUMIDITY, newMode);
- break;
- case "Temperature":
- newMode = new StringType(attributeValue);
- updateState(CHANNEL_CURRENTTEMP, newMode);
- break;
- case "SetTemperature":
- newMode = new StringType(attributeValue);
- updateState(CHANNEL_TARGETTEMP, newMode);
- break;
- case "AutoOffTime":
- newMode = new StringType(attributeValue);
- updateState(CHANNEL_AUTOOFFTIME, newMode);
- break;
- case "TimeRemaining":
- newMode = new StringType(attributeValue);
- updateState(CHANNEL_HEATINGREMAINING, newMode);
- break;
- }
+ updateState(CHANNEL_HEATERMODE, newMode);
+ }
+ break;
+ case "Ionizer":
+ switch (attributeValue) {
+ case "0":
+ newMode = OnOffType.OFF;
+ break;
+ case "1":
+ newMode = OnOffType.ON;
+ break;
+ }
+ updateState(CHANNEL_IONIZER, newMode);
+ break;
+ case "AirQuality":
+ switch (attributeValue) {
+ case "0":
+ newMode = new StringType("POOR");
+ break;
+ case "1":
+ newMode = new StringType("MODERATE");
+ break;
+ case "2":
+ newMode = new StringType("GOOD");
+ break;
+ }
+ updateState(CHANNEL_AIRQUALITY, newMode);
+ break;
+ case "FilterLife":
+ int filterLife = Integer.valueOf(attributeValue);
+ if ("purifier".equals(getThing().getThingTypeUID().getId())) {
+ filterLife = Math.round((filterLife / FILTER_LIFE_MINS) * 100);
+ } else {
+ filterLife = Math.round((filterLife / 60480) * 100);
+ }
+ updateState(CHANNEL_FILTERLIFE, new PercentType(String.valueOf(filterLife)));
+ break;
+ case "ExpiredFilterTime":
+ switch (attributeValue) {
+ case "0":
+ newMode = OnOffType.OFF;
+ break;
+ case "1":
+ newMode = OnOffType.ON;
+ break;
+ }
+ updateState(CHANNEL_EXPIREDFILTERTIME, newMode);
+ break;
+ case "FilterPresent":
+ switch (attributeValue) {
+ case "0":
+ newMode = OnOffType.OFF;
+ break;
+ case "1":
+ newMode = OnOffType.ON;
+ break;
+ }
+ updateState(CHANNEL_FILTERPRESENT, newMode);
+ break;
+ case "FANMode":
+ switch (attributeValue) {
+ case "0":
+ newMode = new StringType("OFF");
+ break;
+ case "1":
+ newMode = new StringType("LOW");
+ break;
+ case "2":
+ newMode = new StringType("MED");
+ break;
+ case "3":
+ newMode = new StringType("HIGH");
+ break;
+ case "4":
+ newMode = new StringType("AUTO");
+ break;
+ }
+ updateState(CHANNEL_PURIFIERMODE, newMode);
+ break;
+ case "DesiredHumidity":
+ switch (attributeValue) {
+ case "0":
+ newMode = new PercentType("45");
+ break;
+ case "1":
+ newMode = new PercentType("50");
+ break;
+ case "2":
+ newMode = new PercentType("55");
+ break;
+ case "3":
+ newMode = new PercentType("60");
+ break;
+ case "4":
+ newMode = new PercentType("100");
+ break;
+ }
+ updateState(CHANNEL_DESIREDHUMIDITY, newMode);
+ break;
+ case "CurrentHumidity":
+ newMode = new StringType(attributeValue);
+ updateState(CHANNEL_CURRENTHUMIDITY, newMode);
+ break;
+ case "Temperature":
+ newMode = new StringType(attributeValue);
+ updateState(CHANNEL_CURRENTTEMP, newMode);
+ break;
+ case "SetTemperature":
+ newMode = new StringType(attributeValue);
+ updateState(CHANNEL_TARGETTEMP, newMode);
+ break;
+ case "AutoOffTime":
+ newMode = new StringType(attributeValue);
+ updateState(CHANNEL_AUTOOFFTIME, newMode);
+ break;
+ case "TimeRemaining":
+ newMode = new StringType(attributeValue);
+ updateState(CHANNEL_HEATINGREMAINING, newMode);
+ break;
}
}
+ updateStatus(ThingStatus.ONLINE);
} catch (RuntimeException | ParserConfigurationException | SAXException | IOException e) {
logger.debug("Failed to get actual state for device '{}':", getThing().getUID(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
- updateStatus(ThingStatus.ONLINE);
}
}
try {
lastChangedAt = Long.parseLong(splitInsightParams[1]) * 1000; // convert s to ms
} catch (NumberFormatException e) {
- logger.error("Unable to parse lastChangedAt value '{}' for device '{}'; expected long",
+ logger.warn("Unable to parse lastChangedAt value '{}' for device '{}'; expected long",
splitInsightParams[1], getThing().getUID());
}
ZonedDateTime zoned = ZonedDateTime.ofInstant(Instant.ofEpochMilli(lastChangedAt),
import static org.openhab.binding.wemo.internal.WemoBindingConstants.*;
import static org.openhab.binding.wemo.internal.WemoUtil.*;
-import java.util.HashMap;
-import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
private final Logger logger = LoggerFactory.getLogger(WemoLightHandler.class);
- private Map<String, Boolean> subscriptionState = new HashMap<>();
-
- private final Object upnpLock = new Object();
private final Object jobLock = new Object();
private @Nullable WemoBridgeHandler wemoBridgeHandler;
*/
private static final int DIM_STEPSIZE = 5;
- protected static final String SUBSCRIPTION = "bridge1";
-
/**
* The default refresh initial delay in Seconds.
*/
@Override
public void initialize() {
+ super.initialize();
// initialize() is only called if the required parameter 'deviceID' is available
wemoLightID = (String) getConfig().get(DEVICE_ID);
final Bridge bridge = getBridge();
if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE) {
- UpnpIOService localService = service;
- if (localService != null) {
- localService.registerParticipant(this);
- }
+ addSubscription(BRIDGEEVENT);
host = getHost();
pollingJob = scheduler.scheduleWithFixedDelay(this::poll, DEFAULT_REFRESH_INITIAL_DELAY,
DEFAULT_REFRESH_INTERVAL_SECONDS, TimeUnit.SECONDS);
@Override
public void dispose() {
- logger.debug("WeMoLightHandler disposed.");
+ logger.debug("WemoLightHandler disposed.");
ScheduledFuture<?> job = this.pollingJob;
if (job != null && !job.isCancelled()) {
job.cancel(true);
}
this.pollingJob = null;
- removeSubscription();
+ super.dispose();
}
private synchronized @Nullable WemoBridgeHandler getWemoBridgeHandler() {
Bridge bridge = getBridge();
if (bridge == null) {
- logger.error("Required bridge not defined for device {}.", wemoLightID);
+ logger.warn("Required bridge not defined for device {}.", wemoLightID);
return null;
}
ThingHandler handler = bridge.getHandler();
logger.debug("UPnP device {} not yet registered", getUDN());
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING,
"@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
- synchronized (upnpLock) {
- subscriptionState = new HashMap<>();
- }
return;
}
- updateStatus(ThingStatus.ONLINE);
getDeviceState();
- addSubscription();
} catch (Exception e) {
logger.debug("Exception during poll: {}", e.getMessage(), e);
}
public void handleCommand(ChannelUID channelUID, Command command) {
String localHost = getHost();
if (localHost.isEmpty()) {
- logger.error("Failed to send command '{}' for device '{}': IP address missing", command,
+ logger.warn("Failed to send command '{}' for device '{}': IP address missing", command,
getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
}
String wemoURL = getWemoURL(localHost, BASICACTION);
if (wemoURL == null) {
- logger.error("Failed to send command '{}' for device '{}': URL cannot be created", command,
+ logger.debug("Failed to send command '{}' for device '{}': URL cannot be created", command,
getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url");
+ "</CapabilityValue></DeviceStatus>" + "</DeviceStatusList>"
+ "</u:SetDeviceStatus>" + "</s:Body>" + "</s:Envelope>";
- String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
- if (wemoCallResponse != null) {
- if (logger.isTraceEnabled()) {
- logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID());
- logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader,
- getThing().getUID());
- logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID());
- logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse,
- getThing().getUID());
- }
- if ("10008".equals(capability)) {
- OnOffType binaryState = null;
- binaryState = "0".equals(value) ? OnOffType.OFF : OnOffType.ON;
- updateState(CHANNEL_STATE, binaryState);
- }
+ wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
+ if ("10008".equals(capability)) {
+ OnOffType binaryState = null;
+ binaryState = "0".equals(value) ? OnOffType.OFF : OnOffType.ON;
+ updateState(CHANNEL_STATE, binaryState);
}
+ updateStatus(ThingStatus.ONLINE);
}
} catch (Exception e) {
- throw new IllegalStateException("Could not send command to WeMo Bridge", e);
+ logger.warn("Failed to send command '{}' for device '{}': {}", command, getThing().getUID(),
+ e.getMessage());
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
}
}
}
public void getDeviceState() {
String localHost = getHost();
if (localHost.isEmpty()) {
- logger.error("Failed to get actual state for device '{}': IP address missing", getThing().getUID());
+ logger.warn("Failed to get actual state for device '{}': IP address missing", getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
return;
logger.debug("Request actual state for LightID '{}'", wemoLightID);
String wemoURL = getWemoURL(localHost, BRIDGEACTION);
if (wemoURL == null) {
- logger.error("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID());
+ logger.debug("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url");
return;
+ wemoLightID + "</DeviceIDs>" + "</u:GetDeviceStatus>" + "</s:Body>" + "</s:Envelope>";
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
- if (wemoCallResponse != null) {
- if (logger.isTraceEnabled()) {
- logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID());
- logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID());
- logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID());
- logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID());
- }
- wemoCallResponse = unescapeXml(wemoCallResponse);
- String response = substringBetween(wemoCallResponse, "<CapabilityValue>", "</CapabilityValue>");
- logger.trace("wemoNewLightState = {}", response);
- String[] splitResponse = response.split(",");
- if (splitResponse[0] != null) {
- OnOffType binaryState = null;
- binaryState = "0".equals(splitResponse[0]) ? OnOffType.OFF : OnOffType.ON;
- updateState(CHANNEL_STATE, binaryState);
- }
- if (splitResponse[1] != null) {
- String splitBrightness[] = splitResponse[1].split(":");
- if (splitBrightness[0] != null) {
- int newBrightnessValue = Integer.valueOf(splitBrightness[0]);
- int newBrightness = Math.round(newBrightnessValue * 100 / 255);
- logger.trace("newBrightness = {}", newBrightness);
- State newBrightnessState = new PercentType(newBrightness);
- updateState(CHANNEL_BRIGHTNESS, newBrightnessState);
- currentBrightness = newBrightness;
- }
+ wemoCallResponse = unescapeXml(wemoCallResponse);
+ String response = substringBetween(wemoCallResponse, "<CapabilityValue>", "</CapabilityValue>");
+ logger.trace("wemoNewLightState = {}", response);
+ String[] splitResponse = response.split(",");
+ if (splitResponse[0] != null) {
+ OnOffType binaryState = null;
+ binaryState = "0".equals(splitResponse[0]) ? OnOffType.OFF : OnOffType.ON;
+ updateState(CHANNEL_STATE, binaryState);
+ }
+ if (splitResponse[1] != null) {
+ String splitBrightness[] = splitResponse[1].split(":");
+ if (splitBrightness[0] != null) {
+ int newBrightnessValue = Integer.valueOf(splitBrightness[0]);
+ int newBrightness = Math.round(newBrightnessValue * 100 / 255);
+ logger.trace("newBrightness = {}", newBrightness);
+ State newBrightnessState = new PercentType(newBrightness);
+ updateState(CHANNEL_BRIGHTNESS, newBrightnessState);
+ currentBrightness = newBrightness;
}
}
+ updateStatus(ThingStatus.ONLINE);
} catch (Exception e) {
- throw new IllegalStateException("Could not retrieve new Wemo light state", e);
+ logger.debug("Could not retrieve new Wemo light state for '{}':", getThing().getUID(), e);
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
}
}
- @Override
- public void onServiceSubscribed(@Nullable String service, boolean succeeded) {
- }
-
@Override
public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) {
logger.trace("Received pair '{}':'{}' (service '{}') for thing '{}'",
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);
- }
- }
- }
- }
}
@Override
public void initialize() {
+ super.initialize();
Configuration configuration = getConfig();
if (configuration.get(UDN) != null) {
logger.debug("Initializing WemoMakerHandler for UDN '{}'", configuration.get(UDN));
- UpnpIOService localService = service;
- if (localService != null) {
- localService.registerParticipant(this);
- }
host = getHost();
pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS,
TimeUnit.SECONDS);
@Override
public void dispose() {
- logger.debug("WeMoMakerHandler disposed.");
+ logger.debug("WemoMakerHandler disposed.");
ScheduledFuture<?> job = this.pollingJob;
if (job != null && !job.isCancelled()) {
job.cancel(true);
}
this.pollingJob = null;
- UpnpIOService localService = service;
- if (localService != null) {
- localService.unregisterParticipant(this);
- }
+ super.dispose();
}
private void poll() {
"@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
return;
}
- updateStatus(ThingStatus.ONLINE);
updateWemoState();
} catch (Exception e) {
logger.debug("Exception during poll: {}", e.getMessage(), e);
public void handleCommand(ChannelUID channelUID, Command command) {
String localHost = getHost();
if (localHost.isEmpty()) {
- logger.error("Failed to send command '{}' for device '{}': IP address missing", command,
+ logger.warn("Failed to send command '{}' for device '{}': IP address missing", command,
getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
}
String wemoURL = getWemoURL(localHost, BASICACTION);
if (wemoURL == null) {
- logger.error("Failed to send command '{}' for device '{}': URL cannot be created", command,
+ logger.debug("Failed to send command '{}' for device '{}': URL cannot be created", command,
getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url");
boolean binaryState = OnOffType.ON.equals(command) ? true : false;
String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\"";
String content = createBinaryStateContent(binaryState);
- String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
- if (wemoCallResponse != null && logger.isTraceEnabled()) {
- logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID());
- logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID());
- logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID());
- logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse,
- getThing().getUID());
- }
+ wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
+ updateStatus(ThingStatus.ONLINE);
} catch (Exception e) {
- logger.error("Failed to send command '{}' for device '{}' ", command, getThing().getUID(), e);
+ logger.warn("Failed to send command '{}' for device '{}' ", command, getThing().getUID(), e);
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
}
}
}
protected void updateWemoState() {
String localHost = getHost();
if (localHost.isEmpty()) {
- logger.error("Failed to get actual state for device '{}': IP address missing", getThing().getUID());
+ logger.warn("Failed to get actual state for device '{}': IP address missing", getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
return;
String actionService = DEVICEACTION;
String wemoURL = getWemoURL(localHost, actionService);
if (wemoURL == null) {
- logger.error("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID());
+ logger.debug("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url");
return;
String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
String content = createStateRequestContent(action, actionService);
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
- if (wemoCallResponse != null) {
- if (logger.isTraceEnabled()) {
- logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID());
- logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID());
- logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID());
- logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID());
- }
- try {
- String stringParser = substringBetween(wemoCallResponse, "<attributeList>", "</attributeList>");
- logger.trace("Escaped Maker response for device '{}' :", getThing().getUID());
- logger.trace("'{}'", stringParser);
-
- // Due to Belkins bad response formatting, we need to run this twice.
- stringParser = unescapeXml(stringParser);
- stringParser = unescapeXml(stringParser);
- logger.trace("Maker response '{}' for device '{}' received", stringParser, getThing().getUID());
-
- stringParser = "<data>" + stringParser + "</data>";
-
- DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
- // see
- // https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
- dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
- dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
- dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
- dbf.setXIncludeAware(false);
- dbf.setExpandEntityReferences(false);
- DocumentBuilder db = dbf.newDocumentBuilder();
- InputSource is = new InputSource();
- is.setCharacterStream(new StringReader(stringParser));
-
- Document doc = db.parse(is);
- NodeList nodes = doc.getElementsByTagName("attribute");
-
- // iterate the attributes
- for (int i = 0; i < nodes.getLength(); i++) {
- Element element = (Element) nodes.item(i);
-
- NodeList deviceIndex = element.getElementsByTagName("name");
- Element line = (Element) deviceIndex.item(0);
- String attributeName = getCharacterDataFromElement(line);
- logger.trace("attributeName: {}", attributeName);
-
- NodeList deviceID = element.getElementsByTagName("value");
- line = (Element) deviceID.item(0);
- String attributeValue = getCharacterDataFromElement(line);
- logger.trace("attributeValue: {}", attributeValue);
-
- switch (attributeName) {
- case "Switch":
- State relayState = "0".equals(attributeValue) ? OnOffType.OFF : OnOffType.ON;
- logger.debug("New relayState '{}' for device '{}' received", relayState,
- getThing().getUID());
- updateState(CHANNEL_RELAY, relayState);
- break;
- case "Sensor":
- State sensorState = "1".equals(attributeValue) ? OnOffType.OFF : OnOffType.ON;
- logger.debug("New sensorState '{}' for device '{}' received", sensorState,
- getThing().getUID());
- updateState(CHANNEL_SENSOR, sensorState);
- break;
- }
+ try {
+ String stringParser = substringBetween(wemoCallResponse, "<attributeList>", "</attributeList>");
+ logger.trace("Escaped Maker response for device '{}' :", getThing().getUID());
+ logger.trace("'{}'", stringParser);
+
+ // Due to Belkins bad response formatting, we need to run this twice.
+ stringParser = unescapeXml(stringParser);
+ stringParser = unescapeXml(stringParser);
+ logger.trace("Maker response '{}' for device '{}' received", stringParser, getThing().getUID());
+
+ stringParser = "<data>" + stringParser + "</data>";
+
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+ // see
+ // https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
+ dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
+ dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+ dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
+ dbf.setXIncludeAware(false);
+ dbf.setExpandEntityReferences(false);
+ DocumentBuilder db = dbf.newDocumentBuilder();
+ InputSource is = new InputSource();
+ is.setCharacterStream(new StringReader(stringParser));
+
+ Document doc = db.parse(is);
+ NodeList nodes = doc.getElementsByTagName("attribute");
+
+ // iterate the attributes
+ for (int i = 0; i < nodes.getLength(); i++) {
+ Element element = (Element) nodes.item(i);
+
+ NodeList deviceIndex = element.getElementsByTagName("name");
+ Element line = (Element) deviceIndex.item(0);
+ String attributeName = getCharacterDataFromElement(line);
+ logger.trace("attributeName: {}", attributeName);
+
+ NodeList deviceID = element.getElementsByTagName("value");
+ line = (Element) deviceID.item(0);
+ String attributeValue = getCharacterDataFromElement(line);
+ logger.trace("attributeValue: {}", attributeValue);
+
+ switch (attributeName) {
+ case "Switch":
+ State relayState = "0".equals(attributeValue) ? OnOffType.OFF : OnOffType.ON;
+ logger.debug("New relayState '{}' for device '{}' received", relayState,
+ getThing().getUID());
+ updateState(CHANNEL_RELAY, relayState);
+ break;
+ case "Sensor":
+ State sensorState = "1".equals(attributeValue) ? OnOffType.OFF : OnOffType.ON;
+ logger.debug("New sensorState '{}' for device '{}' received", sensorState,
+ getThing().getUID());
+ updateState(CHANNEL_SENSOR, sensorState);
+ break;
}
- } catch (Exception e) {
- logger.error("Failed to parse attributeList for WeMo Maker '{}'", this.getThing().getUID(), e);
}
+ updateStatus(ThingStatus.ONLINE);
+ } catch (Exception e) {
+ logger.warn("Failed to parse attributeList for WeMo Maker '{}'", this.getThing().getUID(), e);
}
} catch (Exception e) {
- logger.error("Failed to get attributes for device '{}'", getThing().getUID(), e);
+ logger.warn("Failed to get attributes for device '{}'", getThing().getUID(), e);
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
}
import java.util.Properties;
import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.wemo.internal.WemoBindingConstants;
import org.openhab.core.io.net.http.HttpUtil;
import org.slf4j.Logger;
private final Logger logger = LoggerFactory.getLogger(WemoHttpCall.class);
- public @Nullable String executeCall(String wemoURL, String soapHeader, String content) {
- try {
- Properties wemoHeaders = new Properties();
- wemoHeaders.setProperty("CONTENT-TYPE", WemoBindingConstants.HTTP_CALL_CONTENT_HEADER);
- wemoHeaders.put("SOAPACTION", soapHeader);
-
- InputStream wemoContent = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
-
- String wemoCallResponse = HttpUtil.executeUrl("POST", wemoURL, wemoHeaders, wemoContent, null, 2000);
- return wemoCallResponse;
- } catch (IOException e) {
- // throw new IllegalStateException("Could not call WeMo", e);
- logger.debug("Could not make HTTP call to WeMo");
- return null;
- }
+ public String executeCall(String wemoURL, String soapHeader, String content) throws IOException {
+ Properties wemoHeaders = new Properties();
+ wemoHeaders.setProperty("CONTENT-TYPE", WemoBindingConstants.HTTP_CALL_CONTENT_HEADER);
+ wemoHeaders.put("SOAPACTION", soapHeader);
+
+ InputStream wemoContent = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
+
+ logger.trace("Performing HTTP call for URL: '{}', header: '{}', request body: '{}'", wemoURL, soapHeader,
+ content);
+ String responseBody = HttpUtil.executeUrl("POST", wemoURL, wemoHeaders, wemoContent, null, 2000);
+ logger.trace("HTTP response body: '{}'", responseBody);
+
+ return responseBody;
}
}
@Test
public void assertSupportedThingIsDiscovered()
- throws MalformedURLException, URISyntaxException, ValidationException {
+ throws MalformedURLException, URISyntaxException, ValidationException, IOException {
String model = WemoBindingConstants.THING_TYPE_MZ100.getId();
addUpnpDevice(SERVICE_ID, SERVICE_NUMBER, model);
@Test
public void assertThatThingHandlesOnOffCommandCorrectly()
- throws MalformedURLException, URISyntaxException, ValidationException {
+ throws MalformedURLException, URISyntaxException, ValidationException, IOException {
Command command = OnOffType.OFF;
Thing thing = createThing(THING_TYPE_UID, DEFAULT_TEST_CHANNEL, DEFAULT_TEST_CHANNEL_TYPE);
@Test
public void assertThatThingHandlesREFRESHCommandCorrectly()
- throws MalformedURLException, URISyntaxException, ValidationException {
+ throws MalformedURLException, URISyntaxException, ValidationException, IOException {
Command command = RefreshType.REFRESH;
Thing thing = createThing(THING_TYPE_UID, DEFAULT_TEST_CHANNEL, DEFAULT_TEST_CHANNEL_TYPE);
@Test
public void handleONcommandForBRIGHTNESSchannel()
- throws MalformedURLException, URISyntaxException, ValidationException {
+ throws MalformedURLException, URISyntaxException, ValidationException, IOException {
Command command = OnOffType.ON;
String channelID = WemoBindingConstants.CHANNEL_BRIGHTNESS;
@Test
public void handlePercentCommandForBRIGHTNESSChannel()
- throws MalformedURLException, URISyntaxException, ValidationException {
+ throws MalformedURLException, URISyntaxException, ValidationException, IOException {
// Set brightness value to 20 Percent
Command command = new PercentType(20);
String channelID = WemoBindingConstants.CHANNEL_BRIGHTNESS;
@Test
public void handleIncreaseCommandForBRIGHTNESSchannel()
- throws MalformedURLException, URISyntaxException, ValidationException {
+ throws MalformedURLException, URISyntaxException, ValidationException, IOException {
// The value is increased by 5 Percents by default
Command command = IncreaseDecreaseType.INCREASE;
String channelID = WemoBindingConstants.CHANNEL_BRIGHTNESS;
@Test
public void handleDecreaseCommandForBRIGHTNESSchannel()
- throws MalformedURLException, URISyntaxException, ValidationException {
+ throws MalformedURLException, URISyntaxException, ValidationException, IOException {
// The value can not be decreased below 0
Command command = IncreaseDecreaseType.DECREASE;
String channelID = WemoBindingConstants.CHANNEL_BRIGHTNESS;
}
@Test
- public void handleOnCommandForSTATEChannel() throws MalformedURLException, URISyntaxException, ValidationException {
+ public void handleOnCommandForSTATEChannel()
+ throws MalformedURLException, URISyntaxException, ValidationException, IOException {
Command command = OnOffType.ON;
String channelID = WemoBindingConstants.CHANNEL_STATE;
@Test
public void handleREFRESHCommandForChannelSTATE()
- throws MalformedURLException, URISyntaxException, ValidationException {
+ throws MalformedURLException, URISyntaxException, ValidationException, IOException {
Command command = RefreshType.REFRESH;
String channelID = WemoBindingConstants.CHANNEL_STATE;
}
private void assertRequestForCommand(String channelID, Command command, String action, String value,
- String capitability) throws MalformedURLException, URISyntaxException, ValidationException {
+ String capitability) throws MalformedURLException, URISyntaxException, ValidationException, IOException {
Thing bridge = createBridge(BRIDGE_TYPE_UID);
Thing thing = createThing(THING_TYPE_UID, DEFAULT_TEST_CHANNEL, DEFAULT_TEST_CHANNEL_TYPE);
@Test
public void assertThatThingHandlesOnOffCommandCorrectly()
- throws MalformedURLException, URISyntaxException, ValidationException {
+ throws MalformedURLException, URISyntaxException, ValidationException, IOException {
Command command = OnOffType.OFF;
Thing thing = createThing(THING_TYPE_UID, DEFAULT_TEST_CHANNEL, DEFAULT_TEST_CHANNEL_TYPE);
@Test
public void assertThatThingHandlesREFRESHCommand()
- throws MalformedURLException, URISyntaxException, ValidationException {
+ throws MalformedURLException, URISyntaxException, ValidationException, IOException {
Command command = RefreshType.REFRESH;
Thing thing = createThing(THING_TYPE_UID, DEFAULT_TEST_CHANNEL, DEFAULT_TEST_CHANNEL_TYPE);