]> git.basschouten.com Git - openhab-addons.git/blob
71926ae0cebfdb1574202ec05e0fd64ffef391b0
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.util.concurrent.ScheduledFuture;
19 import java.util.concurrent.TimeUnit;
20
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.jupnp.UpnpService;
24 import org.openhab.binding.wemo.internal.http.WemoHttpCall;
25 import org.openhab.core.config.core.Configuration;
26 import org.openhab.core.io.transport.upnp.UpnpIOService;
27 import org.openhab.core.library.types.IncreaseDecreaseType;
28 import org.openhab.core.library.types.OnOffType;
29 import org.openhab.core.library.types.PercentType;
30 import org.openhab.core.thing.Bridge;
31 import org.openhab.core.thing.ChannelUID;
32 import org.openhab.core.thing.Thing;
33 import org.openhab.core.thing.ThingStatus;
34 import org.openhab.core.thing.ThingStatusDetail;
35 import org.openhab.core.thing.ThingStatusInfo;
36 import org.openhab.core.thing.binding.ThingHandler;
37 import org.openhab.core.types.Command;
38 import org.openhab.core.types.RefreshType;
39 import org.openhab.core.types.State;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 /**
44  * {@link WemoLightHandler} is the handler for a WeMo light, responsible for handling commands and state updates for the
45  * different channels of a WeMo light.
46  *
47  * @author Hans-Jörg Merk - Initial contribution
48  */
49 @NonNullByDefault
50 public class WemoLightHandler extends WemoBaseThingHandler {
51
52     private final Logger logger = LoggerFactory.getLogger(WemoLightHandler.class);
53
54     private final Object jobLock = new Object();
55
56     private @Nullable WemoBridgeHandler wemoBridgeHandler;
57
58     private @Nullable String wemoLightID;
59
60     private int currentBrightness;
61
62     /**
63      * Set dimming stepsize to 5%
64      */
65     private static final int DIM_STEPSIZE = 5;
66
67     /**
68      * The default refresh initial delay in Seconds.
69      */
70     private static final int DEFAULT_REFRESH_INITIAL_DELAY = 15;
71
72     private @Nullable ScheduledFuture<?> pollingJob;
73
74     public WemoLightHandler(Thing thing, UpnpIOService upnpIOService, UpnpService upnpService,
75             WemoHttpCall wemoHttpcaller) {
76         super(thing, upnpIOService, upnpService, wemoHttpcaller);
77
78         logger.debug("Creating a WemoLightHandler for thing '{}'", getThing().getUID());
79     }
80
81     @Override
82     public void initialize() {
83         super.initialize();
84         // initialize() is only called if the required parameter 'deviceID' is available
85         wemoLightID = (String) getConfig().get(DEVICE_ID);
86
87         final Bridge bridge = getBridge();
88         if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE) {
89             addSubscription(BRIDGEEVENT);
90             pollingJob = scheduler.scheduleWithFixedDelay(this::poll, DEFAULT_REFRESH_INITIAL_DELAY,
91                     DEFAULT_REFRESH_INTERVAL_SECONDS, TimeUnit.SECONDS);
92             updateStatus(ThingStatus.UNKNOWN);
93         } else {
94             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.BRIDGE_OFFLINE);
95         }
96     }
97
98     @Override
99     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
100         if (bridgeStatusInfo.getStatus().equals(ThingStatus.ONLINE)) {
101             updateStatus(ThingStatus.ONLINE);
102         } else {
103             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
104             ScheduledFuture<?> job = this.pollingJob;
105             if (job != null && !job.isCancelled()) {
106                 job.cancel(true);
107             }
108             this.pollingJob = null;
109         }
110     }
111
112     @Override
113     public void dispose() {
114         logger.debug("WemoLightHandler disposed.");
115
116         ScheduledFuture<?> job = this.pollingJob;
117         if (job != null && !job.isCancelled()) {
118             job.cancel(true);
119         }
120         this.pollingJob = null;
121         super.dispose();
122     }
123
124     private synchronized @Nullable WemoBridgeHandler getWemoBridgeHandler() {
125         Bridge bridge = getBridge();
126         if (bridge == null) {
127             logger.warn("Required bridge not defined for device {}.", wemoLightID);
128             return null;
129         }
130         ThingHandler handler = bridge.getHandler();
131         if (handler instanceof WemoBridgeHandler) {
132             this.wemoBridgeHandler = (WemoBridgeHandler) handler;
133         } else {
134             logger.debug("No available bridge handler found for {} bridge {} .", wemoLightID, bridge.getUID());
135             return null;
136         }
137         return this.wemoBridgeHandler;
138     }
139
140     private void poll() {
141         synchronized (jobLock) {
142             if (pollingJob == null) {
143                 return;
144             }
145             try {
146                 logger.debug("Polling job");
147                 // Check if the Wemo device is set in the UPnP service registry
148                 if (!isUpnpDeviceRegistered()) {
149                     logger.debug("UPnP device {} not yet registered", getUDN());
150                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
151                             "@text/config-status.pending.device-not-registered [\"" + getUDN() + "\"]");
152                     return;
153                 }
154                 getDeviceState();
155             } catch (Exception e) {
156                 logger.debug("Exception during poll: {}", e.getMessage(), e);
157             }
158         }
159     }
160
161     @Override
162     public void handleCommand(ChannelUID channelUID, Command command) {
163         String wemoURL = getWemoURL(BASICACTION);
164         if (wemoURL == null) {
165             logger.debug("Failed to send command '{}' for device '{}': URL cannot be created", command,
166                     getThing().getUID());
167             return;
168         }
169         if (command instanceof RefreshType) {
170             try {
171                 getDeviceState();
172             } catch (Exception e) {
173                 logger.debug("Exception during poll", e);
174             }
175         } else {
176             Configuration configuration = getConfig();
177             configuration.get(DEVICE_ID);
178
179             WemoBridgeHandler wemoBridge = getWemoBridgeHandler();
180             if (wemoBridge == null) {
181                 logger.debug("wemoBridgeHandler not found, cannot handle command");
182                 return;
183             }
184             String devUDN = "uuid:" + wemoBridge.getThing().getConfiguration().get(UDN).toString();
185             logger.trace("WeMo Bridge to send command to : {}", devUDN);
186
187             String value = null;
188             String capability = null;
189             switch (channelUID.getId()) {
190                 case CHANNEL_BRIGHTNESS:
191                     capability = "10008";
192                     if (command instanceof PercentType) {
193                         int newBrightness = ((PercentType) command).intValue();
194                         logger.trace("wemoLight received Value {}", newBrightness);
195                         int value1 = Math.round(newBrightness * 255 / 100);
196                         value = value1 + ":0";
197                         currentBrightness = newBrightness;
198                     } else if (command instanceof OnOffType) {
199                         switch (command.toString()) {
200                             case "ON":
201                                 value = "255:0";
202                                 break;
203                             case "OFF":
204                                 value = "0:0";
205                                 break;
206                         }
207                     } else if (command instanceof IncreaseDecreaseType) {
208                         int newBrightness;
209                         switch (command.toString()) {
210                             case "INCREASE":
211                                 currentBrightness = currentBrightness + DIM_STEPSIZE;
212                                 newBrightness = Math.round(currentBrightness * 255 / 100);
213                                 if (newBrightness > 255) {
214                                     newBrightness = 255;
215                                 }
216                                 value = newBrightness + ":0";
217                                 break;
218                             case "DECREASE":
219                                 currentBrightness = currentBrightness - DIM_STEPSIZE;
220                                 newBrightness = Math.round(currentBrightness * 255 / 100);
221                                 if (newBrightness < 0) {
222                                     newBrightness = 0;
223                                 }
224                                 value = newBrightness + ":0";
225                                 break;
226                         }
227                     }
228                     break;
229                 case CHANNEL_STATE:
230                     capability = "10006";
231                     switch (command.toString()) {
232                         case "ON":
233                             value = "1";
234                             break;
235                         case "OFF":
236                             value = "0";
237                             break;
238                     }
239                     break;
240             }
241             try {
242                 if (capability != null && value != null) {
243                     String soapHeader = "\"urn:Belkin:service:bridge:1#SetDeviceStatus\"";
244                     String content = "<?xml version=\"1.0\"?>"
245                             + "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
246                             + "<s:Body>" + "<u:SetDeviceStatus xmlns:u=\"urn:Belkin:service:bridge:1\">"
247                             + "<DeviceStatusList>"
248                             + "&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&lt;DeviceStatus&gt;&lt;DeviceID&gt;"
249                             + wemoLightID
250                             + "&lt;/DeviceID&gt;&lt;IsGroupAction&gt;NO&lt;/IsGroupAction&gt;&lt;CapabilityID&gt;"
251                             + capability + "&lt;/CapabilityID&gt;&lt;CapabilityValue&gt;" + value
252                             + "&lt;/CapabilityValue&gt;&lt;/DeviceStatus&gt;" + "</DeviceStatusList>"
253                             + "</u:SetDeviceStatus>" + "</s:Body>" + "</s:Envelope>";
254
255                     wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
256                     if ("10008".equals(capability)) {
257                         OnOffType binaryState = null;
258                         binaryState = "0".equals(value) ? OnOffType.OFF : OnOffType.ON;
259                         updateState(CHANNEL_STATE, binaryState);
260                     }
261                     updateStatus(ThingStatus.ONLINE);
262                 }
263             } catch (Exception e) {
264                 logger.warn("Failed to send command '{}' for device '{}': {}", command, getThing().getUID(),
265                         e.getMessage());
266                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
267             }
268         }
269     }
270
271     @Override
272     public @Nullable String getUDN() {
273         WemoBridgeHandler wemoBridge = getWemoBridgeHandler();
274         if (wemoBridge == null) {
275             logger.debug("wemoBridgeHandler not found");
276             return null;
277         }
278         return (String) wemoBridge.getThing().getConfiguration().get(UDN);
279     }
280
281     /**
282      * The {@link getDeviceState} is used for polling the actual state of a WeMo Light and updating the according
283      * channel states.
284      */
285     public void getDeviceState() {
286         logger.debug("Request actual state for LightID '{}'", wemoLightID);
287         String wemoURL = getWemoURL(BRIDGEACTION);
288         if (wemoURL == null) {
289             logger.debug("Failed to get actual state for device '{}': URL cannot be created", getThing().getUID());
290             return;
291         }
292         try {
293             String soapHeader = "\"urn:Belkin:service:bridge:1#GetDeviceStatus\"";
294             String content = "<?xml version=\"1.0\"?>"
295                     + "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
296                     + "<s:Body>" + "<u:GetDeviceStatus xmlns:u=\"urn:Belkin:service:bridge:1\">" + "<DeviceIDs>"
297                     + wemoLightID + "</DeviceIDs>" + "</u:GetDeviceStatus>" + "</s:Body>" + "</s:Envelope>";
298
299             String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
300             wemoCallResponse = unescapeXml(wemoCallResponse);
301             String response = substringBetween(wemoCallResponse, "<CapabilityValue>", "</CapabilityValue>");
302             logger.trace("wemoNewLightState = {}", response);
303             String[] splitResponse = response.split(",");
304             if (splitResponse[0] != null) {
305                 OnOffType binaryState = null;
306                 binaryState = "0".equals(splitResponse[0]) ? OnOffType.OFF : OnOffType.ON;
307                 updateState(CHANNEL_STATE, binaryState);
308             }
309             if (splitResponse[1] != null) {
310                 String splitBrightness[] = splitResponse[1].split(":");
311                 if (splitBrightness[0] != null) {
312                     int newBrightnessValue = Integer.valueOf(splitBrightness[0]);
313                     int newBrightness = Math.round(newBrightnessValue * 100 / 255);
314                     logger.trace("newBrightness = {}", newBrightness);
315                     State newBrightnessState = new PercentType(newBrightness);
316                     updateState(CHANNEL_BRIGHTNESS, newBrightnessState);
317                     currentBrightness = newBrightness;
318                 }
319             }
320             updateStatus(ThingStatus.ONLINE);
321         } catch (Exception e) {
322             logger.debug("Could not retrieve new Wemo light state for '{}':", getThing().getUID(), e);
323             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
324         }
325     }
326
327     @Override
328     public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) {
329         logger.trace("Received pair '{}':'{}' (service '{}') for thing '{}'",
330                 new Object[] { variable, value, service, this.getThing().getUID() });
331         String capabilityId = substringBetween(value, "<CapabilityId>", "</CapabilityId>");
332         String newValue = substringBetween(value, "<Value>", "</Value>");
333         switch (capabilityId) {
334             case "10006":
335                 OnOffType binaryState = null;
336                 binaryState = "0".equals(newValue) ? OnOffType.OFF : OnOffType.ON;
337                 updateState(CHANNEL_STATE, binaryState);
338                 break;
339             case "10008":
340                 String splitValue[] = newValue.split(":");
341                 if (splitValue[0] != null) {
342                     int newBrightnessValue = Integer.valueOf(splitValue[0]);
343                     int newBrightness = Math.round(newBrightnessValue * 100 / 255);
344                     State newBrightnessState = new PercentType(newBrightness);
345                     updateState(CHANNEL_BRIGHTNESS, newBrightnessState);
346                     currentBrightness = newBrightness;
347                 }
348                 break;
349         }
350     }
351 }