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