2 * Copyright (c) 2010-2022 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.wemo.internal.handler;
15 import static org.openhab.binding.wemo.internal.WemoBindingConstants.*;
16 import static org.openhab.binding.wemo.internal.WemoUtil.*;
18 import java.util.HashMap;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.wemo.internal.http.WemoHttpCall;
26 import org.openhab.core.config.core.Configuration;
27 import org.openhab.core.io.transport.upnp.UpnpIOService;
28 import org.openhab.core.library.types.OnOffType;
29 import org.openhab.core.thing.ChannelUID;
30 import org.openhab.core.thing.Thing;
31 import org.openhab.core.thing.ThingStatus;
32 import org.openhab.core.thing.ThingStatusDetail;
33 import org.openhab.core.thing.ThingTypeUID;
34 import org.openhab.core.types.Command;
35 import org.openhab.core.types.RefreshType;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
40 * The {@link WemoHandler} is responsible for handling commands, which are
41 * sent to one of the channels and to update their states.
43 * @author Hans-Jörg Merk - Initial contribution
44 * @author Kai Kreuzer - some refactoring for performance and simplification
45 * @author Stefan Bußweiler - Added new thing status handling
46 * @author Erdoan Hadzhiyusein - Adapted the class to work with the new DateTimeType
47 * @author Mihir Patil - Added standby switch
50 public abstract class WemoHandler extends WemoBaseThingHandler {
52 private final Logger logger = LoggerFactory.getLogger(WemoHandler.class);
54 private final Object upnpLock = new Object();
55 private final Object jobLock = new Object();
57 private Map<String, Boolean> subscriptionState = new HashMap<>();
59 private @Nullable ScheduledFuture<?> pollingJob;
61 public WemoHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
62 super(thing, upnpIOService, wemoHttpCaller);
64 logger.debug("Creating a WemoHandler for thing '{}'", getThing().getUID());
68 public void initialize() {
69 Configuration configuration = getConfig();
71 if (configuration.get(UDN) != null) {
72 logger.debug("Initializing WemoHandler for UDN '{}'", configuration.get(UDN));
73 UpnpIOService localService = service;
74 if (localService != null) {
75 localService.registerParticipant(this);
78 pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS,
80 updateStatus(ThingStatus.ONLINE);
82 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
83 "@text/config-status.error.missing-udn");
84 logger.debug("Cannot initalize WemoHandler. UDN not set.");
89 public void dispose() {
90 logger.debug("WemoHandler disposed for thing {}", getThing().getUID());
92 ScheduledFuture<?> job = this.pollingJob;
96 this.pollingJob = null;
100 private void poll() {
101 synchronized (jobLock) {
102 if (pollingJob == null) {
106 logger.debug("Polling job");
108 // Check if the Wemo device is set in the UPnP service registry
109 // If not, set the thing state to ONLINE/CONFIG-PENDING and wait for the next poll
110 if (!isUpnpDeviceRegistered()) {
111 logger.debug("UPnP device {} not yet registered", getUDN());
112 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING,
113 "@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
114 synchronized (upnpLock) {
115 subscriptionState = new HashMap<>();
119 updateStatus(ThingStatus.ONLINE);
122 } catch (Exception e) {
123 logger.debug("Exception during poll: {}", e.getMessage(), e);
129 public void handleCommand(ChannelUID channelUID, Command command) {
130 String localHost = getHost();
131 if (localHost.isEmpty()) {
132 logger.error("Failed to send command '{}' for device '{}': IP address missing", command,
133 getThing().getUID());
134 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
135 "@text/config-status.error.missing-ip");
138 String wemoURL = getWemoURL(localHost, BASICACTION);
139 if (wemoURL == null) {
140 logger.error("Failed to send command '{}' for device '{}': URL cannot be created", command,
141 getThing().getUID());
142 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
143 "@text/config-status.error.missing-url");
146 if (command instanceof RefreshType) {
149 } catch (Exception e) {
150 logger.debug("Exception during poll", e);
152 } else if (CHANNEL_STATE.equals(channelUID.getId())) {
153 if (command instanceof OnOffType) {
155 boolean binaryState = OnOffType.ON.equals(command) ? true : false;
156 String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\"";
157 String content = createBinaryStateContent(binaryState);
158 String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
159 if (wemoCallResponse != null && logger.isTraceEnabled()) {
160 logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID());
161 logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID());
162 logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID());
163 logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse,
164 getThing().getUID());
166 } catch (Exception e) {
167 logger.error("Failed to send command '{}' for device '{}': {}", command, getThing().getUID(),
169 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
171 updateStatus(ThingStatus.ONLINE);
177 public void onServiceSubscribed(@Nullable String service, boolean succeeded) {
178 if (service != null) {
179 logger.debug("WeMo {}: Subscription to service {} {}", getUDN(), service,
180 succeeded ? "succeeded" : "failed");
181 subscriptionState.put(service, succeeded);
185 private synchronized void addSubscription() {
186 synchronized (upnpLock) {
187 UpnpIOService localService = service;
188 if (localService != null) {
189 if (localService.isRegistered(this)) {
190 logger.debug("Checking WeMo GENA subscription for '{}'", getThing().getUID());
192 ThingTypeUID thingTypeUID = thing.getThingTypeUID();
193 String subscription = BASICEVENT;
195 if (subscriptionState.get(subscription) == null) {
196 logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(),
198 localService.addSubscription(this, subscription, SUBSCRIPTION_DURATION_SECONDS);
199 subscriptionState.put(subscription, true);
202 if (THING_TYPE_INSIGHT.equals(thingTypeUID)) {
203 subscription = INSIGHTEVENT;
204 if (subscriptionState.get(subscription) == null) {
205 logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(),
207 localService.addSubscription(this, subscription, SUBSCRIPTION_DURATION_SECONDS);
208 subscriptionState.put(subscription, true);
213 "Setting up WeMo GENA subscription for '{}' FAILED - service.isRegistered(this) is FALSE",
214 getThing().getUID());
220 private synchronized void removeSubscription() {
221 synchronized (upnpLock) {
222 UpnpIOService localService = service;
223 if (localService != null) {
224 if (localService.isRegistered(this)) {
225 logger.debug("Removing WeMo GENA subscription for '{}'", getThing().getUID());
226 ThingTypeUID thingTypeUID = thing.getThingTypeUID();
227 String subscription = BASICEVENT;
229 if (subscriptionState.get(subscription) != null) {
230 logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription);
231 localService.removeSubscription(this, subscription);
234 if (THING_TYPE_INSIGHT.equals(thingTypeUID)) {
235 subscription = INSIGHTEVENT;
236 if (subscriptionState.get(subscription) != null) {
237 logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription);
238 localService.removeSubscription(this, subscription);
241 subscriptionState = new HashMap<>();
242 localService.unregisterParticipant(this);
249 * The {@link updateWemoState} polls the actual state of a WeMo device and
250 * calls {@link onValueReceived} to update the statemap and channels..
253 protected void updateWemoState() {
254 String actionService = BASICACTION;
255 String localhost = getHost();
256 if (localhost.isEmpty()) {
257 logger.error("Failed to get actual state for device '{}': IP address missing", getThing().getUID());
258 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
259 "@text/config-status.error.missing-ip");
262 String action = "GetBinaryState";
263 String variable = "BinaryState";
265 if ("insight".equals(getThing().getThingTypeUID().getId())) {
266 action = "GetInsightParams";
267 variable = "InsightParams";
268 actionService = INSIGHTACTION;
270 String wemoURL = getWemoURL(localhost, actionService);
271 if (wemoURL == null) {
272 logger.error("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID());
273 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
274 "@text/config-status.error.missing-url");
277 String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
278 String content = createStateRequestContent(action, actionService);
280 String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
281 if (wemoCallResponse != null) {
282 if (logger.isTraceEnabled()) {
283 logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID());
284 logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID());
285 logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID());
286 logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID());
288 if ("InsightParams".equals(variable)) {
289 value = substringBetween(wemoCallResponse, "<InsightParams>", "</InsightParams>");
291 value = substringBetween(wemoCallResponse, "<BinaryState>", "</BinaryState>");
293 if (value.length() != 0) {
294 logger.trace("New state '{}' for device '{}' received", value, getThing().getUID());
295 this.onValueReceived(variable, value, actionService + "1");
298 } catch (Exception e) {
299 logger.error("Failed to get actual state for device '{}': {}", getThing().getUID(), e.getMessage());