]> git.basschouten.com Git - openhab-addons.git/blob
7abd6f56da5559b3012526633acc04a4a9913b81
[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.io.IOException;
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.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.wemo.internal.http.WemoHttpCall;
29 import org.openhab.core.config.core.Configuration;
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 @NonNullByDefault
51 public class WemoCrockpotHandler extends WemoBaseThingHandler {
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     private final Object jobLock = new Object();
58
59     private final Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<>());
60
61     private @Nullable ScheduledFuture<?> pollingJob;
62
63     public WemoCrockpotHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
64         super(thing, upnpIOService, wemoHttpCaller);
65
66         logger.debug("Creating a WemoCrockpotHandler for thing '{}'", getThing().getUID());
67     }
68
69     @Override
70     public void initialize() {
71         super.initialize();
72         Configuration configuration = getConfig();
73
74         if (configuration.get(UDN) != null) {
75             logger.debug("Initializing WemoCrockpotHandler for UDN '{}'", configuration.get(UDN));
76             addSubscription(BASICEVENT);
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 WemoCrockpotHandler. UDN not set.");
85         }
86     }
87
88     @Override
89     public void dispose() {
90         logger.debug("WeMoCrockpotHandler disposed.");
91         ScheduledFuture<?> job = this.pollingJob;
92         if (job != null && !job.isCancelled()) {
93             job.cancel(true);
94         }
95         this.pollingJob = null;
96         super.dispose();
97     }
98
99     private void poll() {
100         synchronized (jobLock) {
101             if (pollingJob == null) {
102                 return;
103             }
104             try {
105                 logger.debug("Polling job");
106                 host = getHost();
107                 // Check if the Wemo device is set in the UPnP service registry
108                 // If not, set the thing state to ONLINE/CONFIG-PENDING and wait for the next poll
109                 if (!isUpnpDeviceRegistered()) {
110                     logger.debug("UPnP device {} not yet registered", getUDN());
111                     updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING,
112                             "@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
113                     return;
114                 }
115                 updateWemoState();
116             } catch (Exception e) {
117                 logger.debug("Exception during poll: {}", e.getMessage(), e);
118             }
119         }
120     }
121
122     @Override
123     public void handleCommand(ChannelUID channelUID, Command command) {
124         String localHost = getHost();
125         if (localHost.isEmpty()) {
126             logger.warn("Failed to send command '{}' for device '{}': IP address missing", command,
127                     getThing().getUID());
128             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
129                     "@text/config-status.error.missing-ip");
130             return;
131         }
132         String wemoURL = getWemoURL(localHost, BASICACTION);
133         if (wemoURL == null) {
134             logger.debug("Failed to send command '{}' for device '{}': URL cannot be created", command,
135                     getThing().getUID());
136             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
137                     "@text/config-status.error.missing-url");
138             return;
139         }
140         String mode = "0";
141         String time = null;
142
143         if (command instanceof RefreshType) {
144             updateWemoState();
145         } else if (CHANNEL_COOKMODE.equals(channelUID.getId())) {
146             String commandString = command.toString();
147             switch (commandString) {
148                 case "OFF":
149                     mode = "0";
150                     time = "0";
151                     break;
152                 case "WARM":
153                     mode = "50";
154                     break;
155                 case "LOW":
156                     mode = "51";
157                     break;
158                 case "HIGH":
159                     mode = "52";
160                     break;
161             }
162             try {
163                 String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\"";
164                 String content = "<?xml version=\"1.0\"?>"
165                         + "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
166                         + "<s:Body>" + "<u:SetCrockpotState xmlns:u=\"urn:Belkin:service:basicevent:1\">" + "<mode>"
167                         + mode + "</mode>" + "<time>" + time + "</time>" + "</u:SetCrockpotState>" + "</s:Body>"
168                         + "</s:Envelope>";
169                 wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
170                 updateStatus(ThingStatus.ONLINE);
171             } catch (IOException e) {
172                 logger.debug("Failed to send command '{}' for device '{}':", command, getThing().getUID(), e);
173                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
174             }
175         }
176     }
177
178     @Override
179     public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) {
180         logger.debug("Received pair '{}':'{}' (service '{}') for thing '{}'", variable, value, service,
181                 this.getThing().getUID());
182
183         updateStatus(ThingStatus.ONLINE);
184         if (variable != null && value != null) {
185             this.stateMap.put(variable, value);
186         }
187     }
188
189     /**
190      * The {@link updateWemoState} polls the actual state of a WeMo device and
191      * calls {@link onValueReceived} to update the statemap and channels..
192      *
193      */
194     protected void updateWemoState() {
195         String localHost = getHost();
196         if (localHost.isEmpty()) {
197             logger.warn("Failed to get actual state for device '{}': IP address missing", getThing().getUID());
198             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
199                     "@text/config-status.error.missing-ip");
200             return;
201         }
202         String actionService = BASICEVENT;
203         String wemoURL = getWemoURL(localHost, actionService);
204         if (wemoURL == null) {
205             logger.warn("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID());
206             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
207                     "@text/config-status.error.missing-url");
208             return;
209         }
210         try {
211             String action = "GetCrockpotState";
212             String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
213             String content = createStateRequestContent(action, actionService);
214             String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
215             String mode = substringBetween(wemoCallResponse, "<mode>", "</mode>");
216             String time = substringBetween(wemoCallResponse, "<time>", "</time>");
217             String coockedTime = substringBetween(wemoCallResponse, "<coockedTime>", "</coockedTime>");
218
219             State newMode = new StringType(mode);
220             State newCoockedTime = DecimalType.valueOf(coockedTime);
221             switch (mode) {
222                 case "0":
223                     newMode = new StringType("OFF");
224                     break;
225                 case "50":
226                     newMode = new StringType("WARM");
227                     State warmTime = DecimalType.valueOf(time);
228                     updateState(CHANNEL_WARMCOOKTIME, warmTime);
229                     break;
230                 case "51":
231                     newMode = new StringType("LOW");
232                     State lowTime = DecimalType.valueOf(time);
233                     updateState(CHANNEL_LOWCOOKTIME, lowTime);
234                     break;
235                 case "52":
236                     newMode = new StringType("HIGH");
237                     State highTime = DecimalType.valueOf(time);
238                     updateState(CHANNEL_HIGHCOOKTIME, highTime);
239                     break;
240             }
241             updateState(CHANNEL_COOKMODE, newMode);
242             updateState(CHANNEL_COOKEDTIME, newCoockedTime);
243             updateStatus(ThingStatus.ONLINE);
244         } catch (IOException e) {
245             logger.debug("Failed to get actual state for device '{}': {}", getThing().getUID(), e.getMessage(), e);
246             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
247         }
248     }
249 }