2 * Copyright (c) 2010-2020 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.*;
17 import java.math.BigDecimal;
19 import java.time.Instant;
20 import java.time.ZonedDateTime;
21 import java.util.Collections;
22 import java.util.HashMap;
25 import java.util.TimeZone;
26 import java.util.concurrent.ScheduledFuture;
27 import java.util.concurrent.TimeUnit;
29 import org.apache.commons.lang.StringUtils;
30 import org.openhab.binding.wemo.internal.http.WemoHttpCall;
31 import org.openhab.core.config.core.Configuration;
32 import org.openhab.core.io.transport.upnp.UpnpIOParticipant;
33 import org.openhab.core.io.transport.upnp.UpnpIOService;
34 import org.openhab.core.library.types.DateTimeType;
35 import org.openhab.core.library.types.DecimalType;
36 import org.openhab.core.library.types.IncreaseDecreaseType;
37 import org.openhab.core.library.types.OnOffType;
38 import org.openhab.core.library.types.PercentType;
39 import org.openhab.core.thing.ChannelUID;
40 import org.openhab.core.thing.Thing;
41 import org.openhab.core.thing.ThingStatus;
42 import org.openhab.core.thing.ThingStatusDetail;
43 import org.openhab.core.thing.ThingTypeUID;
44 import org.openhab.core.types.Command;
45 import org.openhab.core.types.RefreshType;
46 import org.openhab.core.types.State;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
51 * The {@link WemoDimmerHandler} is responsible for handling commands, which are
52 * sent to one of the channels and to update their states.
54 * @author Hans-Jörg Merk - Initial contribution
57 public class WemoDimmerHandler extends AbstractWemoHandler implements UpnpIOParticipant {
59 private final Logger logger = LoggerFactory.getLogger(WemoDimmerHandler.class);
60 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_DIMMER);
61 private Map<String, Boolean> subscriptionState = new HashMap<>();
62 private Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<>());
63 protected static final int SUBSCRIPTION_DURATION = 600;
64 private UpnpIOService service;
65 private int currentBrightness;
66 private int currentNightModeBrightness;
67 private String currentNightModeState = null;
69 * Set dimming stepsize to 5%
71 private static final int DIM_STEPSIZE = 5;
73 * The default refresh interval in Seconds.
75 private int DEFAULT_REFRESH_INTERVAL = 60;
76 private ScheduledFuture<?> refreshJob;
77 private Runnable refreshRunnable = new Runnable() {
82 if (!isUpnpDeviceRegistered()) {
83 logger.debug("WeMo UPnP device {} not yet registered", getUDN());
87 } catch (Exception e) {
88 logger.debug("Exception during poll : {}", e.getMessage(), e);
93 public WemoDimmerHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemohttpCaller) {
95 this.wemoHttpCaller = wemohttpCaller;
96 logger.debug("Creating a WemoDimmerHandler for thing '{}'", getThing().getUID());
97 if (upnpIOService != null) {
98 this.service = upnpIOService;
100 logger.debug("upnpIOService not set.");
105 public void initialize() {
106 Configuration configuration = getConfig();
107 if (configuration.get("udn") != null) {
108 logger.debug("Initializing WemoDimmerHandler for UDN '{}'", configuration.get("udn"));
109 service.registerParticipant(this);
113 logger.debug("Cannot initalize WemoDimmerHandler. UDN not set.");
118 public void dispose() {
119 logger.debug("WeMoDimmerHandler disposed.");
120 removeSubscription();
121 if (refreshJob != null && !refreshJob.isCancelled()) {
122 refreshJob.cancel(true);
128 public void handleCommand(ChannelUID channelUID, Command command) {
129 logger.trace("Command '{}' received for channel '{}'", command, channelUID);
130 if (command instanceof RefreshType) {
133 } catch (Exception e) {
134 logger.debug("Exception during poll", e);
137 String action = "SetBinaryState";
138 String argument = "BinaryState";
140 String timeStamp = null;
141 switch (channelUID.getId()) {
142 case CHANNEL_BRIGHTNESS:
143 if (command instanceof OnOffType) {
144 value = command.equals(OnOffType.OFF) ? "0" : "1";
145 setBinaryState(action, argument, value);
146 if (command.equals(OnOffType.OFF)) {
147 State brightnessState = new PercentType("0");
148 updateState(CHANNEL_BRIGHTNESS, brightnessState);
149 updateState(CHANNEL_TIMERSTART, OnOffType.OFF);
151 State brightnessState = new PercentType(currentBrightness);
152 updateState(CHANNEL_BRIGHTNESS, brightnessState);
154 } else if (command instanceof PercentType) {
155 int newBrightness = ((PercentType) command).intValue();
156 value = String.valueOf(newBrightness);
157 currentBrightness = newBrightness;
158 argument = "brightness";
159 if (value.equals("0")) {
161 argument = "brightness";
162 setBinaryState(action, argument, "1");
164 argument = "BinaryState";
165 setBinaryState(action, argument, "0");
166 } else if (this.stateMap.get("BinaryState").equals("0")) {
167 argument = "BinaryState";
168 setBinaryState(action, argument, "1");
170 argument = "brightness";
171 setBinaryState(action, argument, value);
172 } else if (command instanceof IncreaseDecreaseType) {
174 switch (command.toString()) {
176 newBrightness = currentBrightness + DIM_STEPSIZE;
177 if (newBrightness > 100) {
180 value = String.valueOf(newBrightness);
181 currentBrightness = newBrightness;
184 newBrightness = currentBrightness - DIM_STEPSIZE;
185 if (newBrightness < 0) {
188 value = String.valueOf(newBrightness);
189 currentBrightness = newBrightness;
192 argument = "brightness";
193 if (value.equals("0")) {
195 argument = "brightness";
196 setBinaryState(action, argument, "1");
198 argument = "BinaryState";
199 setBinaryState(action, argument, "0");
200 } else if (this.stateMap.get("BinaryState").equals("0")) {
201 argument = "BinaryState";
202 setBinaryState(action, argument, "1");
204 argument = "brightness";
205 setBinaryState(action, argument, value);
208 case CHANNEL_FADERCOUNTDOWNTIME:
210 if (command instanceof DecimalType) {
211 int commandValue = Integer.valueOf(String.valueOf(command));
212 commandValue = commandValue * 60;
213 String commandString = String.valueOf(commandValue);
214 value = "<BinaryState></BinaryState>" + "<Duration></Duration>" + "<EndAction></EndAction>"
215 + "<brightness></brightness>" + "<fader>" + commandString + ":-1:1:0:0</fader>"
217 setBinaryState(action, argument, value);
220 case CHANNEL_FADERENABLED:
222 if (command.equals(OnOffType.ON)) {
223 value = "<BinaryState></BinaryState>" + "<Duration></Duration>" + "<EndAction></EndAction>"
224 + "<brightness></brightness>" + "<fader>600:-1:1:0:0</fader>" + "<UDN></UDN>";
225 } else if (command.equals(OnOffType.OFF)) {
226 value = "<BinaryState></BinaryState>" + "<Duration></Duration>" + "<EndAction></EndAction>"
227 + "<brightness></brightness>" + "<fader>600:-1:0:0:0</fader>" + "<UDN></UDN>";
229 setBinaryState(action, argument, value);
231 case CHANNEL_TIMERSTART:
233 long ts = System.currentTimeMillis() / 1000;
234 timeStamp = String.valueOf(ts);
235 logger.info("timestamp '{}' created", timeStamp);
236 String faderSeconds = null;
237 String faderEnabled = null;
238 String[] splitFader = this.stateMap.get("fader").split(":");
239 if (splitFader[0] != null) {
240 faderSeconds = splitFader[0];
242 if (splitFader[0] != null) {
243 faderEnabled = splitFader[2];
245 if (faderSeconds != null && faderEnabled != null) {
246 if (command.equals(OnOffType.ON)) {
247 value = "<BinaryState></BinaryState>" + "<Duration></Duration>" + "<EndAction></EndAction>"
248 + "<brightness></brightness>" + "<fader>" + faderSeconds + ":" + timeStamp + ":"
249 + faderEnabled + ":0:0</fader>" + "<UDN></UDN>";
250 updateState(CHANNEL_STATE, OnOffType.ON);
251 } else if (command.equals(OnOffType.OFF)) {
252 value = "<BinaryState></BinaryState>" + "<Duration></Duration>" + "<EndAction></EndAction>"
253 + "<brightness></brightness>" + "<fader>" + faderSeconds + ":-1:" + faderEnabled
254 + ":0:0</fader>" + "<UDN></UDN>";
257 setBinaryState(action, argument, value);
259 case CHANNEL_NIGHTMODE:
260 action = "ConfigureNightMode";
261 argument = "NightModeConfiguration";
262 String nightModeBrightness = String.valueOf(currentNightModeBrightness);
263 if (command.equals(OnOffType.ON)) {
264 value = "<startTime>0</startTime> \\n<nightMode>1</nightMode> \\n<endTime>23400</endTime> \\n<nightModeBrightness>"
265 + nightModeBrightness + "</nightModeBrightness> \\n";
266 } else if (command.equals(OnOffType.OFF)) {
267 value = "<startTime>0</startTime> \\n<nightMode>0</nightMode> \\n<endTime>23400</endTime> \\n<nightModeBrightness>"
268 + nightModeBrightness + "</nightModeBrightness> \\n";
270 setBinaryState(action, argument, value);
272 case CHANNEL_NIGHTMODEBRIGHTNESS:
273 action = "ConfigureNightMode";
274 argument = "NightModeConfiguration";
275 if (command instanceof PercentType) {
276 int newBrightness = ((PercentType) command).intValue();
277 String newNightModeBrightness = String.valueOf(newBrightness);
278 value = "<startTime>0</startTime> \\n<nightMode>" + currentNightModeState
279 + "</nightMode> \\n<endTime>23400</endTime> \\n<nightModeBrightness>"
280 + newNightModeBrightness + "</nightModeBrightness> \\n";
281 currentNightModeBrightness = newBrightness;
282 } else if (command instanceof IncreaseDecreaseType) {
284 String newNightModeBrightness = null;
285 switch (command.toString()) {
287 newBrightness = currentNightModeBrightness + DIM_STEPSIZE;
288 if (newBrightness > 100) {
291 newNightModeBrightness = String.valueOf(newBrightness);
292 currentBrightness = newBrightness;
295 newBrightness = currentNightModeBrightness - DIM_STEPSIZE;
296 if (newBrightness < 0) {
299 newNightModeBrightness = String.valueOf(newBrightness);
300 currentNightModeBrightness = newBrightness;
303 value = "<startTime>0</startTime> \\n<nightMode>" + currentNightModeState
304 + "</nightMode> \\n<endTime>23400</endTime> \\n<nightModeBrightness>"
305 + newNightModeBrightness + "</nightModeBrightness> \\n";
307 setBinaryState(action, argument, value);
314 public void onServiceSubscribed(String service, boolean succeeded) {
315 logger.debug("WeMo {}: Subscription to service {} {}", getUDN(), service, succeeded ? "succeeded" : "failed");
316 subscriptionState.put(service, succeeded);
320 public void onValueReceived(String variable, String value, String service) {
321 logger.debug("Received pair '{}':'{}' (service '{}') for thing '{}'",
322 new Object[] { variable, value, service, this.getThing().getUID() });
323 updateStatus(ThingStatus.ONLINE);
324 this.stateMap.put(variable, value);
327 State state = value.equals("0") ? OnOffType.OFF : OnOffType.ON;
328 logger.debug("State '{}' for device '{}' received", state, getThing().getUID());
329 updateState(CHANNEL_BRIGHTNESS, state);
330 if (state.equals(OnOffType.OFF)) {
331 updateState(CHANNEL_TIMERSTART, OnOffType.OFF);
335 logger.debug("brightness '{}' for device '{}' received", value, getThing().getUID());
336 int newBrightnessValue = Integer.valueOf(value);
337 State newBrightnessState = new PercentType(newBrightnessValue);
338 if (this.stateMap.get("BinaryState").equals("1")) {
339 updateState(CHANNEL_BRIGHTNESS, newBrightnessState);
341 currentBrightness = newBrightnessValue;
344 logger.debug("fader '{}' for device '{}' received", value, getThing().getUID());
345 String[] splitFader = value.split(":");
346 if (splitFader[0] != null) {
347 int faderSeconds = Integer.valueOf(splitFader[0]);
348 State faderMinutes = new DecimalType(faderSeconds / 60);
349 logger.debug("faderTime '{} minutes' for device '{}' received", faderMinutes, getThing().getUID());
350 updateState(CHANNEL_FADERCOUNTDOWNTIME, faderMinutes);
352 if (splitFader[1] != null) {
353 State isTimerRunning = splitFader[1].equals("-1") ? OnOffType.OFF : OnOffType.ON;
354 logger.debug("isTimerRunning '{}' for device '{}' received", isTimerRunning, getThing().getUID());
355 updateState(CHANNEL_TIMERSTART, isTimerRunning);
356 if (isTimerRunning.equals(OnOffType.ON)) {
357 updateState(CHANNEL_STATE, OnOffType.ON);
360 if (splitFader[2] != null) {
361 State isFaderEnabled = splitFader[1].equals("0") ? OnOffType.OFF : OnOffType.ON;
362 logger.debug("isFaderEnabled '{}' for device '{}' received", isFaderEnabled, getThing().getUID());
363 updateState(CHANNEL_FADERENABLED, isFaderEnabled);
367 State nightModeState = value.equals("0") ? OnOffType.OFF : OnOffType.ON;
368 currentNightModeState = value;
369 logger.debug("nightModeState '{}' for device '{}' received", nightModeState, getThing().getUID());
370 updateState(CHANNEL_NIGHTMODE, nightModeState);
373 State startTimeState = getDateTimeState(value);
374 logger.debug("startTimeState '{}' for device '{}' received", startTimeState, getThing().getUID());
375 updateState(CHANNEL_STARTTIME, startTimeState);
378 State endTimeState = getDateTimeState(value);
379 logger.debug("endTimeState '{}' for device '{}' received", endTimeState, getThing().getUID());
380 updateState(CHANNEL_ENDTIME, endTimeState);
382 case "nightModeBrightness":
383 int nightModeBrightnessValue = Integer.valueOf(value);
384 currentNightModeBrightness = nightModeBrightnessValue;
385 State nightModeBrightnessState = new PercentType(nightModeBrightnessValue);
386 logger.debug("nightModeBrightnessState '{}' for device '{}' received", nightModeBrightnessState,
387 getThing().getUID());
388 updateState(CHANNEL_NIGHTMODEBRIGHTNESS, nightModeBrightnessState);
393 private synchronized void onSubscription() {
394 if (service.isRegistered(this)) {
395 logger.debug("Checking WeMo GENA subscription for '{}'", this);
396 String subscription = "basicevent1";
397 if ((subscriptionState.get(subscription) == null) || !subscriptionState.get(subscription).booleanValue()) {
398 logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(), subscription);
399 service.addSubscription(this, subscription, SUBSCRIPTION_DURATION);
400 subscriptionState.put(subscription, true);
403 logger.debug("Setting up WeMo GENA subscription for '{}' FAILED - service.isRegistered(this) is FALSE",
408 private synchronized void removeSubscription() {
409 logger.debug("Removing WeMo GENA subscription for '{}'", this);
410 if (service.isRegistered(this)) {
411 String subscription = "basicevent1";
412 if ((subscriptionState.get(subscription) != null) && subscriptionState.get(subscription).booleanValue()) {
413 logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription);
414 service.removeSubscription(this, subscription);
416 subscriptionState = new HashMap<>();
417 service.unregisterParticipant(this);
421 private synchronized void onUpdate() {
422 if (refreshJob == null || refreshJob.isCancelled()) {
423 Configuration config = getThing().getConfiguration();
424 int refreshInterval = DEFAULT_REFRESH_INTERVAL;
425 Object refreshConfig = config.get("refresh");
426 if (refreshConfig != null) {
427 refreshInterval = ((BigDecimal) refreshConfig).intValue();
429 refreshJob = scheduler.scheduleWithFixedDelay(refreshRunnable, 10, refreshInterval, TimeUnit.SECONDS);
433 private boolean isUpnpDeviceRegistered() {
434 return service.isRegistered(this);
438 public String getUDN() {
439 return (String) this.getThing().getConfiguration().get(UDN);
443 * The {@link updateWemoState} polls the actual state of a WeMo device and
444 * calls {@link onValueReceived} to update the statemap and channels..
447 protected void updateWemoState() {
448 String action = "GetBinaryState";
449 String variable = null;
450 String actionService = "basicevent";
452 String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
453 String content = "<?xml version=\"1.0\"?>"
454 + "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
455 + "<s:Body>" + "<u:" + action + " xmlns:u=\"urn:Belkin:service:" + actionService + ":1\">" + "</u:"
456 + action + ">" + "</s:Body>" + "</s:Envelope>";
458 String wemoURL = getWemoURL(actionService);
459 if (wemoURL != null) {
460 String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
461 if (wemoCallResponse != null) {
462 logger.trace("State response '{}' for device '{}' received", wemoCallResponse, getThing().getUID());
463 value = StringUtils.substringBetween(wemoCallResponse, "<BinaryState>", "</BinaryState>");
465 variable = "BinaryState";
466 logger.trace("New state '{}' for device '{}' received", value, getThing().getUID());
467 this.onValueReceived(variable, value, actionService + "1");
469 value = StringUtils.substringBetween(wemoCallResponse, "<brightness>", "</brightness>");
471 variable = "brightness";
472 logger.trace("New brightness '{}' for device '{}' received", value, getThing().getUID());
473 this.onValueReceived(variable, value, actionService + "1");
475 value = StringUtils.substringBetween(wemoCallResponse, "<fader>", "</fader>");
478 logger.trace("New fader value '{}' for device '{}' received", value, getThing().getUID());
479 this.onValueReceived(variable, value, actionService + "1");
483 } catch (Exception e) {
484 logger.debug("Failed to get actual state for device '{}': {}", getThing().getUID(), e.getMessage());
485 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
487 updateStatus(ThingStatus.ONLINE);
488 action = "GetNightModeConfiguration";
491 soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
492 content = "<?xml version=\"1.0\"?>"
493 + "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
494 + "<s:Body>" + "<u:" + action + " xmlns:u=\"urn:Belkin:service:" + actionService + ":1\">" + "</u:"
495 + action + ">" + "</s:Body>" + "</s:Envelope>";
497 String wemoURL = getWemoURL(actionService);
498 if (wemoURL != null) {
499 String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
500 if (wemoCallResponse != null) {
501 logger.trace("GetNightModeConfiguration response '{}' for device '{}' received", wemoCallResponse,
502 getThing().getUID());
503 value = StringUtils.substringBetween(wemoCallResponse, "<startTime>", "</startTime>");
505 variable = "startTime";
506 logger.trace("New startTime '{}' for device '{}' received", value, getThing().getUID());
507 this.onValueReceived(variable, value, actionService + "1");
509 value = StringUtils.substringBetween(wemoCallResponse, "<endTime>", "</endTime>");
511 variable = "endTime";
512 logger.trace("New endTime '{}' for device '{}' received", value, getThing().getUID());
513 this.onValueReceived(variable, value, actionService + "1");
515 value = StringUtils.substringBetween(wemoCallResponse, "<nightMode>", "</nightMode>");
517 variable = "nightMode";
518 logger.trace("New nightMode state '{}' for device '{}' received", value, getThing().getUID());
519 this.onValueReceived(variable, value, actionService + "1");
521 value = StringUtils.substringBetween(wemoCallResponse, "<nightModeBrightness>",
522 "</nightModeBrightness>");
524 variable = "nightModeBrightness";
525 logger.trace("New nightModeBrightness '{}' for device '{}' received", value,
526 getThing().getUID());
527 this.onValueReceived(variable, value, actionService + "1");
531 } catch (Exception e) {
532 logger.debug("Failed to get actual NightMode state for device '{}': {}", getThing().getUID(),
534 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
536 updateStatus(ThingStatus.ONLINE);
539 public String getWemoURL(String actionService) {
540 URL descriptorURL = service.getDescriptorURL(this);
541 String wemoURL = null;
542 if (descriptorURL != null) {
543 String deviceURL = StringUtils.substringBefore(descriptorURL.toString(), "/setup.xml");
544 wemoURL = deviceURL + "/upnp/control/" + actionService + "1";
550 @SuppressWarnings("null")
551 public State getDateTimeState(String attributeValue) {
552 if (attributeValue != null) {
555 value = Long.parseLong(attributeValue) * 1000; // convert s to ms
556 } catch (NumberFormatException e) {
557 logger.warn("Unable to parse attributeValue '{}' for device '{}'; expected long", attributeValue,
558 getThing().getUID());
561 ZonedDateTime zoned = ZonedDateTime.ofInstant(Instant.ofEpochMilli(value),
562 TimeZone.getDefault().toZoneId());
563 State dateTimeState = new DateTimeType(zoned);
564 if (dateTimeState != null) {
565 logger.trace("New attribute '{}' received", dateTimeState);
566 return dateTimeState;
572 public void setBinaryState(String action, String argument, String value) {
574 String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\"";
575 String content = "<?xml version=\"1.0\"?>"
576 + "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
577 + "<s:Body>" + "<u:" + action + " xmlns:u=\"urn:Belkin:service:basicevent:1\">" + "<" + argument
578 + ">" + value + "</" + argument + ">" + "</u:" + action + ">" + "</s:Body>" + "</s:Envelope>";
579 String wemoURL = getWemoURL("basicevent");
580 if (wemoURL != null) {
581 logger.trace("About to send content to Dimmer {}", content);
582 wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
584 } catch (Exception e) {
585 logger.debug("Failed to set binaryState '{}' for device '{}': {}", value, getThing().getUID(),
587 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
591 public void setTimerStart(String action, String argument, String value) {
593 String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\"";
594 String content = "<?xml version=\"1.0\"?>"
595 + "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
596 + "<s:Body>" + "<u:SetBinaryState xmlns:u=\"urn:Belkin:service:basicevent:1\">" + value
597 + "</u:SetBinaryState>" + "</s:Body>" + "</s:Envelope>";
598 String wemoURL = getWemoURL("basicevent");
599 if (wemoURL != null) {
600 logger.trace("About to send content to Dimmer {}", content);
601 wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
603 } catch (Exception e) {
604 logger.debug("Failed to set binaryState '{}' for device '{}': {}", value, getThing().getUID(),
606 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
611 public void onStatusChanged(boolean status) {