]> git.basschouten.com Git - openhab-addons.git/blob
f864d402500aec005de68bd4731d3debf0f3a68a
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.wemo.internal.handler;
14
15 import static org.openhab.binding.wemo.internal.WemoBindingConstants.*;
16 import static org.openhab.binding.wemo.internal.WemoUtil.*;
17
18 import java.net.URL;
19 import java.time.Instant;
20 import java.time.ZonedDateTime;
21 import java.util.Collections;
22 import java.util.HashMap;
23 import java.util.Map;
24 import java.util.Set;
25 import java.util.TimeZone;
26 import java.util.concurrent.ScheduledFuture;
27 import java.util.concurrent.TimeUnit;
28
29 import org.eclipse.jdt.annotation.NonNullByDefault;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.openhab.binding.wemo.internal.http.WemoHttpCall;
32 import org.openhab.core.config.core.Configuration;
33 import org.openhab.core.io.transport.upnp.UpnpIOParticipant;
34 import org.openhab.core.io.transport.upnp.UpnpIOService;
35 import org.openhab.core.library.types.DateTimeType;
36 import org.openhab.core.library.types.DecimalType;
37 import org.openhab.core.library.types.IncreaseDecreaseType;
38 import org.openhab.core.library.types.OnOffType;
39 import org.openhab.core.library.types.PercentType;
40 import org.openhab.core.thing.ChannelUID;
41 import org.openhab.core.thing.Thing;
42 import org.openhab.core.thing.ThingStatus;
43 import org.openhab.core.thing.ThingStatusDetail;
44 import org.openhab.core.thing.ThingTypeUID;
45 import org.openhab.core.types.Command;
46 import org.openhab.core.types.RefreshType;
47 import org.openhab.core.types.State;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51 /**
52  * The {@link WemoDimmerHandler} is responsible for handling commands, which are
53  * sent to one of the channels and to update their states.
54  *
55  * @author Hans-Jörg Merk - Initial contribution
56  */
57 @NonNullByDefault
58 public class WemoDimmerHandler extends AbstractWemoHandler implements UpnpIOParticipant {
59
60     private final Logger logger = LoggerFactory.getLogger(WemoDimmerHandler.class);
61
62     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_DIMMER);
63
64     private final Object upnpLock = new Object();
65     private final Object jobLock = new Object();
66
67     private final Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<>());
68
69     private @Nullable UpnpIOService service;
70
71     private WemoHttpCall wemoCall;
72
73     private String host = "";
74
75     private Map<String, Boolean> subscriptionState = new HashMap<>();
76
77     private @Nullable ScheduledFuture<?> pollingJob;
78
79     private int currentBrightness;
80     private int currentNightModeBrightness;
81     private @Nullable String currentNightModeState;
82     /**
83      * Set dimming stepsize to 5%
84      */
85     private static final int DIM_STEPSIZE = 5;
86
87     public WemoDimmerHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
88         super(thing, wemoHttpCaller);
89
90         this.service = upnpIOService;
91         this.wemoCall = wemoHttpCaller;
92
93         logger.debug("Creating a WemoDimmerHandler for thing '{}'", getThing().getUID());
94     }
95
96     @Override
97     public void initialize() {
98         Configuration configuration = getConfig();
99
100         if (configuration.get(UDN) != null) {
101             logger.debug("Initializing WemoDimmerHandler for UDN '{}'", configuration.get(UDN));
102             UpnpIOService localService = service;
103             if (localService != null) {
104                 localService.registerParticipant(this);
105             }
106             host = getHost();
107             pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVALL_SECONDS,
108                     TimeUnit.SECONDS);
109             updateStatus(ThingStatus.ONLINE);
110         } else {
111             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
112                     "@text/config-status.error.missing-udn");
113             logger.debug("Cannot initalize WemoDimmerHandler. UDN not set.");
114         }
115     }
116
117     @Override
118     public void dispose() {
119         logger.debug("WeMoDimmerHandler disposed.");
120
121         ScheduledFuture<?> job = this.pollingJob;
122         if (job != null && !job.isCancelled()) {
123             job.cancel(true);
124         }
125         this.pollingJob = null;
126         removeSubscription();
127     }
128
129     private void poll() {
130         synchronized (jobLock) {
131             if (pollingJob == null) {
132                 return;
133             }
134             try {
135                 logger.debug("Polling job");
136                 host = getHost();
137                 // Check if the Wemo device is set in the UPnP service registry
138                 // If not, set the thing state to ONLINE/CONFIG-PENDING and wait for the next poll
139                 if (!isUpnpDeviceRegistered()) {
140                     logger.debug("UPnP device {} not yet registered", getUDN());
141                     updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING,
142                             "@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
143                     synchronized (upnpLock) {
144                         subscriptionState = new HashMap<>();
145                     }
146                     return;
147                 }
148                 updateStatus(ThingStatus.ONLINE);
149                 updateWemoState();
150                 addSubscription();
151             } catch (Exception e) {
152                 logger.debug("Exception during poll: {}", e.getMessage(), e);
153             }
154         }
155     }
156
157     @Override
158     public void handleCommand(ChannelUID channelUID, Command command) {
159         logger.trace("Command '{}' received for channel '{}'", command, channelUID);
160         if (command instanceof RefreshType) {
161             try {
162                 updateWemoState();
163             } catch (Exception e) {
164                 logger.debug("Exception during poll", e);
165             }
166         } else {
167             String action = "SetBinaryState";
168             String argument = "BinaryState";
169             String value = "0";
170             String timeStamp = null;
171             switch (channelUID.getId()) {
172                 case CHANNEL_BRIGHTNESS:
173                     String binaryState = this.stateMap.get("BinaryState");
174                     if (command instanceof OnOffType) {
175                         value = command.equals(OnOffType.OFF) ? "0" : "1";
176                         setBinaryState(action, argument, value);
177                         if (command.equals(OnOffType.OFF)) {
178                             State brightnessState = new PercentType("0");
179                             updateState(CHANNEL_BRIGHTNESS, brightnessState);
180                             updateState(CHANNEL_TIMERSTART, OnOffType.OFF);
181                         } else {
182                             State brightnessState = new PercentType(currentBrightness);
183                             updateState(CHANNEL_BRIGHTNESS, brightnessState);
184                         }
185                     } else if (command instanceof PercentType) {
186                         int newBrightness = ((PercentType) command).intValue();
187                         value = String.valueOf(newBrightness);
188                         currentBrightness = newBrightness;
189                         argument = "brightness";
190                         if ("0".equals(value)) {
191                             value = "1";
192                             argument = "brightness";
193                             setBinaryState(action, argument, "1");
194                             value = "0";
195                             argument = "BinaryState";
196                             setBinaryState(action, argument, "0");
197                         } else if ("0".equals(binaryState)) {
198                             argument = "BinaryState";
199                             setBinaryState(action, argument, "1");
200                         }
201                         argument = "brightness";
202                         setBinaryState(action, argument, value);
203                     } else if (command instanceof IncreaseDecreaseType) {
204                         int newBrightness;
205                         switch (command.toString()) {
206                             case "INCREASE":
207                                 newBrightness = currentBrightness + DIM_STEPSIZE;
208                                 if (newBrightness > 100) {
209                                     newBrightness = 100;
210                                 }
211                                 value = String.valueOf(newBrightness);
212                                 currentBrightness = newBrightness;
213                                 break;
214                             case "DECREASE":
215                                 newBrightness = currentBrightness - DIM_STEPSIZE;
216                                 if (newBrightness < 0) {
217                                     newBrightness = 0;
218                                 }
219                                 value = String.valueOf(newBrightness);
220                                 currentBrightness = newBrightness;
221                                 break;
222                         }
223                         argument = "brightness";
224                         if ("0".equals(value)) {
225                             value = "1";
226                             argument = "brightness";
227                             setBinaryState(action, argument, "1");
228                             value = "0";
229                             argument = "BinaryState";
230                             setBinaryState(action, argument, "0");
231                         } else if ("0".equals(binaryState)) {
232                             argument = "BinaryState";
233                             setBinaryState(action, argument, "1");
234                         }
235                         argument = "brightness";
236                         setBinaryState(action, argument, value);
237                     }
238                     break;
239                 case CHANNEL_FADERCOUNTDOWNTIME:
240                     argument = "Fader";
241                     if (command instanceof DecimalType) {
242                         int commandValue = Integer.valueOf(String.valueOf(command));
243                         commandValue = commandValue * 60;
244                         String commandString = String.valueOf(commandValue);
245                         value = "<BinaryState></BinaryState>" + "<Duration></Duration>" + "<EndAction></EndAction>"
246                                 + "<brightness></brightness>" + "<fader>" + commandString + ":-1:1:0:0</fader>"
247                                 + "<UDN></UDN>";
248                         setBinaryState(action, argument, value);
249                     }
250                     break;
251                 case CHANNEL_FADERENABLED:
252                     argument = "Fader";
253                     if (command.equals(OnOffType.ON)) {
254                         value = "<BinaryState></BinaryState>" + "<Duration></Duration>" + "<EndAction></EndAction>"
255                                 + "<brightness></brightness>" + "<fader>600:-1:1:0:0</fader>" + "<UDN></UDN>";
256                     } else if (command.equals(OnOffType.OFF)) {
257                         value = "<BinaryState></BinaryState>" + "<Duration></Duration>" + "<EndAction></EndAction>"
258                                 + "<brightness></brightness>" + "<fader>600:-1:0:0:0</fader>" + "<UDN></UDN>";
259                     }
260                     setBinaryState(action, argument, value);
261                     break;
262                 case CHANNEL_TIMERSTART:
263                     argument = "Fader";
264                     long ts = System.currentTimeMillis() / 1000;
265                     timeStamp = String.valueOf(ts);
266                     logger.info("timestamp '{}' created", timeStamp);
267                     String faderSeconds = null;
268                     String faderEnabled = null;
269                     String fader = this.stateMap.get("fader");
270                     if (fader != null) {
271                         String[] splitFader = fader.split(":");
272                         if (splitFader[0] != null) {
273                             faderSeconds = splitFader[0];
274                         }
275                         if (splitFader[0] != null) {
276                             faderEnabled = splitFader[2];
277                         }
278                     }
279                     if (faderSeconds != null && faderEnabled != null) {
280                         if (OnOffType.ON.equals(command)) {
281                             value = "<BinaryState></BinaryState>" + "<Duration></Duration>" + "<EndAction></EndAction>"
282                                     + "<brightness></brightness>" + "<fader>" + faderSeconds + ":" + timeStamp + ":"
283                                     + faderEnabled + ":0:0</fader>" + "<UDN></UDN>";
284                             updateState(CHANNEL_STATE, OnOffType.ON);
285                         } else if (OnOffType.OFF.equals(command)) {
286                             value = "<BinaryState></BinaryState>" + "<Duration></Duration>" + "<EndAction></EndAction>"
287                                     + "<brightness></brightness>" + "<fader>" + faderSeconds + ":-1:" + faderEnabled
288                                     + ":0:0</fader>" + "<UDN></UDN>";
289                         }
290                     }
291                     setBinaryState(action, argument, value);
292                     break;
293                 case CHANNEL_NIGHTMODE:
294                     action = "ConfigureNightMode";
295                     argument = "NightModeConfiguration";
296                     String nightModeBrightness = String.valueOf(currentNightModeBrightness);
297                     if (OnOffType.ON.equals(command)) {
298                         value = "&lt;startTime&gt;0&lt;/startTime&gt; \\n&lt;nightMode&gt;1&lt;/nightMode&gt; \\n&lt;endTime&gt;23400&lt;/endTime&gt; \\n&lt;nightModeBrightness&gt;"
299                                 + nightModeBrightness + "&lt;/nightModeBrightness&gt; \\n";
300                     } else if (OnOffType.OFF.equals(command)) {
301                         value = "&lt;startTime&gt;0&lt;/startTime&gt; \\n&lt;nightMode&gt;0&lt;/nightMode&gt; \\n&lt;endTime&gt;23400&lt;/endTime&gt; \\n&lt;nightModeBrightness&gt;"
302                                 + nightModeBrightness + "&lt;/nightModeBrightness&gt; \\n";
303                     }
304                     setBinaryState(action, argument, value);
305                     break;
306                 case CHANNEL_NIGHTMODEBRIGHTNESS:
307                     action = "ConfigureNightMode";
308                     argument = "NightModeConfiguration";
309                     if (command instanceof PercentType) {
310                         int newBrightness = ((PercentType) command).intValue();
311                         String newNightModeBrightness = String.valueOf(newBrightness);
312                         value = "&lt;startTime&gt;0&lt;/startTime&gt; \\n&lt;nightMode&gt;" + currentNightModeState
313                                 + "&lt;/nightMode&gt; \\n&lt;endTime&gt;23400&lt;/endTime&gt; \\n&lt;nightModeBrightness&gt;"
314                                 + newNightModeBrightness + "&lt;/nightModeBrightness&gt; \\n";
315                         currentNightModeBrightness = newBrightness;
316                     } else if (command instanceof IncreaseDecreaseType) {
317                         int newBrightness;
318                         String newNightModeBrightness = null;
319                         switch (command.toString()) {
320                             case "INCREASE":
321                                 newBrightness = currentNightModeBrightness + DIM_STEPSIZE;
322                                 if (newBrightness > 100) {
323                                     newBrightness = 100;
324                                 }
325                                 newNightModeBrightness = String.valueOf(newBrightness);
326                                 currentBrightness = newBrightness;
327                                 break;
328                             case "DECREASE":
329                                 newBrightness = currentNightModeBrightness - DIM_STEPSIZE;
330                                 if (newBrightness < 0) {
331                                     newBrightness = 0;
332                                 }
333                                 newNightModeBrightness = String.valueOf(newBrightness);
334                                 currentNightModeBrightness = newBrightness;
335                                 break;
336                         }
337                         value = "&lt;startTime&gt;0&lt;/startTime&gt; \\n&lt;nightMode&gt;" + currentNightModeState
338                                 + "&lt;/nightMode&gt; \\n&lt;endTime&gt;23400&lt;/endTime&gt; \\n&lt;nightModeBrightness&gt;"
339                                 + newNightModeBrightness + "&lt;/nightModeBrightness&gt; \\n";
340                     }
341                     setBinaryState(action, argument, value);
342                     break;
343             }
344         }
345     }
346
347     @Override
348     public void onServiceSubscribed(@Nullable String service, boolean succeeded) {
349         if (service != null) {
350             logger.debug("WeMo {}: Subscription to service {} {}", getUDN(), service,
351                     succeeded ? "succeeded" : "failed");
352             subscriptionState.put(service, succeeded);
353         }
354     }
355
356     @Override
357     public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) {
358         logger.debug("Received pair '{}':'{}' (service '{}') for thing '{}'",
359                 new Object[] { variable, value, service, this.getThing().getUID() });
360         updateStatus(ThingStatus.ONLINE);
361         if (variable != null && value != null) {
362             String oldBinaryState = this.stateMap.get("BinaryState");
363             this.stateMap.put(variable, value);
364             switch (variable) {
365                 case "BinaryState":
366                     if (oldBinaryState == null || !oldBinaryState.equals(value)) {
367                         State state = "0".equals(value) ? OnOffType.OFF : OnOffType.ON;
368                         logger.debug("State '{}' for device '{}' received", state, getThing().getUID());
369                         updateState(CHANNEL_BRIGHTNESS, state);
370                         if (state.equals(OnOffType.OFF)) {
371                             updateState(CHANNEL_TIMERSTART, OnOffType.OFF);
372                         }
373                     }
374                     break;
375                 case "brightness":
376                     logger.debug("brightness '{}' for device '{}' received", value, getThing().getUID());
377                     int newBrightnessValue = Integer.valueOf(value);
378                     State newBrightnessState = new PercentType(newBrightnessValue);
379                     String binaryState = this.stateMap.get("BinaryState");
380                     if (binaryState != null) {
381                         if ("1".equals(binaryState)) {
382                             updateState(CHANNEL_BRIGHTNESS, newBrightnessState);
383                         }
384                     }
385                     currentBrightness = newBrightnessValue;
386                     break;
387                 case "fader":
388                     logger.debug("fader '{}' for device '{}' received", value, getThing().getUID());
389                     String[] splitFader = value.split(":");
390                     if (splitFader[0] != null) {
391                         int faderSeconds = Integer.valueOf(splitFader[0]);
392                         State faderMinutes = new DecimalType(faderSeconds / 60);
393                         logger.debug("faderTime '{} minutes' for device '{}' received", faderMinutes,
394                                 getThing().getUID());
395                         updateState(CHANNEL_FADERCOUNTDOWNTIME, faderMinutes);
396                     }
397                     if (splitFader[1] != null) {
398                         State isTimerRunning = splitFader[1].equals("-1") ? OnOffType.OFF : OnOffType.ON;
399                         logger.debug("isTimerRunning '{}' for device '{}' received", isTimerRunning,
400                                 getThing().getUID());
401                         updateState(CHANNEL_TIMERSTART, isTimerRunning);
402                         if (isTimerRunning.equals(OnOffType.ON)) {
403                             updateState(CHANNEL_STATE, OnOffType.ON);
404                         }
405                     }
406                     if (splitFader[2] != null) {
407                         State isFaderEnabled = splitFader[1].equals("0") ? OnOffType.OFF : OnOffType.ON;
408                         logger.debug("isFaderEnabled '{}' for device '{}' received", isFaderEnabled,
409                                 getThing().getUID());
410                         updateState(CHANNEL_FADERENABLED, isFaderEnabled);
411                     }
412                     break;
413                 case "nightMode":
414                     State nightModeState = "0".equals(value) ? OnOffType.OFF : OnOffType.ON;
415                     currentNightModeState = value;
416                     logger.debug("nightModeState '{}' for device '{}' received", nightModeState, getThing().getUID());
417                     updateState(CHANNEL_NIGHTMODE, nightModeState);
418                     break;
419                 case "startTime":
420                     State startTimeState = getDateTimeState(value);
421                     logger.debug("startTimeState '{}' for device '{}' received", startTimeState, getThing().getUID());
422                     if (startTimeState != null) {
423                         updateState(CHANNEL_STARTTIME, startTimeState);
424                     }
425                     break;
426                 case "endTime":
427                     State endTimeState = getDateTimeState(value);
428                     logger.debug("endTimeState '{}' for device '{}' received", endTimeState, getThing().getUID());
429                     if (endTimeState != null) {
430                         updateState(CHANNEL_ENDTIME, endTimeState);
431                     }
432                     break;
433                 case "nightModeBrightness":
434                     int nightModeBrightnessValue = Integer.valueOf(value);
435                     currentNightModeBrightness = nightModeBrightnessValue;
436                     State nightModeBrightnessState = new PercentType(nightModeBrightnessValue);
437                     logger.debug("nightModeBrightnessState '{}' for device '{}' received", nightModeBrightnessState,
438                             getThing().getUID());
439                     updateState(CHANNEL_NIGHTMODEBRIGHTNESS, nightModeBrightnessState);
440                     break;
441             }
442         }
443     }
444
445     private synchronized void addSubscription() {
446         UpnpIOService localService = service;
447         if (localService != null) {
448             if (localService.isRegistered(this)) {
449                 logger.debug("Checking WeMo GENA subscription for '{}'", getThing().getUID());
450                 String subscription = BASICEVENT;
451                 if (subscriptionState.get(subscription) == null) {
452                     logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(),
453                             subscription);
454                     localService.addSubscription(this, subscription, SUBSCRIPTION_DURATION_SECONDS);
455                     subscriptionState.put(subscription, true);
456                 }
457             } else {
458                 logger.debug("Setting up WeMo GENA subscription for '{}' FAILED - service.isRegistered(this) is FALSE",
459                         getThing().getUID());
460             }
461         }
462     }
463
464     private synchronized void removeSubscription() {
465         logger.debug("Removing WeMo GENA subscription for '{}'", getThing().getUID());
466         UpnpIOService localService = service;
467         if (localService != null) {
468             if (localService.isRegistered(this)) {
469                 String subscription = BASICEVENT;
470                 if (subscriptionState.get(subscription) != null) {
471                     logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription);
472                     localService.removeSubscription(this, subscription);
473                 }
474                 subscriptionState = new HashMap<>();
475                 localService.unregisterParticipant(this);
476             }
477         }
478     }
479
480     private boolean isUpnpDeviceRegistered() {
481         UpnpIOService localService = service;
482         if (localService != null) {
483             return localService.isRegistered(this);
484         }
485         return false;
486     }
487
488     @Override
489     public String getUDN() {
490         return (String) this.getThing().getConfiguration().get(UDN);
491     }
492
493     /**
494      * The {@link updateWemoState} polls the actual state of a WeMo device and
495      * calls {@link onValueReceived} to update the statemap and channels..
496      *
497      */
498     protected void updateWemoState() {
499         String localHost = getHost();
500         if (localHost.isEmpty()) {
501             logger.error("Failed to get actual state for device '{}': IP address missing", getThing().getUID());
502             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
503                     "@text/config-status.error.missing-ip");
504             return;
505         }
506         String wemoURL = getWemoURL(localHost, BASICACTION);
507         if (wemoURL == null) {
508             logger.error("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID());
509             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
510                     "@text/config-status.error.missing-url");
511             return;
512         }
513         String action = "GetBinaryState";
514         String variable = null;
515         String actionService = BASICACTION;
516         String value = null;
517         String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
518         String content = createStateRequestContent(action, actionService);
519         try {
520             String wemoCallResponse = wemoCall.executeCall(wemoURL, soapHeader, content);
521             if (wemoCallResponse != null) {
522                 if (logger.isTraceEnabled()) {
523                     logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID());
524                     logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID());
525                     logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID());
526                     logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID());
527                 }
528                 value = substringBetween(wemoCallResponse, "<BinaryState>", "</BinaryState>");
529                 variable = "BinaryState";
530                 this.onValueReceived(variable, value, actionService + "1");
531                 value = substringBetween(wemoCallResponse, "<brightness>", "</brightness>");
532                 variable = "brightness";
533                 this.onValueReceived(variable, value, actionService + "1");
534                 value = substringBetween(wemoCallResponse, "<fader>", "</fader>");
535                 variable = "fader";
536                 this.onValueReceived(variable, value, actionService + "1");
537                 updateStatus(ThingStatus.ONLINE);
538             }
539         } catch (Exception e) {
540             logger.debug("Failed to get actual state for device '{}': {}", getThing().getUID(), e.getMessage());
541             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
542         }
543         action = "GetNightModeConfiguration";
544         variable = null;
545         value = null;
546         soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
547         content = createStateRequestContent(action, actionService);
548         try {
549             String wemoCallResponse = wemoCall.executeCall(wemoURL, soapHeader, content);
550             if (wemoCallResponse != null) {
551                 if (logger.isTraceEnabled()) {
552                     logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID());
553                     logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID());
554                     logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID());
555                     logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID());
556                 }
557                 value = substringBetween(wemoCallResponse, "<startTime>", "</startTime>");
558                 variable = "startTime";
559                 this.onValueReceived(variable, value, actionService + "1");
560                 value = substringBetween(wemoCallResponse, "<endTime>", "</endTime>");
561                 variable = "endTime";
562                 this.onValueReceived(variable, value, actionService + "1");
563                 value = substringBetween(wemoCallResponse, "<nightMode>", "</nightMode>");
564                 variable = "nightMode";
565                 this.onValueReceived(variable, value, actionService + "1");
566                 value = substringBetween(wemoCallResponse, "<nightModeBrightness>", "</nightModeBrightness>");
567                 variable = "nightModeBrightness";
568                 this.onValueReceived(variable, value, actionService + "1");
569                 updateStatus(ThingStatus.ONLINE);
570
571             }
572         } catch (Exception e) {
573             logger.debug("Failed to get actual NightMode state for device '{}': {}", getThing().getUID(),
574                     e.getMessage());
575             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
576         }
577     }
578
579     public @Nullable State getDateTimeState(String attributeValue) {
580         long value = 0;
581         try {
582             value = Long.parseLong(attributeValue);
583         } catch (NumberFormatException e) {
584             logger.error("Unable to parse attributeValue '{}' for device '{}'; expected long", attributeValue,
585                     getThing().getUID());
586             return null;
587         }
588         ZonedDateTime zoned = ZonedDateTime.ofInstant(Instant.ofEpochSecond(value), TimeZone.getDefault().toZoneId());
589         State dateTimeState = new DateTimeType(zoned);
590         logger.trace("New attribute brewed '{}' received", dateTimeState);
591         return dateTimeState;
592     }
593
594     public void setBinaryState(String action, String argument, String value) {
595         String localHost = getHost();
596         if (localHost.isEmpty()) {
597             logger.error("Failed to set binary state for device '{}': IP address missing", getThing().getUID());
598             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
599                     "@text/config-status.error.missing-ip");
600             return;
601         }
602         String wemoURL = getWemoURL(localHost, BASICACTION);
603         if (wemoURL == null) {
604             logger.error("Failed to set binary state for device '{}': URL cannot be created", getThing().getUID());
605             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
606                     "@text/config-status.error.missing-url");
607             return;
608         }
609         try {
610             String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\"";
611             String content = "<?xml version=\"1.0\"?>"
612                     + "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
613                     + "<s:Body>" + "<u:" + action + " xmlns:u=\"urn:Belkin:service:basicevent:1\">" + "<" + argument
614                     + ">" + value + "</" + argument + ">" + "</u:" + action + ">" + "</s:Body>" + "</s:Envelope>";
615
616             String wemoCallResponse = wemoCall.executeCall(wemoURL, soapHeader, content);
617             if (wemoCallResponse != null && logger.isTraceEnabled()) {
618                 logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID());
619                 logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID());
620                 logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID());
621                 logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID());
622             }
623         } catch (Exception e) {
624             logger.debug("Failed to set binaryState '{}' for device '{}': {}", value, getThing().getUID(),
625                     e.getMessage());
626             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
627         }
628     }
629
630     public void setTimerStart(String action, String argument, String value) {
631         String localHost = getHost();
632         if (localHost.isEmpty()) {
633             logger.error("Failed to set timerStart for device '{}': IP address missing", getThing().getUID());
634             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
635                     "@text/config-status.error.missing-ip");
636             return;
637         }
638         String wemoURL = getWemoURL(localHost, BASICACTION);
639         if (wemoURL == null) {
640             logger.error("Failed to set timerStart for device '{}': URL cannot be created", getThing().getUID());
641             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
642                     "@text/config-status.error.missing-url");
643             return;
644         }
645         try {
646             String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\"";
647             String content = "<?xml version=\"1.0\"?>"
648                     + "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
649                     + "<s:Body>" + "<u:SetBinaryState xmlns:u=\"urn:Belkin:service:basicevent:1\">" + value
650                     + "</u:SetBinaryState>" + "</s:Body>" + "</s:Envelope>";
651             String wemoCallResponse = wemoCall.executeCall(wemoURL, soapHeader, content);
652             if (wemoCallResponse != null && logger.isTraceEnabled()) {
653                 logger.trace("wemoCall to URL '{}' for device '{}'", wemoURL, getThing().getUID());
654                 logger.trace("wemoCall with soapHeader '{}' for device '{}'", soapHeader, getThing().getUID());
655                 logger.trace("wemoCall with content '{}' for device '{}'", content, getThing().getUID());
656                 logger.trace("wemoCall with response '{}' for device '{}'", wemoCallResponse, getThing().getUID());
657             }
658         } catch (Exception e) {
659             logger.debug("Failed to set timerStart '{}' for device '{}': {}", value, getThing().getUID(),
660                     e.getMessage());
661             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
662         }
663     }
664
665     public String getHost() {
666         String localHost = host;
667         if (!localHost.isEmpty()) {
668             return localHost;
669         }
670         UpnpIOService localService = service;
671         if (localService != null) {
672             URL descriptorURL = localService.getDescriptorURL(this);
673             if (descriptorURL != null) {
674                 return descriptorURL.getHost();
675             }
676         }
677         return "";
678     }
679
680     @Override
681     public void onStatusChanged(boolean status) {
682     }
683 }