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