]> git.basschouten.com Git - openhab-addons.git/blob
ecdd033d507cd0acf34ca46bc7e23b2f59913b5b
[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.tellstick.internal.live;
14
15 import java.math.BigDecimal;
16 import java.util.concurrent.ExecutionException;
17 import java.util.concurrent.Future;
18 import java.util.concurrent.TimeUnit;
19 import java.util.concurrent.TimeoutException;
20
21 import javax.xml.bind.JAXBContext;
22 import javax.xml.bind.JAXBException;
23 import javax.xml.stream.FactoryConfigurationError;
24 import javax.xml.stream.XMLInputFactory;
25 import javax.xml.stream.XMLStreamException;
26 import javax.xml.stream.XMLStreamReader;
27
28 import org.asynchttpclient.AsyncHttpClient;
29 import org.asynchttpclient.AsyncHttpClientConfig;
30 import org.asynchttpclient.DefaultAsyncHttpClient;
31 import org.asynchttpclient.DefaultAsyncHttpClientConfig;
32 import org.asynchttpclient.DefaultAsyncHttpClientConfig.Builder;
33 import org.asynchttpclient.Response;
34 import org.asynchttpclient.oauth.ConsumerKey;
35 import org.asynchttpclient.oauth.OAuthSignatureCalculator;
36 import org.asynchttpclient.oauth.RequestToken;
37 import org.openhab.binding.tellstick.internal.TelldusBindingException;
38 import org.openhab.binding.tellstick.internal.handler.TelldusDeviceController;
39 import org.openhab.binding.tellstick.internal.live.xml.TelldusLiveResponse;
40 import org.openhab.binding.tellstick.internal.live.xml.TellstickNetDevice;
41 import org.openhab.core.library.types.IncreaseDecreaseType;
42 import org.openhab.core.library.types.OnOffType;
43 import org.openhab.core.library.types.PercentType;
44 import org.openhab.core.types.Command;
45 import org.openhab.core.types.State;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48 import org.tellstick.JNA;
49 import org.tellstick.device.TellstickDevice;
50 import org.tellstick.device.TellstickDeviceEvent;
51 import org.tellstick.device.TellstickException;
52 import org.tellstick.device.TellstickSensorEvent;
53 import org.tellstick.device.iface.Device;
54 import org.tellstick.device.iface.DeviceChangeListener;
55 import org.tellstick.device.iface.SensorListener;
56 import org.tellstick.device.iface.SwitchableDevice;
57
58 /**
59  * {@link TelldusLiveDeviceController} is the communication with Telldus Live service (Tellstick.NET and ZNET)
60  * This controller uses XML based Rest API to communicate with Telldus Live.
61  *
62  * @author Jarle Hjortland - Initial contribution
63  */
64 public class TelldusLiveDeviceController implements DeviceChangeListener, SensorListener, TelldusDeviceController {
65     private final Logger logger = LoggerFactory.getLogger(TelldusLiveDeviceController.class);
66     private long lastSend = 0;
67     public static final long DEFAULT_INTERVAL_BETWEEN_SEND = 250;
68     static final int REQUEST_TIMEOUT_MS = 5000;
69     private AsyncHttpClient client;
70     static final String HTTP_API_TELLDUS_COM_XML = "http://api.telldus.com/xml/";
71     static final String HTTP_TELLDUS_CLIENTS = HTTP_API_TELLDUS_COM_XML + "clients/list";
72     static final String HTTP_TELLDUS_DEVICES = HTTP_API_TELLDUS_COM_XML + "devices/list?supportedMethods=19";
73     static final String HTTP_TELLDUS_SENSORS = HTTP_API_TELLDUS_COM_XML
74             + "sensors/list?includeValues=1&includeScale=1&includeUnit=1";
75     static final String HTTP_TELLDUS_SENSOR_INFO = HTTP_API_TELLDUS_COM_XML + "sensor/info";
76     static final String HTTP_TELLDUS_DEVICE_DIM = HTTP_API_TELLDUS_COM_XML + "device/dim?id=%d&level=%d";
77     static final String HTTP_TELLDUS_DEVICE_TURNOFF = HTTP_API_TELLDUS_COM_XML + "device/turnOff?id=%d";
78     static final String HTTP_TELLDUS_DEVICE_TURNON = HTTP_API_TELLDUS_COM_XML + "device/turnOn?id=%d";
79     private static final int MAX_RETRIES = 3;
80
81     public TelldusLiveDeviceController() {
82     }
83
84     @Override
85     public void dispose() {
86         try {
87             client.close();
88         } catch (Exception e) {
89             logger.error("Failed to close client", e);
90         }
91     }
92
93     void connectHttpClient(String publicKey, String privateKey, String token, String tokenSecret) {
94         ConsumerKey consumer = new ConsumerKey(publicKey, privateKey);
95         RequestToken user = new RequestToken(token, tokenSecret);
96         OAuthSignatureCalculator calc = new OAuthSignatureCalculator(consumer, user);
97         this.client = new DefaultAsyncHttpClient(createAsyncHttpClientConfig());
98         try {
99             this.client.setSignatureCalculator(calc);
100             Response response = client.prepareGet(HTTP_TELLDUS_CLIENTS).execute().get();
101             logger.debug("Response {} statusText {}", response.getResponseBody(), response.getStatusText());
102         } catch (InterruptedException | ExecutionException e) {
103             logger.error("Failed to connect", e);
104         }
105     }
106
107     private AsyncHttpClientConfig createAsyncHttpClientConfig() {
108         Builder builder = new DefaultAsyncHttpClientConfig.Builder();
109         builder.setConnectTimeout(REQUEST_TIMEOUT_MS);
110         return builder.build();
111     }
112
113     @Override
114     public void handleSendEvent(Device device, int resendCount, boolean isdimmer, Command command)
115             throws TellstickException {
116         logger.info("Send {} to {}", command, device);
117         if (device instanceof TellstickNetDevice) {
118             if (command == OnOffType.ON) {
119                 turnOn(device);
120             } else if (command == OnOffType.OFF) {
121                 turnOff(device);
122             } else if (command instanceof PercentType) {
123                 dim(device, (PercentType) command);
124             } else if (command instanceof IncreaseDecreaseType) {
125                 increaseDecrease(device, ((IncreaseDecreaseType) command));
126             }
127         } else if (device instanceof SwitchableDevice) {
128             if (command == OnOffType.ON) {
129                 if (isdimmer) {
130                     logger.debug("Turn off first in case it is allready on");
131                     turnOff(device);
132                 }
133                 turnOn(device);
134             } else if (command == OnOffType.OFF) {
135                 turnOff(device);
136             }
137         } else {
138             logger.warn("Cannot send to {}", device);
139         }
140     }
141
142     private void increaseDecrease(Device dev, IncreaseDecreaseType increaseDecreaseType) throws TellstickException {
143         String strValue = ((TellstickDevice) dev).getData();
144         double value = 0;
145         if (strValue != null) {
146             value = Double.valueOf(strValue);
147         }
148         int percent = (int) Math.round((value / 255) * 100);
149         if (IncreaseDecreaseType.INCREASE == increaseDecreaseType) {
150             percent = Math.min(percent + 10, 100);
151         } else if (IncreaseDecreaseType.DECREASE == increaseDecreaseType) {
152             percent = Math.max(percent - 10, 0);
153         }
154         dim(dev, new PercentType(percent));
155     }
156
157     private void dim(Device dev, PercentType command) throws TellstickException {
158         double value = command.doubleValue();
159
160         // 0 means OFF and 100 means ON
161         if (value == 0 && dev instanceof TellstickNetDevice) {
162             turnOff(dev);
163         } else if (value == 100 && dev instanceof TellstickNetDevice) {
164             turnOn(dev);
165         } else if (dev instanceof TellstickNetDevice
166                 && (((TellstickNetDevice) dev).getMethods() & JNA.CLibrary.TELLSTICK_DIM) > 0) {
167             long tdVal = Math.round((value / 100) * 255);
168             TelldusLiveResponse response = callRestMethod(String.format(HTTP_TELLDUS_DEVICE_DIM, dev.getId(), tdVal),
169                     TelldusLiveResponse.class);
170             handleResponse((TellstickNetDevice) dev, response);
171         } else {
172             throw new TelldusBindingException("Cannot send DIM to " + dev);
173         }
174     }
175
176     private void turnOff(Device dev) throws TellstickException {
177         if (dev instanceof TellstickNetDevice) {
178             TelldusLiveResponse response = callRestMethod(String.format(HTTP_TELLDUS_DEVICE_TURNOFF, dev.getId()),
179                     TelldusLiveResponse.class);
180             handleResponse((TellstickNetDevice) dev, response);
181         } else {
182             throw new TelldusBindingException("Cannot send OFF to " + dev);
183         }
184     }
185
186     private void handleResponse(TellstickNetDevice device, TelldusLiveResponse response) throws TellstickException {
187         if (response == null || (response.status == null && response.error == null)) {
188             throw new TelldusBindingException("No response " + response);
189         } else if (response.error != null) {
190             if (response.error.equals("The client for this device is currently offline")) {
191                 device.setOnline(false);
192                 device.setUpdated(true);
193             }
194             throw new TelldusBindingException("Error " + response.error);
195         } else if (!response.status.trim().equals("success")) {
196             throw new TelldusBindingException("Response " + response.status);
197         }
198     }
199
200     private void turnOn(Device dev) throws TellstickException {
201         if (dev instanceof TellstickNetDevice) {
202             TelldusLiveResponse response = callRestMethod(String.format(HTTP_TELLDUS_DEVICE_TURNON, dev.getId()),
203                     TelldusLiveResponse.class);
204             handleResponse((TellstickNetDevice) dev, response);
205         } else {
206             throw new TelldusBindingException("Cannot send ON to " + dev);
207         }
208     }
209
210     @Override
211     public State calcState(Device dev) {
212         TellstickNetDevice device = (TellstickNetDevice) dev;
213         State st = null;
214         if (device.getOnline()) {
215             switch (device.getState()) {
216                 case JNA.CLibrary.TELLSTICK_TURNON:
217                     st = OnOffType.ON;
218                     break;
219                 case JNA.CLibrary.TELLSTICK_TURNOFF:
220                     st = OnOffType.OFF;
221                     break;
222                 case JNA.CLibrary.TELLSTICK_DIM:
223                     BigDecimal dimValue = new BigDecimal(device.getStatevalue());
224                     if (dimValue.intValue() == 0) {
225                         st = OnOffType.OFF;
226                     } else if (dimValue.intValue() >= 255) {
227                         st = OnOffType.ON;
228                     } else {
229                         st = OnOffType.ON;
230                     }
231                     break;
232                 default:
233                     logger.warn("Could not handle {} for {}", device.getState(), device);
234             }
235         }
236         return st;
237     }
238
239     @Override
240     public BigDecimal calcDimValue(Device device) {
241         BigDecimal dimValue = new BigDecimal(0);
242         switch (((TellstickNetDevice) device).getState()) {
243             case JNA.CLibrary.TELLSTICK_TURNON:
244                 dimValue = new BigDecimal(100);
245                 break;
246             case JNA.CLibrary.TELLSTICK_TURNOFF:
247                 break;
248             case JNA.CLibrary.TELLSTICK_DIM:
249                 dimValue = new BigDecimal(((TellstickNetDevice) device).getStatevalue());
250                 dimValue = dimValue.multiply(new BigDecimal(100));
251                 dimValue = dimValue.divide(new BigDecimal(255), 0, BigDecimal.ROUND_HALF_UP);
252                 break;
253             default:
254                 logger.warn("Could not handle {} for {}", (((TellstickNetDevice) device).getState()), device);
255         }
256         return dimValue;
257     }
258
259     public long getLastSend() {
260         return lastSend;
261     }
262
263     public void setLastSend(long currentTimeMillis) {
264         lastSend = currentTimeMillis;
265     }
266
267     @Override
268     public void onRequest(TellstickSensorEvent newDevices) {
269         setLastSend(newDevices.getTimestamp());
270     }
271
272     @Override
273     public void onRequest(TellstickDeviceEvent newDevices) {
274         setLastSend(newDevices.getTimestamp());
275     }
276
277     <T> T callRestMethod(String uri, Class<T> response) throws TelldusLiveException {
278         T resultObj = null;
279         try {
280             for (int i = 0; i < MAX_RETRIES; i++) {
281                 try {
282                     resultObj = innerCallRest(uri, response);
283                     break;
284                 } catch (TimeoutException e) {
285                     logger.warn("TimeoutException error in get", e);
286                 } catch (InterruptedException e) {
287                     logger.warn("InterruptedException error in get", e);
288                 }
289             }
290         } catch (JAXBException e) {
291             logger.warn("Encoding error in get", e);
292             logResponse(uri, e);
293             throw new TelldusLiveException(e);
294         } catch (XMLStreamException e) {
295             logger.warn("Communication error in get", e);
296             logResponse(uri, e);
297             throw new TelldusLiveException(e);
298         } catch (ExecutionException e) {
299             logger.warn("ExecutionException error in get", e);
300             throw new TelldusLiveException(e);
301         }
302         return resultObj;
303     }
304
305     private <T> T innerCallRest(String uri, Class<T> response) throws InterruptedException, ExecutionException,
306             TimeoutException, JAXBException, FactoryConfigurationError, XMLStreamException {
307         Future<Response> future = client.prepareGet(uri).execute();
308         Response resp = future.get(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
309         // TelldusLiveHandler.logger.info("Devices" + resp.getResponseBody());
310         JAXBContext jc = JAXBContext.newInstance(response);
311         XMLInputFactory xif = XMLInputFactory.newInstance();
312         xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
313         xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
314         XMLStreamReader xsr = xif.createXMLStreamReader(resp.getResponseBodyAsStream());
315         // xsr = new PropertyRenamerDelegate(xsr);
316
317         @SuppressWarnings("unchecked")
318         T obj = (T) jc.createUnmarshaller().unmarshal(xsr);
319         if (logger.isTraceEnabled()) {
320             logger.trace("Request [{}] Response:{}", uri, resp.getResponseBody());
321         }
322         return obj;
323     }
324
325     private void logResponse(String uri, Exception e) {
326         if (e != null) {
327             logger.warn("Request [{}] Failure:{}", uri, e.getMessage());
328         }
329     }
330 }