[wemo] make UPnP more robust and fix polling/portscan (#12001)
* [wemo] make UPnP more robust * [wemo] change thing status updates Signed-off-by: Hans-Jörg Merk <github@hmerk.de>pull/12106/head
parent
d4fb20d529
commit
d9c31e626a
|
@ -116,6 +116,15 @@ public class WemoBindingConstants {
|
|||
public static final int LINK_DISCOVERY_SERVICE_INITIAL_DELAY = 5;
|
||||
public static final String HTTP_CALL_CONTENT_HEADER = "text/xml; charset=utf-8";
|
||||
|
||||
public static final String BASICACTION = "basicevent";
|
||||
public static final String BASICEVENT = "basicevent1";
|
||||
public static final String BRIDGEACTION = "bridge";
|
||||
public static final String BRIDGEEVENT = "bridge1";
|
||||
public static final String DEVICEACTION = "deviceevent";
|
||||
public static final String DEVICEEVENT = "deviceevent1";
|
||||
public static final String INSIGHTACTION = "insight";
|
||||
public static final String INSIGHTEVENT = "insight1";
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_BRIDGE_THING_TYPES = Collections.singleton(THING_TYPE_BRIDGE);
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_LIGHT_THING_TYPES = Collections.singleton(THING_TYPE_MZ100);
|
||||
|
|
|
@ -64,7 +64,7 @@ public class WemoHandlerFactory extends BaseThingHandlerFactory {
|
|||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = WemoBindingConstants.SUPPORTED_THING_TYPES;
|
||||
|
||||
private UpnpIOService upnpIOService;
|
||||
private final UpnpIOService upnpIOService;
|
||||
private @Nullable WemoHttpCallFactory wemoHttpCallFactory;
|
||||
|
||||
@Override
|
||||
|
@ -103,14 +103,14 @@ public class WemoHandlerFactory extends BaseThingHandlerFactory {
|
|||
WemoBridgeHandler handler = new WemoBridgeHandler((Bridge) thing);
|
||||
registerDeviceDiscoveryService(handler, wemoHttpcaller);
|
||||
return handler;
|
||||
} else if (thingTypeUID.equals(WemoBindingConstants.THING_TYPE_MAKER)) {
|
||||
logger.debug("Creating a WemoMakerHandler for thing '{}' with UDN '{}'", thing.getUID(),
|
||||
thing.getConfiguration().get(UDN));
|
||||
return new WemoMakerHandler(thing, upnpIOService, wemoHttpcaller);
|
||||
} else if (WemoBindingConstants.SUPPORTED_DEVICE_THING_TYPES.contains(thing.getThingTypeUID())) {
|
||||
logger.debug("Creating a WemoHandler for thing '{}' with UDN '{}'", thing.getUID(),
|
||||
thing.getConfiguration().get(UDN));
|
||||
return new WemoHandler(thing, upnpIOService, wemoHttpcaller);
|
||||
} else if (thingTypeUID.equals(WemoBindingConstants.THING_TYPE_MAKER)) {
|
||||
logger.debug("Creating a WemoMakerHandler for thing '{}' with UDN '{}'", thing.getUID(),
|
||||
thing.getConfiguration().get(UDN));
|
||||
return new WemoMakerHandler(thing, upnpIOService, wemoHttpcaller);
|
||||
} else if (thingTypeUID.equals(WemoBindingConstants.THING_TYPE_COFFEE)) {
|
||||
logger.debug("Creating a WemoCoffeeHandler for thing '{}' with UDN '{}'", thing.getUID(),
|
||||
thing.getConfiguration().get(UDN));
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
package org.openhab.binding.wemo.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
|
@ -23,6 +22,9 @@ import java.util.regex.Pattern;
|
|||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.io.net.http.HttpUtil;
|
||||
import org.w3c.dom.CharacterData;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
/**
|
||||
* {@link WemoUtil} implements some helper functions.
|
||||
|
@ -123,11 +125,10 @@ public class WemoUtil {
|
|||
return unescapedOutput.toString();
|
||||
}
|
||||
|
||||
public static @Nullable String getWemoURL(URL descriptorURL, String actionService) {
|
||||
public static @Nullable String getWemoURL(String host, String actionService) {
|
||||
int portCheckStart = 49151;
|
||||
int portCheckStop = 49157;
|
||||
String port = null;
|
||||
String host = substringBetween(descriptorURL.toString(), "://", ":");
|
||||
for (int i = portCheckStart; i < portCheckStop; i++) {
|
||||
if (serviceAvailableFunction.apply(host, i)) {
|
||||
port = String.valueOf(i);
|
||||
|
@ -155,4 +156,30 @@ public class WemoUtil {
|
|||
entities.put("quot", "\"");
|
||||
return entities;
|
||||
}
|
||||
|
||||
public static String createBinaryStateContent(boolean binaryState) {
|
||||
String binary = binaryState == true ? "1" : "0";
|
||||
String content = "<?xml version=\"1.0\"?>"
|
||||
+ "<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\">" + "<BinaryState>"
|
||||
+ binary + "</BinaryState>" + "</u:SetBinaryState>" + "</s:Body>" + "</s:Envelope>";
|
||||
return content;
|
||||
}
|
||||
|
||||
public static String createStateRequestContent(String action, String actionService) {
|
||||
String content = "<?xml version=\"1.0\"?>"
|
||||
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
|
||||
+ "<s:Body>" + "<u:" + action + " xmlns:u=\"urn:Belkin:service:" + actionService + ":1\">" + "</u:"
|
||||
+ action + ">" + "</s:Body>" + "</s:Envelope>";
|
||||
return content;
|
||||
}
|
||||
|
||||
public static String getCharacterDataFromElement(Element e) {
|
||||
Node child = e.getFirstChild();
|
||||
if (child instanceof CharacterData) {
|
||||
CharacterData cd = (CharacterData) child;
|
||||
return cd.getData();
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@ import org.openhab.binding.wemo.internal.WemoBindingConstants;
|
|||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.upnp.UpnpDiscoveryParticipant;
|
||||
import org.openhab.core.config.discovery.upnp.internal.UpnpDiscoveryService;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
|
@ -34,7 +33,7 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
/**
|
||||
* The {@link WemoDiscoveryParticipant} is responsible for discovering new and
|
||||
* removed Wemo devices. It uses the central {@link UpnpDiscoveryService}.
|
||||
* removed Wemo devices.
|
||||
*
|
||||
* @author Hans-Jörg Merk - Initial contribution
|
||||
* @author Kai Kreuzer - some refactoring for performance and simplification
|
||||
|
|
|
@ -65,8 +65,9 @@ public class WemoDiscoveryService extends AbstractDiscoveryService {
|
|||
@Override
|
||||
protected void startScan() {
|
||||
logger.debug("Starting UPnP RootDevice search...");
|
||||
if (upnpService != null) {
|
||||
upnpService.getControlPoint().search(new RootDeviceHeader());
|
||||
UpnpService localService = upnpService;
|
||||
if (localService != null) {
|
||||
localService.getControlPoint().search(new RootDeviceHeader());
|
||||
} else {
|
||||
logger.debug("upnpService not set");
|
||||
}
|
||||
|
|
|
@ -58,7 +58,8 @@ public class WemoBridgeHandler extends BaseBridgeHandler {
|
|||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
logger.debug("Cannot initalize WemoBridgeHandler. UDN not set.");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/config-status.error.missing-udn");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@ import static org.openhab.binding.wemo.internal.WemoBindingConstants.*;
|
|||
import static org.openhab.binding.wemo.internal.WemoUtil.*;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URL;
|
||||
import java.time.Instant;
|
||||
import java.time.ZonedDateTime;
|
||||
|
@ -51,10 +50,8 @@ import org.openhab.core.types.RefreshType;
|
|||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.w3c.dom.CharacterData;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.InputSource;
|
||||
|
||||
|
@ -72,31 +69,18 @@ public class WemoCoffeeHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_COFFEE);
|
||||
|
||||
private Map<String, Boolean> subscriptionState = new HashMap<>();
|
||||
private final Object upnpLock = new Object();
|
||||
private final Object jobLock = new Object();
|
||||
|
||||
private UpnpIOService service;
|
||||
private @Nullable UpnpIOService service;
|
||||
|
||||
private WemoHttpCall wemoCall;
|
||||
|
||||
private @Nullable ScheduledFuture<?> refreshJob;
|
||||
private String host = "";
|
||||
|
||||
private final Runnable refreshRunnable = new Runnable() {
|
||||
private Map<String, Boolean> subscriptionState = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (!isUpnpDeviceRegistered()) {
|
||||
logger.debug("WeMo UPnP device {} not yet registered", getUDN());
|
||||
}
|
||||
|
||||
updateWemoState();
|
||||
onSubscription();
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception during poll", e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
};
|
||||
private @Nullable ScheduledFuture<?> pollingJob;
|
||||
|
||||
public WemoCoffeeHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
|
||||
super(thing, wemoHttpCaller);
|
||||
|
@ -104,19 +88,26 @@ public class WemoCoffeeHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
this.wemoCall = wemoHttpCaller;
|
||||
this.service = upnpIOService;
|
||||
|
||||
logger.debug("Creating a WemoCoffeeHandler V0.4 for thing '{}'", getThing().getUID());
|
||||
logger.debug("Creating a WemoCoffeeHandler for thing '{}'", getThing().getUID());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
Configuration configuration = getConfig();
|
||||
|
||||
if (configuration.get("udn") != null) {
|
||||
logger.debug("Initializing WemoCoffeeHandler for UDN '{}'", configuration.get("udn"));
|
||||
onSubscription();
|
||||
onUpdate();
|
||||
if (configuration.get(UDN) != null) {
|
||||
logger.debug("Initializing WemoCoffeeHandler for UDN '{}'", configuration.get(UDN));
|
||||
UpnpIOService localService = service;
|
||||
if (localService != null) {
|
||||
localService.registerParticipant(this);
|
||||
}
|
||||
host = getHost();
|
||||
pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVALL_SECONDS,
|
||||
TimeUnit.SECONDS);
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/config-status.error.missing-udn");
|
||||
logger.debug("Cannot initalize WemoCoffeeHandler. UDN not set.");
|
||||
}
|
||||
}
|
||||
|
@ -124,19 +115,61 @@ public class WemoCoffeeHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("WeMoCoffeeHandler disposed.");
|
||||
|
||||
ScheduledFuture<?> job = refreshJob;
|
||||
ScheduledFuture<?> job = this.pollingJob;
|
||||
if (job != null && !job.isCancelled()) {
|
||||
job.cancel(true);
|
||||
}
|
||||
refreshJob = null;
|
||||
this.pollingJob = null;
|
||||
removeSubscription();
|
||||
}
|
||||
|
||||
private void poll() {
|
||||
synchronized (jobLock) {
|
||||
if (pollingJob == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
logger.debug("Polling job");
|
||||
|
||||
host = getHost();
|
||||
// Check if the Wemo device is set in the UPnP service registry
|
||||
// If not, set the thing state to ONLINE/CONFIG-PENDING and wait for the next poll
|
||||
if (!isUpnpDeviceRegistered()) {
|
||||
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 handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.trace("Command '{}' received for channel '{}'", command, channelUID);
|
||||
|
||||
String localHost = getHost();
|
||||
if (localHost.isEmpty()) {
|
||||
logger.error("Failed to send command '{}' for device '{}': IP address missing", command,
|
||||
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 send command '{}' for device '{}': URL cannot be created", command,
|
||||
getThing().getUID());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/config-status.error.missing-url");
|
||||
return;
|
||||
}
|
||||
if (command instanceof RefreshType) {
|
||||
try {
|
||||
updateWemoState();
|
||||
|
@ -162,15 +195,19 @@ public class WemoCoffeeHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
+ "<attribute><name>Cleaning</name><value>NULL</value></attribute></attributeList>"
|
||||
+ "</u:SetAttributes>" + "</s:Body>" + "</s:Envelope>";
|
||||
|
||||
URL descriptorURL = service.getDescriptorURL(this);
|
||||
String wemoURL = getWemoURL(descriptorURL, "basicevent");
|
||||
|
||||
if (wemoURL != null) {
|
||||
String wemoCallResponse = wemoCall.executeCall(wemoURL, soapHeader, content);
|
||||
if (wemoCallResponse != null) {
|
||||
updateState(CHANNEL_STATE, OnOffType.ON);
|
||||
State newMode = new StringType("Brewing");
|
||||
updateState(CHANNEL_COFFEEMODE, newMode);
|
||||
String wemoCallResponse = wemoCall.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());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
@ -179,7 +216,8 @@ public class WemoCoffeeHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
|
||||
}
|
||||
}
|
||||
// if command.equals(OnOffType.OFF) we do nothing because WeMo Coffee Maker cannot be switched off
|
||||
// if command.equals(OnOffType.OFF) we do nothing because WeMo Coffee Maker cannot be switched
|
||||
// off
|
||||
// remotely
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
@ -200,53 +238,53 @@ public class WemoCoffeeHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
// We can subscribe to GENA events, but there is no usefull response right now.
|
||||
}
|
||||
|
||||
private synchronized void onSubscription() {
|
||||
if (service.isRegistered(this)) {
|
||||
logger.debug("Checking WeMo GENA subscription for '{}'", this);
|
||||
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 = "deviceevent1";
|
||||
if (subscriptionState.get(subscription) == null) {
|
||||
logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(), subscription);
|
||||
service.addSubscription(this, subscription, SUBSCRIPTION_DURATION_SECONDS);
|
||||
subscriptionState.put(subscription, true);
|
||||
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());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.debug("Setting up WeMo GENA subscription for '{}' FAILED - service.isRegistered(this) is FALSE",
|
||||
this);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void removeSubscription() {
|
||||
logger.debug("Removing WeMo GENA subscription for '{}'", this);
|
||||
|
||||
if (service.isRegistered(this)) {
|
||||
String subscription = "deviceevent1";
|
||||
if (subscriptionState.get(subscription) != null) {
|
||||
logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription);
|
||||
service.removeSubscription(this, subscription);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
subscriptionState = new HashMap<>();
|
||||
service.unregisterParticipant(this);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void onUpdate() {
|
||||
ScheduledFuture<?> job = refreshJob;
|
||||
if (job == null || job.isCancelled()) {
|
||||
Configuration config = getThing().getConfiguration();
|
||||
int refreshInterval = DEFAULT_REFRESH_INTERVALL_SECONDS;
|
||||
Object refreshConfig = config.get("pollingInterval");
|
||||
if (refreshConfig != null) {
|
||||
refreshInterval = ((BigDecimal) refreshConfig).intValue();
|
||||
logger.debug("Setting WemoCoffeeHandler refreshInterval to '{}' seconds", refreshInterval);
|
||||
}
|
||||
refreshJob = scheduler.scheduleWithFixedDelay(refreshRunnable, 0, refreshInterval, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isUpnpDeviceRegistered() {
|
||||
return service.isRegistered(this);
|
||||
UpnpIOService localService = service;
|
||||
if (localService != null) {
|
||||
return localService.isRegistered(this);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -258,154 +296,163 @@ public class WemoCoffeeHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
* The {@link updateWemoState} polls the actual state of a WeMo CoffeeMaker.
|
||||
*/
|
||||
protected void updateWemoState() {
|
||||
String action = "GetAttributes";
|
||||
String actionService = "deviceevent";
|
||||
|
||||
String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
|
||||
String content = "<?xml version=\"1.0\"?>"
|
||||
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
|
||||
+ "<s:Body>" + "<u:" + action + " xmlns:u=\"urn:Belkin:service:" + actionService + ":1\">" + "</u:"
|
||||
+ action + ">" + "</s:Body>" + "</s:Envelope>";
|
||||
|
||||
String localHost = getHost();
|
||||
if (localHost.isEmpty()) {
|
||||
logger.error("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());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/config-status.error.missing-url");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
URL descriptorURL = service.getDescriptorURL(this);
|
||||
String wemoURL = getWemoURL(descriptorURL, actionService);
|
||||
String action = "GetAttributes";
|
||||
String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
|
||||
String content = createStateRequestContent(action, actionService);
|
||||
String wemoCallResponse = wemoCall.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>");
|
||||
|
||||
if (wemoURL != null) {
|
||||
String wemoCallResponse = wemoCall.executeCall(wemoURL, soapHeader, content);
|
||||
if (wemoCallResponse != null) {
|
||||
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);
|
||||
|
||||
// Due to Belkins bad response formatting, we need to run this twice.
|
||||
stringParser = unescapeXml(stringParser);
|
||||
stringParser = unescapeXml(stringParser);
|
||||
logger.trace("CoffeeMaker response '{}' for device '{}' received", stringParser,
|
||||
getThing().getUID());
|
||||
|
||||
logger.trace("CoffeeMaker response '{}' for device '{}' received", stringParser,
|
||||
getThing().getUID());
|
||||
stringParser = "<data>" + stringParser + "</data>";
|
||||
|
||||
stringParser = "<data>" + stringParser + "</data>";
|
||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
// see
|
||||
// https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
|
||||
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
||||
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
||||
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
|
||||
dbf.setXIncludeAware(false);
|
||||
dbf.setExpandEntityReferences(false);
|
||||
DocumentBuilder db = dbf.newDocumentBuilder();
|
||||
InputSource is = new InputSource();
|
||||
is.setCharacterStream(new StringReader(stringParser));
|
||||
|
||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
// see
|
||||
// https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
|
||||
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
||||
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
||||
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
|
||||
dbf.setXIncludeAware(false);
|
||||
dbf.setExpandEntityReferences(false);
|
||||
DocumentBuilder db = dbf.newDocumentBuilder();
|
||||
InputSource is = new InputSource();
|
||||
is.setCharacterStream(new StringReader(stringParser));
|
||||
Document doc = db.parse(is);
|
||||
NodeList nodes = doc.getElementsByTagName("attribute");
|
||||
|
||||
Document doc = db.parse(is);
|
||||
NodeList nodes = doc.getElementsByTagName("attribute");
|
||||
// iterate the attributes
|
||||
for (int i = 0; i < nodes.getLength(); i++) {
|
||||
Element element = (Element) nodes.item(i);
|
||||
|
||||
// iterate the attributes
|
||||
for (int i = 0; i < nodes.getLength(); i++) {
|
||||
Element element = (Element) nodes.item(i);
|
||||
NodeList deviceIndex = element.getElementsByTagName("name");
|
||||
Element line = (Element) deviceIndex.item(0);
|
||||
String attributeName = getCharacterDataFromElement(line);
|
||||
logger.trace("attributeName: {}", attributeName);
|
||||
|
||||
NodeList deviceIndex = element.getElementsByTagName("name");
|
||||
Element line = (Element) deviceIndex.item(0);
|
||||
String attributeName = getCharacterDataFromElement(line);
|
||||
logger.trace("attributeName: {}", attributeName);
|
||||
NodeList deviceID = element.getElementsByTagName("value");
|
||||
line = (Element) deviceID.item(0);
|
||||
String attributeValue = getCharacterDataFromElement(line);
|
||||
logger.trace("attributeValue: {}", attributeValue);
|
||||
|
||||
NodeList deviceID = element.getElementsByTagName("value");
|
||||
line = (Element) deviceID.item(0);
|
||||
String attributeValue = getCharacterDataFromElement(line);
|
||||
logger.trace("attributeValue: {}", attributeValue);
|
||||
switch (attributeName) {
|
||||
case "Mode":
|
||||
State newMode = new StringType("Brewing");
|
||||
State newAttributeValue;
|
||||
|
||||
switch (attributeName) {
|
||||
case "Mode":
|
||||
State newMode = new StringType("Brewing");
|
||||
State newAttributeValue;
|
||||
|
||||
switch (attributeValue) {
|
||||
case "0":
|
||||
updateState(CHANNEL_STATE, OnOffType.ON);
|
||||
newMode = new StringType("Refill");
|
||||
updateState(CHANNEL_COFFEEMODE, newMode);
|
||||
break;
|
||||
case "1":
|
||||
updateState(CHANNEL_STATE, OnOffType.OFF);
|
||||
newMode = new StringType("PlaceCarafe");
|
||||
updateState(CHANNEL_COFFEEMODE, newMode);
|
||||
break;
|
||||
case "2":
|
||||
updateState(CHANNEL_STATE, OnOffType.OFF);
|
||||
newMode = new StringType("RefillWater");
|
||||
updateState(CHANNEL_COFFEEMODE, newMode);
|
||||
break;
|
||||
case "3":
|
||||
updateState(CHANNEL_STATE, OnOffType.OFF);
|
||||
newMode = new StringType("Ready");
|
||||
updateState(CHANNEL_COFFEEMODE, newMode);
|
||||
break;
|
||||
case "4":
|
||||
updateState(CHANNEL_STATE, OnOffType.ON);
|
||||
newMode = new StringType("Brewing");
|
||||
updateState(CHANNEL_COFFEEMODE, newMode);
|
||||
break;
|
||||
case "5":
|
||||
updateState(CHANNEL_STATE, OnOffType.OFF);
|
||||
newMode = new StringType("Brewed");
|
||||
updateState(CHANNEL_COFFEEMODE, newMode);
|
||||
break;
|
||||
case "6":
|
||||
updateState(CHANNEL_STATE, OnOffType.OFF);
|
||||
newMode = new StringType("CleaningBrewing");
|
||||
updateState(CHANNEL_COFFEEMODE, newMode);
|
||||
break;
|
||||
case "7":
|
||||
updateState(CHANNEL_STATE, OnOffType.OFF);
|
||||
newMode = new StringType("CleaningSoaking");
|
||||
updateState(CHANNEL_COFFEEMODE, newMode);
|
||||
break;
|
||||
case "8":
|
||||
updateState(CHANNEL_STATE, OnOffType.OFF);
|
||||
newMode = new StringType("BrewFailCarafeRemoved");
|
||||
updateState(CHANNEL_COFFEEMODE, newMode);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "ModeTime":
|
||||
newAttributeValue = new DecimalType(attributeValue);
|
||||
updateState(CHANNEL_MODETIME, newAttributeValue);
|
||||
break;
|
||||
case "TimeRemaining":
|
||||
newAttributeValue = new DecimalType(attributeValue);
|
||||
updateState(CHANNEL_TIMEREMAINING, newAttributeValue);
|
||||
break;
|
||||
case "WaterLevelReached":
|
||||
newAttributeValue = new DecimalType(attributeValue);
|
||||
updateState(CHANNEL_WATERLEVELREACHED, newAttributeValue);
|
||||
break;
|
||||
case "CleanAdvise":
|
||||
newAttributeValue = attributeValue.equals("0") ? OnOffType.OFF : OnOffType.ON;
|
||||
updateState(CHANNEL_CLEANADVISE, newAttributeValue);
|
||||
break;
|
||||
case "FilterAdvise":
|
||||
newAttributeValue = attributeValue.equals("0") ? OnOffType.OFF : OnOffType.ON;
|
||||
updateState(CHANNEL_FILTERADVISE, newAttributeValue);
|
||||
break;
|
||||
case "Brewed":
|
||||
newAttributeValue = getDateTimeState(attributeValue);
|
||||
if (newAttributeValue != null) {
|
||||
updateState(CHANNEL_BREWED, newAttributeValue);
|
||||
}
|
||||
break;
|
||||
case "LastCleaned":
|
||||
newAttributeValue = getDateTimeState(attributeValue);
|
||||
if (newAttributeValue != null) {
|
||||
updateState(CHANNEL_LASTCLEANED, newAttributeValue);
|
||||
}
|
||||
break;
|
||||
}
|
||||
switch (attributeValue) {
|
||||
case "0":
|
||||
updateState(CHANNEL_STATE, OnOffType.ON);
|
||||
newMode = new StringType("Refill");
|
||||
updateState(CHANNEL_COFFEEMODE, newMode);
|
||||
break;
|
||||
case "1":
|
||||
updateState(CHANNEL_STATE, OnOffType.OFF);
|
||||
newMode = new StringType("PlaceCarafe");
|
||||
updateState(CHANNEL_COFFEEMODE, newMode);
|
||||
break;
|
||||
case "2":
|
||||
updateState(CHANNEL_STATE, OnOffType.OFF);
|
||||
newMode = new StringType("RefillWater");
|
||||
updateState(CHANNEL_COFFEEMODE, newMode);
|
||||
break;
|
||||
case "3":
|
||||
updateState(CHANNEL_STATE, OnOffType.OFF);
|
||||
newMode = new StringType("Ready");
|
||||
updateState(CHANNEL_COFFEEMODE, newMode);
|
||||
break;
|
||||
case "4":
|
||||
updateState(CHANNEL_STATE, OnOffType.ON);
|
||||
newMode = new StringType("Brewing");
|
||||
updateState(CHANNEL_COFFEEMODE, newMode);
|
||||
break;
|
||||
case "5":
|
||||
updateState(CHANNEL_STATE, OnOffType.OFF);
|
||||
newMode = new StringType("Brewed");
|
||||
updateState(CHANNEL_COFFEEMODE, newMode);
|
||||
break;
|
||||
case "6":
|
||||
updateState(CHANNEL_STATE, OnOffType.OFF);
|
||||
newMode = new StringType("CleaningBrewing");
|
||||
updateState(CHANNEL_COFFEEMODE, newMode);
|
||||
break;
|
||||
case "7":
|
||||
updateState(CHANNEL_STATE, OnOffType.OFF);
|
||||
newMode = new StringType("CleaningSoaking");
|
||||
updateState(CHANNEL_COFFEEMODE, newMode);
|
||||
break;
|
||||
case "8":
|
||||
updateState(CHANNEL_STATE, OnOffType.OFF);
|
||||
newMode = new StringType("BrewFailCarafeRemoved");
|
||||
updateState(CHANNEL_COFFEEMODE, newMode);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "ModeTime":
|
||||
newAttributeValue = new DecimalType(attributeValue);
|
||||
updateState(CHANNEL_MODETIME, newAttributeValue);
|
||||
break;
|
||||
case "TimeRemaining":
|
||||
newAttributeValue = new DecimalType(attributeValue);
|
||||
updateState(CHANNEL_TIMEREMAINING, newAttributeValue);
|
||||
break;
|
||||
case "WaterLevelReached":
|
||||
newAttributeValue = new DecimalType(attributeValue);
|
||||
updateState(CHANNEL_WATERLEVELREACHED, newAttributeValue);
|
||||
break;
|
||||
case "CleanAdvise":
|
||||
newAttributeValue = "0".equals(attributeValue) ? OnOffType.OFF : OnOffType.ON;
|
||||
updateState(CHANNEL_CLEANADVISE, newAttributeValue);
|
||||
break;
|
||||
case "FilterAdvise":
|
||||
newAttributeValue = "0".equals(attributeValue) ? OnOffType.OFF : OnOffType.ON;
|
||||
updateState(CHANNEL_FILTERADVISE, newAttributeValue);
|
||||
break;
|
||||
case "Brewed":
|
||||
newAttributeValue = getDateTimeState(attributeValue);
|
||||
if (newAttributeValue != null) {
|
||||
updateState(CHANNEL_BREWED, newAttributeValue);
|
||||
}
|
||||
break;
|
||||
case "LastCleaned":
|
||||
newAttributeValue = getDateTimeState(attributeValue);
|
||||
if (newAttributeValue != null) {
|
||||
updateState(CHANNEL_LASTCLEANED, newAttributeValue);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to parse attributeList for WeMo CoffeMaker '{}'", this.getThing().getUID(),
|
||||
e);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to parse attributeList for WeMo CoffeMaker '{}'", this.getThing().getUID(), e);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
@ -428,13 +475,19 @@ public class WemoCoffeeHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
return dateTimeState;
|
||||
}
|
||||
|
||||
public static String getCharacterDataFromElement(Element e) {
|
||||
Node child = e.getFirstChild();
|
||||
if (child instanceof CharacterData) {
|
||||
CharacterData cd = (CharacterData) child;
|
||||
return cd.getData();
|
||||
public String getHost() {
|
||||
String localHost = host;
|
||||
if (!localHost.isEmpty()) {
|
||||
return localHost;
|
||||
}
|
||||
return "?";
|
||||
UpnpIOService localService = service;
|
||||
if (localService != null) {
|
||||
URL descriptorURL = localService.getDescriptorURL(this);
|
||||
if (descriptorURL != null) {
|
||||
return descriptorURL.getHost();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -15,7 +15,6 @@ package org.openhab.binding.wemo.internal.handler;
|
|||
import static org.openhab.binding.wemo.internal.WemoBindingConstants.*;
|
||||
import static org.openhab.binding.wemo.internal.WemoUtil.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
@ -56,23 +55,20 @@ public class WemoCrockpotHandler extends AbstractWemoHandler implements UpnpIOPa
|
|||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_CROCKPOT);
|
||||
|
||||
private final Map<String, Boolean> subscriptionState = new HashMap<>();
|
||||
private final Object upnpLock = new Object();
|
||||
private final Object jobLock = new Object();
|
||||
|
||||
private final Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
private UpnpIOService service;
|
||||
private @Nullable UpnpIOService service;
|
||||
|
||||
private WemoHttpCall wemoCall;
|
||||
|
||||
private @Nullable ScheduledFuture<?> refreshJob;
|
||||
private String host = "";
|
||||
|
||||
private final Runnable refreshRunnable = () -> {
|
||||
updateWemoState();
|
||||
if (!isUpnpDeviceRegistered()) {
|
||||
logger.debug("WeMo UPnP device {} not yet registered", getUDN());
|
||||
} else {
|
||||
onSubscription();
|
||||
}
|
||||
};
|
||||
private Map<String, Boolean> subscriptionState = new HashMap<>();
|
||||
|
||||
private @Nullable ScheduledFuture<?> pollingJob;
|
||||
|
||||
public WemoCrockpotHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
|
||||
super(thing, wemoHttpCaller);
|
||||
|
@ -87,13 +83,19 @@ public class WemoCrockpotHandler extends AbstractWemoHandler implements UpnpIOPa
|
|||
public void initialize() {
|
||||
Configuration configuration = getConfig();
|
||||
|
||||
if (configuration.get("udn") != null) {
|
||||
logger.debug("Initializing WemoCrockpotHandler for UDN '{}'", configuration.get("udn"));
|
||||
service.registerParticipant(this);
|
||||
onSubscription();
|
||||
onUpdate();
|
||||
if (configuration.get(UDN) != null) {
|
||||
logger.debug("Initializing WemoCrockpotHandler for UDN '{}'", configuration.get(UDN));
|
||||
UpnpIOService localService = service;
|
||||
if (localService != null) {
|
||||
localService.registerParticipant(this);
|
||||
}
|
||||
host = getHost();
|
||||
pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVALL_SECONDS,
|
||||
TimeUnit.SECONDS);
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/config-status.error.missing-udn");
|
||||
logger.debug("Cannot initalize WemoCrockpotHandler. UDN not set.");
|
||||
}
|
||||
}
|
||||
|
@ -101,18 +103,60 @@ public class WemoCrockpotHandler extends AbstractWemoHandler implements UpnpIOPa
|
|||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("WeMoCrockpotHandler disposed.");
|
||||
|
||||
ScheduledFuture<?> job = refreshJob;
|
||||
ScheduledFuture<?> job = this.pollingJob;
|
||||
if (job != null && !job.isCancelled()) {
|
||||
job.cancel(true);
|
||||
}
|
||||
refreshJob = null;
|
||||
this.pollingJob = null;
|
||||
removeSubscription();
|
||||
}
|
||||
|
||||
private void poll() {
|
||||
synchronized (jobLock) {
|
||||
if (pollingJob == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
logger.debug("Polling job");
|
||||
host = getHost();
|
||||
// Check if the Wemo device is set in the UPnP service registry
|
||||
// If not, set the thing state to ONLINE/CONFIG-PENDING and wait for the next poll
|
||||
if (!isUpnpDeviceRegistered()) {
|
||||
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 handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.trace("Command '{}' received for channel '{}'", command, channelUID);
|
||||
String localHost = getHost();
|
||||
if (localHost.isEmpty()) {
|
||||
logger.error("Failed to send command '{}' for device '{}': IP address missing", command,
|
||||
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 send command '{}' for device '{}': URL cannot be created", command,
|
||||
getThing().getUID());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/config-status.error.missing-url");
|
||||
return;
|
||||
}
|
||||
String mode = "0";
|
||||
String time = null;
|
||||
|
||||
|
@ -142,12 +186,12 @@ public class WemoCrockpotHandler extends AbstractWemoHandler implements UpnpIOPa
|
|||
+ "<s:Body>" + "<u:SetCrockpotState xmlns:u=\"urn:Belkin:service:basicevent:1\">" + "<mode>"
|
||||
+ mode + "</mode>" + "<time>" + time + "</time>" + "</u:SetCrockpotState>" + "</s:Body>"
|
||||
+ "</s:Envelope>";
|
||||
|
||||
URL descriptorURL = service.getDescriptorURL(this);
|
||||
String wemoURL = getWemoURL(descriptorURL, "basicevent");
|
||||
|
||||
if (wemoURL != null) {
|
||||
wemoCall.executeCall(wemoURL, soapHeader, content);
|
||||
String wemoCallResponse = wemoCall.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) {
|
||||
logger.debug("Failed to send command '{}' for device '{}':", command, getThing().getUID(), e);
|
||||
|
@ -177,54 +221,55 @@ public class WemoCrockpotHandler extends AbstractWemoHandler implements UpnpIOPa
|
|||
}
|
||||
}
|
||||
|
||||
private synchronized void onSubscription() {
|
||||
if (service.isRegistered(this)) {
|
||||
logger.debug("Checking WeMo GENA subscription for '{}'", this);
|
||||
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 = "basicevent1";
|
||||
String subscription = BASICEVENT;
|
||||
|
||||
if (subscriptionState.get(subscription) == null) {
|
||||
logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(), subscription);
|
||||
service.addSubscription(this, subscription, SUBSCRIPTION_DURATION_SECONDS);
|
||||
subscriptionState.put(subscription, true);
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
logger.debug("Setting up WeMo GENA subscription for '{}' FAILED - service.isRegistered(this) is FALSE",
|
||||
this);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void removeSubscription() {
|
||||
logger.debug("Removing WeMo GENA subscription for '{}'", this);
|
||||
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 (service.isRegistered(this)) {
|
||||
String subscription = "basicevent1";
|
||||
|
||||
if (subscriptionState.get(subscription) != null) {
|
||||
logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription);
|
||||
service.removeSubscription(this, subscription);
|
||||
if (subscriptionState.get(subscription) != null) {
|
||||
logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription);
|
||||
localService.removeSubscription(this, subscription);
|
||||
}
|
||||
subscriptionState.remove(subscription);
|
||||
localService.unregisterParticipant(this);
|
||||
}
|
||||
}
|
||||
|
||||
subscriptionState.remove(subscription);
|
||||
service.unregisterParticipant(this);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void onUpdate() {
|
||||
ScheduledFuture<?> job = refreshJob;
|
||||
if (job == null || job.isCancelled()) {
|
||||
Configuration config = getThing().getConfiguration();
|
||||
int refreshInterval = DEFAULT_REFRESH_INTERVALL_SECONDS;
|
||||
Object refreshConfig = config.get("refresh");
|
||||
refreshInterval = refreshConfig == null ? DEFAULT_REFRESH_INTERVALL_SECONDS
|
||||
: ((BigDecimal) refreshConfig).intValue();
|
||||
refreshJob = scheduler.scheduleWithFixedDelay(refreshRunnable, 0, refreshInterval, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isUpnpDeviceRegistered() {
|
||||
return service.isRegistered(this);
|
||||
UpnpIOService localService = service;
|
||||
if (localService != null) {
|
||||
return localService.isRegistered(this);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -238,52 +283,61 @@ public class WemoCrockpotHandler extends AbstractWemoHandler implements UpnpIOPa
|
|||
*
|
||||
*/
|
||||
protected void updateWemoState() {
|
||||
String action = "GetCrockpotState";
|
||||
String actionService = "basicevent";
|
||||
|
||||
String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
|
||||
String content = "<?xml version=\"1.0\"?>"
|
||||
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
|
||||
+ "<s:Body>" + "<u:" + action + " xmlns:u=\"urn:Belkin:service:" + actionService + ":1\">" + "</u:"
|
||||
+ action + ">" + "</s:Body>" + "</s:Envelope>";
|
||||
|
||||
String localHost = getHost();
|
||||
if (localHost.isEmpty()) {
|
||||
logger.error("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());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/config-status.error.missing-url");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
URL descriptorURL = service.getDescriptorURL(this);
|
||||
String wemoURL = getWemoURL(descriptorURL, actionService);
|
||||
|
||||
if (wemoURL != null) {
|
||||
String wemoCallResponse = wemoCall.executeCall(wemoURL, soapHeader, content);
|
||||
if (wemoCallResponse != null) {
|
||||
logger.trace("State response '{}' for device '{}' received", 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 action = "GetCrockpotState";
|
||||
String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
|
||||
String content = createStateRequestContent(action, actionService);
|
||||
String wemoCallResponse = wemoCall.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);
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
logger.debug("Failed to get actual state for device '{}': {}", getThing().getUID(), e.getMessage(), e);
|
||||
|
@ -295,4 +349,19 @@ public class WemoCrockpotHandler extends AbstractWemoHandler implements UpnpIOPa
|
|||
@Override
|
||||
public void onStatusChanged(boolean status) {
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
String localHost = host;
|
||||
if (!localHost.isEmpty()) {
|
||||
return localHost;
|
||||
}
|
||||
UpnpIOService localService = service;
|
||||
if (localService != null) {
|
||||
URL descriptorURL = localService.getDescriptorURL(this);
|
||||
if (descriptorURL != null) {
|
||||
return descriptorURL.getHost();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ package org.openhab.binding.wemo.internal.handler;
|
|||
import static org.openhab.binding.wemo.internal.WemoBindingConstants.*;
|
||||
import static org.openhab.binding.wemo.internal.WemoUtil.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URL;
|
||||
import java.time.Instant;
|
||||
import java.time.ZonedDateTime;
|
||||
|
@ -62,12 +61,21 @@ public class WemoDimmerHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_DIMMER);
|
||||
|
||||
private Map<String, Boolean> subscriptionState = new HashMap<>();
|
||||
private Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<>());
|
||||
private final Object upnpLock = new Object();
|
||||
private final Object jobLock = new Object();
|
||||
|
||||
private final Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
private @Nullable UpnpIOService service;
|
||||
|
||||
private UpnpIOService service;
|
||||
private WemoHttpCall wemoCall;
|
||||
|
||||
private String host = "";
|
||||
|
||||
private Map<String, Boolean> subscriptionState = new HashMap<>();
|
||||
|
||||
private @Nullable ScheduledFuture<?> pollingJob;
|
||||
|
||||
private int currentBrightness;
|
||||
private int currentNightModeBrightness;
|
||||
private @Nullable String currentNightModeState;
|
||||
|
@ -76,23 +84,6 @@ public class WemoDimmerHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
*/
|
||||
private static final int DIM_STEPSIZE = 5;
|
||||
|
||||
private @Nullable ScheduledFuture<?> refreshJob;
|
||||
private Runnable refreshRunnable = new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (!isUpnpDeviceRegistered()) {
|
||||
logger.debug("WeMo UPnP device {} not yet registered", getUDN());
|
||||
}
|
||||
updateWemoState();
|
||||
onSubscription();
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception during poll : {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public WemoDimmerHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
|
||||
super(thing, wemoHttpCaller);
|
||||
|
||||
|
@ -105,12 +96,20 @@ public class WemoDimmerHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
@Override
|
||||
public void initialize() {
|
||||
Configuration configuration = getConfig();
|
||||
if (configuration.get("udn") != null) {
|
||||
logger.debug("Initializing WemoDimmerHandler for UDN '{}'", configuration.get("udn"));
|
||||
service.registerParticipant(this);
|
||||
onSubscription();
|
||||
onUpdate();
|
||||
|
||||
if (configuration.get(UDN) != null) {
|
||||
logger.debug("Initializing WemoDimmerHandler for UDN '{}'", configuration.get(UDN));
|
||||
UpnpIOService localService = service;
|
||||
if (localService != null) {
|
||||
localService.registerParticipant(this);
|
||||
}
|
||||
host = getHost();
|
||||
pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVALL_SECONDS,
|
||||
TimeUnit.SECONDS);
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/config-status.error.missing-udn");
|
||||
logger.debug("Cannot initalize WemoDimmerHandler. UDN not set.");
|
||||
}
|
||||
}
|
||||
|
@ -119,15 +118,42 @@ public class WemoDimmerHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
public void dispose() {
|
||||
logger.debug("WeMoDimmerHandler disposed.");
|
||||
|
||||
ScheduledFuture<?> job = refreshJob;
|
||||
ScheduledFuture<?> job = this.pollingJob;
|
||||
if (job != null && !job.isCancelled()) {
|
||||
job.cancel(true);
|
||||
}
|
||||
refreshJob = null;
|
||||
|
||||
this.pollingJob = null;
|
||||
removeSubscription();
|
||||
}
|
||||
|
||||
private void poll() {
|
||||
synchronized (jobLock) {
|
||||
if (pollingJob == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
logger.debug("Polling job");
|
||||
host = getHost();
|
||||
// Check if the Wemo device is set in the UPnP service registry
|
||||
// If not, set the thing state to ONLINE/CONFIG-PENDING and wait for the next poll
|
||||
if (!isUpnpDeviceRegistered()) {
|
||||
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 handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.trace("Command '{}' received for channel '{}'", command, channelUID);
|
||||
|
@ -161,7 +187,7 @@ public class WemoDimmerHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
value = String.valueOf(newBrightness);
|
||||
currentBrightness = newBrightness;
|
||||
argument = "brightness";
|
||||
if (value.equals("0")) {
|
||||
if ("0".equals(value)) {
|
||||
value = "1";
|
||||
argument = "brightness";
|
||||
setBinaryState(action, argument, "1");
|
||||
|
@ -195,7 +221,7 @@ public class WemoDimmerHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
break;
|
||||
}
|
||||
argument = "brightness";
|
||||
if (value.equals("0")) {
|
||||
if ("0".equals(value)) {
|
||||
value = "1";
|
||||
argument = "brightness";
|
||||
setBinaryState(action, argument, "1");
|
||||
|
@ -251,12 +277,12 @@ public class WemoDimmerHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
}
|
||||
}
|
||||
if (faderSeconds != null && faderEnabled != null) {
|
||||
if (command.equals(OnOffType.ON)) {
|
||||
if (OnOffType.ON.equals(command)) {
|
||||
value = "<BinaryState></BinaryState>" + "<Duration></Duration>" + "<EndAction></EndAction>"
|
||||
+ "<brightness></brightness>" + "<fader>" + faderSeconds + ":" + timeStamp + ":"
|
||||
+ faderEnabled + ":0:0</fader>" + "<UDN></UDN>";
|
||||
updateState(CHANNEL_STATE, OnOffType.ON);
|
||||
} else if (command.equals(OnOffType.OFF)) {
|
||||
} else if (OnOffType.OFF.equals(command)) {
|
||||
value = "<BinaryState></BinaryState>" + "<Duration></Duration>" + "<EndAction></EndAction>"
|
||||
+ "<brightness></brightness>" + "<fader>" + faderSeconds + ":-1:" + faderEnabled
|
||||
+ ":0:0</fader>" + "<UDN></UDN>";
|
||||
|
@ -268,10 +294,10 @@ public class WemoDimmerHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
action = "ConfigureNightMode";
|
||||
argument = "NightModeConfiguration";
|
||||
String nightModeBrightness = String.valueOf(currentNightModeBrightness);
|
||||
if (command.equals(OnOffType.ON)) {
|
||||
if (OnOffType.ON.equals(command)) {
|
||||
value = "<startTime>0</startTime> \\n<nightMode>1</nightMode> \\n<endTime>23400</endTime> \\n<nightModeBrightness>"
|
||||
+ nightModeBrightness + "</nightModeBrightness> \\n";
|
||||
} else if (command.equals(OnOffType.OFF)) {
|
||||
} else if (OnOffType.OFF.equals(command)) {
|
||||
value = "<startTime>0</startTime> \\n<nightMode>0</nightMode> \\n<endTime>23400</endTime> \\n<nightModeBrightness>"
|
||||
+ nightModeBrightness + "</nightModeBrightness> \\n";
|
||||
}
|
||||
|
@ -338,7 +364,7 @@ public class WemoDimmerHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
switch (variable) {
|
||||
case "BinaryState":
|
||||
if (oldBinaryState == null || !oldBinaryState.equals(value)) {
|
||||
State state = value.equals("0") ? OnOffType.OFF : OnOffType.ON;
|
||||
State state = "0".equals(value) ? OnOffType.OFF : OnOffType.ON;
|
||||
logger.debug("State '{}' for device '{}' received", state, getThing().getUID());
|
||||
updateState(CHANNEL_BRIGHTNESS, state);
|
||||
if (state.equals(OnOffType.OFF)) {
|
||||
|
@ -352,7 +378,7 @@ public class WemoDimmerHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
State newBrightnessState = new PercentType(newBrightnessValue);
|
||||
String binaryState = this.stateMap.get("BinaryState");
|
||||
if (binaryState != null) {
|
||||
if (binaryState.equals("1")) {
|
||||
if ("1".equals(binaryState)) {
|
||||
updateState(CHANNEL_BRIGHTNESS, newBrightnessState);
|
||||
}
|
||||
}
|
||||
|
@ -385,7 +411,7 @@ public class WemoDimmerHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
}
|
||||
break;
|
||||
case "nightMode":
|
||||
State nightModeState = value.equals("0") ? OnOffType.OFF : OnOffType.ON;
|
||||
State nightModeState = "0".equals(value) ? OnOffType.OFF : OnOffType.ON;
|
||||
currentNightModeState = value;
|
||||
logger.debug("nightModeState '{}' for device '{}' received", nightModeState, getThing().getUID());
|
||||
updateState(CHANNEL_NIGHTMODE, nightModeState);
|
||||
|
@ -413,53 +439,50 @@ public class WemoDimmerHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
updateState(CHANNEL_NIGHTMODEBRIGHTNESS, nightModeBrightnessState);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void onSubscription() {
|
||||
if (service.isRegistered(this)) {
|
||||
logger.debug("Checking WeMo GENA subscription for '{}'", this);
|
||||
String subscription = "basicevent1";
|
||||
if (subscriptionState.get(subscription) == null) {
|
||||
logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(), subscription);
|
||||
service.addSubscription(this, subscription, SUBSCRIPTION_DURATION_SECONDS);
|
||||
subscriptionState.put(subscription, true);
|
||||
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());
|
||||
}
|
||||
} else {
|
||||
logger.debug("Setting up WeMo GENA subscription for '{}' FAILED - service.isRegistered(this) is FALSE",
|
||||
this);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void removeSubscription() {
|
||||
logger.debug("Removing WeMo GENA subscription for '{}'", this);
|
||||
if (service.isRegistered(this)) {
|
||||
String subscription = "basicevent1";
|
||||
if (subscriptionState.get(subscription) != null) {
|
||||
logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription);
|
||||
service.removeSubscription(this, subscription);
|
||||
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);
|
||||
}
|
||||
subscriptionState = new HashMap<>();
|
||||
service.unregisterParticipant(this);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void onUpdate() {
|
||||
ScheduledFuture<?> job = refreshJob;
|
||||
if (job == null || job.isCancelled()) {
|
||||
Configuration config = getThing().getConfiguration();
|
||||
int refreshInterval = DEFAULT_REFRESH_INTERVALL_SECONDS;
|
||||
Object refreshConfig = config.get("refresh");
|
||||
if (refreshConfig != null) {
|
||||
refreshInterval = ((BigDecimal) refreshConfig).intValue();
|
||||
}
|
||||
refreshJob = scheduler.scheduleWithFixedDelay(refreshRunnable, 10, refreshInterval, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isUpnpDeviceRegistered() {
|
||||
return service.isRegistered(this);
|
||||
UpnpIOService localService = service;
|
||||
if (localService != null) {
|
||||
return localService.isRegistered(this);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -473,83 +496,84 @@ public class WemoDimmerHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
*
|
||||
*/
|
||||
protected void updateWemoState() {
|
||||
String localHost = getHost();
|
||||
if (localHost.isEmpty()) {
|
||||
logger.error("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());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/config-status.error.missing-url");
|
||||
return;
|
||||
}
|
||||
String action = "GetBinaryState";
|
||||
String variable = null;
|
||||
String actionService = "basicevent";
|
||||
String actionService = BASICACTION;
|
||||
String value = null;
|
||||
String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
|
||||
String content = "<?xml version=\"1.0\"?>"
|
||||
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
|
||||
+ "<s:Body>" + "<u:" + action + " xmlns:u=\"urn:Belkin:service:" + actionService + ":1\">" + "</u:"
|
||||
+ action + ">" + "</s:Body>" + "</s:Envelope>";
|
||||
String content = createStateRequestContent(action, actionService);
|
||||
try {
|
||||
URL descriptorURL = service.getDescriptorURL(this);
|
||||
String wemoURL = getWemoURL(descriptorURL, "basicevent");
|
||||
|
||||
if (wemoURL != null) {
|
||||
String wemoCallResponse = wemoCall.executeCall(wemoURL, soapHeader, content);
|
||||
if (wemoCallResponse != null) {
|
||||
logger.trace("State response '{}' for device '{}' received", wemoCallResponse, getThing().getUID());
|
||||
value = substringBetween(wemoCallResponse, "<BinaryState>", "</BinaryState>");
|
||||
variable = "BinaryState";
|
||||
logger.trace("New state '{}' for device '{}' received", value, getThing().getUID());
|
||||
this.onValueReceived(variable, value, actionService + "1");
|
||||
value = substringBetween(wemoCallResponse, "<brightness>", "</brightness>");
|
||||
variable = "brightness";
|
||||
logger.trace("New brightness '{}' for device '{}' received", value, getThing().getUID());
|
||||
this.onValueReceived(variable, value, actionService + "1");
|
||||
value = substringBetween(wemoCallResponse, "<fader>", "</fader>");
|
||||
variable = "fader";
|
||||
logger.trace("New fader value '{}' for device '{}' received", value, getThing().getUID());
|
||||
this.onValueReceived(variable, value, actionService + "1");
|
||||
String wemoCallResponse = wemoCall.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);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.debug("Failed to get actual state for device '{}': {}", getThing().getUID(), e.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
action = "GetNightModeConfiguration";
|
||||
variable = null;
|
||||
value = null;
|
||||
soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
|
||||
content = "<?xml version=\"1.0\"?>"
|
||||
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
|
||||
+ "<s:Body>" + "<u:" + action + " xmlns:u=\"urn:Belkin:service:" + actionService + ":1\">" + "</u:"
|
||||
+ action + ">" + "</s:Body>" + "</s:Envelope>";
|
||||
content = createStateRequestContent(action, actionService);
|
||||
try {
|
||||
URL descriptorURL = service.getDescriptorURL(this);
|
||||
String wemoURL = getWemoURL(descriptorURL, "basicevent");
|
||||
|
||||
if (wemoURL != null) {
|
||||
String wemoCallResponse = wemoCall.executeCall(wemoURL, soapHeader, content);
|
||||
if (wemoCallResponse != null) {
|
||||
logger.trace("GetNightModeConfiguration response '{}' for device '{}' received", wemoCallResponse,
|
||||
getThing().getUID());
|
||||
value = substringBetween(wemoCallResponse, "<startTime>", "</startTime>");
|
||||
variable = "startTime";
|
||||
logger.trace("New startTime '{}' for device '{}' received", value, getThing().getUID());
|
||||
this.onValueReceived(variable, value, actionService + "1");
|
||||
value = substringBetween(wemoCallResponse, "<endTime>", "</endTime>");
|
||||
variable = "endTime";
|
||||
logger.trace("New endTime '{}' for device '{}' received", value, getThing().getUID());
|
||||
this.onValueReceived(variable, value, actionService + "1");
|
||||
value = substringBetween(wemoCallResponse, "<nightMode>", "</nightMode>");
|
||||
variable = "nightMode";
|
||||
logger.trace("New nightMode state '{}' for device '{}' received", value, getThing().getUID());
|
||||
this.onValueReceived(variable, value, actionService + "1");
|
||||
value = substringBetween(wemoCallResponse, "<nightModeBrightness>", "</nightModeBrightness>");
|
||||
variable = "nightModeBrightness";
|
||||
logger.trace("New nightModeBrightness '{}' for device '{}' received", value, getThing().getUID());
|
||||
this.onValueReceived(variable, value, actionService + "1");
|
||||
String wemoCallResponse = wemoCall.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);
|
||||
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.debug("Failed to get actual NightMode state for device '{}': {}", getThing().getUID(),
|
||||
e.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
public @Nullable State getDateTimeState(String attributeValue) {
|
||||
|
@ -568,6 +592,20 @@ public class WemoDimmerHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
}
|
||||
|
||||
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());
|
||||
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());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/config-status.error.missing-url");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\"";
|
||||
String content = "<?xml version=\"1.0\"?>"
|
||||
|
@ -575,11 +613,12 @@ public class WemoDimmerHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
+ "<s:Body>" + "<u:" + action + " xmlns:u=\"urn:Belkin:service:basicevent:1\">" + "<" + argument
|
||||
+ ">" + value + "</" + argument + ">" + "</u:" + action + ">" + "</s:Body>" + "</s:Envelope>";
|
||||
|
||||
URL descriptorURL = service.getDescriptorURL(this);
|
||||
String wemoURL = getWemoURL(descriptorURL, "basicevent");
|
||||
|
||||
if (wemoURL != null) {
|
||||
wemoCall.executeCall(wemoURL, soapHeader, content);
|
||||
String wemoCallResponse = wemoCall.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 (Exception e) {
|
||||
logger.debug("Failed to set binaryState '{}' for device '{}': {}", value, getThing().getUID(),
|
||||
|
@ -589,26 +628,55 @@ public class WemoDimmerHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
}
|
||||
|
||||
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());
|
||||
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());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/config-status.error.missing-url");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\"";
|
||||
String content = "<?xml version=\"1.0\"?>"
|
||||
+ "<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>";
|
||||
|
||||
URL descriptorURL = service.getDescriptorURL(this);
|
||||
String wemoURL = getWemoURL(descriptorURL, "basicevent");
|
||||
|
||||
if (wemoURL != null) {
|
||||
wemoCall.executeCall(wemoURL, soapHeader, content);
|
||||
String wemoCallResponse = wemoCall.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 (Exception e) {
|
||||
logger.debug("Failed to set binaryState '{}' for device '{}': {}", value, getThing().getUID(),
|
||||
logger.debug("Failed to set timerStart '{}' for device '{}': {}", value, getThing().getUID(),
|
||||
e.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
String localHost = host;
|
||||
if (!localHost.isEmpty()) {
|
||||
return localHost;
|
||||
}
|
||||
UpnpIOService localService = service;
|
||||
if (localService != null) {
|
||||
URL descriptorURL = localService.getDescriptorURL(this);
|
||||
if (descriptorURL != null) {
|
||||
return descriptorURL.getHost();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStatusChanged(boolean status) {
|
||||
}
|
||||
|
|
|
@ -71,32 +71,20 @@ public class WemoHandler extends AbstractWemoHandler implements UpnpIOParticipan
|
|||
.of(THING_TYPE_SOCKET, THING_TYPE_INSIGHT, THING_TYPE_LIGHTSWITCH, THING_TYPE_MOTION)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
private Map<String, Boolean> subscriptionState = new HashMap<>();
|
||||
private final Object upnpLock = new Object();
|
||||
private final Object jobLock = new Object();
|
||||
|
||||
private final Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
protected UpnpIOService service;
|
||||
private @Nullable UpnpIOService service;
|
||||
|
||||
private WemoHttpCall wemoCall;
|
||||
|
||||
private @Nullable ScheduledFuture<?> refreshJob;
|
||||
private Map<String, Boolean> subscriptionState = new HashMap<>();
|
||||
|
||||
private final Runnable refreshRunnable = new Runnable() {
|
||||
private @Nullable ScheduledFuture<?> pollingJob;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (!isUpnpDeviceRegistered()) {
|
||||
logger.debug("WeMo UPnP device {} not yet registered", getUDN());
|
||||
}
|
||||
|
||||
updateWemoState();
|
||||
onSubscription();
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception during poll", e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
};
|
||||
private String host = "";
|
||||
|
||||
public WemoHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
|
||||
super(thing, wemoHttpCaller);
|
||||
|
@ -111,63 +99,100 @@ public class WemoHandler extends AbstractWemoHandler implements UpnpIOParticipan
|
|||
public void initialize() {
|
||||
Configuration configuration = getConfig();
|
||||
|
||||
if (configuration.get("udn") != null) {
|
||||
logger.debug("Initializing WemoHandler for UDN '{}'", configuration.get("udn"));
|
||||
service.registerParticipant(this);
|
||||
onSubscription();
|
||||
onUpdate();
|
||||
if (configuration.get(UDN) != null) {
|
||||
logger.debug("Initializing WemoHandler for UDN '{}'", configuration.get(UDN));
|
||||
UpnpIOService localService = service;
|
||||
if (localService != null) {
|
||||
localService.registerParticipant(this);
|
||||
}
|
||||
host = getHost();
|
||||
pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVALL_SECONDS,
|
||||
TimeUnit.SECONDS);
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/config-status.error.missing-udn");
|
||||
logger.debug("Cannot initalize WemoHandler. UDN not set.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("WeMoHandler disposed.");
|
||||
logger.debug("WemoHandler disposed for thing {}", getThing().getUID());
|
||||
|
||||
ScheduledFuture<?> job = refreshJob;
|
||||
if (job != null && !job.isCancelled()) {
|
||||
ScheduledFuture<?> job = this.pollingJob;
|
||||
if (job != null) {
|
||||
job.cancel(true);
|
||||
}
|
||||
refreshJob = null;
|
||||
this.pollingJob = null;
|
||||
removeSubscription();
|
||||
}
|
||||
|
||||
private void poll() {
|
||||
synchronized (jobLock) {
|
||||
if (pollingJob == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
logger.debug("Polling job");
|
||||
host = getHost();
|
||||
// Check if the Wemo device is set in the UPnP service registry
|
||||
// If not, set the thing state to ONLINE/CONFIG-PENDING and wait for the next poll
|
||||
if (!isUpnpDeviceRegistered()) {
|
||||
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 handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.trace("Command '{}' received for channel '{}'", command, channelUID);
|
||||
|
||||
String localHost = getHost();
|
||||
if (localHost.isEmpty()) {
|
||||
logger.error("Failed to send command '{}' for device '{}': IP address missing", command,
|
||||
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 send command '{}' for device '{}': URL cannot be created", command,
|
||||
getThing().getUID());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/config-status.error.missing-url");
|
||||
return;
|
||||
}
|
||||
if (command instanceof RefreshType) {
|
||||
try {
|
||||
updateWemoState();
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception during poll", e);
|
||||
}
|
||||
} else if (channelUID.getId().equals(CHANNEL_STATE)) {
|
||||
} else if (CHANNEL_STATE.equals(channelUID.getId())) {
|
||||
if (command instanceof OnOffType) {
|
||||
try {
|
||||
String binaryState = null;
|
||||
|
||||
if (command.equals(OnOffType.ON)) {
|
||||
binaryState = "1";
|
||||
} else if (command.equals(OnOffType.OFF)) {
|
||||
binaryState = "0";
|
||||
}
|
||||
|
||||
boolean binaryState = OnOffType.ON.equals(command) ? true : false;
|
||||
String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\"";
|
||||
|
||||
String content = "<?xml version=\"1.0\"?>"
|
||||
+ "<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\">"
|
||||
+ "<BinaryState>" + binaryState + "</BinaryState>" + "</u:SetBinaryState>" + "</s:Body>"
|
||||
+ "</s:Envelope>";
|
||||
|
||||
URL descriptorURL = service.getDescriptorURL(this);
|
||||
String wemoURL = getWemoURL(descriptorURL, "basicevent");
|
||||
|
||||
if (wemoURL != null) {
|
||||
wemoCall.executeCall(wemoURL, soapHeader, content);
|
||||
String content = createBinaryStateContent(binaryState);
|
||||
String wemoCallResponse = wemoCall.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 (Exception e) {
|
||||
logger.error("Failed to send command '{}' for device '{}': {}", command, getThing().getUID(),
|
||||
|
@ -195,19 +220,23 @@ public class WemoHandler extends AbstractWemoHandler implements UpnpIOParticipan
|
|||
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
|
||||
if (!"BinaryState".equals(variable) && !"InsightParams".equals(variable)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String oldValue = this.stateMap.get(variable);
|
||||
if (variable != null && value != null) {
|
||||
this.stateMap.put(variable, value);
|
||||
}
|
||||
|
||||
if (getThing().getThingTypeUID().getId().equals("insight")) {
|
||||
String insightParams = stateMap.get("InsightParams");
|
||||
if (value != null && value.length() > 1) {
|
||||
String insightParams = stateMap.get(variable);
|
||||
|
||||
if (insightParams != null) {
|
||||
String[] splitInsightParams = insightParams.split("\\|");
|
||||
|
||||
if (splitInsightParams[0] != null) {
|
||||
OnOffType binaryState = null;
|
||||
binaryState = splitInsightParams[0].equals("0") ? OnOffType.OFF : OnOffType.ON;
|
||||
OnOffType binaryState = "0".equals(splitInsightParams[0]) ? OnOffType.OFF : OnOffType.ON;
|
||||
logger.trace("New InsightParam binaryState '{}' for device '{}' received", binaryState,
|
||||
getThing().getUID());
|
||||
updateState(CHANNEL_STATE, binaryState);
|
||||
|
@ -278,106 +307,112 @@ public class WemoHandler extends AbstractWemoHandler implements UpnpIOParticipan
|
|||
getThing().getUID());
|
||||
updateState(CHANNEL_ENERGYTOTAL, energyTotal);
|
||||
|
||||
BigDecimal standByLimitMW = new BigDecimal(splitInsightParams[10]);
|
||||
State standByLimit = new QuantityType<>(
|
||||
standByLimitMW.divide(new BigDecimal(1000), 0, RoundingMode.HALF_UP), Units.WATT); // recalculate
|
||||
// mW to W
|
||||
logger.trace("New InsightParam standByLimit '{}' for device '{}' received", standByLimit,
|
||||
getThing().getUID());
|
||||
updateState(CHANNEL_STANDBYLIMIT, standByLimit);
|
||||
if (splitInsightParams.length > 10 && splitInsightParams[10] != null) {
|
||||
BigDecimal standByLimitMW = new BigDecimal(splitInsightParams[10]);
|
||||
State standByLimit = new QuantityType<>(
|
||||
standByLimitMW.divide(new BigDecimal(1000), 0, RoundingMode.HALF_UP), Units.WATT); // recalculate
|
||||
// mW to W
|
||||
logger.trace("New InsightParam standByLimit '{}' for device '{}' received", standByLimit,
|
||||
getThing().getUID());
|
||||
updateState(CHANNEL_STANDBYLIMIT, standByLimit);
|
||||
|
||||
if (currentMW.divide(new BigDecimal(1000), 0, RoundingMode.HALF_UP).intValue() > standByLimitMW
|
||||
.divide(new BigDecimal(1000), 0, RoundingMode.HALF_UP).intValue()) {
|
||||
updateState(CHANNEL_ONSTANDBY, OnOffType.OFF);
|
||||
} else {
|
||||
updateState(CHANNEL_ONSTANDBY, OnOffType.ON);
|
||||
if (currentMW.divide(new BigDecimal(1000), 0, RoundingMode.HALF_UP).intValue() > standByLimitMW
|
||||
.divide(new BigDecimal(1000), 0, RoundingMode.HALF_UP).intValue()) {
|
||||
updateState(CHANNEL_ONSTANDBY, OnOffType.OFF);
|
||||
} else {
|
||||
updateState(CHANNEL_ONSTANDBY, OnOffType.ON);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else if (value != null && value.length() == 1) {
|
||||
String binaryState = stateMap.get("BinaryState");
|
||||
if (binaryState != null) {
|
||||
State state = binaryState.equals("0") ? OnOffType.OFF : OnOffType.ON;
|
||||
logger.debug("State '{}' for device '{}' received", state, getThing().getUID());
|
||||
if (getThing().getThingTypeUID().getId().equals("motion")) {
|
||||
updateState(CHANNEL_MOTIONDETECTION, state);
|
||||
if (state.equals(OnOffType.ON)) {
|
||||
State lastMotionDetected = new DateTimeType();
|
||||
updateState(CHANNEL_LASTMOTIONDETECTED, lastMotionDetected);
|
||||
if (oldValue == null || !oldValue.equals(binaryState)) {
|
||||
State state = "0".equals(binaryState) ? OnOffType.OFF : OnOffType.ON;
|
||||
logger.debug("State '{}' for device '{}' received", state, getThing().getUID());
|
||||
if ("motion".equals(getThing().getThingTypeUID().getId())) {
|
||||
updateState(CHANNEL_MOTIONDETECTION, state);
|
||||
if (OnOffType.ON.equals(state)) {
|
||||
State lastMotionDetected = new DateTimeType();
|
||||
updateState(CHANNEL_LASTMOTIONDETECTED, lastMotionDetected);
|
||||
}
|
||||
} else {
|
||||
updateState(CHANNEL_STATE, state);
|
||||
}
|
||||
} else {
|
||||
updateState(CHANNEL_STATE, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void onSubscription() {
|
||||
if (service.isRegistered(this)) {
|
||||
logger.debug("Checking WeMo GENA subscription for '{}'", this);
|
||||
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 = "basicevent1";
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
String subscription = BASICEVENT;
|
||||
|
||||
if (subscriptionState.get(subscription) == null) {
|
||||
logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(), subscription);
|
||||
service.addSubscription(this, subscription, SUBSCRIPTION_DURATION_SECONDS);
|
||||
subscriptionState.put(subscription, true);
|
||||
}
|
||||
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 (thingTypeUID.equals(THING_TYPE_INSIGHT)) {
|
||||
subscription = "insight1";
|
||||
if (subscriptionState.get(subscription) == null) {
|
||||
logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(),
|
||||
subscription);
|
||||
service.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());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.debug("Setting up WeMo GENA subscription for '{}' FAILED - service.isRegistered(this) is FALSE",
|
||||
this);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void removeSubscription() {
|
||||
logger.debug("Removing WeMo GENA subscription for '{}'", this);
|
||||
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 (service.isRegistered(this)) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
String subscription = "basicevent1";
|
||||
if (subscriptionState.get(subscription) != null) {
|
||||
logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription);
|
||||
localService.removeSubscription(this, subscription);
|
||||
}
|
||||
|
||||
if (subscriptionState.get(subscription) != null) {
|
||||
logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription);
|
||||
service.removeSubscription(this, subscription);
|
||||
}
|
||||
|
||||
if (thingTypeUID.equals(THING_TYPE_INSIGHT)) {
|
||||
subscription = "insight1";
|
||||
if (subscriptionState.get(subscription) != null) {
|
||||
logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription);
|
||||
service.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);
|
||||
}
|
||||
}
|
||||
subscriptionState = new HashMap<>();
|
||||
service.unregisterParticipant(this);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void onUpdate() {
|
||||
ScheduledFuture<?> job = refreshJob;
|
||||
if (job == null || job.isCancelled()) {
|
||||
Configuration config = getThing().getConfiguration();
|
||||
int refreshInterval = DEFAULT_REFRESH_INTERVALL_SECONDS;
|
||||
Object refreshConfig = config.get("refresh");
|
||||
if (refreshConfig != null) {
|
||||
refreshInterval = ((BigDecimal) refreshConfig).intValue();
|
||||
}
|
||||
refreshJob = scheduler.scheduleWithFixedDelay(refreshRunnable, 0, refreshInterval, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isUpnpDeviceRegistered() {
|
||||
return service.isRegistered(this);
|
||||
UpnpIOService localService = service;
|
||||
if (localService != null) {
|
||||
return localService.isRegistered(this);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -385,46 +420,69 @@ public class WemoHandler extends AbstractWemoHandler implements UpnpIOParticipan
|
|||
return (String) this.getThing().getConfiguration().get(UDN);
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
String localHost = host;
|
||||
if (!localHost.isEmpty()) {
|
||||
return localHost;
|
||||
}
|
||||
UpnpIOService localService = service;
|
||||
if (localService != null) {
|
||||
URL descriptorURL = localService.getDescriptorURL(this);
|
||||
if (descriptorURL != null) {
|
||||
return descriptorURL.getHost();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 actionService = BASICACTION;
|
||||
String localhost = getHost();
|
||||
if (localhost.isEmpty()) {
|
||||
logger.error("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 action = "GetBinaryState";
|
||||
String variable = "BinaryState";
|
||||
String actionService = "basicevent";
|
||||
String value = null;
|
||||
|
||||
if (getThing().getThingTypeUID().getId().equals("insight")) {
|
||||
if ("insight".equals(getThing().getThingTypeUID().getId())) {
|
||||
action = "GetInsightParams";
|
||||
variable = "InsightParams";
|
||||
actionService = "insight";
|
||||
actionService = INSIGHTACTION;
|
||||
}
|
||||
String wemoURL = getWemoURL(localhost, actionService);
|
||||
if (wemoURL == null) {
|
||||
logger.error("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 = "<?xml version=\"1.0\"?>"
|
||||
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
|
||||
+ "<s:Body>" + "<u:" + action + " xmlns:u=\"urn:Belkin:service:" + actionService + ":1\">" + "</u:"
|
||||
+ action + ">" + "</s:Body>" + "</s:Envelope>";
|
||||
|
||||
String content = createStateRequestContent(action, actionService);
|
||||
try {
|
||||
URL descriptorURL = service.getDescriptorURL(this);
|
||||
String wemoURL = getWemoURL(descriptorURL, actionService);
|
||||
|
||||
if (wemoURL != null) {
|
||||
String wemoCallResponse = wemoCall.executeCall(wemoURL, soapHeader, content);
|
||||
if (wemoCallResponse != null) {
|
||||
logger.trace("State response '{}' for device '{}' received", wemoCallResponse, getThing().getUID());
|
||||
if (variable.equals("InsightParams")) {
|
||||
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");
|
||||
}
|
||||
String wemoCallResponse = wemoCall.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");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
|
|
@ -17,7 +17,6 @@ import static org.openhab.binding.wemo.internal.WemoUtil.*;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
@ -49,10 +48,8 @@ import org.openhab.core.types.RefreshType;
|
|||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.w3c.dom.CharacterData;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
|
@ -72,22 +69,21 @@ public class WemoHolmesHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
|
||||
private static final int FILTER_LIFE_DAYS = 330;
|
||||
private static final int FILTER_LIFE_MINS = FILTER_LIFE_DAYS * 24 * 60;
|
||||
private final Map<String, Boolean> subscriptionState = new HashMap<>();
|
||||
|
||||
private final Object upnpLock = new Object();
|
||||
private final Object jobLock = new Object();
|
||||
|
||||
private final Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
private UpnpIOService service;
|
||||
private @Nullable UpnpIOService service;
|
||||
|
||||
private WemoHttpCall wemoCall;
|
||||
|
||||
private @Nullable ScheduledFuture<?> refreshJob;
|
||||
private String host = "";
|
||||
|
||||
private final Runnable refreshRunnable = () -> {
|
||||
if (!isUpnpDeviceRegistered()) {
|
||||
logger.debug("WeMo UPnP device {} not yet registered", getUDN());
|
||||
} else {
|
||||
updateWemoState();
|
||||
onSubscription();
|
||||
}
|
||||
};
|
||||
private Map<String, Boolean> subscriptionState = new HashMap<>();
|
||||
|
||||
private @Nullable ScheduledFuture<?> pollingJob;
|
||||
|
||||
public WemoHolmesHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
|
||||
super(thing, wemoHttpCaller);
|
||||
|
@ -102,13 +98,19 @@ public class WemoHolmesHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
public void initialize() {
|
||||
Configuration configuration = getConfig();
|
||||
|
||||
if (configuration.get("udn") != null) {
|
||||
logger.debug("Initializing WemoHolmesHandler for UDN '{}'", configuration.get("udn"));
|
||||
service.registerParticipant(this);
|
||||
onSubscription();
|
||||
onUpdate();
|
||||
if (configuration.get(UDN) != null) {
|
||||
logger.debug("Initializing WemoHolmesHandler for UDN '{}'", configuration.get(UDN));
|
||||
UpnpIOService localService = service;
|
||||
if (localService != null) {
|
||||
localService.registerParticipant(this);
|
||||
}
|
||||
host = getHost();
|
||||
pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVALL_SECONDS,
|
||||
TimeUnit.SECONDS);
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/config-status.error.missing-udn");
|
||||
logger.debug("Cannot initalize WemoHolmesHandler. UDN not set.");
|
||||
}
|
||||
}
|
||||
|
@ -117,18 +119,60 @@ public class WemoHolmesHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
public void dispose() {
|
||||
logger.debug("WemoHolmesHandler disposed.");
|
||||
|
||||
ScheduledFuture<?> job = refreshJob;
|
||||
ScheduledFuture<?> job = this.pollingJob;
|
||||
if (job != null && !job.isCancelled()) {
|
||||
job.cancel(true);
|
||||
}
|
||||
refreshJob = null;
|
||||
this.pollingJob = null;
|
||||
removeSubscription();
|
||||
}
|
||||
|
||||
private void poll() {
|
||||
synchronized (jobLock) {
|
||||
if (pollingJob == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
logger.debug("Polling job");
|
||||
host = getHost();
|
||||
// Check if the Wemo device is set in the UPnP service registry
|
||||
// If not, set the thing state to ONLINE/CONFIG-PENDING and wait for the next poll
|
||||
if (!isUpnpDeviceRegistered()) {
|
||||
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 handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.trace("Command '{}' received for channel '{}'", command, channelUID);
|
||||
|
||||
String localHost = getHost();
|
||||
if (localHost.isEmpty()) {
|
||||
logger.error("Failed to send command '{}' for device '{}': IP address missing", command,
|
||||
getThing().getUID());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/config-status.error.missing-ip");
|
||||
return;
|
||||
}
|
||||
String wemoURL = getWemoURL(localHost, DEVICEACTION);
|
||||
if (wemoURL == null) {
|
||||
logger.error("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");
|
||||
return;
|
||||
}
|
||||
String attribute = null;
|
||||
String value = null;
|
||||
|
||||
|
@ -236,12 +280,12 @@ public class WemoHolmesHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
+ "<attributeList><attribute><name>" + attribute + "</name><value>" + value
|
||||
+ "</value></attribute></attributeList>" + "</u:SetAttributes>" + "</s:Body>"
|
||||
+ "</s:Envelope>";
|
||||
|
||||
URL descriptorURL = service.getDescriptorURL(this);
|
||||
String wemoURL = getWemoURL(descriptorURL, "deviceevent");
|
||||
|
||||
if (wemoURL != null) {
|
||||
wemoCall.executeCall(wemoURL, soapHeader, content);
|
||||
String wemoCallResponse = wemoCall.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) {
|
||||
logger.debug("Failed to send command '{}' for device '{}':", command, getThing().getUID(), e);
|
||||
|
@ -270,54 +314,55 @@ public class WemoHolmesHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
}
|
||||
}
|
||||
|
||||
private synchronized void onSubscription() {
|
||||
if (service.isRegistered(this)) {
|
||||
logger.debug("Checking WeMo GENA subscription for '{}'", this);
|
||||
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 = "basicevent1";
|
||||
String subscription = BASICEVENT;
|
||||
|
||||
if (subscriptionState.get(subscription) == null) {
|
||||
logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(), subscription);
|
||||
service.addSubscription(this, subscription, SUBSCRIPTION_DURATION_SECONDS);
|
||||
subscriptionState.put(subscription, true);
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
logger.debug("Setting up WeMo GENA subscription for '{}' FAILED - service.isRegistered(this) is FALSE",
|
||||
this);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void removeSubscription() {
|
||||
logger.debug("Removing WeMo GENA subscription for '{}'", this);
|
||||
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 (service.isRegistered(this)) {
|
||||
String subscription = "basicevent1";
|
||||
|
||||
if (subscriptionState.get(subscription) != null) {
|
||||
logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription);
|
||||
service.removeSubscription(this, subscription);
|
||||
if (subscriptionState.get(subscription) != null) {
|
||||
logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription);
|
||||
localService.removeSubscription(this, subscription);
|
||||
}
|
||||
subscriptionState.remove(subscription);
|
||||
localService.unregisterParticipant(this);
|
||||
}
|
||||
}
|
||||
|
||||
subscriptionState.remove(subscription);
|
||||
service.unregisterParticipant(this);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void onUpdate() {
|
||||
ScheduledFuture<?> job = refreshJob;
|
||||
if (job == null || job.isCancelled()) {
|
||||
Configuration config = getThing().getConfiguration();
|
||||
int refreshInterval = DEFAULT_REFRESH_INTERVALL_SECONDS;
|
||||
Object refreshConfig = config.get("refresh");
|
||||
refreshInterval = refreshConfig == null ? DEFAULT_REFRESH_INTERVALL_SECONDS
|
||||
: ((BigDecimal) refreshConfig).intValue();
|
||||
refreshJob = scheduler.scheduleWithFixedDelay(refreshRunnable, 0, refreshInterval, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isUpnpDeviceRegistered() {
|
||||
return service.isRegistered(this);
|
||||
UpnpIOService localService = service;
|
||||
if (localService != null) {
|
||||
return localService.isRegistered(this);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -331,164 +376,77 @@ public class WemoHolmesHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
*
|
||||
*/
|
||||
protected void updateWemoState() {
|
||||
String action = "GetAttributes";
|
||||
String actionService = "deviceevent";
|
||||
|
||||
String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
|
||||
String content = "<?xml version=\"1.0\"?>"
|
||||
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
|
||||
+ "<s:Body>" + "<u:" + action + " xmlns:u=\"urn:Belkin:service:" + actionService + ":1\">" + "</u:"
|
||||
+ action + ">" + "</s:Body>" + "</s:Envelope>";
|
||||
|
||||
String localHost = getHost();
|
||||
if (localHost.isEmpty()) {
|
||||
logger.error("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());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/config-status.error.missing-url");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
URL descriptorURL = service.getDescriptorURL(this);
|
||||
String wemoURL = getWemoURL(descriptorURL, actionService);
|
||||
String action = "GetAttributes";
|
||||
String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
|
||||
String content = createStateRequestContent(action, actionService);
|
||||
String wemoCallResponse = wemoCall.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 (wemoURL != null) {
|
||||
String wemoCallResponse = wemoCall.executeCall(wemoURL, soapHeader, content);
|
||||
if (wemoCallResponse != null) {
|
||||
logger.trace("State response '{}' for device '{}' received", wemoCallResponse, getThing().getUID());
|
||||
String stringParser = substringBetween(wemoCallResponse, "<attributeList>", "</attributeList>");
|
||||
|
||||
String stringParser = substringBetween(wemoCallResponse, "<attributeList>", "</attributeList>");
|
||||
// Due to Belkins bad response formatting, we need to run this twice.
|
||||
stringParser = unescapeXml(stringParser);
|
||||
stringParser = unescapeXml(stringParser);
|
||||
|
||||
// Due to Belkins bad response formatting, we need to run this twice.
|
||||
stringParser = unescapeXml(stringParser);
|
||||
stringParser = unescapeXml(stringParser);
|
||||
logger.trace("AirPurifier response '{}' for device '{}' received", stringParser, getThing().getUID());
|
||||
|
||||
logger.trace("AirPurifier response '{}' for device '{}' received", stringParser,
|
||||
getThing().getUID());
|
||||
stringParser = "<data>" + stringParser + "</data>";
|
||||
|
||||
stringParser = "<data>" + stringParser + "</data>";
|
||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
// see
|
||||
// https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
|
||||
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
||||
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
||||
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
|
||||
dbf.setXIncludeAware(false);
|
||||
dbf.setExpandEntityReferences(false);
|
||||
DocumentBuilder db = dbf.newDocumentBuilder();
|
||||
InputSource is = new InputSource();
|
||||
is.setCharacterStream(new StringReader(stringParser));
|
||||
|
||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
// see
|
||||
// https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
|
||||
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
||||
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
||||
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
|
||||
dbf.setXIncludeAware(false);
|
||||
dbf.setExpandEntityReferences(false);
|
||||
DocumentBuilder db = dbf.newDocumentBuilder();
|
||||
InputSource is = new InputSource();
|
||||
is.setCharacterStream(new StringReader(stringParser));
|
||||
Document doc = db.parse(is);
|
||||
NodeList nodes = doc.getElementsByTagName("attribute");
|
||||
|
||||
Document doc = db.parse(is);
|
||||
NodeList nodes = doc.getElementsByTagName("attribute");
|
||||
// iterate the attributes
|
||||
for (int i = 0; i < nodes.getLength(); i++) {
|
||||
Element element = (Element) nodes.item(i);
|
||||
|
||||
// iterate the attributes
|
||||
for (int i = 0; i < nodes.getLength(); i++) {
|
||||
Element element = (Element) nodes.item(i);
|
||||
NodeList deviceIndex = element.getElementsByTagName("name");
|
||||
Element line = (Element) deviceIndex.item(0);
|
||||
String attributeName = getCharacterDataFromElement(line);
|
||||
logger.trace("attributeName: {}", attributeName);
|
||||
|
||||
NodeList deviceIndex = element.getElementsByTagName("name");
|
||||
Element line = (Element) deviceIndex.item(0);
|
||||
String attributeName = getCharacterDataFromElement(line);
|
||||
logger.trace("attributeName: {}", attributeName);
|
||||
NodeList deviceID = element.getElementsByTagName("value");
|
||||
line = (Element) deviceID.item(0);
|
||||
String attributeValue = getCharacterDataFromElement(line);
|
||||
logger.trace("attributeValue: {}", attributeValue);
|
||||
|
||||
NodeList deviceID = element.getElementsByTagName("value");
|
||||
line = (Element) deviceID.item(0);
|
||||
String attributeValue = getCharacterDataFromElement(line);
|
||||
logger.trace("attributeValue: {}", attributeValue);
|
||||
|
||||
State newMode = new StringType();
|
||||
switch (attributeName) {
|
||||
case "Mode":
|
||||
if ("purifier".equals(getThing().getThingTypeUID().getId())) {
|
||||
switch (attributeValue) {
|
||||
case "0":
|
||||
newMode = new StringType("OFF");
|
||||
break;
|
||||
case "1":
|
||||
newMode = new StringType("LOW");
|
||||
break;
|
||||
case "2":
|
||||
newMode = new StringType("MED");
|
||||
break;
|
||||
case "3":
|
||||
newMode = new StringType("HIGH");
|
||||
break;
|
||||
case "4":
|
||||
newMode = new StringType("AUTO");
|
||||
break;
|
||||
}
|
||||
updateState(CHANNEL_PURIFIERMODE, newMode);
|
||||
} else {
|
||||
switch (attributeValue) {
|
||||
case "0":
|
||||
newMode = new StringType("OFF");
|
||||
break;
|
||||
case "1":
|
||||
newMode = new StringType("FROSTPROTECT");
|
||||
break;
|
||||
case "2":
|
||||
newMode = new StringType("HIGH");
|
||||
break;
|
||||
case "3":
|
||||
newMode = new StringType("LOW");
|
||||
break;
|
||||
case "4":
|
||||
newMode = new StringType("ECO");
|
||||
break;
|
||||
}
|
||||
updateState(CHANNEL_HEATERMODE, newMode);
|
||||
}
|
||||
break;
|
||||
case "Ionizer":
|
||||
switch (attributeValue) {
|
||||
case "0":
|
||||
newMode = OnOffType.OFF;
|
||||
break;
|
||||
case "1":
|
||||
newMode = OnOffType.ON;
|
||||
break;
|
||||
}
|
||||
updateState(CHANNEL_IONIZER, newMode);
|
||||
break;
|
||||
case "AirQuality":
|
||||
switch (attributeValue) {
|
||||
case "0":
|
||||
newMode = new StringType("POOR");
|
||||
break;
|
||||
case "1":
|
||||
newMode = new StringType("MODERATE");
|
||||
break;
|
||||
case "2":
|
||||
newMode = new StringType("GOOD");
|
||||
break;
|
||||
}
|
||||
updateState(CHANNEL_AIRQUALITY, newMode);
|
||||
break;
|
||||
case "FilterLife":
|
||||
int filterLife = Integer.valueOf(attributeValue);
|
||||
if ("purifier".equals(getThing().getThingTypeUID().getId())) {
|
||||
filterLife = Math.round((filterLife / FILTER_LIFE_MINS) * 100);
|
||||
} else {
|
||||
filterLife = Math.round((filterLife / 60480) * 100);
|
||||
}
|
||||
updateState(CHANNEL_FILTERLIFE, new PercentType(String.valueOf(filterLife)));
|
||||
break;
|
||||
case "ExpiredFilterTime":
|
||||
switch (attributeValue) {
|
||||
case "0":
|
||||
newMode = OnOffType.OFF;
|
||||
break;
|
||||
case "1":
|
||||
newMode = OnOffType.ON;
|
||||
break;
|
||||
}
|
||||
updateState(CHANNEL_EXPIREDFILTERTIME, newMode);
|
||||
break;
|
||||
case "FilterPresent":
|
||||
switch (attributeValue) {
|
||||
case "0":
|
||||
newMode = OnOffType.OFF;
|
||||
break;
|
||||
case "1":
|
||||
newMode = OnOffType.ON;
|
||||
break;
|
||||
}
|
||||
updateState(CHANNEL_FILTERPRESENT, newMode);
|
||||
break;
|
||||
case "FANMode":
|
||||
State newMode = new StringType();
|
||||
switch (attributeName) {
|
||||
case "Mode":
|
||||
if ("purifier".equals(getThing().getThingTypeUID().getId())) {
|
||||
switch (attributeValue) {
|
||||
case "0":
|
||||
newMode = new StringType("OFF");
|
||||
|
@ -507,48 +465,143 @@ public class WemoHolmesHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -559,13 +612,19 @@ public class WemoHolmesHandler extends AbstractWemoHandler implements UpnpIOPart
|
|||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
public static String getCharacterDataFromElement(Element e) {
|
||||
Node child = e.getFirstChild();
|
||||
if (child instanceof CharacterData) {
|
||||
CharacterData cd = (CharacterData) child;
|
||||
return cd.getData();
|
||||
public String getHost() {
|
||||
String localHost = host;
|
||||
if (!localHost.isEmpty()) {
|
||||
return localHost;
|
||||
}
|
||||
return "?";
|
||||
UpnpIOService localService = service;
|
||||
if (localService != null) {
|
||||
URL descriptorURL = localService.getDescriptorURL(this);
|
||||
if (descriptorURL != null) {
|
||||
return descriptorURL.getHost();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -15,7 +15,6 @@ package org.openhab.binding.wemo.internal.handler;
|
|||
import static org.openhab.binding.wemo.internal.WemoBindingConstants.*;
|
||||
import static org.openhab.binding.wemo.internal.WemoUtil.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
@ -57,15 +56,21 @@ public class WemoLightHandler extends AbstractWemoHandler implements UpnpIOParti
|
|||
|
||||
private Map<String, Boolean> subscriptionState = new HashMap<>();
|
||||
|
||||
private UpnpIOService service;
|
||||
private WemoHttpCall wemoCall;
|
||||
private final Object upnpLock = new Object();
|
||||
private final Object jobLock = new Object();
|
||||
|
||||
private @Nullable WemoBridgeHandler wemoBridgeHandler;
|
||||
|
||||
private @Nullable UpnpIOService service;
|
||||
|
||||
private String host = "";
|
||||
|
||||
private @Nullable String wemoLightID;
|
||||
|
||||
private int currentBrightness;
|
||||
|
||||
private WemoHttpCall wemoCall;
|
||||
|
||||
/**
|
||||
* Set dimming stepsize to 5%
|
||||
*/
|
||||
|
@ -78,31 +83,15 @@ public class WemoLightHandler extends AbstractWemoHandler implements UpnpIOParti
|
|||
*/
|
||||
private static final int DEFAULT_REFRESH_INITIAL_DELAY = 15;
|
||||
|
||||
private @Nullable ScheduledFuture<?> refreshJob;
|
||||
|
||||
private final Runnable refreshRunnable = new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (!isUpnpDeviceRegistered()) {
|
||||
logger.debug("WeMo UPnP device {} not yet registered", getUDN());
|
||||
}
|
||||
|
||||
getDeviceState();
|
||||
onSubscription();
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception during poll", e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
};
|
||||
private @Nullable ScheduledFuture<?> pollingJob;
|
||||
|
||||
public WemoLightHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpcaller) {
|
||||
super(thing, wemoHttpcaller);
|
||||
|
||||
this.service = upnpIOService;
|
||||
this.wemoCall = wemoHttpcaller;
|
||||
|
||||
logger.debug("Creating a WemoLightHandler for thing '{}'", getThing().getUID());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -112,9 +101,14 @@ public class WemoLightHandler extends AbstractWemoHandler implements UpnpIOParti
|
|||
|
||||
final Bridge bridge = getBridge();
|
||||
if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE) {
|
||||
UpnpIOService localService = service;
|
||||
if (localService != null) {
|
||||
localService.registerParticipant(this);
|
||||
}
|
||||
host = getHost();
|
||||
pollingJob = scheduler.scheduleWithFixedDelay(this::poll, DEFAULT_REFRESH_INITIAL_DELAY,
|
||||
DEFAULT_REFRESH_INTERVALL_SECONDS, TimeUnit.SECONDS);
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
onSubscription();
|
||||
onUpdate();
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.BRIDGE_OFFLINE);
|
||||
}
|
||||
|
@ -124,15 +118,13 @@ public class WemoLightHandler extends AbstractWemoHandler implements UpnpIOParti
|
|||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
if (bridgeStatusInfo.getStatus().equals(ThingStatus.ONLINE)) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
onSubscription();
|
||||
onUpdate();
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.BRIDGE_OFFLINE);
|
||||
ScheduledFuture<?> job = refreshJob;
|
||||
ScheduledFuture<?> job = this.pollingJob;
|
||||
if (job != null && !job.isCancelled()) {
|
||||
job.cancel(true);
|
||||
}
|
||||
refreshJob = null;
|
||||
this.pollingJob = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,11 +132,11 @@ public class WemoLightHandler extends AbstractWemoHandler implements UpnpIOParti
|
|||
public void dispose() {
|
||||
logger.debug("WeMoLightHandler disposed.");
|
||||
|
||||
ScheduledFuture<?> job = refreshJob;
|
||||
ScheduledFuture<?> job = this.pollingJob;
|
||||
if (job != null && !job.isCancelled()) {
|
||||
job.cancel(true);
|
||||
}
|
||||
refreshJob = null;
|
||||
this.pollingJob = null;
|
||||
removeSubscription();
|
||||
}
|
||||
|
||||
|
@ -164,8 +156,52 @@ public class WemoLightHandler extends AbstractWemoHandler implements UpnpIOParti
|
|||
return this.wemoBridgeHandler;
|
||||
}
|
||||
|
||||
private void poll() {
|
||||
synchronized (jobLock) {
|
||||
if (pollingJob == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
logger.debug("Polling job");
|
||||
host = getHost();
|
||||
// Check if the Wemo device is set in the UPnP service registry
|
||||
// If not, set the thing state to ONLINE/CONFIG-PENDING and wait for the next poll
|
||||
if (!isUpnpDeviceRegistered()) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
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,
|
||||
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 send command '{}' for device '{}': URL cannot be created", command,
|
||||
getThing().getUID());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/config-status.error.missing-url");
|
||||
return;
|
||||
}
|
||||
if (command instanceof RefreshType) {
|
||||
try {
|
||||
getDeviceState();
|
||||
|
@ -239,27 +275,32 @@ public class WemoLightHandler extends AbstractWemoHandler implements UpnpIOParti
|
|||
break;
|
||||
}
|
||||
try {
|
||||
String soapHeader = "\"urn:Belkin:service:bridge:1#SetDeviceStatus\"";
|
||||
String content = "<?xml version=\"1.0\"?>"
|
||||
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
|
||||
+ "<s:Body>" + "<u:SetDeviceStatus xmlns:u=\"urn:Belkin:service:bridge:1\">"
|
||||
+ "<DeviceStatusList>"
|
||||
+ "<?xml version="1.0" encoding="UTF-8"?><DeviceStatus><DeviceID>"
|
||||
+ wemoLightID
|
||||
+ "</DeviceID><IsGroupAction>NO</IsGroupAction><CapabilityID>"
|
||||
+ capability + "</CapabilityID><CapabilityValue>" + value
|
||||
+ "</CapabilityValue></DeviceStatus>" + "</DeviceStatusList>"
|
||||
+ "</u:SetDeviceStatus>" + "</s:Body>" + "</s:Envelope>";
|
||||
if (capability != null && value != null) {
|
||||
String soapHeader = "\"urn:Belkin:service:bridge:1#SetDeviceStatus\"";
|
||||
String content = "<?xml version=\"1.0\"?>"
|
||||
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
|
||||
+ "<s:Body>" + "<u:SetDeviceStatus xmlns:u=\"urn:Belkin:service:bridge:1\">"
|
||||
+ "<DeviceStatusList>"
|
||||
+ "<?xml version="1.0" encoding="UTF-8"?><DeviceStatus><DeviceID>"
|
||||
+ wemoLightID
|
||||
+ "</DeviceID><IsGroupAction>NO</IsGroupAction><CapabilityID>"
|
||||
+ capability + "</CapabilityID><CapabilityValue>" + value
|
||||
+ "</CapabilityValue></DeviceStatus>" + "</DeviceStatusList>"
|
||||
+ "</u:SetDeviceStatus>" + "</s:Body>" + "</s:Envelope>";
|
||||
|
||||
URL descriptorURL = service.getDescriptorURL(this);
|
||||
String wemoURL = getWemoURL(descriptorURL, "bridge");
|
||||
|
||||
if (wemoURL != null && capability != null && value != null) {
|
||||
String wemoCallResponse = wemoCall.executeCall(wemoURL, soapHeader, content);
|
||||
if (wemoCallResponse != null) {
|
||||
if (capability.equals("10008")) {
|
||||
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 = value.equals("0") ? OnOffType.OFF : OnOffType.ON;
|
||||
binaryState = "0".equals(value) ? OnOffType.OFF : OnOffType.ON;
|
||||
updateState(CHANNEL_STATE, binaryState);
|
||||
}
|
||||
}
|
||||
|
@ -285,7 +326,21 @@ public class WemoLightHandler extends AbstractWemoHandler implements UpnpIOParti
|
|||
* channel states.
|
||||
*/
|
||||
public void getDeviceState() {
|
||||
String localHost = getHost();
|
||||
if (localHost.isEmpty()) {
|
||||
logger.error("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());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/config-status.error.missing-url");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String soapHeader = "\"urn:Belkin:service:bridge:1#GetDeviceStatus\"";
|
||||
String content = "<?xml version=\"1.0\"?>"
|
||||
|
@ -293,31 +348,32 @@ public class WemoLightHandler extends AbstractWemoHandler implements UpnpIOParti
|
|||
+ "<s:Body>" + "<u:GetDeviceStatus xmlns:u=\"urn:Belkin:service:bridge:1\">" + "<DeviceIDs>"
|
||||
+ wemoLightID + "</DeviceIDs>" + "</u:GetDeviceStatus>" + "</s:Body>" + "</s:Envelope>";
|
||||
|
||||
URL descriptorURL = service.getDescriptorURL(this);
|
||||
String wemoURL = getWemoURL(descriptorURL, "bridge");
|
||||
|
||||
if (wemoURL != null) {
|
||||
String wemoCallResponse = wemoCall.executeCall(wemoURL, soapHeader, content);
|
||||
if (wemoCallResponse != null) {
|
||||
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 = splitResponse[0].equals("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;
|
||||
}
|
||||
String wemoCallResponse = wemoCall.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -339,7 +395,7 @@ public class WemoLightHandler extends AbstractWemoHandler implements UpnpIOParti
|
|||
switch (capabilityId) {
|
||||
case "10006":
|
||||
OnOffType binaryState = null;
|
||||
binaryState = newValue.equals("0") ? OnOffType.OFF : OnOffType.ON;
|
||||
binaryState = "0".equals(newValue) ? OnOffType.OFF : OnOffType.ON;
|
||||
updateState(CHANNEL_STATE, binaryState);
|
||||
break;
|
||||
case "10008":
|
||||
|
@ -359,51 +415,66 @@ public class WemoLightHandler extends AbstractWemoHandler implements UpnpIOParti
|
|||
public void onStatusChanged(boolean status) {
|
||||
}
|
||||
|
||||
private synchronized void onSubscription() {
|
||||
if (service.isRegistered(this)) {
|
||||
logger.debug("Checking WeMo GENA subscription for '{}'", this);
|
||||
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);
|
||||
service.addSubscription(this, SUBSCRIPTION, SUBSCRIPTION_DURATION_SECONDS);
|
||||
subscriptionState.put(SUBSCRIPTION, true);
|
||||
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());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.debug("Setting up WeMo GENA subscription for '{}' FAILED - service.isRegistered(this) is FALSE",
|
||||
this);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void removeSubscription() {
|
||||
if (service.isRegistered(this)) {
|
||||
logger.debug("Removing WeMo GENA subscription for '{}'", this);
|
||||
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);
|
||||
service.removeSubscription(this, SUBSCRIPTION);
|
||||
if (subscriptionState.get(SUBSCRIPTION) != null) {
|
||||
logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), SUBSCRIPTION);
|
||||
localService.removeSubscription(this, SUBSCRIPTION);
|
||||
}
|
||||
subscriptionState = new HashMap<>();
|
||||
localService.unregisterParticipant(this);
|
||||
}
|
||||
}
|
||||
|
||||
subscriptionState = new HashMap<>();
|
||||
service.unregisterParticipant(this);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void onUpdate() {
|
||||
ScheduledFuture<?> job = refreshJob;
|
||||
if (job == null || job.isCancelled()) {
|
||||
Configuration config = getThing().getConfiguration();
|
||||
int refreshInterval = DEFAULT_REFRESH_INTERVALL_SECONDS;
|
||||
Object refreshConfig = config.get("refresh");
|
||||
if (refreshConfig != null) {
|
||||
refreshInterval = ((BigDecimal) refreshConfig).intValue();
|
||||
}
|
||||
logger.trace("Start polling job for LightID '{}'", wemoLightID);
|
||||
refreshJob = scheduler.scheduleWithFixedDelay(refreshRunnable, DEFAULT_REFRESH_INITIAL_DELAY,
|
||||
refreshInterval, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isUpnpDeviceRegistered() {
|
||||
return service.isRegistered(this);
|
||||
UpnpIOService localService = service;
|
||||
if (localService != null) {
|
||||
return localService.isRegistered(this);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
String localHost = host;
|
||||
if (!localHost.isEmpty()) {
|
||||
return localHost;
|
||||
}
|
||||
UpnpIOService localService = service;
|
||||
if (localService != null) {
|
||||
URL descriptorURL = localService.getDescriptorURL(this);
|
||||
if (descriptorURL != null) {
|
||||
return descriptorURL.getHost();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ import static org.openhab.binding.wemo.internal.WemoBindingConstants.*;
|
|||
import static org.openhab.binding.wemo.internal.WemoUtil.*;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
@ -43,10 +42,8 @@ import org.openhab.core.types.RefreshType;
|
|||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.w3c.dom.CharacterData;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.InputSource;
|
||||
|
||||
|
@ -63,23 +60,15 @@ public class WemoMakerHandler extends AbstractWemoHandler implements UpnpIOParti
|
|||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_MAKER);
|
||||
|
||||
private UpnpIOService service;
|
||||
private final Object jobLock = new Object();
|
||||
|
||||
private @Nullable UpnpIOService service;
|
||||
|
||||
private WemoHttpCall wemoCall;
|
||||
|
||||
private @Nullable ScheduledFuture<?> refreshJob;
|
||||
private String host = "";
|
||||
|
||||
private final Runnable refreshRunnable = new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
updateWemoState();
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception during poll", e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
};
|
||||
private @Nullable ScheduledFuture<?> pollingJob;
|
||||
|
||||
public WemoMakerHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpcaller) {
|
||||
super(thing, wemoHttpcaller);
|
||||
|
@ -94,11 +83,19 @@ public class WemoMakerHandler extends AbstractWemoHandler implements UpnpIOParti
|
|||
public void initialize() {
|
||||
Configuration configuration = getConfig();
|
||||
|
||||
if (configuration.get("udn") != null) {
|
||||
logger.debug("Initializing WemoMakerHandler for UDN '{}'", configuration.get("udn"));
|
||||
onUpdate();
|
||||
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_INTERVALL_SECONDS,
|
||||
TimeUnit.SECONDS);
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/config-status.error.missing-udn");
|
||||
logger.debug("Cannot initalize WemoMakerHandler. UDN not set.");
|
||||
}
|
||||
}
|
||||
|
@ -107,17 +104,59 @@ public class WemoMakerHandler extends AbstractWemoHandler implements UpnpIOParti
|
|||
public void dispose() {
|
||||
logger.debug("WeMoMakerHandler disposed.");
|
||||
|
||||
ScheduledFuture<?> job = refreshJob;
|
||||
ScheduledFuture<?> job = this.pollingJob;
|
||||
if (job != null && !job.isCancelled()) {
|
||||
job.cancel(true);
|
||||
}
|
||||
refreshJob = null;
|
||||
this.pollingJob = null;
|
||||
UpnpIOService localService = service;
|
||||
if (localService != null) {
|
||||
localService.unregisterParticipant(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void poll() {
|
||||
synchronized (jobLock) {
|
||||
if (pollingJob == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
logger.debug("Polling job");
|
||||
host = getHost();
|
||||
// Check if the Wemo device is set in the UPnP service registry
|
||||
// If not, set the thing state to ONLINE/CONFIG-PENDING and wait for the next poll
|
||||
if (!isUpnpDeviceRegistered()) {
|
||||
logger.debug("UPnP device {} not yet registered", getUDN());
|
||||
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING,
|
||||
"@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
|
||||
return;
|
||||
}
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
updateWemoState();
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception during poll: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.trace("Command '{}' received for channel '{}'", command, channelUID);
|
||||
|
||||
String localHost = getHost();
|
||||
if (localHost.isEmpty()) {
|
||||
logger.error("Failed to send command '{}' for device '{}': IP address missing", command,
|
||||
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 send command '{}' for device '{}': URL cannot be created", command,
|
||||
getThing().getUID());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/config-status.error.missing-url");
|
||||
return;
|
||||
}
|
||||
if (command instanceof RefreshType) {
|
||||
try {
|
||||
updateWemoState();
|
||||
|
@ -127,27 +166,16 @@ public class WemoMakerHandler extends AbstractWemoHandler implements UpnpIOParti
|
|||
} else if (channelUID.getId().equals(CHANNEL_RELAY)) {
|
||||
if (command instanceof OnOffType) {
|
||||
try {
|
||||
String binaryState = null;
|
||||
|
||||
if (command.equals(OnOffType.ON)) {
|
||||
binaryState = "1";
|
||||
} else if (command.equals(OnOffType.OFF)) {
|
||||
binaryState = "0";
|
||||
}
|
||||
|
||||
boolean binaryState = OnOffType.ON.equals(command) ? true : false;
|
||||
String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\"";
|
||||
|
||||
String content = "<?xml version=\"1.0\"?>"
|
||||
+ "<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\">"
|
||||
+ "<BinaryState>" + binaryState + "</BinaryState>" + "</u:SetBinaryState>" + "</s:Body>"
|
||||
+ "</s:Envelope>";
|
||||
|
||||
URL descriptorURL = service.getDescriptorURL(this);
|
||||
String wemoURL = getWemoURL(descriptorURL, "basicevent");
|
||||
|
||||
if (wemoURL != null) {
|
||||
wemoCall.executeCall(wemoURL, soapHeader, content);
|
||||
String content = createBinaryStateContent(binaryState);
|
||||
String wemoCallResponse = wemoCall.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 (Exception e) {
|
||||
logger.error("Failed to send command '{}' for device '{}' ", command, getThing().getUID(), e);
|
||||
|
@ -156,25 +184,12 @@ public class WemoMakerHandler extends AbstractWemoHandler implements UpnpIOParti
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private synchronized void onSubscription() {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private synchronized void removeSubscription() {
|
||||
}
|
||||
|
||||
private synchronized void onUpdate() {
|
||||
ScheduledFuture<?> job = refreshJob;
|
||||
if (job == null || job.isCancelled()) {
|
||||
Configuration config = getThing().getConfiguration();
|
||||
int refreshInterval = DEFAULT_REFRESH_INTERVALL_SECONDS;
|
||||
Object refreshConfig = config.get("refresh");
|
||||
if (refreshConfig != null) {
|
||||
refreshInterval = ((BigDecimal) refreshConfig).intValue();
|
||||
}
|
||||
refreshJob = scheduler.scheduleWithFixedDelay(refreshRunnable, 0, refreshInterval, TimeUnit.SECONDS);
|
||||
private boolean isUpnpDeviceRegistered() {
|
||||
UpnpIOService localService = service;
|
||||
if (localService != null) {
|
||||
return localService.isRegistered(this);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -186,81 +201,91 @@ public class WemoMakerHandler extends AbstractWemoHandler implements UpnpIOParti
|
|||
* The {@link updateWemoState} polls the actual state of a WeMo Maker.
|
||||
*/
|
||||
protected void updateWemoState() {
|
||||
String action = "GetAttributes";
|
||||
String actionService = "deviceevent";
|
||||
|
||||
String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
|
||||
String content = "<?xml version=\"1.0\"?>"
|
||||
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
|
||||
+ "<s:Body>" + "<u:" + action + " xmlns:u=\"urn:Belkin:service:" + actionService + ":1\">" + "</u:"
|
||||
+ action + ">" + "</s:Body>" + "</s:Envelope>";
|
||||
|
||||
String localHost = getHost();
|
||||
if (localHost.isEmpty()) {
|
||||
logger.error("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());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/config-status.error.missing-url");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
URL descriptorURL = service.getDescriptorURL(this);
|
||||
String wemoURL = getWemoURL(descriptorURL, actionService);
|
||||
String action = "GetAttributes";
|
||||
String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
|
||||
String content = createStateRequestContent(action, actionService);
|
||||
String wemoCallResponse = wemoCall.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);
|
||||
|
||||
if (wemoURL != null) {
|
||||
String wemoCallResponse = wemoCall.executeCall(wemoURL, soapHeader, content);
|
||||
if (wemoCallResponse != null) {
|
||||
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());
|
||||
|
||||
// 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>";
|
||||
|
||||
stringParser = "<data>" + stringParser + "</data>";
|
||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
// see
|
||||
// https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
|
||||
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
||||
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
||||
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
|
||||
dbf.setXIncludeAware(false);
|
||||
dbf.setExpandEntityReferences(false);
|
||||
DocumentBuilder db = dbf.newDocumentBuilder();
|
||||
InputSource is = new InputSource();
|
||||
is.setCharacterStream(new StringReader(stringParser));
|
||||
|
||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
// see
|
||||
// https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
|
||||
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
||||
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
||||
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
|
||||
dbf.setXIncludeAware(false);
|
||||
dbf.setExpandEntityReferences(false);
|
||||
DocumentBuilder db = dbf.newDocumentBuilder();
|
||||
InputSource is = new InputSource();
|
||||
is.setCharacterStream(new StringReader(stringParser));
|
||||
Document doc = db.parse(is);
|
||||
NodeList nodes = doc.getElementsByTagName("attribute");
|
||||
|
||||
Document doc = db.parse(is);
|
||||
NodeList nodes = doc.getElementsByTagName("attribute");
|
||||
// iterate the attributes
|
||||
for (int i = 0; i < nodes.getLength(); i++) {
|
||||
Element element = (Element) nodes.item(i);
|
||||
|
||||
// iterate the attributes
|
||||
for (int i = 0; i < nodes.getLength(); i++) {
|
||||
Element element = (Element) nodes.item(i);
|
||||
NodeList deviceIndex = element.getElementsByTagName("name");
|
||||
Element line = (Element) deviceIndex.item(0);
|
||||
String attributeName = getCharacterDataFromElement(line);
|
||||
logger.trace("attributeName: {}", attributeName);
|
||||
|
||||
NodeList deviceIndex = element.getElementsByTagName("name");
|
||||
Element line = (Element) deviceIndex.item(0);
|
||||
String attributeName = getCharacterDataFromElement(line);
|
||||
logger.trace("attributeName: {}", attributeName);
|
||||
NodeList deviceID = element.getElementsByTagName("value");
|
||||
line = (Element) deviceID.item(0);
|
||||
String attributeValue = getCharacterDataFromElement(line);
|
||||
logger.trace("attributeValue: {}", attributeValue);
|
||||
|
||||
NodeList deviceID = element.getElementsByTagName("value");
|
||||
line = (Element) deviceID.item(0);
|
||||
String attributeValue = getCharacterDataFromElement(line);
|
||||
logger.trace("attributeValue: {}", attributeValue);
|
||||
|
||||
switch (attributeName) {
|
||||
case "Switch":
|
||||
State relayState = attributeValue.equals("0") ? OnOffType.OFF : OnOffType.ON;
|
||||
logger.debug("New relayState '{}' for device '{}' received", relayState,
|
||||
getThing().getUID());
|
||||
updateState(CHANNEL_RELAY, relayState);
|
||||
break;
|
||||
case "Sensor":
|
||||
State sensorState = attributeValue.equals("1") ? OnOffType.OFF : OnOffType.ON;
|
||||
logger.debug("New sensorState '{}' for device '{}' received", sensorState,
|
||||
getThing().getUID());
|
||||
updateState(CHANNEL_SENSOR, sensorState);
|
||||
break;
|
||||
}
|
||||
switch (attributeName) {
|
||||
case "Switch":
|
||||
State relayState = "0".equals(attributeValue) ? OnOffType.OFF : OnOffType.ON;
|
||||
logger.debug("New relayState '{}' for device '{}' received", relayState,
|
||||
getThing().getUID());
|
||||
updateState(CHANNEL_RELAY, relayState);
|
||||
break;
|
||||
case "Sensor":
|
||||
State sensorState = "1".equals(attributeValue) ? OnOffType.OFF : OnOffType.ON;
|
||||
logger.debug("New sensorState '{}' for device '{}' received", sensorState,
|
||||
getThing().getUID());
|
||||
updateState(CHANNEL_SENSOR, sensorState);
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to parse attributeList for WeMo Maker '{}'", this.getThing().getUID(), e);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to parse attributeList for WeMo Maker '{}'", this.getThing().getUID(), e);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
@ -268,13 +293,19 @@ public class WemoMakerHandler extends AbstractWemoHandler implements UpnpIOParti
|
|||
}
|
||||
}
|
||||
|
||||
public static String getCharacterDataFromElement(Element e) {
|
||||
Node child = e.getFirstChild();
|
||||
if (child instanceof CharacterData) {
|
||||
CharacterData cd = (CharacterData) child;
|
||||
return cd.getData();
|
||||
public String getHost() {
|
||||
String localHost = host;
|
||||
if (!localHost.isEmpty()) {
|
||||
return localHost;
|
||||
}
|
||||
return "?";
|
||||
UpnpIOService localService = service;
|
||||
if (localService != null) {
|
||||
URL descriptorURL = localService.getDescriptorURL(this);
|
||||
if (descriptorURL != null) {
|
||||
return descriptorURL.getHost();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -124,3 +124,9 @@ channel-type.wemo.timespan.label = Usage Timespan (s)
|
|||
channel-type.wemo.timespan.description = Time used to measure average usage
|
||||
channel-type.wemo.waterLevelReached.label = WaterLevelReached
|
||||
channel-type.wemo.waterLevelReached.description = Indicates if the WeMo Coffee Maker needs to be refilled
|
||||
|
||||
# Config status messages
|
||||
config-status.pending.device-not-registered = UPnP device is not registered yet.
|
||||
config-status.error.missing-udn = UDN of the WeMo device is missing.
|
||||
config-status.error.missing-ip = IP address of the WeMo device is missing.
|
||||
config-status.error.missing-url = URL for the WeMo device cannot be created.
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
<label>Unique Device Name</label>
|
||||
<description>The UDN identifies the WeMo Device</description>
|
||||
</parameter>
|
||||
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
|
@ -176,7 +175,92 @@
|
|||
<label>Unique Device Name</label>
|
||||
<description>The UDN identifies the WeMo Device</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="Crockpot">
|
||||
<label>Crock-Pot Slow Cooker</label>
|
||||
<description>Crock-Pot Smart Slow Cooker with WeMo</description>
|
||||
|
||||
<channels>
|
||||
<channel id="cookMode" typeId="cookMode"/>
|
||||
<channel id="warmCookTime" typeId="warmCookTime"/>
|
||||
<channel id="lowCookTime" typeId="lowCookTime"/>
|
||||
<channel id="highCookTime" typeId="highCookTime"/>
|
||||
<channel id="cookedTime" typeId="cookedTime"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="udn" type="text">
|
||||
<label>Unique Device Name</label>
|
||||
<description>The UDN identifies the WeMo Device</description>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="Purifier">
|
||||
<label>Holmes Air Purifier</label>
|
||||
<description>Holmes Smart Air Purifier with WeMo</description>
|
||||
|
||||
<channels>
|
||||
<channel id="purifierMode" typeId="purifierMode"/>
|
||||
<channel id="airQuality" typeId="airQuality"/>
|
||||
<channel id="ionizer" typeId="ionizer"/>
|
||||
<channel id="filterLife" typeId="filterLife"/>
|
||||
<channel id="expiredFilterTime" typeId="expiredFilterTime"/>
|
||||
<channel id="filterPresent" typeId="filterPresent"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="udn" type="text">
|
||||
<label>Unique Device Name</label>
|
||||
<description>The UDN identifies the WeMo Device</description>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="Humidifier">
|
||||
<label>Holmes Humidifier</label>
|
||||
<description>Holmes Smart Humidifier with WeMo</description>
|
||||
|
||||
<channels>
|
||||
<channel id="humidifierMode" typeId="humidifierMode"/>
|
||||
<channel id="desiredHumidity" typeId="desiredHumidity"/>
|
||||
<channel id="currentHumidity" typeId="currentHumidity"/>
|
||||
<channel id="waterLEvel" typeId="waterLEvel"/>
|
||||
<channel id="filterLife" typeId="filterLife"/>
|
||||
<channel id="expiredFilterTime" typeId="expiredFilterTime"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="udn" type="text">
|
||||
<label>Unique Device Name</label>
|
||||
<description>The UDN identifies the WeMo Device</description>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="Heater">
|
||||
<label>Holmes Heater</label>
|
||||
<description>Holmes Smart Heater with WeMo</description>
|
||||
|
||||
<channels>
|
||||
<channel id="heaterMode" typeId="heaterMode"/>
|
||||
<channel id="currentTemperature" typeId="currentTemperature"/>
|
||||
<channel id="targetTemperature" typeId="targetTemperature"/>
|
||||
<channel id="autoOffTime" typeId="autoOffTime"/>
|
||||
<channel id="heatingRemaining" typeId="heatingRemaining"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="udn" type="text">
|
||||
<label>Unique Device Name</label>
|
||||
<description>The UDN identifies the WeMo Device</description>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
|
@ -418,4 +502,187 @@
|
|||
<description>Allows setting the brightness of Night Mode</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="cookMode">
|
||||
<item-type>String</item-type>
|
||||
<label>Cooking Mode</label>
|
||||
<description>Shows the operation mode of a WeMo CrockPot</description>
|
||||
<state readOnly="false">
|
||||
<options>
|
||||
<option value="OFF">Not cooking</option>
|
||||
<option value="WARM">Warming</option>
|
||||
<option value="LOW">Low cooking</option>
|
||||
<option value="HIGH">High cooking</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="warmCookTime">
|
||||
<item-type>Number</item-type>
|
||||
<label>WarmCookTime</label>
|
||||
<description>Shows the timer settings for warm cooking mode</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="lowCookTime">
|
||||
<item-type>Number</item-type>
|
||||
<label>LowCookTime</label>
|
||||
<description>Shows the timer settings for low cooking mode</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="highCookTime">
|
||||
<item-type>Number</item-type>
|
||||
<label>HighCookTime</label>
|
||||
<description>Shows the timer settings for high cooking mode</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="cookedTime">
|
||||
<item-type>Number</item-type>
|
||||
<label>CookedTime</label>
|
||||
<description>Shows the elapsed cooking time</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="purifierMode">
|
||||
<item-type>String</item-type>
|
||||
<label>Operation Mode</label>
|
||||
<description>Shows the operation mode of a WeMo enabled Holmes Air Purifier</description>
|
||||
<state readOnly="false">
|
||||
<options>
|
||||
<option value="OFF">Not Running</option>
|
||||
<option value="LOW">Running at low level</option>
|
||||
<option value="MED">Running at medium level</option>
|
||||
<option value="HIGH">Running at high level</option>
|
||||
<option value="AUTO">Running in auto mode</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="airQiality">
|
||||
<item-type>String</item-type>
|
||||
<label>Air Quality</label>
|
||||
<description>Shows the air quality measured by a WeMo enabled Holmes Air Purifier</description>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="POOR"></option>
|
||||
<option value="MODERATE"></option>
|
||||
<option value="GOOD"></option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="ionizer">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Ionizer</label>
|
||||
<description>Switches ionization ON or OFF</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="filterLife">
|
||||
<item-type>Number</item-type>
|
||||
<label>Filter Life</label>
|
||||
<description>Shows the remaining lifetime percentage of the air filter</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="filterExpired">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Filter Time expired</label>
|
||||
<description>Indicates whether the air Filter needs to be replaced</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="filterPresent">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Filter is present</label>
|
||||
<description>Indicates whether the air Filter is present</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="humidifierMode">
|
||||
<item-type>String</item-type>
|
||||
<label>Operation Mode</label>
|
||||
<description>Shows the operation mode of a WeMo enabled Holmes Humidifier</description>
|
||||
<state readOnly="false">
|
||||
<options>
|
||||
<option value="OFF">Not Running</option>
|
||||
<option value="MIN">Running at min level</option>
|
||||
<option value="LOW">Running at low level</option>
|
||||
<option value="MED">Running at medium level</option>
|
||||
<option value="HIGH">Running at high level</option>
|
||||
<option value="MAX">Running in max level</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="currentHumidity">
|
||||
<item-type>Number</item-type>
|
||||
<label>Current Humidity</label>
|
||||
<description>Shows the current humidity of a WeMo enabled Holmes Humidifier</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="desiredHumidity">
|
||||
<item-type>Number</item-type>
|
||||
<label>Target Humidity</label>
|
||||
<description>Shows the target humidity of a WeMo enabled Holmes Humidifier</description>
|
||||
<state readOnly="false">
|
||||
<options>
|
||||
<option value="45"></option>
|
||||
<option value="50"></option>
|
||||
<option value="55"></option>
|
||||
<option value="60"></option>
|
||||
<option value="100"></option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="waterLevel">
|
||||
<item-type>String</item-type>
|
||||
<label>Water Level</label>
|
||||
<description>Shows the water levele of a WeMo enabled Holmes Humidifier</description>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="EMPTY"></option>
|
||||
<option value="LOW"></option>
|
||||
<option value="GOOD"></option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="heaterMode">
|
||||
<item-type>String</item-type>
|
||||
<label>Operation Mode</label>
|
||||
<description>Shows the operation mode of a WeMo enabled Heater</description>
|
||||
<state readOnly="false">
|
||||
<options>
|
||||
<option value="OFF">Not Running</option>
|
||||
<option value="FROSTPROTECT">Running at FrostProtect</option>
|
||||
<option value="HIGH">Running at high level</option>
|
||||
<option value="LOW">Running at low level</option>
|
||||
<option value="ECO">Running in Eco mode</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="currentTemperature">
|
||||
<item-type>Number</item-type>
|
||||
<label>Current Temperature</label>
|
||||
<description>Shows the current temperature measured by a WeMo enabled Heater</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="targetTemperature">
|
||||
<item-type>Number</item-type>
|
||||
<label>Target Temperature</label>
|
||||
<description>Shows the target temperature for a WeMo enabled Heater</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="autoOffTime">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Auto OFF Time</label>
|
||||
<description>Time when a WeMo enabled Heater should switch off</description>
|
||||
<state pattern="%1$tR" readOnly="false"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="heatingRemaining">
|
||||
<item-type>Number</item-type>
|
||||
<label>Remaining heating time</label>
|
||||
<description>Shows the target temperature for a WeMo enabled Heater</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
|
|
|
@ -170,6 +170,7 @@ public class WemoLightHandlerOSGiTest extends GenericWemoLightOSGiTestParent {
|
|||
ChannelUID channelUID = new ChannelUID(thingUID, channelID);
|
||||
ThingHandler handler = thing.getHandler();
|
||||
assertNotNull(handler);
|
||||
|
||||
handler.handleCommand(channelUID, command);
|
||||
|
||||
ArgumentCaptor<String> captur = ArgumentCaptor.forClass(String.class);
|
||||
|
|
|
@ -86,6 +86,7 @@ public class WemoMakerHandlerOSGiTest extends GenericWemoOSGiTest {
|
|||
ChannelUID channelUID = new ChannelUID(thing.getUID(), DEFAULT_TEST_CHANNEL);
|
||||
ThingHandler handler = thing.getHandler();
|
||||
assertNotNull(handler);
|
||||
|
||||
handler.handleCommand(channelUID, command);
|
||||
|
||||
ArgumentCaptor<String> captur = ArgumentCaptor.forClass(String.class);
|
||||
|
@ -121,6 +122,7 @@ public class WemoMakerHandlerOSGiTest extends GenericWemoOSGiTest {
|
|||
ChannelUID channelUID = new ChannelUID(thing.getUID(), DEFAULT_TEST_CHANNEL);
|
||||
ThingHandler handler = thing.getHandler();
|
||||
assertNotNull(handler);
|
||||
|
||||
handler.handleCommand(channelUID, command);
|
||||
|
||||
ArgumentCaptor<String> captur = ArgumentCaptor.forClass(String.class);
|
||||
|
|
Loading…
Reference in New Issue