]> git.basschouten.com Git - openhab-addons.git/blob
efec9447b2ad847afe9c1e0ebdf89ab417bc2c5e
[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.jupnp.UpnpService;
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.UpnpIOService;
32 import org.openhab.core.library.types.DecimalType;
33 import org.openhab.core.library.types.StringType;
34 import org.openhab.core.thing.ChannelUID;
35 import org.openhab.core.thing.Thing;
36 import org.openhab.core.thing.ThingStatus;
37 import org.openhab.core.thing.ThingStatusDetail;
38 import org.openhab.core.thing.ThingTypeUID;
39 import org.openhab.core.types.Command;
40 import org.openhab.core.types.RefreshType;
41 import org.openhab.core.types.State;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45 /**
46  * The {@link WemoCrockpotHandler} is responsible for handling commands, which are
47  * sent to one of the channels and to update their states.
48  *
49  * @author Hans-Jörg Merk - Initial contribution;
50  */
51 @NonNullByDefault
52 public class WemoCrockpotHandler extends WemoBaseThingHandler {
53
54     private final Logger logger = LoggerFactory.getLogger(WemoCrockpotHandler.class);
55
56     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_CROCKPOT);
57
58     private final Object jobLock = new Object();
59
60     private final Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<>());
61
62     private @Nullable ScheduledFuture<?> pollingJob;
63
64     public WemoCrockpotHandler(Thing thing, UpnpIOService upnpIOService, UpnpService upnpService,
65             WemoHttpCall wemoHttpCaller) {
66         super(thing, upnpIOService, upnpService, wemoHttpCaller);
67
68         logger.debug("Creating a WemoCrockpotHandler for thing '{}'", getThing().getUID());
69     }
70
71     @Override
72     public void initialize() {
73         super.initialize();
74         Configuration configuration = getConfig();
75
76         if (configuration.get(UDN) != null) {
77             logger.debug("Initializing WemoCrockpotHandler for UDN '{}'", configuration.get(UDN));
78             addSubscription(BASICEVENT);
79             pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS,
80                     TimeUnit.SECONDS);
81             updateStatus(ThingStatus.UNKNOWN);
82         } else {
83             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
84                     "@text/config-status.error.missing-udn");
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                 // Check if the Wemo device is set in the UPnP service registry
107                 if (!isUpnpDeviceRegistered()) {
108                     logger.debug("UPnP device {} not yet registered", getUDN());
109                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
110                             "@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
111                     return;
112                 }
113                 updateWemoState();
114             } catch (Exception e) {
115                 logger.debug("Exception during poll: {}", e.getMessage(), e);
116             }
117         }
118     }
119
120     @Override
121     public void handleCommand(ChannelUID channelUID, Command command) {
122         String wemoURL = getWemoURL(BASICACTION);
123         if (wemoURL == null) {
124             logger.debug("Failed to send command '{}' for device '{}': URL cannot be created", command,
125                     getThing().getUID());
126             return;
127         }
128         String mode = "0";
129         String time = null;
130
131         if (command instanceof RefreshType) {
132             updateWemoState();
133         } else if (CHANNEL_COOK_MODE.equals(channelUID.getId())) {
134             String commandString = command.toString();
135             switch (commandString) {
136                 case "OFF":
137                     mode = "0";
138                     time = "0";
139                     break;
140                 case "WARM":
141                     mode = "50";
142                     break;
143                 case "LOW":
144                     mode = "51";
145                     break;
146                 case "HIGH":
147                     mode = "52";
148                     break;
149             }
150             try {
151                 String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\"";
152                 String content = "<?xml version=\"1.0\"?>"
153                         + "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
154                         + "<s:Body>" + "<u:SetCrockpotState xmlns:u=\"urn:Belkin:service:basicevent:1\">" + "<mode>"
155                         + mode + "</mode>" + "<time>" + time + "</time>" + "</u:SetCrockpotState>" + "</s:Body>"
156                         + "</s:Envelope>";
157                 wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
158                 updateStatus(ThingStatus.ONLINE);
159             } catch (IOException e) {
160                 logger.debug("Failed to send command '{}' for device '{}':", command, getThing().getUID(), e);
161                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
162             }
163         }
164     }
165
166     @Override
167     public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) {
168         logger.debug("Received pair '{}':'{}' (service '{}') for thing '{}'", variable, value, service,
169                 this.getThing().getUID());
170
171         updateStatus(ThingStatus.ONLINE);
172         if (variable != null && value != null) {
173             this.stateMap.put(variable, value);
174         }
175     }
176
177     /**
178      * The {@link updateWemoState} polls the actual state of a WeMo device and
179      * calls {@link onValueReceived} to update the statemap and channels..
180      *
181      */
182     protected void updateWemoState() {
183         String actionService = BASICEVENT;
184         String wemoURL = getWemoURL(actionService);
185         if (wemoURL == null) {
186             logger.warn("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID());
187             return;
188         }
189         try {
190             String action = "GetCrockpotState";
191             String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
192             String content = createStateRequestContent(action, actionService);
193             String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
194             String mode = substringBetween(wemoCallResponse, "<mode>", "</mode>");
195             String time = substringBetween(wemoCallResponse, "<time>", "</time>");
196             String coockedTime = substringBetween(wemoCallResponse, "<coockedTime>", "</coockedTime>");
197
198             State newMode = new StringType(mode);
199             State newCoockedTime = DecimalType.valueOf(coockedTime);
200             switch (mode) {
201                 case "0":
202                     newMode = new StringType("OFF");
203                     break;
204                 case "50":
205                     newMode = new StringType("WARM");
206                     State warmTime = DecimalType.valueOf(time);
207                     updateState(CHANNEL_WARM_COOK_TIME, warmTime);
208                     break;
209                 case "51":
210                     newMode = new StringType("LOW");
211                     State lowTime = DecimalType.valueOf(time);
212                     updateState(CHANNEL_LOW_COOK_TIME, lowTime);
213                     break;
214                 case "52":
215                     newMode = new StringType("HIGH");
216                     State highTime = DecimalType.valueOf(time);
217                     updateState(CHANNEL_HIGHCOOKTIME, highTime);
218                     break;
219             }
220             updateState(CHANNEL_COOK_MODE, newMode);
221             updateState(CHANNEL_COOKED_TIME, newCoockedTime);
222             updateStatus(ThingStatus.ONLINE);
223         } catch (IOException e) {
224             logger.debug("Failed to get actual state for device '{}': {}", getThing().getUID(), e.getMessage(), e);
225             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
226         }
227     }
228 }