2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.wemo.internal.handler;
15 import static org.openhab.binding.wemo.internal.WemoBindingConstants.*;
16 import static org.openhab.binding.wemo.internal.WemoUtil.*;
18 import java.io.IOException;
19 import java.util.Collections;
20 import java.util.HashMap;
23 import java.util.concurrent.ScheduledFuture;
24 import java.util.concurrent.TimeUnit;
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;
45 * The {@link WemoCrockpotHandler} is responsible for handling commands, which are
46 * sent to one of the channels and to update their states.
48 * @author Hans-Jörg Merk - Initial contribution;
51 public class WemoCrockpotHandler extends WemoBaseThingHandler {
53 private final Logger logger = LoggerFactory.getLogger(WemoCrockpotHandler.class);
55 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_CROCKPOT);
57 private final Object jobLock = new Object();
59 private final Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<>());
61 private @Nullable ScheduledFuture<?> pollingJob;
63 public WemoCrockpotHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
64 super(thing, upnpIOService, wemoHttpCaller);
66 logger.debug("Creating a WemoCrockpotHandler for thing '{}'", getThing().getUID());
70 public void initialize() {
72 Configuration configuration = getConfig();
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,
79 updateStatus(ThingStatus.UNKNOWN);
81 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
82 "@text/config-status.error.missing-udn");
87 public void dispose() {
88 logger.debug("WeMoCrockpotHandler disposed.");
89 ScheduledFuture<?> job = this.pollingJob;
90 if (job != null && !job.isCancelled()) {
93 this.pollingJob = null;
98 synchronized (jobLock) {
99 if (pollingJob == null) {
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() + "\"]");
112 } catch (Exception e) {
113 logger.debug("Exception during poll: {}", e.getMessage(), e);
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());
129 if (command instanceof RefreshType) {
131 } else if (CHANNEL_COOK_MODE.equals(channelUID.getId())) {
132 String commandString = command.toString();
133 switch (commandString) {
149 String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\"";
150 String content = "<?xml version=\"1.0\"?>"
151 + "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
152 + "<s:Body>" + "<u:SetCrockpotState xmlns:u=\"urn:Belkin:service:basicevent:1\">" + "<mode>"
153 + mode + "</mode>" + "<time>" + time + "</time>" + "</u:SetCrockpotState>" + "</s:Body>"
155 wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
156 updateStatus(ThingStatus.ONLINE);
157 } catch (IOException e) {
158 logger.debug("Failed to send command '{}' for device '{}':", command, getThing().getUID(), e);
159 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
165 public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) {
166 logger.debug("Received pair '{}':'{}' (service '{}') for thing '{}'", variable, value, service,
167 this.getThing().getUID());
169 updateStatus(ThingStatus.ONLINE);
170 if (variable != null && value != null) {
171 this.stateMap.put(variable, value);
176 * The {@link updateWemoState} polls the actual state of a WeMo device and
177 * calls {@link onValueReceived} to update the statemap and channels..
180 protected void updateWemoState() {
181 String actionService = BASICEVENT;
182 String wemoURL = getWemoURL(actionService);
183 if (wemoURL == null) {
184 logger.warn("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID());
188 String action = "GetCrockpotState";
189 String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
190 String content = createStateRequestContent(action, actionService);
191 String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
192 String mode = substringBetween(wemoCallResponse, "<mode>", "</mode>");
193 String time = substringBetween(wemoCallResponse, "<time>", "</time>");
194 String coockedTime = substringBetween(wemoCallResponse, "<coockedTime>", "</coockedTime>");
196 State newMode = new StringType(mode);
197 State newCoockedTime = DecimalType.valueOf(coockedTime);
200 newMode = new StringType("OFF");
203 newMode = new StringType("WARM");
204 State warmTime = DecimalType.valueOf(time);
205 updateState(CHANNEL_WARM_COOK_TIME, warmTime);
208 newMode = new StringType("LOW");
209 State lowTime = DecimalType.valueOf(time);
210 updateState(CHANNEL_LOW_COOK_TIME, lowTime);
213 newMode = new StringType("HIGH");
214 State highTime = DecimalType.valueOf(time);
215 updateState(CHANNEL_HIGHCOOKTIME, highTime);
218 updateState(CHANNEL_COOK_MODE, newMode);
219 updateState(CHANNEL_COOKED_TIME, newCoockedTime);
220 updateStatus(ThingStatus.ONLINE);
221 } catch (IOException e) {
222 logger.debug("Failed to get actual state for device '{}': {}", getThing().getUID(), e.getMessage(), e);
223 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());