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 = Set.of(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\"";
151 <?xml version="1.0"?>\
152 <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">\
154 <u:SetCrockpotState xmlns:u="urn:Belkin:service:basicevent:1">\
157 + mode + "</mode>" + "<time>" + time + "</time>" + "</u:SetCrockpotState>" + "</s:Body>"
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());
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());
173 updateStatus(ThingStatus.ONLINE);
174 if (variable != null && value != null) {
175 this.stateMap.put(variable, value);
180 * The {@link updateWemoState} polls the actual state of a WeMo device and
181 * calls {@link onValueReceived} to update the statemap and channels..
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());
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>");
200 State newMode = new StringType(mode);
201 State newCoockedTime = DecimalType.valueOf(coockedTime);
204 newMode = new StringType("OFF");
207 newMode = new StringType("WARM");
208 State warmTime = DecimalType.valueOf(time);
209 updateState(CHANNEL_WARM_COOK_TIME, warmTime);
212 newMode = new StringType("LOW");
213 State lowTime = DecimalType.valueOf(time);
214 updateState(CHANNEL_LOW_COOK_TIME, lowTime);
217 newMode = new StringType("HIGH");
218 State highTime = DecimalType.valueOf(time);
219 updateState(CHANNEL_HIGHCOOKTIME, highTime);
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());