]> git.basschouten.com Git - openhab-addons.git/blob
69ac9b5a5f2b3ecf40fcacdf35e3913d7ba1f132
[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 jobLock = new Object();
63
64     private final Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<>());
65
66     private @Nullable ScheduledFuture<?> pollingJob;
67
68     private int currentBrightness;
69     private int currentNightModeBrightness;
70     private @Nullable String currentNightModeState;
71     /**
72      * Set dimming stepsize to 5%
73      */
74     private static final int DIM_STEPSIZE = 5;
75
76     public WemoDimmerHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
77         super(thing, upnpIOService, wemoHttpCaller);
78
79         logger.debug("Creating a WemoDimmerHandler for thing '{}'", getThing().getUID());
80     }
81
82     @Override
83     public void initialize() {
84         super.initialize();
85         Configuration configuration = getConfig();
86
87         if (configuration.get(UDN) != null) {
88             logger.debug("Initializing WemoDimmerHandler for UDN '{}'", configuration.get(UDN));
89             addSubscription(BASICEVENT);
90             host = getHost();
91             pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS,
92                     TimeUnit.SECONDS);
93             updateStatus(ThingStatus.ONLINE);
94         } else {
95             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
96                     "@text/config-status.error.missing-udn");
97             logger.debug("Cannot initalize WemoDimmerHandler. UDN not set.");
98         }
99     }
100
101     @Override
102     public void dispose() {
103         logger.debug("WeMoDimmerHandler disposed.");
104
105         ScheduledFuture<?> job = this.pollingJob;
106         if (job != null && !job.isCancelled()) {
107             job.cancel(true);
108         }
109         this.pollingJob = null;
110         super.dispose();
111     }
112
113     private void poll() {
114         synchronized (jobLock) {
115             if (pollingJob == null) {
116                 return;
117             }
118             try {
119                 logger.debug("Polling job");
120                 host = getHost();
121                 // Check if the Wemo device is set in the UPnP service registry
122                 // If not, set the thing state to ONLINE/CONFIG-PENDING and wait for the next poll
123                 if (!isUpnpDeviceRegistered()) {
124                     logger.debug("UPnP device {} not yet registered", getUDN());
125                     updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING,
126                             "@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
127                     return;
128                 }
129                 updateWemoState();
130             } catch (Exception e) {
131                 logger.debug("Exception during poll: {}", e.getMessage(), e);
132             }
133         }
134     }
135
136     @Override
137     public void handleCommand(ChannelUID channelUID, Command command) {
138         logger.trace("Command '{}' received for channel '{}'", command, channelUID);
139         if (command instanceof RefreshType) {
140             try {
141                 updateWemoState();
142             } catch (Exception e) {
143                 logger.debug("Exception during poll", e);
144             }
145         } else {
146             String action = "SetBinaryState";
147             String argument = "BinaryState";
148             String value = "0";
149             String timeStamp = null;
150             switch (channelUID.getId()) {
151                 case CHANNEL_BRIGHTNESS:
152                     String binaryState = this.stateMap.get("BinaryState");
153                     if (command instanceof OnOffType) {
154                         value = command.equals(OnOffType.OFF) ? "0" : "1";
155                         setBinaryState(action, argument, value);
156                         if (command.equals(OnOffType.OFF)) {
157                             State brightnessState = new PercentType("0");
158                             updateState(CHANNEL_BRIGHTNESS, brightnessState);
159                             updateState(CHANNEL_TIMERSTART, OnOffType.OFF);
160                         } else {
161                             State brightnessState = new PercentType(currentBrightness);
162                             updateState(CHANNEL_BRIGHTNESS, brightnessState);
163                         }
164                     } else if (command instanceof PercentType) {
165                         int newBrightness = ((PercentType) command).intValue();
166                         value = String.valueOf(newBrightness);
167                         currentBrightness = newBrightness;
168                         argument = "brightness";
169                         if ("0".equals(value)) {
170                             value = "1";
171                             argument = "brightness";
172                             setBinaryState(action, argument, "1");
173                             value = "0";
174                             argument = "BinaryState";
175                             setBinaryState(action, argument, "0");
176                         } else if ("0".equals(binaryState)) {
177                             argument = "BinaryState";
178                             setBinaryState(action, argument, "1");
179                         }
180                         argument = "brightness";
181                         setBinaryState(action, argument, value);
182                     } else if (command instanceof IncreaseDecreaseType) {
183                         int newBrightness;
184                         switch (command.toString()) {
185                             case "INCREASE":
186                                 newBrightness = currentBrightness + DIM_STEPSIZE;
187                                 if (newBrightness > 100) {
188                                     newBrightness = 100;
189                                 }
190                                 value = String.valueOf(newBrightness);
191                                 currentBrightness = newBrightness;
192                                 break;
193                             case "DECREASE":
194                                 newBrightness = currentBrightness - DIM_STEPSIZE;
195                                 if (newBrightness < 0) {
196                                     newBrightness = 0;
197                                 }
198                                 value = String.valueOf(newBrightness);
199                                 currentBrightness = newBrightness;
200                                 break;
201                         }
202                         argument = "brightness";
203                         if ("0".equals(value)) {
204                             value = "1";
205                             argument = "brightness";
206                             setBinaryState(action, argument, "1");
207                             value = "0";
208                             argument = "BinaryState";
209                             setBinaryState(action, argument, "0");
210                         } else if ("0".equals(binaryState)) {
211                             argument = "BinaryState";
212                             setBinaryState(action, argument, "1");
213                         }
214                         argument = "brightness";
215                         setBinaryState(action, argument, value);
216                     }
217                     break;
218                 case CHANNEL_FADERCOUNTDOWNTIME:
219                     argument = "Fader";
220                     if (command instanceof DecimalType) {
221                         int commandValue = Integer.valueOf(String.valueOf(command));
222                         commandValue = commandValue * 60;
223                         String commandString = String.valueOf(commandValue);
224                         value = "<BinaryState></BinaryState>" + "<Duration></Duration>" + "<EndAction></EndAction>"
225                                 + "<brightness></brightness>" + "<fader>" + commandString + ":-1:1:0:0</fader>"
226                                 + "<UDN></UDN>";
227                         setBinaryState(action, argument, value);
228                     }
229                     break;
230                 case CHANNEL_FADERENABLED:
231                     argument = "Fader";
232                     if (command.equals(OnOffType.ON)) {
233                         value = "<BinaryState></BinaryState>" + "<Duration></Duration>" + "<EndAction></EndAction>"
234                                 + "<brightness></brightness>" + "<fader>600:-1:1:0:0</fader>" + "<UDN></UDN>";
235                     } else if (command.equals(OnOffType.OFF)) {
236                         value = "<BinaryState></BinaryState>" + "<Duration></Duration>" + "<EndAction></EndAction>"
237                                 + "<brightness></brightness>" + "<fader>600:-1:0:0:0</fader>" + "<UDN></UDN>";
238                     }
239                     setBinaryState(action, argument, value);
240                     break;
241                 case CHANNEL_TIMERSTART:
242                     argument = "Fader";
243                     long ts = System.currentTimeMillis() / 1000;
244                     timeStamp = String.valueOf(ts);
245                     logger.info("timestamp '{}' created", timeStamp);
246                     String faderSeconds = null;
247                     String faderEnabled = null;
248                     String fader = this.stateMap.get("fader");
249                     if (fader != null) {
250                         String[] splitFader = fader.split(":");
251                         if (splitFader[0] != null) {
252                             faderSeconds = splitFader[0];
253                         }
254                         if (splitFader[0] != null) {
255                             faderEnabled = splitFader[2];
256                         }
257                     }
258                     if (faderSeconds != null && faderEnabled != null) {
259                         if (OnOffType.ON.equals(command)) {
260                             value = "<BinaryState></BinaryState>" + "<Duration></Duration>" + "<EndAction></EndAction>"
261                                     + "<brightness></brightness>" + "<fader>" + faderSeconds + ":" + timeStamp + ":"
262                                     + faderEnabled + ":0:0</fader>" + "<UDN></UDN>";
263                             updateState(CHANNEL_STATE, OnOffType.ON);
264                         } else if (OnOffType.OFF.equals(command)) {
265                             value = "<BinaryState></BinaryState>" + "<Duration></Duration>" + "<EndAction></EndAction>"
266                                     + "<brightness></brightness>" + "<fader>" + faderSeconds + ":-1:" + faderEnabled
267                                     + ":0:0</fader>" + "<UDN></UDN>";
268                         }
269                     }
270                     setBinaryState(action, argument, value);
271                     break;
272                 case CHANNEL_NIGHTMODE:
273                     action = "ConfigureNightMode";
274                     argument = "NightModeConfiguration";
275                     String nightModeBrightness = String.valueOf(currentNightModeBrightness);
276                     if (OnOffType.ON.equals(command)) {
277                         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;"
278                                 + nightModeBrightness + "&lt;/nightModeBrightness&gt; \\n";
279                     } else if (OnOffType.OFF.equals(command)) {
280                         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;"
281                                 + nightModeBrightness + "&lt;/nightModeBrightness&gt; \\n";
282                     }
283                     setBinaryState(action, argument, value);
284                     break;
285                 case CHANNEL_NIGHTMODEBRIGHTNESS:
286                     action = "ConfigureNightMode";
287                     argument = "NightModeConfiguration";
288                     if (command instanceof PercentType) {
289                         int newBrightness = ((PercentType) command).intValue();
290                         String newNightModeBrightness = String.valueOf(newBrightness);
291                         value = "&lt;startTime&gt;0&lt;/startTime&gt; \\n&lt;nightMode&gt;" + currentNightModeState
292                                 + "&lt;/nightMode&gt; \\n&lt;endTime&gt;23400&lt;/endTime&gt; \\n&lt;nightModeBrightness&gt;"
293                                 + newNightModeBrightness + "&lt;/nightModeBrightness&gt; \\n";
294                         currentNightModeBrightness = newBrightness;
295                     } else if (command instanceof IncreaseDecreaseType) {
296                         int newBrightness;
297                         String newNightModeBrightness = null;
298                         switch (command.toString()) {
299                             case "INCREASE":
300                                 newBrightness = currentNightModeBrightness + DIM_STEPSIZE;
301                                 if (newBrightness > 100) {
302                                     newBrightness = 100;
303                                 }
304                                 newNightModeBrightness = String.valueOf(newBrightness);
305                                 currentBrightness = newBrightness;
306                                 break;
307                             case "DECREASE":
308                                 newBrightness = currentNightModeBrightness - DIM_STEPSIZE;
309                                 if (newBrightness < 0) {
310                                     newBrightness = 0;
311                                 }
312                                 newNightModeBrightness = String.valueOf(newBrightness);
313                                 currentNightModeBrightness = newBrightness;
314                                 break;
315                         }
316                         value = "&lt;startTime&gt;0&lt;/startTime&gt; \\n&lt;nightMode&gt;" + currentNightModeState
317                                 + "&lt;/nightMode&gt; \\n&lt;endTime&gt;23400&lt;/endTime&gt; \\n&lt;nightModeBrightness&gt;"
318                                 + newNightModeBrightness + "&lt;/nightModeBrightness&gt; \\n";
319                     }
320                     setBinaryState(action, argument, value);
321                     break;
322             }
323         }
324     }
325
326     @Override
327     public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) {
328         logger.debug("Received pair '{}':'{}' (service '{}') for thing '{}'",
329                 new Object[] { variable, value, service, this.getThing().getUID() });
330         updateStatus(ThingStatus.ONLINE);
331         if (variable != null && value != null) {
332             String oldBinaryState = this.stateMap.get("BinaryState");
333             this.stateMap.put(variable, value);
334             switch (variable) {
335                 case "BinaryState":
336                     if (oldBinaryState == null || !oldBinaryState.equals(value)) {
337                         State state = "0".equals(value) ? OnOffType.OFF : OnOffType.ON;
338                         logger.debug("State '{}' for device '{}' received", state, getThing().getUID());
339                         updateState(CHANNEL_BRIGHTNESS, state);
340                         if (state.equals(OnOffType.OFF)) {
341                             updateState(CHANNEL_TIMERSTART, OnOffType.OFF);
342                         }
343                     }
344                     break;
345                 case "brightness":
346                     logger.debug("brightness '{}' for device '{}' received", value, getThing().getUID());
347                     int newBrightnessValue = Integer.valueOf(value);
348                     State newBrightnessState = new PercentType(newBrightnessValue);
349                     String binaryState = this.stateMap.get("BinaryState");
350                     if (binaryState != null) {
351                         if ("1".equals(binaryState)) {
352                             updateState(CHANNEL_BRIGHTNESS, newBrightnessState);
353                         }
354                     }
355                     currentBrightness = newBrightnessValue;
356                     break;
357                 case "fader":
358                     logger.debug("fader '{}' for device '{}' received", value, getThing().getUID());
359                     String[] splitFader = value.split(":");
360                     if (splitFader[0] != null) {
361                         int faderSeconds = Integer.valueOf(splitFader[0]);
362                         State faderMinutes = new DecimalType(faderSeconds / 60);
363                         logger.debug("faderTime '{} minutes' for device '{}' received", faderMinutes,
364                                 getThing().getUID());
365                         updateState(CHANNEL_FADERCOUNTDOWNTIME, faderMinutes);
366                     }
367                     if (splitFader[1] != null) {
368                         State isTimerRunning = splitFader[1].equals("-1") ? OnOffType.OFF : OnOffType.ON;
369                         logger.debug("isTimerRunning '{}' for device '{}' received", isTimerRunning,
370                                 getThing().getUID());
371                         updateState(CHANNEL_TIMERSTART, isTimerRunning);
372                         if (isTimerRunning.equals(OnOffType.ON)) {
373                             updateState(CHANNEL_STATE, OnOffType.ON);
374                         }
375                     }
376                     if (splitFader[2] != null) {
377                         State isFaderEnabled = splitFader[1].equals("0") ? OnOffType.OFF : OnOffType.ON;
378                         logger.debug("isFaderEnabled '{}' for device '{}' received", isFaderEnabled,
379                                 getThing().getUID());
380                         updateState(CHANNEL_FADERENABLED, isFaderEnabled);
381                     }
382                     break;
383                 case "nightMode":
384                     State nightModeState = "0".equals(value) ? OnOffType.OFF : OnOffType.ON;
385                     currentNightModeState = value;
386                     logger.debug("nightModeState '{}' for device '{}' received", nightModeState, getThing().getUID());
387                     updateState(CHANNEL_NIGHTMODE, nightModeState);
388                     break;
389                 case "startTime":
390                     State startTimeState = getDateTimeState(value);
391                     logger.debug("startTimeState '{}' for device '{}' received", startTimeState, getThing().getUID());
392                     if (startTimeState != null) {
393                         updateState(CHANNEL_STARTTIME, startTimeState);
394                     }
395                     break;
396                 case "endTime":
397                     State endTimeState = getDateTimeState(value);
398                     logger.debug("endTimeState '{}' for device '{}' received", endTimeState, getThing().getUID());
399                     if (endTimeState != null) {
400                         updateState(CHANNEL_ENDTIME, endTimeState);
401                     }
402                     break;
403                 case "nightModeBrightness":
404                     int nightModeBrightnessValue = Integer.valueOf(value);
405                     currentNightModeBrightness = nightModeBrightnessValue;
406                     State nightModeBrightnessState = new PercentType(nightModeBrightnessValue);
407                     logger.debug("nightModeBrightnessState '{}' for device '{}' received", nightModeBrightnessState,
408                             getThing().getUID());
409                     updateState(CHANNEL_NIGHTMODEBRIGHTNESS, nightModeBrightnessState);
410                     break;
411             }
412         }
413     }
414
415     /**
416      * The {@link updateWemoState} polls the actual state of a WeMo device and
417      * calls {@link onValueReceived} to update the statemap and channels..
418      *
419      */
420     protected void updateWemoState() {
421         String localHost = getHost();
422         if (localHost.isEmpty()) {
423             logger.warn("Failed to get actual state for device '{}': IP address missing", getThing().getUID());
424             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
425                     "@text/config-status.error.missing-ip");
426             return;
427         }
428         String wemoURL = getWemoURL(localHost, BASICACTION);
429         if (wemoURL == null) {
430             logger.debug("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID());
431             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
432                     "@text/config-status.error.missing-url");
433             return;
434         }
435         String action = "GetBinaryState";
436         String variable = null;
437         String actionService = BASICACTION;
438         String value = null;
439         String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
440         String content = createStateRequestContent(action, actionService);
441         try {
442             String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
443             value = substringBetween(wemoCallResponse, "<BinaryState>", "</BinaryState>");
444             variable = "BinaryState";
445             this.onValueReceived(variable, value, actionService + "1");
446             value = substringBetween(wemoCallResponse, "<brightness>", "</brightness>");
447             variable = "brightness";
448             this.onValueReceived(variable, value, actionService + "1");
449             value = substringBetween(wemoCallResponse, "<fader>", "</fader>");
450             variable = "fader";
451             this.onValueReceived(variable, value, actionService + "1");
452             updateStatus(ThingStatus.ONLINE);
453         } catch (Exception e) {
454             logger.debug("Failed to get actual state for device '{}': {}", getThing().getUID(), e.getMessage());
455             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
456         }
457         action = "GetNightModeConfiguration";
458         variable = null;
459         value = null;
460         soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
461         content = createStateRequestContent(action, actionService);
462         try {
463             String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
464             value = substringBetween(wemoCallResponse, "<startTime>", "</startTime>");
465             variable = "startTime";
466             this.onValueReceived(variable, value, actionService + "1");
467             value = substringBetween(wemoCallResponse, "<endTime>", "</endTime>");
468             variable = "endTime";
469             this.onValueReceived(variable, value, actionService + "1");
470             value = substringBetween(wemoCallResponse, "<nightMode>", "</nightMode>");
471             variable = "nightMode";
472             this.onValueReceived(variable, value, actionService + "1");
473             value = substringBetween(wemoCallResponse, "<nightModeBrightness>", "</nightModeBrightness>");
474             variable = "nightModeBrightness";
475             this.onValueReceived(variable, value, actionService + "1");
476             updateStatus(ThingStatus.ONLINE);
477         } catch (Exception e) {
478             logger.debug("Failed to get actual NightMode state for device '{}': {}", getThing().getUID(),
479                     e.getMessage());
480             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
481         }
482     }
483
484     public @Nullable State getDateTimeState(String attributeValue) {
485         long value = 0;
486         try {
487             value = Long.parseLong(attributeValue);
488         } catch (NumberFormatException e) {
489             logger.warn("Unable to parse attributeValue '{}' for device '{}'; expected long", attributeValue,
490                     getThing().getUID());
491             return null;
492         }
493         ZonedDateTime zoned = ZonedDateTime.ofInstant(Instant.ofEpochSecond(value), TimeZone.getDefault().toZoneId());
494         State dateTimeState = new DateTimeType(zoned);
495         return dateTimeState;
496     }
497
498     public void setBinaryState(String action, String argument, String value) {
499         String localHost = getHost();
500         if (localHost.isEmpty()) {
501             logger.warn("Failed to set binary state for device '{}': IP address missing", getThing().getUID());
502             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
503                     "@text/config-status.error.missing-ip");
504             return;
505         }
506         String wemoURL = getWemoURL(localHost, BASICACTION);
507         if (wemoURL == null) {
508             logger.debug("Failed to set binary state for device '{}': URL cannot be created", getThing().getUID());
509             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
510                     "@text/config-status.error.missing-url");
511             return;
512         }
513         try {
514             String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\"";
515             String 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:basicevent:1\">" + "<" + argument
518                     + ">" + value + "</" + argument + ">" + "</u:" + action + ">" + "</s:Body>" + "</s:Envelope>";
519
520             wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
521             updateStatus(ThingStatus.ONLINE);
522         } catch (Exception e) {
523             logger.debug("Failed to set binaryState '{}' for device '{}': {}", value, getThing().getUID(),
524                     e.getMessage());
525             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
526         }
527     }
528
529     public void setTimerStart(String action, String argument, String value) {
530         String localHost = getHost();
531         if (localHost.isEmpty()) {
532             logger.warn("Failed to set timerStart for device '{}': IP address missing", getThing().getUID());
533             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
534                     "@text/config-status.error.missing-ip");
535             return;
536         }
537         String wemoURL = getWemoURL(localHost, BASICACTION);
538         if (wemoURL == null) {
539             logger.warn("Failed to set timerStart for device '{}': URL cannot be created", getThing().getUID());
540             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
541                     "@text/config-status.error.missing-url");
542             return;
543         }
544         try {
545             String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\"";
546             String content = "<?xml version=\"1.0\"?>"
547                     + "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
548                     + "<s:Body>" + "<u:SetBinaryState xmlns:u=\"urn:Belkin:service:basicevent:1\">" + value
549                     + "</u:SetBinaryState>" + "</s:Body>" + "</s:Envelope>";
550             wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
551             updateStatus(ThingStatus.ONLINE);
552         } catch (Exception e) {
553             logger.debug("Failed to set timerStart '{}' for device '{}': {}", value, getThing().getUID(),
554                     e.getMessage());
555             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
556         }
557     }
558 }