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.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;
46 * The {@link WemoCrockpotHandler} is responsible for handling commands, which are
47 * sent to one of the channels and to update their states.
49 * @author Hans-Jörg Merk - Initial contribution;
52 public class WemoCrockpotHandler extends WemoBaseThingHandler {
54 private final Logger logger = LoggerFactory.getLogger(WemoCrockpotHandler.class);
56 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_CROCKPOT);
58 private final Object jobLock = new Object();
60 private final Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<>());
62 private @Nullable ScheduledFuture<?> pollingJob;
64 public WemoCrockpotHandler(Thing thing, UpnpIOService upnpIOService, UpnpService upnpService,
65 WemoHttpCall wemoHttpCaller) {
66 super(thing, upnpIOService, upnpService, wemoHttpCaller);
68 logger.debug("Creating a WemoCrockpotHandler for thing '{}'", getThing().getUID());
72 public void initialize() {
74 Configuration configuration = getConfig();
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,
81 updateStatus(ThingStatus.UNKNOWN);
83 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
84 "@text/config-status.error.missing-udn");
89 public void dispose() {
90 logger.debug("WeMoCrockpotHandler disposed.");
91 ScheduledFuture<?> job = this.pollingJob;
92 if (job != null && !job.isCancelled()) {
95 this.pollingJob = null;
100 synchronized (jobLock) {
101 if (pollingJob == null) {
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() + "\"]");
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_COOK_MODE.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_WARM_COOK_TIME, warmTime);
210 newMode = new StringType("LOW");
211 State lowTime = DecimalType.valueOf(time);
212 updateState(CHANNEL_LOW_COOK_TIME, lowTime);
215 newMode = new StringType("HIGH");
216 State highTime = DecimalType.valueOf(time);
217 updateState(CHANNEL_HIGHCOOKTIME, highTime);
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());