]> git.basschouten.com Git - openhab-addons.git/blob
4ca69f11012e910402b31e2f4ac5d29189190471
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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
17 import java.math.BigDecimal;
18 import java.net.URL;
19 import java.util.Collections;
20 import java.util.HashMap;
21 import java.util.Map;
22 import java.util.Set;
23 import java.util.concurrent.ScheduledFuture;
24 import java.util.concurrent.TimeUnit;
25
26 import org.apache.commons.lang3.StringUtils;
27 import org.openhab.binding.wemo.internal.http.WemoHttpCall;
28 import org.openhab.core.config.core.Configuration;
29 import org.openhab.core.io.transport.upnp.UpnpIOParticipant;
30 import org.openhab.core.io.transport.upnp.UpnpIOService;
31 import org.openhab.core.library.types.DecimalType;
32 import org.openhab.core.library.types.StringType;
33 import org.openhab.core.thing.ChannelUID;
34 import org.openhab.core.thing.Thing;
35 import org.openhab.core.thing.ThingStatus;
36 import org.openhab.core.thing.ThingStatusDetail;
37 import org.openhab.core.thing.ThingTypeUID;
38 import org.openhab.core.types.Command;
39 import org.openhab.core.types.RefreshType;
40 import org.openhab.core.types.State;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 /**
45  * The {@link WemoCrockpotHandler} is responsible for handling commands, which are
46  * sent to one of the channels and to update their states.
47  *
48  * @author Hans-Jörg Merk - Initial contribution;
49  */
50
51 public class WemoCrockpotHandler extends AbstractWemoHandler implements UpnpIOParticipant {
52
53     private final Logger logger = LoggerFactory.getLogger(WemoCrockpotHandler.class);
54
55     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_CROCKPOT);
56     /**
57      * The default refresh interval in Seconds.
58      */
59     private static final int DEFAULT_REFRESH_INTERVAL_SECONDS = 120;
60     private final Map<String, Boolean> subscriptionState = new HashMap<>();
61     private final Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<>());
62
63     private UpnpIOService service;
64
65     private ScheduledFuture<?> refreshJob;
66
67     private final Runnable refreshRunnable = () -> {
68         updateWemoState();
69         if (!isUpnpDeviceRegistered()) {
70             logger.debug("WeMo UPnP device {} not yet registered", getUDN());
71         } else {
72             onSubscription();
73         }
74     };
75
76     public WemoCrockpotHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemohttpCaller) {
77         super(thing);
78
79         this.wemoHttpCaller = wemohttpCaller;
80
81         logger.debug("Creating a WemoCrockpotHandler for thing '{}'", getThing().getUID());
82
83         if (upnpIOService != null) {
84             this.service = upnpIOService;
85         } else {
86             logger.debug("upnpIOService not set.");
87         }
88     }
89
90     @Override
91     public void initialize() {
92         Configuration configuration = getConfig();
93
94         if (configuration.get("udn") != null) {
95             logger.debug("Initializing WemoCrockpotHandler for UDN '{}'", configuration.get("udn"));
96             service.registerParticipant(this);
97             onSubscription();
98             onUpdate();
99             updateStatus(ThingStatus.ONLINE);
100         } else {
101             logger.debug("Cannot initalize WemoCrockpotHandler. UDN not set.");
102         }
103     }
104
105     @Override
106     public void dispose() {
107         logger.debug("WeMoCrockpotHandler disposed.");
108
109         removeSubscription();
110
111         if (refreshJob != null && !refreshJob.isCancelled()) {
112             refreshJob.cancel(true);
113             refreshJob = null;
114         }
115     }
116
117     @Override
118     public void handleCommand(ChannelUID channelUID, Command command) {
119         logger.trace("Command '{}' received for channel '{}'", command, channelUID);
120         String mode = "0";
121         String time = null;
122
123         if (command instanceof RefreshType) {
124             updateWemoState();
125         } else if (CHANNEL_COOKMODE.equals(channelUID.getId())) {
126             String commandString = command.toString();
127             switch (commandString) {
128                 case "OFF":
129                     mode = "0";
130                     time = "0";
131                     break;
132                 case "WARM":
133                     mode = "50";
134                     break;
135                 case "LOW":
136                     mode = "51";
137                     break;
138                 case "HIGH":
139                     mode = "52";
140                     break;
141             }
142             try {
143                 String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\"";
144                 String content = "<?xml version=\"1.0\"?>"
145                         + "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
146                         + "<s:Body>" + "<u:SetCrockpotState xmlns:u=\"urn:Belkin:service:basicevent:1\">" + "<mode>"
147                         + mode + "</mode>" + "<time>" + time + "</time>" + "</u:SetCrockpotState>" + "</s:Body>"
148                         + "</s:Envelope>";
149                 String wemoURL = getWemoURL("basicevent");
150
151                 if (wemoURL != null) {
152                     wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
153                 }
154             } catch (RuntimeException e) {
155                 logger.debug("Failed to send command '{}' for device '{}':", command, getThing().getUID(), e);
156                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
157             }
158             updateStatus(ThingStatus.ONLINE);
159         }
160     }
161
162     @Override
163     public void onServiceSubscribed(String service, boolean succeeded) {
164         logger.debug("WeMo {}: Subscription to service {} {}", getUDN(), service, succeeded ? "succeeded" : "failed");
165         subscriptionState.put(service, succeeded);
166     }
167
168     @Override
169     public void onValueReceived(String variable, String value, String service) {
170         logger.debug("Received pair '{}':'{}' (service '{}') for thing '{}'", variable, value, service,
171                 this.getThing().getUID());
172
173         updateStatus(ThingStatus.ONLINE);
174         this.stateMap.put(variable, value);
175     }
176
177     private synchronized void onSubscription() {
178         if (service.isRegistered(this)) {
179             logger.debug("Checking WeMo GENA subscription for '{}'", this);
180
181             String subscription = "basicevent1";
182
183             if ((subscriptionState.get(subscription) == null) || !subscriptionState.get(subscription).booleanValue()) {
184                 logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(), subscription);
185                 service.addSubscription(this, subscription, SUBSCRIPTION_DURATION);
186                 subscriptionState.put(subscription, true);
187             }
188
189         } else {
190             logger.debug("Setting up WeMo GENA subscription for '{}' FAILED - service.isRegistered(this) is FALSE",
191                     this);
192         }
193     }
194
195     private synchronized void removeSubscription() {
196         logger.debug("Removing WeMo GENA subscription for '{}'", this);
197
198         if (service.isRegistered(this)) {
199             String subscription = "basicevent1";
200
201             if ((subscriptionState.get(subscription) != null) && subscriptionState.get(subscription).booleanValue()) {
202                 logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription);
203                 service.removeSubscription(this, subscription);
204             }
205
206             subscriptionState.remove(subscription);
207             service.unregisterParticipant(this);
208         }
209     }
210
211     private synchronized void onUpdate() {
212         if (refreshJob == null || refreshJob.isCancelled()) {
213             Configuration config = getThing().getConfiguration();
214             int refreshInterval = DEFAULT_REFRESH_INTERVAL_SECONDS;
215             Object refreshConfig = config.get("refresh");
216             refreshInterval = refreshConfig == null ? DEFAULT_REFRESH_INTERVAL_SECONDS
217                     : ((BigDecimal) refreshConfig).intValue();
218             refreshJob = scheduler.scheduleWithFixedDelay(refreshRunnable, 0, refreshInterval, TimeUnit.SECONDS);
219         }
220     }
221
222     private boolean isUpnpDeviceRegistered() {
223         return service.isRegistered(this);
224     }
225
226     @Override
227     public String getUDN() {
228         return (String) this.getThing().getConfiguration().get(UDN);
229     }
230
231     /**
232      * The {@link updateWemoState} polls the actual state of a WeMo device and
233      * calls {@link onValueReceived} to update the statemap and channels..
234      *
235      */
236     protected void updateWemoState() {
237         String action = "GetCrockpotState";
238         String actionService = "basicevent";
239
240         String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
241         String content = "<?xml version=\"1.0\"?>"
242                 + "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
243                 + "<s:Body>" + "<u:" + action + " xmlns:u=\"urn:Belkin:service:" + actionService + ":1\">" + "</u:"
244                 + action + ">" + "</s:Body>" + "</s:Envelope>";
245
246         try {
247             String wemoURL = getWemoURL(actionService);
248             if (wemoURL != null) {
249                 String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
250                 if (wemoCallResponse != null) {
251                     logger.trace("State response '{}' for device '{}' received", wemoCallResponse, getThing().getUID());
252                     String mode = StringUtils.substringBetween(wemoCallResponse, "<mode>", "</mode>");
253                     String time = StringUtils.substringBetween(wemoCallResponse, "<time>", "</time>");
254                     String coockedTime = StringUtils.substringBetween(wemoCallResponse, "<coockedTime>",
255                             "</coockedTime>");
256
257                     if (mode != null && time != null && coockedTime != null) {
258                         State newMode = new StringType(mode);
259                         State newCoockedTime = DecimalType.valueOf(coockedTime);
260                         switch (mode) {
261                             case "0":
262                                 newMode = new StringType("OFF");
263                                 break;
264                             case "50":
265                                 newMode = new StringType("WARM");
266                                 State warmTime = DecimalType.valueOf(time);
267                                 updateState(CHANNEL_WARMCOOKTIME, warmTime);
268                                 break;
269                             case "51":
270                                 newMode = new StringType("LOW");
271                                 State lowTime = DecimalType.valueOf(time);
272                                 updateState(CHANNEL_LOWCOOKTIME, lowTime);
273                                 break;
274                             case "52":
275                                 newMode = new StringType("HIGH");
276                                 State highTime = DecimalType.valueOf(time);
277                                 updateState(CHANNEL_HIGHCOOKTIME, highTime);
278                                 break;
279                         }
280                         updateState(CHANNEL_COOKMODE, newMode);
281                         updateState(CHANNEL_COOKEDTIME, newCoockedTime);
282                     }
283                 }
284             }
285         } catch (RuntimeException e) {
286             logger.debug("Failed to get actual state for device '{}': {}", getThing().getUID(), e.getMessage(), e);
287             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
288         }
289         updateStatus(ThingStatus.ONLINE);
290     }
291
292     public String getWemoURL(String actionService) {
293         URL descriptorURL = service.getDescriptorURL(this);
294         String wemoURL = null;
295         if (descriptorURL != null) {
296             String deviceURL = StringUtils.substringBefore(descriptorURL.toString(), "/setup.xml");
297             wemoURL = deviceURL + "/upnp/control/" + actionService + "1";
298             return wemoURL;
299         }
300         return null;
301     }
302
303     @Override
304     public void onStatusChanged(boolean status) {
305     }
306 }