]> git.basschouten.com Git - openhab-addons.git/blob
4b16e08a744330600886ba63c9b7c6bcb648decf
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.wemo.internal.handler;
14
15 import static org.openhab.binding.wemo.internal.WemoBindingConstants.*;
16 import static org.openhab.binding.wemo.internal.WemoUtil.*;
17
18 import java.util.HashMap;
19 import java.util.Map;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
22
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;
38
39 /**
40  * The {@link WemoHandler} is responsible for handling commands, which are
41  * sent to one of the channels and to update their states.
42  *
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
48  */
49 @NonNullByDefault
50 public abstract class WemoHandler extends WemoBaseThingHandler {
51
52     private final Logger logger = LoggerFactory.getLogger(WemoHandler.class);
53
54     private final Object upnpLock = new Object();
55     private final Object jobLock = new Object();
56
57     private Map<String, Boolean> subscriptionState = new HashMap<>();
58
59     private @Nullable ScheduledFuture<?> pollingJob;
60
61     public WemoHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
62         super(thing, upnpIOService, wemoHttpCaller);
63
64         logger.debug("Creating a WemoHandler for thing '{}'", getThing().getUID());
65     }
66
67     @Override
68     public void initialize() {
69         Configuration configuration = getConfig();
70
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);
76             }
77             host = getHost();
78             pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS,
79                     TimeUnit.SECONDS);
80             updateStatus(ThingStatus.ONLINE);
81         } else {
82             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
83                     "@text/config-status.error.missing-udn");
84             logger.debug("Cannot initalize WemoHandler. UDN not set.");
85         }
86     }
87
88     @Override
89     public void dispose() {
90         logger.debug("WemoHandler disposed for thing {}", getThing().getUID());
91
92         ScheduledFuture<?> job = this.pollingJob;
93         if (job != null) {
94             job.cancel(true);
95         }
96         this.pollingJob = null;
97         removeSubscription();
98     }
99
100     private void poll() {
101         synchronized (jobLock) {
102             if (pollingJob == null) {
103                 return;
104             }
105             try {
106                 logger.debug("Polling job");
107                 host = getHost();
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<>();
116                     }
117                     return;
118                 }
119                 updateStatus(ThingStatus.ONLINE);
120                 updateWemoState();
121                 addSubscription();
122             } catch (Exception e) {
123                 logger.debug("Exception during poll: {}", e.getMessage(), e);
124             }
125         }
126     }
127
128     @Override
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");
136             return;
137         }
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");
144             return;
145         }
146         if (command instanceof RefreshType) {
147             try {
148                 updateWemoState();
149             } catch (Exception e) {
150                 logger.debug("Exception during poll", e);
151             }
152         } else if (CHANNEL_STATE.equals(channelUID.getId())) {
153             if (command instanceof OnOffType) {
154                 try {
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());
165                     }
166                 } catch (Exception e) {
167                     logger.error("Failed to send command '{}' for device '{}': {}", command, getThing().getUID(),
168                             e.getMessage());
169                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
170                 }
171                 updateStatus(ThingStatus.ONLINE);
172             }
173         }
174     }
175
176     @Override
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);
182         }
183     }
184
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());
191
192                     ThingTypeUID thingTypeUID = thing.getThingTypeUID();
193                     String subscription = BASICEVENT;
194
195                     if (subscriptionState.get(subscription) == null) {
196                         logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(),
197                                 subscription);
198                         localService.addSubscription(this, subscription, SUBSCRIPTION_DURATION_SECONDS);
199                         subscriptionState.put(subscription, true);
200                     }
201
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(),
206                                     subscription);
207                             localService.addSubscription(this, subscription, SUBSCRIPTION_DURATION_SECONDS);
208                             subscriptionState.put(subscription, true);
209                         }
210                     }
211                 } else {
212                     logger.debug(
213                             "Setting up WeMo GENA subscription for '{}' FAILED - service.isRegistered(this) is FALSE",
214                             getThing().getUID());
215                 }
216             }
217         }
218     }
219
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;
228
229                     if (subscriptionState.get(subscription) != null) {
230                         logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription);
231                         localService.removeSubscription(this, subscription);
232                     }
233
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);
239                         }
240                     }
241                     subscriptionState = new HashMap<>();
242                     localService.unregisterParticipant(this);
243                 }
244             }
245         }
246     }
247
248     /**
249      * The {@link updateWemoState} polls the actual state of a WeMo device and
250      * calls {@link onValueReceived} to update the statemap and channels..
251      *
252      */
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");
260             return;
261         }
262         String action = "GetBinaryState";
263         String variable = "BinaryState";
264         String value = null;
265         if ("insight".equals(getThing().getThingTypeUID().getId())) {
266             action = "GetInsightParams";
267             variable = "InsightParams";
268             actionService = INSIGHTACTION;
269         }
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");
275             return;
276         }
277         String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
278         String content = createStateRequestContent(action, actionService);
279         try {
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());
287                 }
288                 if ("InsightParams".equals(variable)) {
289                     value = substringBetween(wemoCallResponse, "<InsightParams>", "</InsightParams>");
290                 } else {
291                     value = substringBetween(wemoCallResponse, "<BinaryState>", "</BinaryState>");
292                 }
293                 if (value.length() != 0) {
294                     logger.trace("New state '{}' for device '{}' received", value, getThing().getUID());
295                     this.onValueReceived(variable, value, actionService + "1");
296                 }
297             }
298         } catch (Exception e) {
299             logger.error("Failed to get actual state for device '{}': {}", getThing().getUID(), e.getMessage());
300         }
301     }
302 }