2 * Copyright (c) 2010-2022 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.ONLINE);
81 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
82 "@text/config-status.error.missing-udn");
83 logger.debug("Cannot initalize WemoCrockpotHandler. UDN not set.");
88 public void dispose() {
89 logger.debug("WeMoCrockpotHandler disposed.");
90 ScheduledFuture<?> job = this.pollingJob;
91 if (job != null && !job.isCancelled()) {
94 this.pollingJob = null;
99 synchronized (jobLock) {
100 if (pollingJob == null) {
104 logger.debug("Polling job");
105 // Check if the Wemo device is set in the UPnP service registry
106 // If not, set the thing state to ONLINE/CONFIG-PENDING and wait for the next poll
107 if (!isUpnpDeviceRegistered()) {
108 logger.debug("UPnP device {} not yet registered", getUDN());
109 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING,
110 "@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
114 } catch (Exception e) {
115 logger.debug("Exception during poll: {}", e.getMessage(), e);
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());
131 if (command instanceof RefreshType) {
133 } else if (CHANNEL_COOKMODE.equals(channelUID.getId())) {
134 String commandString = command.toString();
135 switch (commandString) {
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>"
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());
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());
171 updateStatus(ThingStatus.ONLINE);
172 if (variable != null && value != null) {
173 this.stateMap.put(variable, value);
178 * The {@link updateWemoState} polls the actual state of a WeMo device and
179 * calls {@link onValueReceived} to update the statemap and channels..
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());
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>");
198 State newMode = new StringType(mode);
199 State newCoockedTime = DecimalType.valueOf(coockedTime);
202 newMode = new StringType("OFF");
205 newMode = new StringType("WARM");
206 State warmTime = DecimalType.valueOf(time);
207 updateState(CHANNEL_WARMCOOKTIME, warmTime);
210 newMode = new StringType("LOW");
211 State lowTime = DecimalType.valueOf(time);
212 updateState(CHANNEL_LOWCOOKTIME, lowTime);
215 newMode = new StringType("HIGH");
216 State highTime = DecimalType.valueOf(time);
217 updateState(CHANNEL_HIGHCOOKTIME, highTime);
220 updateState(CHANNEL_COOKMODE, newMode);
221 updateState(CHANNEL_COOKEDTIME, 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());