]> git.basschouten.com Git - openhab-addons.git/blob
e06ac3950597e12a697161d6a56a184559655002
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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 = Set.of(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             pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS,
78                     TimeUnit.SECONDS);
79             updateStatus(ThingStatus.UNKNOWN);
80         } else {
81             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
82                     "@text/config-status.error.missing-udn");
83         }
84     }
85
86     @Override
87     public void dispose() {
88         logger.debug("WeMoCrockpotHandler disposed.");
89         ScheduledFuture<?> job = this.pollingJob;
90         if (job != null && !job.isCancelled()) {
91             job.cancel(true);
92         }
93         this.pollingJob = null;
94         super.dispose();
95     }
96
97     private void poll() {
98         synchronized (jobLock) {
99             if (pollingJob == null) {
100                 return;
101             }
102             try {
103                 logger.debug("Polling job");
104                 // Check if the Wemo device is set in the UPnP service registry
105                 if (!isUpnpDeviceRegistered()) {
106                     logger.debug("UPnP device {} not yet registered", getUDN());
107                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
108                             "@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
109                     return;
110                 }
111                 updateWemoState();
112             } catch (Exception e) {
113                 logger.debug("Exception during poll: {}", e.getMessage(), e);
114             }
115         }
116     }
117
118     @Override
119     public void handleCommand(ChannelUID channelUID, Command command) {
120         String wemoURL = getWemoURL(BASICACTION);
121         if (wemoURL == null) {
122             logger.debug("Failed to send command '{}' for device '{}': URL cannot be created", command,
123                     getThing().getUID());
124             return;
125         }
126         String mode = "0";
127         String time = null;
128
129         if (command instanceof RefreshType) {
130             updateWemoState();
131         } else if (CHANNEL_COOK_MODE.equals(channelUID.getId())) {
132             String commandString = command.toString();
133             switch (commandString) {
134                 case "OFF":
135                     mode = "0";
136                     time = "0";
137                     break;
138                 case "WARM":
139                     mode = "50";
140                     break;
141                 case "LOW":
142                     mode = "51";
143                     break;
144                 case "HIGH":
145                     mode = "52";
146                     break;
147             }
148             try {
149                 String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\"";
150                 String content = """
151                         <?xml version="1.0"?>\
152                         <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">\
153                         <s:Body>\
154                         <u:SetCrockpotState xmlns:u="urn:Belkin:service:basicevent:1">\
155                         <mode>\
156                         """
157                         + mode + "</mode>" + "<time>" + time + "</time>" + "</u:SetCrockpotState>" + "</s:Body>"
158                         + "</s:Envelope>";
159                 wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
160                 updateStatus(ThingStatus.ONLINE);
161             } catch (IOException e) {
162                 logger.debug("Failed to send command '{}' for device '{}':", command, getThing().getUID(), e);
163                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
164             }
165         }
166     }
167
168     @Override
169     public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) {
170         logger.debug("Received pair '{}':'{}' (service '{}') for thing '{}'", variable, value, service,
171                 this.getThing().getUID());
172
173         updateStatus(ThingStatus.ONLINE);
174         if (variable != null && value != null) {
175             this.stateMap.put(variable, value);
176         }
177     }
178
179     /**
180      * The {@link updateWemoState} polls the actual state of a WeMo device and
181      * calls {@link onValueReceived} to update the statemap and channels..
182      *
183      */
184     protected void updateWemoState() {
185         String actionService = BASICEVENT;
186         String wemoURL = getWemoURL(actionService);
187         if (wemoURL == null) {
188             logger.warn("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID());
189             return;
190         }
191         try {
192             String action = "GetCrockpotState";
193             String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
194             String content = createStateRequestContent(action, actionService);
195             String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
196             String mode = substringBetween(wemoCallResponse, "<mode>", "</mode>");
197             String time = substringBetween(wemoCallResponse, "<time>", "</time>");
198             String coockedTime = substringBetween(wemoCallResponse, "<coockedTime>", "</coockedTime>");
199
200             State newMode = new StringType(mode);
201             State newCoockedTime = DecimalType.valueOf(coockedTime);
202             switch (mode) {
203                 case "0":
204                     newMode = new StringType("OFF");
205                     break;
206                 case "50":
207                     newMode = new StringType("WARM");
208                     State warmTime = DecimalType.valueOf(time);
209                     updateState(CHANNEL_WARM_COOK_TIME, warmTime);
210                     break;
211                 case "51":
212                     newMode = new StringType("LOW");
213                     State lowTime = DecimalType.valueOf(time);
214                     updateState(CHANNEL_LOW_COOK_TIME, lowTime);
215                     break;
216                 case "52":
217                     newMode = new StringType("HIGH");
218                     State highTime = DecimalType.valueOf(time);
219                     updateState(CHANNEL_HIGHCOOKTIME, highTime);
220                     break;
221             }
222             updateState(CHANNEL_COOK_MODE, newMode);
223             updateState(CHANNEL_COOKED_TIME, newCoockedTime);
224             updateStatus(ThingStatus.ONLINE);
225         } catch (IOException e) {
226             logger.debug("Failed to get actual state for device '{}': {}", getThing().getUID(), e.getMessage(), e);
227             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
228         }
229     }
230 }