]> git.basschouten.com Git - openhab-addons.git/blob
a645ed5a2ef91f8fb5a1ec59f3b66ce9bfe65721
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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
17 import java.math.BigDecimal;
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.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;
49
50 /**
51  * The {@link WemoDimmerHandler} is responsible for handling commands, which are
52  * sent to one of the channels and to update their states.
53  *
54  * @author Hans-Jörg Merk - Initial contribution
55  */
56
57 public class WemoDimmerHandler extends AbstractWemoHandler implements UpnpIOParticipant {
58
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;
68     /**
69      * Set dimming stepsize to 5%
70      */
71     private static final int DIM_STEPSIZE = 5;
72     /**
73      * The default refresh interval in Seconds.
74      */
75     private int DEFAULT_REFRESH_INTERVAL = 60;
76     private ScheduledFuture<?> refreshJob;
77     private Runnable refreshRunnable = new Runnable() {
78
79         @Override
80         public void run() {
81             try {
82                 if (!isUpnpDeviceRegistered()) {
83                     logger.debug("WeMo UPnP device {} not yet registered", getUDN());
84                 }
85                 updateWemoState();
86                 onSubscription();
87             } catch (Exception e) {
88                 logger.debug("Exception during poll : {}", e.getMessage(), e);
89             }
90         }
91     };
92
93     public WemoDimmerHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemohttpCaller) {
94         super(thing);
95         this.wemoHttpCaller = wemohttpCaller;
96         logger.debug("Creating a WemoDimmerHandler for thing '{}'", getThing().getUID());
97         if (upnpIOService != null) {
98             this.service = upnpIOService;
99         } else {
100             logger.debug("upnpIOService not set.");
101         }
102     }
103
104     @Override
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);
110             onSubscription();
111             onUpdate();
112         } else {
113             logger.debug("Cannot initalize WemoDimmerHandler. UDN not set.");
114         }
115     }
116
117     @Override
118     public void dispose() {
119         logger.debug("WeMoDimmerHandler disposed.");
120         removeSubscription();
121         if (refreshJob != null && !refreshJob.isCancelled()) {
122             refreshJob.cancel(true);
123             refreshJob = null;
124         }
125     }
126
127     @Override
128     public void handleCommand(ChannelUID channelUID, Command command) {
129         logger.trace("Command '{}' received for channel '{}'", command, channelUID);
130         if (command instanceof RefreshType) {
131             try {
132                 updateWemoState();
133             } catch (Exception e) {
134                 logger.debug("Exception during poll", e);
135             }
136         } else {
137             String action = "SetBinaryState";
138             String argument = "BinaryState";
139             String value = "0";
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);
150                         } else {
151                             State brightnessState = new PercentType(currentBrightness);
152                             updateState(CHANNEL_BRIGHTNESS, brightnessState);
153                         }
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")) {
160                             value = "1";
161                             argument = "brightness";
162                             setBinaryState(action, argument, "1");
163                             value = "0";
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");
169                         }
170                         argument = "brightness";
171                         setBinaryState(action, argument, value);
172                     } else if (command instanceof IncreaseDecreaseType) {
173                         int newBrightness;
174                         switch (command.toString()) {
175                             case "INCREASE":
176                                 newBrightness = currentBrightness + DIM_STEPSIZE;
177                                 if (newBrightness > 100) {
178                                     newBrightness = 100;
179                                 }
180                                 value = String.valueOf(newBrightness);
181                                 currentBrightness = newBrightness;
182                                 break;
183                             case "DECREASE":
184                                 newBrightness = currentBrightness - DIM_STEPSIZE;
185                                 if (newBrightness < 0) {
186                                     newBrightness = 0;
187                                 }
188                                 value = String.valueOf(newBrightness);
189                                 currentBrightness = newBrightness;
190                                 break;
191                         }
192                         argument = "brightness";
193                         if (value.equals("0")) {
194                             value = "1";
195                             argument = "brightness";
196                             setBinaryState(action, argument, "1");
197                             value = "0";
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");
203                         }
204                         argument = "brightness";
205                         setBinaryState(action, argument, value);
206                     }
207                     break;
208                 case CHANNEL_FADERCOUNTDOWNTIME:
209                     argument = "Fader";
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>"
216                                 + "<UDN></UDN>";
217                         setBinaryState(action, argument, value);
218                     }
219                     break;
220                 case CHANNEL_FADERENABLED:
221                     argument = "Fader";
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>";
228                     }
229                     setBinaryState(action, argument, value);
230                     break;
231                 case CHANNEL_TIMERSTART:
232                     argument = "Fader";
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];
241                     }
242                     if (splitFader[0] != null) {
243                         faderEnabled = splitFader[2];
244                     }
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>";
255                         }
256                     }
257                     setBinaryState(action, argument, value);
258                     break;
259                 case CHANNEL_NIGHTMODE:
260                     action = "ConfigureNightMode";
261                     argument = "NightModeConfiguration";
262                     String nightModeBrightness = String.valueOf(currentNightModeBrightness);
263                     if (command.equals(OnOffType.ON)) {
264                         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;"
265                                 + nightModeBrightness + "&lt;/nightModeBrightness&gt; \\n";
266                     } else if (command.equals(OnOffType.OFF)) {
267                         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;"
268                                 + nightModeBrightness + "&lt;/nightModeBrightness&gt; \\n";
269                     }
270                     setBinaryState(action, argument, value);
271                     break;
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 = "&lt;startTime&gt;0&lt;/startTime&gt; \\n&lt;nightMode&gt;" + currentNightModeState
279                                 + "&lt;/nightMode&gt; \\n&lt;endTime&gt;23400&lt;/endTime&gt; \\n&lt;nightModeBrightness&gt;"
280                                 + newNightModeBrightness + "&lt;/nightModeBrightness&gt; \\n";
281                         currentNightModeBrightness = newBrightness;
282                     } else if (command instanceof IncreaseDecreaseType) {
283                         int newBrightness;
284                         String newNightModeBrightness = null;
285                         switch (command.toString()) {
286                             case "INCREASE":
287                                 newBrightness = currentNightModeBrightness + DIM_STEPSIZE;
288                                 if (newBrightness > 100) {
289                                     newBrightness = 100;
290                                 }
291                                 newNightModeBrightness = String.valueOf(newBrightness);
292                                 currentBrightness = newBrightness;
293                                 break;
294                             case "DECREASE":
295                                 newBrightness = currentNightModeBrightness - DIM_STEPSIZE;
296                                 if (newBrightness < 0) {
297                                     newBrightness = 0;
298                                 }
299                                 newNightModeBrightness = String.valueOf(newBrightness);
300                                 currentNightModeBrightness = newBrightness;
301                                 break;
302                         }
303                         value = "&lt;startTime&gt;0&lt;/startTime&gt; \\n&lt;nightMode&gt;" + currentNightModeState
304                                 + "&lt;/nightMode&gt; \\n&lt;endTime&gt;23400&lt;/endTime&gt; \\n&lt;nightModeBrightness&gt;"
305                                 + newNightModeBrightness + "&lt;/nightModeBrightness&gt; \\n";
306                     }
307                     setBinaryState(action, argument, value);
308                     break;
309             }
310         }
311     }
312
313     @Override
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);
317     }
318
319     @Override
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);
325         switch (variable) {
326             case "BinaryState":
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);
332                 }
333                 break;
334             case "brightness":
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);
340                 }
341                 currentBrightness = newBrightnessValue;
342                 break;
343             case "fader":
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);
351                 }
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);
358                     }
359                 }
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);
364                 }
365                 break;
366             case "nightMode":
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);
371                 break;
372             case "startTime":
373                 State startTimeState = getDateTimeState(value);
374                 logger.debug("startTimeState '{}' for device '{}' received", startTimeState, getThing().getUID());
375                 updateState(CHANNEL_STARTTIME, startTimeState);
376                 break;
377             case "endTime":
378                 State endTimeState = getDateTimeState(value);
379                 logger.debug("endTimeState '{}' for device '{}' received", endTimeState, getThing().getUID());
380                 updateState(CHANNEL_ENDTIME, endTimeState);
381                 break;
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);
389                 break;
390         }
391     }
392
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);
401             }
402         } else {
403             logger.debug("Setting up WeMo GENA subscription for '{}' FAILED - service.isRegistered(this) is FALSE",
404                     this);
405         }
406     }
407
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);
415             }
416             subscriptionState = new HashMap<>();
417             service.unregisterParticipant(this);
418         }
419     }
420
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();
428             }
429             refreshJob = scheduler.scheduleWithFixedDelay(refreshRunnable, 10, refreshInterval, TimeUnit.SECONDS);
430         }
431     }
432
433     private boolean isUpnpDeviceRegistered() {
434         return service.isRegistered(this);
435     }
436
437     @Override
438     public String getUDN() {
439         return (String) this.getThing().getConfiguration().get(UDN);
440     }
441
442     /**
443      * The {@link updateWemoState} polls the actual state of a WeMo device and
444      * calls {@link onValueReceived} to update the statemap and channels..
445      *
446      */
447     protected void updateWemoState() {
448         String action = "GetBinaryState";
449         String variable = null;
450         String actionService = "basicevent";
451         String value = null;
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>";
457         try {
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>");
464                     if (value != null) {
465                         variable = "BinaryState";
466                         logger.trace("New state '{}' for device '{}' received", value, getThing().getUID());
467                         this.onValueReceived(variable, value, actionService + "1");
468                     }
469                     value = StringUtils.substringBetween(wemoCallResponse, "<brightness>", "</brightness>");
470                     if (value != null) {
471                         variable = "brightness";
472                         logger.trace("New brightness '{}' for device '{}' received", value, getThing().getUID());
473                         this.onValueReceived(variable, value, actionService + "1");
474                     }
475                     value = StringUtils.substringBetween(wemoCallResponse, "<fader>", "</fader>");
476                     if (value != null) {
477                         variable = "fader";
478                         logger.trace("New fader value '{}' for device '{}' received", value, getThing().getUID());
479                         this.onValueReceived(variable, value, actionService + "1");
480                     }
481                 }
482             }
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());
486         }
487         updateStatus(ThingStatus.ONLINE);
488         action = "GetNightModeConfiguration";
489         variable = null;
490         value = null;
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>";
496         try {
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>");
504                     if (value != null) {
505                         variable = "startTime";
506                         logger.trace("New startTime '{}' for device '{}' received", value, getThing().getUID());
507                         this.onValueReceived(variable, value, actionService + "1");
508                     }
509                     value = StringUtils.substringBetween(wemoCallResponse, "<endTime>", "</endTime>");
510                     if (value != null) {
511                         variable = "endTime";
512                         logger.trace("New endTime '{}' for device '{}' received", value, getThing().getUID());
513                         this.onValueReceived(variable, value, actionService + "1");
514                     }
515                     value = StringUtils.substringBetween(wemoCallResponse, "<nightMode>", "</nightMode>");
516                     if (value != null) {
517                         variable = "nightMode";
518                         logger.trace("New nightMode state '{}' for device '{}' received", value, getThing().getUID());
519                         this.onValueReceived(variable, value, actionService + "1");
520                     }
521                     value = StringUtils.substringBetween(wemoCallResponse, "<nightModeBrightness>",
522                             "</nightModeBrightness>");
523                     if (value != null) {
524                         variable = "nightModeBrightness";
525                         logger.trace("New nightModeBrightness  '{}' for device '{}' received", value,
526                                 getThing().getUID());
527                         this.onValueReceived(variable, value, actionService + "1");
528                     }
529                 }
530             }
531         } catch (Exception e) {
532             logger.debug("Failed to get actual NightMode state for device '{}': {}", getThing().getUID(),
533                     e.getMessage());
534             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
535         }
536         updateStatus(ThingStatus.ONLINE);
537     }
538
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";
545             return wemoURL;
546         }
547         return null;
548     }
549
550     @SuppressWarnings("null")
551     public State getDateTimeState(String attributeValue) {
552         if (attributeValue != null) {
553             long value = 0;
554             try {
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());
559                 return null;
560             }
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;
567             }
568         }
569         return null;
570     }
571
572     public void setBinaryState(String action, String argument, String value) {
573         try {
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);
583             }
584         } catch (Exception e) {
585             logger.debug("Failed to set binaryState '{}' for device '{}': {}", value, getThing().getUID(),
586                     e.getMessage());
587             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
588         }
589     }
590
591     public void setTimerStart(String action, String argument, String value) {
592         try {
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);
602             }
603         } catch (Exception e) {
604             logger.debug("Failed to set binaryState '{}' for device '{}': {}", value, getThing().getUID(),
605                     e.getMessage());
606             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
607         }
608     }
609
610     @Override
611     public void onStatusChanged(boolean status) {
612     }
613 }