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