[wemo] Refactor HTTP handling and improve port probing reliability (#18571)

* Refactor HTTP requests Related to #18453

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
This commit is contained in:
Jacob Laursen
2025-04-24 10:55:35 +02:00
committed by GitHub
parent e14e730f05
commit f62e6327b8
18 changed files with 522 additions and 566 deletions
@@ -0,0 +1,128 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.wemo.internal;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.openhab.binding.wemo.internal.exception.WemoException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ApiController} is responsible for interacting with WeMo devices.
*
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
public class ApiController {
private static final String HTTP_CALL_CONTENT_HEADER = "text/xml; charset=utf-8";
private static final int PORT_RANGE_START = 49151;
private static final int PORT_RANGE_END = 49157;
private static final int HTTP_TIMEOUT_MS = 1000;
private final Logger logger = LoggerFactory.getLogger(ApiController.class);
private final HttpClient httpClient;
private @Nullable Integer lastPort;
public ApiController(HttpClient httpClient) {
this.httpClient = httpClient;
}
public String probeAndExecuteCall(String host, @Nullable Integer portFromService, String actionService,
String soapHeader, String soapBody) throws InterruptedException, WemoException {
// Build prioritized list of ports to try
Set<Integer> portsToCheck = new LinkedHashSet<>();
// Last known working port
Integer lastPort = this.lastPort;
if (lastPort != null) {
portsToCheck.add(lastPort);
}
// Port announced via UPnP service
if (portFromService != null) {
portsToCheck.add(portFromService);
}
// Add remaining ports from the defined range
for (int port = PORT_RANGE_START; port <= PORT_RANGE_END; port++) {
portsToCheck.add(port);
}
for (Integer port : portsToCheck) {
String wemoURL = "http://" + host + ":" + port + "/upnp/control/" + actionService + "1";
try {
String response = executeCall(wemoURL, soapHeader, soapBody);
this.lastPort = port;
return response;
} catch (WemoException e) {
Throwable cause = e.getCause();
if (cause == null) {
logger.debug("Request for {} failed: {}", wemoURL, e.getMessage());
} else {
logger.debug("Request for {} failed: {} -> {}", wemoURL, e.getMessage(), cause.getMessage());
}
}
}
this.lastPort = null;
String attemptedPorts = portsToCheck.stream().map(String::valueOf).collect(Collectors.joining(", "));
throw new WemoException(
"Failed to connect to device. All attempts for " + host + " on ports [" + attemptedPorts + "] failed.");
}
public String executeCall(String wemoURL, String soapHeader, String soapBody)
throws InterruptedException, WemoException {
Request request = httpClient.newRequest(wemoURL).timeout(HTTP_TIMEOUT_MS, TimeUnit.MILLISECONDS)
.header(HttpHeader.CONTENT_TYPE, HTTP_CALL_CONTENT_HEADER).header("SOAPACTION", soapHeader)
.method(HttpMethod.POST).content(new StringContentProvider(soapBody));
logger.trace("POST request for URL: '{}', header: '{}', request body: '{}'", wemoURL, soapHeader, soapBody);
try {
ContentResponse response = request.send();
String responseContent = response.getContentAsString();
logger.trace("Response content: '{}'", responseContent);
int status = response.getStatus();
if (!HttpStatus.isSuccess(status)) {
throw new WemoException("The request failed with HTTP error " + status, status);
}
return responseContent;
} catch (TimeoutException e) {
throw new WemoException("The HTTP request timed out", e);
} catch (ExecutionException e) {
throw new WemoException("The HTTP request failed", e);
}
}
}
@@ -112,7 +112,6 @@ public class WemoBindingConstants {
public static final int DEFAULT_REFRESH_INTERVAL_SECONDS = 60;
public static final int SUBSCRIPTION_DURATION_SECONDS = 1800;
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";
@@ -21,6 +21,7 @@ import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.wemo.internal.discovery.WemoLinkDiscoveryService;
import org.openhab.binding.wemo.internal.handler.WemoBridgeHandler;
import org.openhab.binding.wemo.internal.handler.WemoCoffeeHandler;
@@ -32,8 +33,8 @@ import org.openhab.binding.wemo.internal.handler.WemoLightHandler;
import org.openhab.binding.wemo.internal.handler.WemoMakerHandler;
import org.openhab.binding.wemo.internal.handler.WemoMotionHandler;
import org.openhab.binding.wemo.internal.handler.WemoSwitchHandler;
import org.openhab.binding.wemo.internal.http.WemoHttpCall;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.io.transport.upnp.UpnpIOService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
@@ -46,8 +47,6 @@ import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -67,7 +66,7 @@ public class WemoHandlerFactory extends BaseThingHandlerFactory {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = WemoBindingConstants.SUPPORTED_THING_TYPES;
private final UpnpIOService upnpIOService;
private @Nullable WemoHttpCallFactory wemoHttpCallFactory;
private final HttpClient httpClient;
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
@@ -77,17 +76,10 @@ public class WemoHandlerFactory extends BaseThingHandlerFactory {
private final Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
@Activate
public WemoHandlerFactory(final @Reference UpnpIOService upnpIOService) {
public WemoHandlerFactory(final @Reference UpnpIOService upnpIOService,
final @Reference HttpClientFactory httpClientFactory) {
this.upnpIOService = upnpIOService;
}
@Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC)
public void setWemoHttpCallFactory(WemoHttpCallFactory wemoHttpCallFactory) {
this.wemoHttpCallFactory = wemoHttpCallFactory;
}
public void unsetWemoHttpCallFactory(WemoHttpCallFactory wemoHttpCallFactory) {
this.wemoHttpCallFactory = null;
this.httpClient = httpClientFactory.getCommonHttpClient();
}
@Override
@@ -95,59 +87,55 @@ public class WemoHandlerFactory extends BaseThingHandlerFactory {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
logger.debug("Trying to create a handler for ThingType '{}", thingTypeUID);
WemoHttpCallFactory wemoHttpCallFactory = this.wemoHttpCallFactory;
WemoHttpCall wemoHttpcaller = wemoHttpCallFactory == null ? new WemoHttpCall()
: wemoHttpCallFactory.createHttpCall();
if (thingTypeUID.equals(WemoBindingConstants.THING_TYPE_BRIDGE)) {
logger.debug("Creating a WemoBridgeHandler for thing '{}' with UDN '{}'", thing.getUID(),
thing.getConfiguration().get(UDN));
WemoBridgeHandler handler = new WemoBridgeHandler((Bridge) thing);
registerDeviceDiscoveryService(handler, wemoHttpcaller);
registerDeviceDiscoveryService(handler);
return handler;
} else if (WemoBindingConstants.THING_TYPE_INSIGHT.equals(thing.getThingTypeUID())) {
logger.debug("Creating a WemoInsightHandler for thing '{}' with UDN '{}'", thing.getUID(),
thing.getConfiguration().get(UDN));
return new WemoInsightHandler(thing, upnpIOService, wemoHttpcaller);
return new WemoInsightHandler(thing, upnpIOService, httpClient);
} else if (WemoBindingConstants.THING_TYPE_SOCKET.equals(thing.getThingTypeUID())
|| WemoBindingConstants.THING_TYPE_LIGHTSWITCH.equals(thing.getThingTypeUID())) {
logger.debug("Creating a WemoSwitchHandler for thing '{}' with UDN '{}'", thing.getUID(),
thing.getConfiguration().get(UDN));
return new WemoSwitchHandler(thing, upnpIOService, wemoHttpcaller);
return new WemoSwitchHandler(thing, upnpIOService, httpClient);
} else if (WemoBindingConstants.THING_TYPE_MOTION.equals(thing.getThingTypeUID())) {
logger.debug("Creating a WemoMotionHandler for thing '{}' with UDN '{}'", thing.getUID(),
thing.getConfiguration().get(UDN));
return new WemoMotionHandler(thing, upnpIOService, wemoHttpcaller);
return new WemoMotionHandler(thing, upnpIOService, httpClient);
} 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);
return new WemoMakerHandler(thing, upnpIOService, httpClient);
} else if (thingTypeUID.equals(WemoBindingConstants.THING_TYPE_COFFEE)) {
logger.debug("Creating a WemoCoffeeHandler for thing '{}' with UDN '{}'", thing.getUID(),
thing.getConfiguration().get(UDN));
return new WemoCoffeeHandler(thing, upnpIOService, wemoHttpcaller);
return new WemoCoffeeHandler(thing, upnpIOService, httpClient);
} else if (thingTypeUID.equals(WemoBindingConstants.THING_TYPE_DIMMER)) {
logger.debug("Creating a WemoDimmerHandler for thing '{}' with UDN '{}'", thing.getUID(),
thing.getConfiguration().get("udn"));
return new WemoDimmerHandler(thing, upnpIOService, wemoHttpcaller);
return new WemoDimmerHandler(thing, upnpIOService, httpClient);
} else if (thingTypeUID.equals(WemoBindingConstants.THING_TYPE_CROCKPOT)) {
logger.debug("Creating a WemoCockpotHandler for thing '{}' with UDN '{}'", thing.getUID(),
thing.getConfiguration().get("udn"));
return new WemoCrockpotHandler(thing, upnpIOService, wemoHttpcaller);
return new WemoCrockpotHandler(thing, upnpIOService, httpClient);
} else if (thingTypeUID.equals(WemoBindingConstants.THING_TYPE_PURIFIER)) {
logger.debug("Creating a WemoHolmesHandler for thing '{}' with UDN '{}'", thing.getUID(),
thing.getConfiguration().get("udn"));
return new WemoHolmesHandler(thing, upnpIOService, wemoHttpcaller);
return new WemoHolmesHandler(thing, upnpIOService, httpClient);
} else if (thingTypeUID.equals(WemoBindingConstants.THING_TYPE_HUMIDIFIER)) {
logger.debug("Creating a WemoHolmesHandler for thing '{}' with UDN '{}'", thing.getUID(),
thing.getConfiguration().get("udn"));
return new WemoHolmesHandler(thing, upnpIOService, wemoHttpcaller);
return new WemoHolmesHandler(thing, upnpIOService, httpClient);
} else if (thingTypeUID.equals(WemoBindingConstants.THING_TYPE_HEATER)) {
logger.debug("Creating a WemoHolmesHandler for thing '{}' with UDN '{}'", thing.getUID(),
thing.getConfiguration().get("udn"));
return new WemoHolmesHandler(thing, upnpIOService, wemoHttpcaller);
return new WemoHolmesHandler(thing, upnpIOService, httpClient);
} else if (thingTypeUID.equals(WemoBindingConstants.THING_TYPE_MZ100)) {
return new WemoLightHandler(thing, upnpIOService, wemoHttpcaller);
return new WemoLightHandler(thing, upnpIOService, httpClient);
} else {
logger.warn("ThingHandler not found for {}", thingTypeUID);
return null;
@@ -164,10 +152,9 @@ public class WemoHandlerFactory extends BaseThingHandlerFactory {
}
}
private synchronized void registerDeviceDiscoveryService(WemoBridgeHandler wemoBridgeHandler,
WemoHttpCall wemoHttpCaller) {
private synchronized void registerDeviceDiscoveryService(WemoBridgeHandler wemoBridgeHandler) {
WemoLinkDiscoveryService discoveryService = new WemoLinkDiscoveryService(wemoBridgeHandler, upnpIOService,
wemoHttpCaller);
httpClient);
this.discoveryServiceRegs.put(wemoBridgeHandler.getThing().getUID(),
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
}
@@ -28,8 +28,9 @@ import javax.xml.parsers.DocumentBuilderFactory;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.wemo.internal.ApiController;
import org.openhab.binding.wemo.internal.handler.WemoBridgeHandler;
import org.openhab.binding.wemo.internal.http.WemoHttpCall;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
@@ -56,51 +57,25 @@ import org.xml.sax.InputSource;
@NonNullByDefault
public class WemoLinkDiscoveryService extends AbstractDiscoveryService implements UpnpIOParticipant {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_MZ100);
private static final String NORMALIZE_ID_REGEX = "[^a-zA-Z0-9_]";
private static final int DISCOVERY_TIMEOUT_SECONDS = 20;
private static final int SCAN_INTERVAL_SECONDS = 120;
private final Logger logger = LoggerFactory.getLogger(WemoLinkDiscoveryService.class);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_MZ100);
public static final String NORMALIZE_ID_REGEX = "[^a-zA-Z0-9_]";
/**
* Maximum time to search for devices in seconds.
*/
private static final int SEARCH_TIME = 20;
/**
* Scan interval for scanning job in seconds.
*/
private static final int SCAN_INTERVAL = 120;
/**
* The handler for WeMo Link bridge
*/
private final WemoBridgeHandler wemoBridgeHandler;
/**
* Job which will do the background scanning
*/
private final WemoLinkScan scanningRunnable;
private final UpnpIOService service;
private final ApiController apiController;
/**
* Schedule for scanning
*/
private @Nullable ScheduledFuture<?> scanningJob;
/**
* The Upnp service
*/
private UpnpIOService service;
private final WemoHttpCall wemoHttpCaller;
public WemoLinkDiscoveryService(WemoBridgeHandler wemoBridgeHandler, UpnpIOService upnpIOService,
WemoHttpCall wemoHttpCaller) {
super(SEARCH_TIME);
public WemoLinkDiscoveryService(final WemoBridgeHandler wemoBridgeHandler, final UpnpIOService upnpIOService,
final HttpClient httpClient) {
super(DISCOVERY_TIMEOUT_SECONDS);
this.service = upnpIOService;
this.wemoBridgeHandler = wemoBridgeHandler;
this.wemoHttpCaller = wemoHttpCaller;
this.apiController = new ApiController(httpClient);
this.scanningRunnable = new WemoLinkScan();
this.activate(null);
@@ -139,7 +114,7 @@ public class WemoLinkDiscoveryService extends AbstractDiscoveryService implement
String deviceURL = substringBefore(descriptorURL.toString(), "/setup.xml");
String wemoURL = deviceURL + "/upnp/control/bridge1";
String endDeviceRequest = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
String endDeviceRequest = apiController.executeCall(wemoURL, soapHeader, content);
logger.trace("endDeviceRequest answered '{}'", endDeviceRequest);
@@ -246,7 +221,7 @@ public class WemoLinkDiscoveryService extends AbstractDiscoveryService implement
if (job == null || job.isCancelled()) {
this.scanningJob = scheduler.scheduleWithFixedDelay(this.scanningRunnable,
LINK_DISCOVERY_SERVICE_INITIAL_DELAY, SCAN_INTERVAL, TimeUnit.SECONDS);
LINK_DISCOVERY_SERVICE_INITIAL_DELAY, SCAN_INTERVAL_SECONDS, TimeUnit.SECONDS);
} else {
logger.trace("scanningJob active");
}
@@ -10,18 +10,23 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.wemo.internal;
package org.openhab.binding.wemo.internal.exception;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.wemo.internal.http.WemoHttpCall;
/**
* {@link WemoHttpCallFactory} creates {@link WemoHttpCall}s.
* {@link MissingHostException} is thrown when attempting to communicate
* with a WeMo device while the host/IP address has not yet been obtained
* through UPnP.
*
* @author Wouter Born - Initial contribution
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
public interface WemoHttpCallFactory {
public class MissingHostException extends WemoException {
WemoHttpCall createHttpCall();
private static final long serialVersionUID = 1L;
public MissingHostException(String message) {
super(message);
}
}
@@ -0,0 +1,49 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.wemo.internal.exception;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* {@link WemoException} is thrown in case of an error communicating
* with a WeMo device.
*
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
public class WemoException extends Exception {
private static final long serialVersionUID = 1L;
private int httpStatus = 0;
public WemoException(String message) {
super(message);
}
public WemoException(Throwable cause) {
super(cause);
}
public WemoException(String message, Throwable cause) {
super(message, cause);
}
public WemoException(String message, int httpStatus) {
super(message);
this.httpStatus = httpStatus;
}
public int getHttpStatus() {
return httpStatus;
}
}
@@ -14,19 +14,17 @@ package org.openhab.binding.wemo.internal.handler;
import java.net.URL;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.wemo.internal.ApiController;
import org.openhab.binding.wemo.internal.WemoBindingConstants;
import org.openhab.binding.wemo.internal.http.WemoHttpCall;
import org.openhab.binding.wemo.internal.exception.MissingHostException;
import org.openhab.binding.wemo.internal.exception.WemoException;
import org.openhab.core.io.transport.upnp.UpnpIOParticipant;
import org.openhab.core.io.transport.upnp.UpnpIOService;
import org.openhab.core.thing.ChannelUID;
@@ -47,29 +45,24 @@ import org.slf4j.LoggerFactory;
@NonNullByDefault
public abstract class WemoBaseThingHandler extends BaseThingHandler implements UpnpIOParticipant {
private static final int PORT_RANGE_START = 49151;
private static final int PORT_RANGE_END = 49157;
private final Logger logger = LoggerFactory.getLogger(WemoBaseThingHandler.class);
private final UpnpIOService service;
private final ApiController apiController;
private final Object upnpLock = new Object();
protected WemoHttpCall wemoHttpCaller;
private @Nullable String host;
private Map<String, Instant> subscriptions = new ConcurrentHashMap<>();
public WemoBaseThingHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
public WemoBaseThingHandler(final Thing thing, final UpnpIOService upnpIOService, final HttpClient httpClient) {
super(thing);
this.apiController = new ApiController(httpClient);
this.service = upnpIOService;
this.wemoHttpCaller = wemoHttpCaller;
}
@Override
public void initialize() {
logger.debug("Registering UPnP participant for {}", getThing().getUID());
service.registerParticipant(this);
initializeHost();
}
@Override
@@ -152,57 +145,6 @@ public abstract class WemoBaseThingHandler extends BaseThingHandler implements U
}
}
public @Nullable String getWemoURL(String actionService) {
String host = getHost();
if (host == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
return null;
}
int port = scanForPort(host);
if (port == 0) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-url");
return null;
}
return "http://" + host + ":" + port + "/upnp/control/" + actionService + "1";
}
private @Nullable String getHost() {
if (host != null) {
return host;
}
initializeHost();
return host;
}
private void initializeHost() {
host = getHostFromService();
}
private int scanForPort(String host) {
Integer portFromService = getPortFromService();
List<Integer> portsToCheck = new ArrayList<>(PORT_RANGE_END - PORT_RANGE_START + 1);
Stream<Integer> portRange = IntStream.rangeClosed(PORT_RANGE_START, PORT_RANGE_END).boxed();
if (portFromService != null) {
portsToCheck.add(portFromService);
portRange = portRange.filter(p -> p.intValue() != portFromService);
}
portsToCheck.addAll(portRange.collect(Collectors.toList()));
int port = 0;
for (Integer portCheck : portsToCheck) {
String urlProbe = "http://" + host + ":" + portCheck;
logger.trace("Probing {} to find port", urlProbe);
if (!wemoHttpCaller.probeURL(urlProbe)) {
continue;
}
port = portCheck;
logger.trace("Successfully detected port {}", port);
break;
}
return port;
}
private @Nullable String getHostFromService() {
URL descriptorURL = service.getDescriptorURL(this);
if (descriptorURL != null) {
@@ -221,4 +163,24 @@ public abstract class WemoBaseThingHandler extends BaseThingHandler implements U
}
return null;
}
protected String probeAndExecuteCall(String actionService, String soapHeader, String soapBody)
throws WemoException {
String host = this.host;
if (host == null) {
host = this.host = getHostFromService();
}
if (host == null) {
throw new MissingHostException("Host/IP address is missing");
}
try {
return apiController.probeAndExecuteCall(host, getPortFromService(), actionService, soapHeader, soapBody);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new WemoException("Interrupted", e);
}
}
}
@@ -26,7 +26,9 @@ import javax.xml.parsers.DocumentBuilderFactory;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.wemo.internal.http.WemoHttpCall;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.wemo.internal.exception.MissingHostException;
import org.openhab.binding.wemo.internal.exception.WemoException;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.io.transport.upnp.UpnpIOService;
import org.openhab.core.library.types.DateTimeType;
@@ -66,8 +68,8 @@ public class WemoCoffeeHandler extends WemoBaseThingHandler {
private @Nullable ScheduledFuture<?> pollingJob;
public WemoCoffeeHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
super(thing, upnpIOService, wemoHttpCaller);
public WemoCoffeeHandler(final Thing thing, final UpnpIOService upnpIOService, final HttpClient httpClient) {
super(thing, upnpIOService, httpClient);
logger.debug("Creating a WemoCoffeeHandler for thing '{}'", getThing().getUID());
}
@@ -91,10 +93,9 @@ public class WemoCoffeeHandler extends WemoBaseThingHandler {
@Override
public void dispose() {
logger.debug("WemoCoffeeHandler disposed.");
ScheduledFuture<?> job = this.pollingJob;
if (job != null && !job.isCancelled()) {
job.cancel(true);
ScheduledFuture<?> pollingJob = this.pollingJob;
if (pollingJob != null) {
pollingJob.cancel(true);
}
this.pollingJob = null;
super.dispose();
@@ -102,78 +103,61 @@ public class WemoCoffeeHandler extends WemoBaseThingHandler {
private void poll() {
synchronized (jobLock) {
if (pollingJob == null) {
logger.debug("Polling job for thing {}", getThing().getUID());
// Check if the Wemo device is set in the UPnP service registry
if (!isUpnpDeviceRegistered()) {
logger.debug("UPnP device {} not yet registered", getUDN());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
"@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
return;
}
try {
logger.debug("Polling job");
// Check if the Wemo device is set in the UPnP service registry
if (!isUpnpDeviceRegistered()) {
logger.debug("UPnP device {} not yet registered", getUDN());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
"@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
return;
}
updateWemoState();
} catch (Exception e) {
logger.debug("Exception during poll: {}", e.getMessage(), e);
}
updateWemoState();
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
String wemoURL = getWemoURL(BASICACTION);
if (wemoURL == null) {
logger.debug("Failed to send command '{}' for device '{}': URL cannot be created", command,
getThing().getUID());
return;
}
if (command instanceof RefreshType) {
try {
updateWemoState();
} catch (Exception e) {
logger.debug("Exception during poll", e);
}
updateWemoState();
} else if (channelUID.getId().equals(CHANNEL_STATE)) {
if (command instanceof OnOffType) {
if (command.equals(OnOffType.ON)) {
try {
String soapHeader = "\"urn:Belkin:service:deviceevent:1#SetAttributes\"";
if (command.equals(OnOffType.ON)) {
try {
String soapHeader = "\"urn:Belkin:service:deviceevent:1#SetAttributes\"";
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:SetAttributes xmlns:u="urn:Belkin:service:deviceevent:1">\
<attributeList>&lt;attribute&gt;&lt;name&gt;Brewed&lt;/name&gt;&lt;value&gt;NULL&lt;/value&gt;&lt;/attribute&gt;\
&lt;attribute&gt;&lt;name&gt;LastCleaned&lt;/name&gt;&lt;value&gt;NULL&lt;/value&gt;&lt;/attribute&gt;&lt;attribute&gt;\
&lt;name&gt;ModeTime&lt;/name&gt;&lt;value&gt;NULL&lt;/value&gt;&lt;/attribute&gt;&lt;attribute&gt;&lt;name&gt;Brewing&lt;/name&gt;\
&lt;value&gt;NULL&lt;/value&gt;&lt;/attribute&gt;&lt;attribute&gt;&lt;name&gt;TimeRemaining&lt;/name&gt;&lt;value&gt;NULL&lt;/value&gt;\
&lt;/attribute&gt;&lt;attribute&gt;&lt;name&gt;WaterLevelReached&lt;/name&gt;&lt;value&gt;NULL&lt;/value&gt;&lt;/attribute&gt;&lt;\
attribute&gt;&lt;name&gt;Mode&lt;/name&gt;&lt;value&gt;4&lt;/value&gt;&lt;/attribute&gt;&lt;attribute&gt;&lt;name&gt;CleanAdvise&lt;/name&gt;\
&lt;value&gt;NULL&lt;/value&gt;&lt;/attribute&gt;&lt;attribute&gt;&lt;name&gt;FilterAdvise&lt;/name&gt;&lt;value&gt;NULL&lt;/value&gt;&lt;/attribute&gt;\
&lt;attribute&gt;&lt;name&gt;Cleaning&lt;/name&gt;&lt;value&gt;NULL&lt;/value&gt;&lt;/attribute&gt;</attributeList>\
</u:SetAttributes>\
</s:Body>\
</s:Envelope>\
""";
String 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:SetAttributes xmlns:u="urn:Belkin:service:deviceevent:1">\
<attributeList>&lt;attribute&gt;&lt;name&gt;Brewed&lt;/name&gt;&lt;value&gt;NULL&lt;/value&gt;&lt;/attribute&gt;\
&lt;attribute&gt;&lt;name&gt;LastCleaned&lt;/name&gt;&lt;value&gt;NULL&lt;/value&gt;&lt;/attribute&gt;&lt;attribute&gt;\
&lt;name&gt;ModeTime&lt;/name&gt;&lt;value&gt;NULL&lt;/value&gt;&lt;/attribute&gt;&lt;attribute&gt;&lt;name&gt;Brewing&lt;/name&gt;\
&lt;value&gt;NULL&lt;/value&gt;&lt;/attribute&gt;&lt;attribute&gt;&lt;name&gt;TimeRemaining&lt;/name&gt;&lt;value&gt;NULL&lt;/value&gt;\
&lt;/attribute&gt;&lt;attribute&gt;&lt;name&gt;WaterLevelReached&lt;/name&gt;&lt;value&gt;NULL&lt;/value&gt;&lt;/attribute&gt;&lt;\
attribute&gt;&lt;name&gt;Mode&lt;/name&gt;&lt;value&gt;4&lt;/value&gt;&lt;/attribute&gt;&lt;attribute&gt;&lt;name&gt;CleanAdvise&lt;/name&gt;\
&lt;value&gt;NULL&lt;/value&gt;&lt;/attribute&gt;&lt;attribute&gt;&lt;name&gt;FilterAdvise&lt;/name&gt;&lt;value&gt;NULL&lt;/value&gt;&lt;/attribute&gt;\
&lt;attribute&gt;&lt;name&gt;Cleaning&lt;/name&gt;&lt;value&gt;NULL&lt;/value&gt;&lt;/attribute&gt;</attributeList>\
</u:SetAttributes>\
</s:Body>\
</s:Envelope>\
""";
wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
updateState(CHANNEL_STATE, OnOffType.ON);
State newMode = new StringType("Brewing");
updateState(CHANNEL_COFFEE_MODE, newMode);
updateStatus(ThingStatus.ONLINE);
} catch (Exception e) {
logger.warn("Failed to send command '{}' for device '{}': {}", command, getThing().getUID(),
e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
}
probeAndExecuteCall(BASICACTION, soapHeader, content);
updateState(CHANNEL_STATE, OnOffType.ON);
State newMode = new StringType("Brewing");
updateState(CHANNEL_COFFEE_MODE, newMode);
updateStatus(ThingStatus.ONLINE);
} catch (MissingHostException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
} catch (WemoException e) {
logger.warn("Failed to send command '{}' for thing '{}': {}", command, getThing().getUID(),
e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
// if command.equals(OnOffType.OFF) we do nothing because WeMo Coffee Maker cannot be switched
// off remotely
}
// if command.equals(OnOffType.OFF) we do nothing because WeMo Coffee Maker cannot be switched
// off remotely
}
}
@@ -187,16 +171,11 @@ public class WemoCoffeeHandler extends WemoBaseThingHandler {
*/
protected void updateWemoState() {
String actionService = DEVICEACTION;
String wemoURL = getWemoURL(actionService);
if (wemoURL == null) {
logger.debug("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID());
return;
}
try {
String action = "GetAttributes";
String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
String content = createStateRequestContent(action, actionService);
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
String wemoCallResponse = probeAndExecuteCall(actionService, soapHeader, content);
try {
String stringParser = substringBetween(wemoCallResponse, "<attributeList>", "</attributeList>");
@@ -204,7 +183,7 @@ public class WemoCoffeeHandler extends WemoBaseThingHandler {
stringParser = unescapeXml(stringParser);
stringParser = unescapeXml(stringParser);
logger.trace("CoffeeMaker response '{}' for device '{}' received", stringParser, getThing().getUID());
logger.trace("CoffeeMaker response '{}' for thing '{}' received", stringParser, getThing().getUID());
stringParser = "<data>" + stringParser + "</data>";
@@ -328,8 +307,11 @@ public class WemoCoffeeHandler extends WemoBaseThingHandler {
} catch (Exception e) {
logger.warn("Failed to parse attributeList for WeMo CoffeMaker '{}'", this.getThing().getUID(), e);
}
} catch (Exception e) {
logger.warn("Failed to get attributes for device '{}'", getThing().getUID(), e);
} catch (MissingHostException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
} catch (WemoException e) {
logger.debug("Failed to get attributes for thing '{}'", getThing().getUID(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
@@ -339,7 +321,7 @@ public class WemoCoffeeHandler extends WemoBaseThingHandler {
try {
value = Long.parseLong(attributeValue);
} catch (NumberFormatException e) {
logger.warn("Unable to parse attributeValue '{}' for device '{}'; expected long", attributeValue,
logger.warn("Unable to parse attributeValue '{}' for thing '{}'; expected long", attributeValue,
getThing().getUID());
return null;
}
@@ -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.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -25,7 +24,9 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.wemo.internal.http.WemoHttpCall;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.wemo.internal.exception.MissingHostException;
import org.openhab.binding.wemo.internal.exception.WemoException;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.io.transport.upnp.UpnpIOService;
import org.openhab.core.library.types.DecimalType;
@@ -60,8 +61,8 @@ public class WemoCrockpotHandler extends WemoBaseThingHandler {
private @Nullable ScheduledFuture<?> pollingJob;
public WemoCrockpotHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
super(thing, upnpIOService, wemoHttpCaller);
public WemoCrockpotHandler(final Thing thing, final UpnpIOService upnpIOService, final HttpClient httpClient) {
super(thing, upnpIOService, httpClient);
logger.debug("Creating a WemoCrockpotHandler for thing '{}'", getThing().getUID());
}
@@ -85,10 +86,9 @@ public class WemoCrockpotHandler extends WemoBaseThingHandler {
@Override
public void dispose() {
logger.debug("WeMoCrockpotHandler disposed.");
ScheduledFuture<?> job = this.pollingJob;
if (job != null && !job.isCancelled()) {
job.cancel(true);
ScheduledFuture<?> pollingJob = this.pollingJob;
if (pollingJob != null) {
pollingJob.cancel(true);
}
this.pollingJob = null;
super.dispose();
@@ -96,33 +96,20 @@ public class WemoCrockpotHandler extends WemoBaseThingHandler {
private void poll() {
synchronized (jobLock) {
if (pollingJob == null) {
logger.debug("Polling job for thing {}", getThing().getUID());
// Check if the Wemo device is set in the UPnP service registry
if (!isUpnpDeviceRegistered()) {
logger.debug("UPnP device {} not yet registered", getUDN());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
"@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
return;
}
try {
logger.debug("Polling job");
// Check if the Wemo device is set in the UPnP service registry
if (!isUpnpDeviceRegistered()) {
logger.debug("UPnP device {} not yet registered", getUDN());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
"@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
return;
}
updateWemoState();
} catch (Exception e) {
logger.debug("Exception during poll: {}", e.getMessage(), e);
}
updateWemoState();
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
String wemoURL = getWemoURL(BASICACTION);
if (wemoURL == null) {
logger.debug("Failed to send command '{}' for device '{}': URL cannot be created", command,
getThing().getUID());
return;
}
String mode = "0";
String time = null;
@@ -156,10 +143,13 @@ public class WemoCrockpotHandler extends WemoBaseThingHandler {
"""
+ mode + "</mode>" + "<time>" + time + "</time>" + "</u:SetCrockpotState>" + "</s:Body>"
+ "</s:Envelope>";
wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
probeAndExecuteCall(BASICACTION, soapHeader, content);
updateStatus(ThingStatus.ONLINE);
} catch (IOException e) {
logger.debug("Failed to send command '{}' for device '{}':", command, getThing().getUID(), e);
} catch (MissingHostException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
} catch (WemoException e) {
logger.warn("Failed to send command '{}' for thing '{}':", command, getThing().getUID(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
@@ -168,7 +158,7 @@ public class WemoCrockpotHandler extends WemoBaseThingHandler {
@Override
public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) {
logger.debug("Received pair '{}':'{}' (service '{}') for thing '{}'", variable, value, service,
this.getThing().getUID());
getThing().getUID());
updateStatus(ThingStatus.ONLINE);
if (variable != null && value != null) {
@@ -183,16 +173,11 @@ public class WemoCrockpotHandler extends WemoBaseThingHandler {
*/
protected void updateWemoState() {
String actionService = BASICEVENT;
String wemoURL = getWemoURL(actionService);
if (wemoURL == null) {
logger.warn("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID());
return;
}
try {
String action = "GetCrockpotState";
String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
String content = createStateRequestContent(action, actionService);
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
String wemoCallResponse = probeAndExecuteCall(actionService, soapHeader, content);
String mode = substringBetween(wemoCallResponse, "<mode>", "</mode>");
String time = substringBetween(wemoCallResponse, "<time>", "</time>");
String coockedTime = substringBetween(wemoCallResponse, "<coockedTime>", "</coockedTime>");
@@ -222,8 +207,11 @@ public class WemoCrockpotHandler extends WemoBaseThingHandler {
updateState(CHANNEL_COOK_MODE, newMode);
updateState(CHANNEL_COOKED_TIME, newCoockedTime);
updateStatus(ThingStatus.ONLINE);
} catch (IOException e) {
logger.debug("Failed to get actual state for device '{}': {}", getThing().getUID(), e.getMessage(), e);
} catch (MissingHostException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
} catch (WemoException e) {
logger.debug("Failed to get actual state for thing '{}': {}", getThing().getUID(), e.getMessage(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
@@ -25,7 +25,9 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.wemo.internal.http.WemoHttpCall;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.wemo.internal.exception.MissingHostException;
import org.openhab.binding.wemo.internal.exception.WemoException;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.io.transport.upnp.UpnpIOService;
import org.openhab.core.library.types.DateTimeType;
@@ -71,8 +73,8 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
*/
private static final int DIM_STEPSIZE = 5;
public WemoDimmerHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
super(thing, upnpIOService, wemoHttpCaller);
public WemoDimmerHandler(final Thing thing, final UpnpIOService upnpIOService, final HttpClient httpClient) {
super(thing, upnpIOService, httpClient);
logger.debug("Creating a WemoDimmerHandler for thing '{}'", getThing().getUID());
}
@@ -96,11 +98,9 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
@Override
public void dispose() {
logger.debug("WeMoDimmerHandler disposed.");
ScheduledFuture<?> job = this.pollingJob;
if (job != null && !job.isCancelled()) {
job.cancel(true);
ScheduledFuture<?> pollingJob = this.pollingJob;
if (pollingJob != null) {
pollingJob.cancel(true);
}
this.pollingJob = null;
super.dispose();
@@ -108,22 +108,15 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
private void poll() {
synchronized (jobLock) {
if (pollingJob == null) {
logger.debug("Polling job for thing {}", getThing().getUID());
// Check if the Wemo device is set in the UPnP service registry
if (!isUpnpDeviceRegistered()) {
logger.debug("UPnP device {} not yet registered", getUDN());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
"@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
return;
}
try {
logger.debug("Polling job");
// Check if the Wemo device is set in the UPnP service registry
if (!isUpnpDeviceRegistered()) {
logger.debug("UPnP device {} not yet registered", getUDN());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
"@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
return;
}
updateWemoState();
} catch (Exception e) {
logger.debug("Exception during poll: {}", e.getMessage(), e);
}
updateWemoState();
}
}
@@ -345,7 +338,7 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
@Override
public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) {
logger.debug("Received pair '{}':'{}' (service '{}') for thing '{}'",
new Object[] { variable, value, service, this.getThing().getUID() });
new Object[] { variable, value, service, getThing().getUID() });
updateStatus(ThingStatus.ONLINE);
if (variable != null && value != null) {
String oldBinaryState = this.stateMap.get("BinaryState");
@@ -354,7 +347,7 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
case "BinaryState":
if (oldBinaryState == null || !oldBinaryState.equals(value)) {
State state = OnOffType.from(!"0".equals(value));
logger.debug("State '{}' for device '{}' received", state, getThing().getUID());
logger.debug("State '{}' for thing '{}' received", state, getThing().getUID());
updateState(CHANNEL_BRIGHTNESS, state);
if (state.equals(OnOffType.OFF)) {
updateState(CHANNEL_TIMER_START, OnOffType.OFF);
@@ -362,7 +355,7 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
}
break;
case "brightness":
logger.debug("brightness '{}' for device '{}' received", value, getThing().getUID());
logger.debug("brightness '{}' for thing '{}' received", value, getThing().getUID());
int newBrightnessValue = Integer.valueOf(value);
State newBrightnessState = new PercentType(newBrightnessValue);
String binaryState = this.stateMap.get("BinaryState");
@@ -374,18 +367,18 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
currentBrightness = newBrightnessValue;
break;
case "fader":
logger.debug("fader '{}' for device '{}' received", value, getThing().getUID());
logger.debug("fader '{}' for thing '{}' received", value, getThing().getUID());
String[] splitFader = value.split(":");
if (splitFader[0] != null) {
int faderSeconds = Integer.valueOf(splitFader[0]);
State faderMinutes = new DecimalType(faderSeconds / 60);
logger.debug("faderTime '{} minutes' for device '{}' received", faderMinutes,
logger.debug("faderTime '{} minutes' for thing '{}' received", faderMinutes,
getThing().getUID());
updateState(CHANNEL_FADER_COUNT_DOWN_TIME, faderMinutes);
}
if (splitFader[1] != null) {
State isTimerRunning = OnOffType.from(!"-1".equals(splitFader[1]));
logger.debug("isTimerRunning '{}' for device '{}' received", isTimerRunning,
logger.debug("isTimerRunning '{}' for thing '{}' received", isTimerRunning,
getThing().getUID());
updateState(CHANNEL_TIMER_START, isTimerRunning);
if (isTimerRunning.equals(OnOffType.ON)) {
@@ -394,7 +387,7 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
}
if (splitFader[2] != null) {
State isFaderEnabled = OnOffType.from(!"0".equals(splitFader[1]));
logger.debug("isFaderEnabled '{}' for device '{}' received", isFaderEnabled,
logger.debug("isFaderEnabled '{}' for thing '{}' received", isFaderEnabled,
getThing().getUID());
updateState(CHANNEL_FADER_ENABLED, isFaderEnabled);
}
@@ -402,19 +395,19 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
case "nightMode":
State nightModeState = OnOffType.from(!"0".equals(value));
currentNightModeState = value;
logger.debug("nightModeState '{}' for device '{}' received", nightModeState, getThing().getUID());
logger.debug("nightModeState '{}' for thing '{}' received", nightModeState, getThing().getUID());
updateState(CHANNEL_NIGHT_MODE, nightModeState);
break;
case "startTime":
State startTimeState = getDateTimeState(value);
logger.debug("startTimeState '{}' for device '{}' received", startTimeState, getThing().getUID());
logger.debug("startTimeState '{}' for thing '{}' received", startTimeState, getThing().getUID());
if (startTimeState != null) {
updateState(CHANNEL_START_TIME, startTimeState);
}
break;
case "endTime":
State endTimeState = getDateTimeState(value);
logger.debug("endTimeState '{}' for device '{}' received", endTimeState, getThing().getUID());
logger.debug("endTimeState '{}' for thing '{}' received", endTimeState, getThing().getUID());
if (endTimeState != null) {
updateState(CHANNEL_END_TIME, endTimeState);
}
@@ -423,7 +416,7 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
int nightModeBrightnessValue = Integer.valueOf(value);
currentNightModeBrightness = nightModeBrightnessValue;
State nightModeBrightnessState = new PercentType(nightModeBrightnessValue);
logger.debug("nightModeBrightnessState '{}' for device '{}' received", nightModeBrightnessState,
logger.debug("nightModeBrightnessState '{}' for thing '{}' received", nightModeBrightnessState,
getThing().getUID());
updateState(CHANNEL_NIGHT_MODE_BRIGHTNESS, nightModeBrightnessState);
break;
@@ -437,11 +430,6 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
*
*/
protected void updateWemoState() {
String wemoURL = getWemoURL(BASICACTION);
if (wemoURL == null) {
logger.debug("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID());
return;
}
String action = "GetBinaryState";
String variable = null;
String actionService = BASICACTION;
@@ -449,7 +437,7 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
String content = createStateRequestContent(action, actionService);
try {
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
String wemoCallResponse = probeAndExecuteCall(BASICACTION, soapHeader, content);
value = substringBetween(wemoCallResponse, "<BinaryState>", "</BinaryState>");
variable = "BinaryState";
this.onValueReceived(variable, value, actionService + "1");
@@ -460,8 +448,11 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
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());
} catch (MissingHostException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
} catch (WemoException e) {
logger.debug("Failed to get actual state for thing '{}': {}", getThing().getUID(), e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
action = "GetNightModeConfiguration";
@@ -470,7 +461,7 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
content = createStateRequestContent(action, actionService);
try {
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
String wemoCallResponse = probeAndExecuteCall(BASICACTION, soapHeader, content);
value = substringBetween(wemoCallResponse, "<startTime>", "</startTime>");
variable = "startTime";
this.onValueReceived(variable, value, actionService + "1");
@@ -484,8 +475,11 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
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(),
} catch (MissingHostException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
} catch (WemoException e) {
logger.debug("Failed to get actual NightMode state for thing '{}': {}", getThing().getUID(),
e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
@@ -496,7 +490,7 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
try {
value = Long.parseLong(attributeValue);
} catch (NumberFormatException e) {
logger.warn("Unable to parse attributeValue '{}' for device '{}'; expected long", attributeValue,
logger.warn("Unable to parse attributeValue '{}' for thing '{}'; expected long", attributeValue,
getThing().getUID());
return null;
}
@@ -504,11 +498,6 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
}
public void setBinaryState(String action, String argument, String value) {
String wemoURL = getWemoURL(BASICACTION);
if (wemoURL == null) {
logger.debug("Failed to set binary state for device '{}': URL cannot be created", getThing().getUID());
return;
}
try {
String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\"";
String content = """
@@ -520,21 +509,19 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
+ action + " xmlns:u=\"urn:Belkin:service:basicevent:1\">" + "<" + argument + ">" + value + "</"
+ argument + ">" + "</u:" + action + ">" + "</s:Body>" + "</s:Envelope>";
wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
probeAndExecuteCall(BASICACTION, soapHeader, content);
updateStatus(ThingStatus.ONLINE);
} catch (Exception e) {
logger.debug("Failed to set binaryState '{}' for device '{}': {}", value, getThing().getUID(),
} catch (MissingHostException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
} catch (WemoException e) {
logger.warn("Failed to set binaryState '{}' for thing '{}': {}", value, getThing().getUID(),
e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
public void setTimerStart(String action, String argument, String value) {
String wemoURL = getWemoURL(BASICACTION);
if (wemoURL == null) {
logger.warn("Failed to set timerStart for device '{}': URL cannot be created", getThing().getUID());
return;
}
try {
String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\"";
String content = """
@@ -544,10 +531,13 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
<u:SetBinaryState xmlns:u="urn:Belkin:service:basicevent:1">\
"""
+ value + "</u:SetBinaryState>" + "</s:Body>" + "</s:Envelope>";
wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
probeAndExecuteCall(BASICACTION, soapHeader, content);
updateStatus(ThingStatus.ONLINE);
} catch (Exception e) {
logger.debug("Failed to set timerStart '{}' for device '{}': {}", value, getThing().getUID(),
} catch (MissingHostException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
} catch (WemoException e) {
logger.debug("Failed to set timerStart '{}' for thing '{}': {}", value, getThing().getUID(),
e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
@@ -20,7 +20,9 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.wemo.internal.http.WemoHttpCall;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.wemo.internal.exception.MissingHostException;
import org.openhab.binding.wemo.internal.exception.WemoException;
import org.openhab.core.io.transport.upnp.UpnpIOService;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
@@ -52,8 +54,8 @@ public abstract class WemoHandler extends WemoBaseThingHandler {
private @Nullable ScheduledFuture<?> pollingJob;
public WemoHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
super(thing, upnpIOService, wemoHttpCaller);
public WemoHandler(final Thing thing, final UpnpIOService upnpIOService, final HttpClient httpClient) {
super(thing, upnpIOService, httpClient);
logger.debug("Creating a WemoHandler for thing '{}'", getThing().getUID());
}
@@ -72,11 +74,9 @@ public abstract class WemoHandler extends WemoBaseThingHandler {
@Override
public void dispose() {
logger.debug("WemoHandler disposed for thing {}", getThing().getUID());
ScheduledFuture<?> job = this.pollingJob;
if (job != null) {
job.cancel(true);
ScheduledFuture<?> pollingJob = this.pollingJob;
if (pollingJob != null) {
pollingJob.cancel(true);
}
this.pollingJob = null;
super.dispose();
@@ -84,51 +84,37 @@ public abstract class WemoHandler extends WemoBaseThingHandler {
private void poll() {
synchronized (jobLock) {
if (pollingJob == null) {
logger.debug("Polling job for thing {}", getThing().getUID());
// Check if the Wemo device is set in the UPnP service registry
if (!isUpnpDeviceRegistered()) {
logger.debug("UPnP device {} not yet registered", getUDN());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
"@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
return;
}
try {
logger.debug("Polling job");
// Check if the Wemo device is set in the UPnP service registry
if (!isUpnpDeviceRegistered()) {
logger.debug("UPnP device {} not yet registered", getUDN());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
"@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
return;
}
updateWemoState();
} catch (Exception e) {
logger.debug("Exception during poll: {}", e.getMessage(), e);
}
updateWemoState();
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
String wemoURL = getWemoURL(BASICACTION);
if (wemoURL == null) {
logger.debug("Failed to send command '{}' for device '{}': URL cannot be created", command,
getThing().getUID());
return;
}
if (command instanceof RefreshType) {
try {
updateWemoState();
} catch (Exception e) {
logger.debug("Exception during poll", e);
}
updateWemoState();
} else if (CHANNEL_STATE.equals(channelUID.getId())) {
if (command instanceof OnOffType) {
try {
boolean binaryState = OnOffType.ON.equals(command) ? true : false;
String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\"";
String content = createBinaryStateContent(binaryState);
wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
probeAndExecuteCall(BASICACTION, soapHeader, content);
updateStatus(ThingStatus.ONLINE);
} catch (Exception e) {
logger.warn("Failed to send command '{}' for device '{}': {}", command, getThing().getUID(),
} catch (MissingHostException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
} catch (WemoException e) {
logger.warn("Failed to send command '{}' for thing '{}': {}", command, getThing().getUID(),
e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
}
@@ -149,27 +135,25 @@ public abstract class WemoHandler extends WemoBaseThingHandler {
variable = "InsightParams";
actionService = INSIGHTACTION;
}
String wemoURL = getWemoURL(actionService);
if (wemoURL == null) {
logger.debug("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID());
return;
}
String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
String content = createStateRequestContent(action, actionService);
try {
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
String wemoCallResponse = probeAndExecuteCall(actionService, soapHeader, content);
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());
logger.trace("New state '{}' for thing '{}' received", value, getThing().getUID());
this.onValueReceived(variable, value, actionService + "1");
}
updateStatus(ThingStatus.ONLINE);
} catch (Exception e) {
logger.warn("Failed to get actual state for device '{}': {}", getThing().getUID(), e.getMessage());
} catch (MissingHostException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
} catch (WemoException e) {
logger.debug("Failed to get actual state for thing '{}': {}", getThing().getUID(), e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
@@ -30,7 +30,9 @@ import javax.xml.parsers.ParserConfigurationException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.wemo.internal.http.WemoHttpCall;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.wemo.internal.exception.MissingHostException;
import org.openhab.binding.wemo.internal.exception.WemoException;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.io.transport.upnp.UpnpIOService;
import org.openhab.core.library.types.OnOffType;
@@ -74,8 +76,8 @@ public class WemoHolmesHandler extends WemoBaseThingHandler {
private @Nullable ScheduledFuture<?> pollingJob;
public WemoHolmesHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
super(thing, upnpIOService, wemoHttpCaller);
public WemoHolmesHandler(final Thing thing, final UpnpIOService upnpIOService, final HttpClient httpClient) {
super(thing, upnpIOService, httpClient);
logger.debug("Creating a WemoHolmesHandler for thing '{}'", getThing().getUID());
}
@@ -99,11 +101,9 @@ public class WemoHolmesHandler extends WemoBaseThingHandler {
@Override
public void dispose() {
logger.debug("WemoHolmesHandler disposed.");
ScheduledFuture<?> job = this.pollingJob;
if (job != null && !job.isCancelled()) {
job.cancel(true);
ScheduledFuture<?> pollingJob = this.pollingJob;
if (pollingJob != null) {
pollingJob.cancel(true);
}
this.pollingJob = null;
super.dispose();
@@ -111,33 +111,20 @@ public class WemoHolmesHandler extends WemoBaseThingHandler {
private void poll() {
synchronized (jobLock) {
if (pollingJob == null) {
logger.debug("Polling job for thing {}", getThing().getUID());
// Check if the Wemo device is set in the UPnP service registry
if (!isUpnpDeviceRegistered()) {
logger.debug("UPnP device {} not yet registered", getUDN());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
"@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
return;
}
try {
logger.debug("Polling job");
// Check if the Wemo device is set in the UPnP service registry
if (!isUpnpDeviceRegistered()) {
logger.debug("UPnP device {} not yet registered", getUDN());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
"@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
return;
}
updateWemoState();
} catch (Exception e) {
logger.debug("Exception during poll: {}", e.getMessage(), e);
}
updateWemoState();
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
String wemoURL = getWemoURL(DEVICEACTION);
if (wemoURL == null) {
logger.debug("Failed to send command '{}' for device '{}': URL cannot be created", command,
getThing().getUID());
return;
}
String attribute = null;
String value = null;
@@ -237,6 +224,7 @@ public class WemoHolmesHandler extends WemoBaseThingHandler {
attribute = "SetTemperature";
value = command.toString();
}
try {
String soapHeader = "\"urn:Belkin:service:deviceevent:1#SetAttributes\"";
String content = """
@@ -249,10 +237,13 @@ public class WemoHolmesHandler extends WemoBaseThingHandler {
+ attribute + "&lt;/name&gt;&lt;value&gt;" + value
+ "&lt;/value&gt;&lt;/attribute&gt;</attributeList>" + "</u:SetAttributes>" + "</s:Body>"
+ "</s:Envelope>";
wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
probeAndExecuteCall(DEVICEACTION, soapHeader, content);
updateStatus(ThingStatus.ONLINE);
} catch (IOException e) {
logger.debug("Failed to send command '{}' for device '{}':", command, getThing().getUID(), e);
} catch (MissingHostException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
} catch (WemoException e) {
logger.warn("Failed to send command '{}' for thing '{}':", command, getThing().getUID(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
@@ -260,7 +251,7 @@ public class WemoHolmesHandler extends WemoBaseThingHandler {
@Override
public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) {
logger.debug("Received pair '{}':'{}' (service '{}') for thing '{}'", variable, value, service,
this.getThing().getUID());
getThing().getUID());
updateStatus(ThingStatus.ONLINE);
if (variable != null && value != null) {
@@ -275,23 +266,18 @@ public class WemoHolmesHandler extends WemoBaseThingHandler {
*/
protected void updateWemoState() {
String actionService = DEVICEACTION;
String wemoURL = getWemoURL(actionService);
if (wemoURL == null) {
logger.debug("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID());
return;
}
try {
String action = "GetAttributes";
String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
String content = createStateRequestContent(action, actionService);
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
String wemoCallResponse = probeAndExecuteCall(actionService, soapHeader, content);
String stringParser = substringBetween(wemoCallResponse, "<attributeList>", "</attributeList>");
// Due to Belkins bad response formatting, we need to run this twice.
stringParser = unescapeXml(stringParser);
stringParser = unescapeXml(stringParser);
logger.trace("AirPurifier response '{}' for device '{}' received", stringParser, getThing().getUID());
logger.trace("AirPurifier response '{}' for thing '{}' received", stringParser, getThing().getUID());
stringParser = "<data>" + stringParser + "</data>";
@@ -486,8 +472,11 @@ public class WemoHolmesHandler extends WemoBaseThingHandler {
}
}
updateStatus(ThingStatus.ONLINE);
} catch (RuntimeException | ParserConfigurationException | SAXException | IOException e) {
logger.debug("Failed to get actual state for device '{}':", getThing().getUID(), e);
} catch (MissingHostException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
} catch (RuntimeException | ParserConfigurationException | SAXException | IOException | WemoException e) {
logger.debug("Failed to get actual state for thing '{}':", getThing().getUID(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
@@ -20,11 +20,11 @@ import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.wemo.internal.InsightParser;
import org.openhab.binding.wemo.internal.WemoBindingConstants;
import org.openhab.binding.wemo.internal.WemoPowerBank;
import org.openhab.binding.wemo.internal.config.WemoInsightConfiguration;
import org.openhab.binding.wemo.internal.http.WemoHttpCall;
import org.openhab.core.io.transport.upnp.UpnpIOService;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
@@ -51,8 +51,8 @@ public class WemoInsightHandler extends WemoHandler {
private int currentPowerSlidingSeconds;
private int currentPowerDeltaTrigger;
public WemoInsightHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
super(thing, upnpIOService, wemoHttpCaller);
public WemoInsightHandler(final Thing thing, final UpnpIOService upnpIOService, final HttpClient httpClient) {
super(thing, upnpIOService, httpClient);
}
@Override
@@ -77,7 +77,7 @@ public class WemoInsightHandler extends WemoHandler {
@Override
public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) {
logger.debug("Received pair '{}':'{}' (service '{}') for thing '{}'",
new Object[] { variable, value, service, this.getThing().getUID() });
new Object[] { variable, value, service, getThing().getUID() });
updateStatus(ThingStatus.ONLINE);
@@ -99,7 +99,7 @@ public class WemoInsightHandler extends WemoHandler {
String channel = entry.getKey();
State state = entry.getValue();
logger.trace("New InsightParam {} '{}' for device '{}' received", channel, state,
logger.trace("New InsightParam {} '{}' for thing '{}' received", channel, state,
getThing().getUID());
updateState(channel, state);
if (channel.equals(WemoBindingConstants.CHANNEL_CURRENT_POWER_RAW)
@@ -20,7 +20,9 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.wemo.internal.http.WemoHttpCall;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.wemo.internal.exception.MissingHostException;
import org.openhab.binding.wemo.internal.exception.WemoException;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.io.transport.upnp.UpnpIOService;
import org.openhab.core.library.types.IncreaseDecreaseType;
@@ -70,8 +72,8 @@ public class WemoLightHandler extends WemoBaseThingHandler {
private @Nullable ScheduledFuture<?> pollingJob;
public WemoLightHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpcaller) {
super(thing, upnpIOService, wemoHttpcaller);
public WemoLightHandler(final Thing thing, final UpnpIOService upnpIOService, final HttpClient httpClient) {
super(thing, upnpIOService, httpClient);
logger.debug("Creating a WemoLightHandler for thing '{}'", getThing().getUID());
}
@@ -109,11 +111,9 @@ public class WemoLightHandler extends WemoBaseThingHandler {
@Override
public void dispose() {
logger.debug("WemoLightHandler disposed.");
ScheduledFuture<?> job = this.pollingJob;
if (job != null && !job.isCancelled()) {
job.cancel(true);
ScheduledFuture<?> pollingJob = this.pollingJob;
if (pollingJob != null) {
pollingJob.cancel(true);
}
this.pollingJob = null;
super.dispose();
@@ -137,11 +137,8 @@ public class WemoLightHandler extends WemoBaseThingHandler {
private void poll() {
synchronized (jobLock) {
if (pollingJob == null) {
return;
}
try {
logger.debug("Polling job");
logger.debug("Polling job for thing {}", getThing().getUID());
// Check if the Wemo device is set in the UPnP service registry
if (!isUpnpDeviceRegistered()) {
logger.debug("UPnP device {} not yet registered", getUDN());
@@ -158,12 +155,6 @@ public class WemoLightHandler extends WemoBaseThingHandler {
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
String wemoURL = getWemoURL(BASICACTION);
if (wemoURL == null) {
logger.debug("Failed to send command '{}' for device '{}': URL cannot be created", command,
getThing().getUID());
return;
}
if (command instanceof RefreshType) {
try {
getDeviceState();
@@ -180,7 +171,7 @@ public class WemoLightHandler extends WemoBaseThingHandler {
return;
}
String devUDN = "uuid:" + wemoBridge.getThing().getConfiguration().get(UDN).toString();
logger.trace("WeMo Bridge to send command to : {}", devUDN);
logger.trace("WeMo Bridge to send command to: {}", devUDN);
String value = null;
String capability = null;
@@ -236,6 +227,7 @@ public class WemoLightHandler extends WemoBaseThingHandler {
}
break;
}
try {
if (capability != null && value != null) {
String soapHeader = "\"urn:Belkin:service:bridge:1#SetDeviceStatus\"";
@@ -253,7 +245,7 @@ public class WemoLightHandler extends WemoBaseThingHandler {
+ "&lt;/CapabilityValue&gt;&lt;/DeviceStatus&gt;" + "</DeviceStatusList>"
+ "</u:SetDeviceStatus>" + "</s:Body>" + "</s:Envelope>";
wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
probeAndExecuteCall(BASICACTION, soapHeader, content);
if ("10008".equals(capability)) {
OnOffType binaryState = null;
binaryState = OnOffType.from(!"0".equals(value));
@@ -261,10 +253,13 @@ public class WemoLightHandler extends WemoBaseThingHandler {
}
updateStatus(ThingStatus.ONLINE);
}
} catch (Exception e) {
logger.warn("Failed to send command '{}' for device '{}': {}", command, getThing().getUID(),
} catch (MissingHostException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
} catch (WemoException e) {
logger.warn("Failed to send command '{}' for thing '{}': {}", command, getThing().getUID(),
e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
}
@@ -285,11 +280,7 @@ public class WemoLightHandler extends WemoBaseThingHandler {
*/
public void getDeviceState() {
logger.debug("Request actual state for LightID '{}'", wemoLightID);
String wemoURL = getWemoURL(BRIDGEACTION);
if (wemoURL == null) {
logger.debug("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID());
return;
}
try {
String soapHeader = "\"urn:Belkin:service:bridge:1#GetDeviceStatus\"";
String content = """
@@ -301,7 +292,7 @@ public class WemoLightHandler extends WemoBaseThingHandler {
"""
+ wemoLightID + "</DeviceIDs>" + "</u:GetDeviceStatus>" + "</s:Body>" + "</s:Envelope>";
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
String wemoCallResponse = probeAndExecuteCall(BRIDGEACTION, soapHeader, content);
wemoCallResponse = unescapeXml(wemoCallResponse);
String response = substringBetween(wemoCallResponse, "<CapabilityValue>", "</CapabilityValue>");
logger.trace("wemoNewLightState = {}", response);
@@ -323,16 +314,19 @@ public class WemoLightHandler extends WemoBaseThingHandler {
}
}
updateStatus(ThingStatus.ONLINE);
} catch (Exception e) {
logger.debug("Could not retrieve new Wemo light state for '{}':", getThing().getUID(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
} catch (MissingHostException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
} catch (WemoException e) {
logger.debug("Could not retrieve new Wemo light state for thing '{}':", getThing().getUID(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
@Override
public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) {
logger.trace("Received pair '{}':'{}' (service '{}') for thing '{}'",
new Object[] { variable, value, service, this.getThing().getUID() });
new Object[] { variable, value, service, getThing().getUID() });
String capabilityId = substringBetween(value, "<CapabilityId>", "</CapabilityId>");
String newValue = substringBetween(value, "<Value>", "</Value>");
switch (capabilityId) {
@@ -25,7 +25,9 @@ import javax.xml.parsers.DocumentBuilderFactory;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.wemo.internal.http.WemoHttpCall;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.wemo.internal.exception.MissingHostException;
import org.openhab.binding.wemo.internal.exception.WemoException;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.io.transport.upnp.UpnpIOService;
import org.openhab.core.library.types.OnOffType;
@@ -61,8 +63,8 @@ public class WemoMakerHandler extends WemoBaseThingHandler {
private @Nullable ScheduledFuture<?> pollingJob;
public WemoMakerHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpcaller) {
super(thing, upnpIOService, wemoHttpcaller);
public WemoMakerHandler(final Thing thing, final UpnpIOService upnpIOService, final HttpClient httpClient) {
super(thing, upnpIOService, httpClient);
logger.debug("Creating a WemoMakerHandler for thing '{}'", getThing().getUID());
}
@@ -85,11 +87,9 @@ public class WemoMakerHandler extends WemoBaseThingHandler {
@Override
public void dispose() {
logger.debug("WemoMakerHandler disposed.");
ScheduledFuture<?> job = this.pollingJob;
if (job != null && !job.isCancelled()) {
job.cancel(true);
ScheduledFuture<?> pollingJob = this.pollingJob;
if (pollingJob != null) {
pollingJob.cancel(true);
}
this.pollingJob = null;
super.dispose();
@@ -97,50 +97,36 @@ public class WemoMakerHandler extends WemoBaseThingHandler {
private void poll() {
synchronized (jobLock) {
if (pollingJob == null) {
logger.debug("Polling job for thing {}", getThing().getUID());
// Check if the Wemo device is set in the UPnP service registry
if (!isUpnpDeviceRegistered()) {
logger.debug("UPnP device {} not yet registered", getUDN());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
"@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
return;
}
try {
logger.debug("Polling job");
// Check if the Wemo device is set in the UPnP service registry
if (!isUpnpDeviceRegistered()) {
logger.debug("UPnP device {} not yet registered", getUDN());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
"@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
return;
}
updateWemoState();
} catch (Exception e) {
logger.debug("Exception during poll: {}", e.getMessage(), e);
}
updateWemoState();
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
String wemoURL = getWemoURL(BASICACTION);
if (wemoURL == null) {
logger.debug("Failed to send command '{}' for device '{}': URL cannot be created", command,
getThing().getUID());
return;
}
if (command instanceof RefreshType) {
try {
updateWemoState();
} catch (Exception e) {
logger.debug("Exception during poll", e);
}
updateWemoState();
} else if (channelUID.getId().equals(CHANNEL_RELAY)) {
if (command instanceof OnOffType) {
try {
boolean binaryState = OnOffType.ON.equals(command) ? true : false;
String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\"";
String content = createBinaryStateContent(binaryState);
wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
probeAndExecuteCall(BASICACTION, soapHeader, content);
updateStatus(ThingStatus.ONLINE);
} catch (Exception e) {
logger.warn("Failed to send command '{}' for device '{}' ", command, getThing().getUID(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
} catch (MissingHostException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
} catch (WemoException e) {
logger.warn("Failed to send command '{}' for thing '{}' ", command, getThing().getUID(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
}
@@ -151,25 +137,20 @@ public class WemoMakerHandler extends WemoBaseThingHandler {
*/
protected void updateWemoState() {
String actionService = DEVICEACTION;
String wemoURL = getWemoURL(actionService);
if (wemoURL == null) {
logger.debug("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID());
return;
}
try {
String action = "GetAttributes";
String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
String content = createStateRequestContent(action, actionService);
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
String wemoCallResponse = probeAndExecuteCall(actionService, soapHeader, content);
try {
String stringParser = substringBetween(wemoCallResponse, "<attributeList>", "</attributeList>");
logger.trace("Escaped Maker response for device '{}' :", getThing().getUID());
logger.trace("Escaped Maker response for thing '{}' :", 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());
logger.trace("Maker response '{}' for thing '{}' received", stringParser, getThing().getUID());
stringParser = "<data>" + stringParser + "</data>";
@@ -205,13 +186,13 @@ public class WemoMakerHandler extends WemoBaseThingHandler {
switch (attributeName) {
case "Switch":
State relayState = OnOffType.from(!"0".equals(attributeValue));
logger.debug("New relayState '{}' for device '{}' received", relayState,
logger.debug("New relayState '{}' for thing '{}' received", relayState,
getThing().getUID());
updateState(CHANNEL_RELAY, relayState);
break;
case "Sensor":
State sensorState = OnOffType.from(!"1".equals(attributeValue));
logger.debug("New sensorState '{}' for device '{}' received", sensorState,
logger.debug("New sensorState '{}' for thing '{}' received", sensorState,
getThing().getUID());
updateState(CHANNEL_SENSOR, sensorState);
break;
@@ -221,8 +202,11 @@ public class WemoMakerHandler extends WemoBaseThingHandler {
} catch (Exception e) {
logger.warn("Failed to parse attributeList for WeMo Maker '{}'", this.getThing().getUID(), e);
}
} catch (Exception e) {
logger.warn("Failed to get attributes for device '{}'", getThing().getUID(), e);
} catch (MissingHostException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/config-status.error.missing-ip");
} catch (WemoException e) {
logger.debug("Failed to get attributes for thing '{}'", getThing().getUID(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
@@ -17,8 +17,8 @@ import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.wemo.internal.WemoBindingConstants;
import org.openhab.binding.wemo.internal.http.WemoHttpCall;
import org.openhab.core.io.transport.upnp.UpnpIOService;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.OnOffType;
@@ -40,8 +40,8 @@ public class WemoMotionHandler extends WemoHandler {
private final Logger logger = LoggerFactory.getLogger(WemoMotionHandler.class);
private final Map<String, String> stateMap = new ConcurrentHashMap<>();
public WemoMotionHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
super(thing, upnpIOService, wemoHttpCaller);
public WemoMotionHandler(final Thing thing, final UpnpIOService upnpIOService, final HttpClient httpClient) {
super(thing, upnpIOService, httpClient);
}
@Override
@@ -54,7 +54,7 @@ public class WemoMotionHandler extends WemoHandler {
@Override
public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) {
logger.debug("Received pair '{}':'{}' (service '{}') for thing '{}'",
new Object[] { variable, value, service, this.getThing().getUID() });
new Object[] { variable, value, service, getThing().getUID() });
updateStatus(ThingStatus.ONLINE);
@@ -72,7 +72,7 @@ public class WemoMotionHandler extends WemoHandler {
if (binaryState != null) {
if (oldValue == null || !oldValue.equals(binaryState)) {
State state = OnOffType.from(!"0".equals(binaryState));
logger.debug("State '{}' for device '{}' received", state, getThing().getUID());
logger.debug("State '{}' for thing '{}' received", state, getThing().getUID());
updateState(WemoBindingConstants.CHANNEL_MOTION_DETECTION, state);
if (OnOffType.ON.equals(state)) {
State lastMotionDetected = new DateTimeType();
@@ -17,8 +17,8 @@ import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.wemo.internal.WemoBindingConstants;
import org.openhab.binding.wemo.internal.http.WemoHttpCall;
import org.openhab.core.io.transport.upnp.UpnpIOService;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.Thing;
@@ -39,8 +39,8 @@ public class WemoSwitchHandler extends WemoHandler {
private final Logger logger = LoggerFactory.getLogger(WemoSwitchHandler.class);
private final Map<String, String> stateMap = new ConcurrentHashMap<>();
public WemoSwitchHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
super(thing, upnpIOService, wemoHttpCaller);
public WemoSwitchHandler(final Thing thing, final UpnpIOService upnpIOService, final HttpClient httpClient) {
super(thing, upnpIOService, httpClient);
}
@Override
@@ -53,7 +53,7 @@ public class WemoSwitchHandler extends WemoHandler {
@Override
public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) {
logger.debug("Received pair '{}':'{}' (service '{}') for thing '{}'",
new Object[] { variable, value, service, this.getThing().getUID() });
new Object[] { variable, value, service, getThing().getUID() });
updateStatus(ThingStatus.ONLINE);
@@ -71,7 +71,7 @@ public class WemoSwitchHandler extends WemoHandler {
if (binaryState != null) {
if (oldValue == null || !oldValue.equals(binaryState)) {
State state = OnOffType.from(!"0".equals(binaryState));
logger.debug("State '{}' for device '{}' received", state, getThing().getUID());
logger.debug("State '{}' for thing '{}' received", state, getThing().getUID());
updateState(WemoBindingConstants.CHANNEL_STATE, state);
}
}
@@ -1,60 +0,0 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.wemo.internal.http;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Properties;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.wemo.internal.WemoBindingConstants;
import org.openhab.core.io.net.http.HttpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link WemoHttpCall} is responsible for calling a WeMo device to send commands or retrieve status updates.
*
* @author Hans-Jörg Merk - Initial contribution
*/
@NonNullByDefault
public class WemoHttpCall {
private final Logger logger = LoggerFactory.getLogger(WemoHttpCall.class);
public String executeCall(String wemoURL, String soapHeader, String content) throws IOException {
Properties wemoHeaders = new Properties();
wemoHeaders.setProperty("CONTENT-TYPE", WemoBindingConstants.HTTP_CALL_CONTENT_HEADER);
wemoHeaders.put("SOAPACTION", soapHeader);
InputStream wemoContent = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
logger.trace("Performing HTTP call for URL: '{}', header: '{}', request body: '{}'", wemoURL, soapHeader,
content);
String responseBody = HttpUtil.executeUrl("POST", wemoURL, wemoHeaders, wemoContent, null, 2000);
logger.trace("HTTP response body: '{}'", responseBody);
return responseBody;
}
public boolean probeURL(String url) {
try {
HttpUtil.executeUrl("GET", url, 250);
return true;
} catch (IOException e) {
return false;
}
}
}