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