]> git.basschouten.com Git - openhab-addons.git/blob
a9fd1bb7b14ef1d8d2eee0d9a1591c2d9e29b702
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.local;
14
15 import java.math.BigDecimal;
16 import java.math.RoundingMode;
17 import java.util.concurrent.ExecutionException;
18 import java.util.concurrent.TimeUnit;
19 import java.util.concurrent.TimeoutException;
20
21 import org.eclipse.jetty.client.HttpClient;
22 import org.eclipse.jetty.client.api.ContentResponse;
23 import org.eclipse.jetty.client.api.Request;
24 import org.eclipse.jetty.http.HttpMethod;
25 import org.openhab.binding.tellstick.internal.TelldusBindingException;
26 import org.openhab.binding.tellstick.internal.conf.TelldusLocalConfiguration;
27 import org.openhab.binding.tellstick.internal.handler.TelldusDeviceController;
28 import org.openhab.binding.tellstick.internal.local.dto.TelldusLocalResponseDTO;
29 import org.openhab.binding.tellstick.internal.local.dto.TellstickLocalDeviceDTO;
30 import org.openhab.core.library.types.IncreaseDecreaseType;
31 import org.openhab.core.library.types.OnOffType;
32 import org.openhab.core.library.types.PercentType;
33 import org.openhab.core.types.Command;
34 import org.openhab.core.types.State;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37 import org.tellstick.JNA;
38 import org.tellstick.device.TellstickDevice;
39 import org.tellstick.device.TellstickDeviceEvent;
40 import org.tellstick.device.TellstickException;
41 import org.tellstick.device.TellstickSensorEvent;
42 import org.tellstick.device.iface.Device;
43 import org.tellstick.device.iface.DeviceChangeListener;
44 import org.tellstick.device.iface.SensorListener;
45 import org.tellstick.device.iface.SwitchableDevice;
46
47 import com.google.gson.Gson;
48 import com.google.gson.JsonSyntaxException;
49
50 /**
51  * {@link TelldusLocalDeviceController} handles the communication with Telldus Local API (Tellstick ZNET v1/v2)
52  * This controller uses JSON based Rest API to communicate with Telldus Local API.
53  *
54  * @author Jan Gustafsson - Initial contribution
55  */
56 public class TelldusLocalDeviceController implements DeviceChangeListener, SensorListener, TelldusDeviceController {
57     private final Logger logger = LoggerFactory.getLogger(TelldusLocalDeviceController.class);
58     private long lastSend = 0;
59     public static final long DEFAULT_INTERVAL_BETWEEN_SEND_SEC = 250;
60     private final HttpClient httpClient;
61     private final Gson gson = new Gson();
62     private String localApiUrl;
63     private String authorizationHeader = "Bearer ";
64     static final String HTTP_LOCAL_API = "api/";
65     static final String HTTP_LOCAL_API_DEVICES = HTTP_LOCAL_API + "devices/list?supportedMethods=19&includeIgnored=0";
66     static final String HTTP_LOCAL_API_SENSORS = HTTP_LOCAL_API
67             + "sensors/list?includeValues=1&includeScale=1&includeUnit=1&includeIgnored=0";
68     static final String HTTP_LOCAL_API_SENSOR_INFO = HTTP_LOCAL_API + "sensor/info";
69     static final String HTTP_LOCAL_API_DEVICE_DIM = HTTP_LOCAL_API + "device/dim?id=%d&level=%d";
70     static final String HTTP_LOCAL_API_DEVICE_TURNOFF = HTTP_LOCAL_API + "device/turnOff?id=%d";
71     static final String HTTP_LOCAL_DEVICE_TURNON = HTTP_LOCAL_API + "device/turnOn?id=%d";
72     private static final int MAX_RETRIES = 3;
73     private static final int REQUEST_TIMEOUT_MS = 10_000;
74
75     public TelldusLocalDeviceController(TelldusLocalConfiguration configuration, HttpClient httpClient) {
76         this.httpClient = httpClient;
77         localApiUrl = "http://" + configuration.ipAddress + "/";
78         authorizationHeader = authorizationHeader + configuration.accessToken;
79     }
80
81     @Override
82     public void dispose() {
83     }
84
85     @Override
86     public void handleSendEvent(Device device, int resendCount, boolean isdimmer, Command command)
87             throws TellstickException {
88         logger.debug("Send {} to {}", command, device);
89         try {
90             if (device instanceof TellstickLocalDeviceDTO) {
91                 if (command == OnOffType.ON) {
92                     turnOn(device);
93                 } else if (command == OnOffType.OFF) {
94                     turnOff(device);
95                 } else if (command instanceof PercentType percentCommand) {
96                     dim(device, percentCommand);
97                 } else if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
98                     increaseDecrease(device, increaseDecreaseCommand);
99                 }
100             } else if (device instanceof SwitchableDevice) {
101                 if (command == OnOffType.ON) {
102                     if (isdimmer) {
103                         logger.trace("Turn off first in case it is allready on");
104                         turnOff(device);
105                     }
106                     turnOn(device);
107                 } else if (command == OnOffType.OFF) {
108                     turnOff(device);
109                 }
110             } else {
111                 logger.warn("Cannot send to {}", device);
112             }
113         } catch (InterruptedException e) {
114             logger.debug("OH is shut-down.");
115         }
116     }
117
118     private void increaseDecrease(Device dev, IncreaseDecreaseType increaseDecreaseType)
119             throws TellstickException, InterruptedException {
120         String strValue = ((TellstickDevice) dev).getData();
121         double value = 0;
122         if (strValue != null) {
123             value = Double.valueOf(strValue);
124         }
125         int percent = (int) Math.round((value / 255) * 100);
126         if (IncreaseDecreaseType.INCREASE == increaseDecreaseType) {
127             percent = Math.min(percent + 10, 100);
128         } else if (IncreaseDecreaseType.DECREASE == increaseDecreaseType) {
129             percent = Math.max(percent - 10, 0);
130         }
131         dim(dev, new PercentType(percent));
132     }
133
134     private void dim(Device dev, PercentType command) throws TellstickException, InterruptedException {
135         double value = command.doubleValue();
136
137         // 0 means OFF and 100 means ON
138         if (value == 0 && dev instanceof TellstickLocalDeviceDTO) {
139             turnOff(dev);
140         } else if (value == 100 && dev instanceof TellstickLocalDeviceDTO) {
141             turnOn(dev);
142         } else if (dev instanceof TellstickLocalDeviceDTO device
143                 && (device.getMethods() & JNA.CLibrary.TELLSTICK_DIM) > 0) {
144             long tdVal = Math.round((value / 100) * 255);
145             TelldusLocalResponseDTO response = callRestMethod(
146                     String.format(HTTP_LOCAL_API_DEVICE_DIM, dev.getId(), tdVal), TelldusLocalResponseDTO.class);
147             handleResponse(device, response);
148         } else {
149             throw new TelldusBindingException("Cannot send DIM to " + dev);
150         }
151     }
152
153     private void turnOff(Device dev) throws TellstickException, InterruptedException {
154         if (dev instanceof TellstickLocalDeviceDTO device) {
155             TelldusLocalResponseDTO response = callRestMethod(String.format(HTTP_LOCAL_API_DEVICE_TURNOFF, dev.getId()),
156                     TelldusLocalResponseDTO.class);
157             handleResponse(device, response);
158         } else {
159             throw new TelldusBindingException("Cannot send OFF to " + dev);
160         }
161     }
162
163     private void handleResponse(TellstickLocalDeviceDTO device, TelldusLocalResponseDTO response)
164             throws TellstickException {
165         if (response == null || (response.getStatus() == null && response.getError() == null)) {
166             throw new TelldusBindingException("No response " + response);
167         } else if (response.getError() != null) {
168             device.setUpdated(true);
169             throw new TelldusBindingException("Error " + response.getError());
170         } else if (!"success".equals(response.getStatus().trim())) {
171             throw new TelldusBindingException("Response " + response.getStatus());
172         }
173     }
174
175     private void turnOn(Device dev) throws TellstickException, InterruptedException {
176         if (dev instanceof TellstickLocalDeviceDTO device) {
177             TelldusLocalResponseDTO response = callRestMethod(String.format(HTTP_LOCAL_DEVICE_TURNON, dev.getId()),
178                     TelldusLocalResponseDTO.class);
179             handleResponse(device, response);
180         } else {
181             throw new TelldusBindingException("Cannot send ON to " + dev);
182         }
183     }
184
185     @Override
186     public State calcState(Device dev) {
187         TellstickLocalDeviceDTO device = (TellstickLocalDeviceDTO) dev;
188         State st = null;
189
190         switch (device.getState()) {
191             case JNA.CLibrary.TELLSTICK_TURNON:
192                 st = OnOffType.ON;
193                 break;
194             case JNA.CLibrary.TELLSTICK_TURNOFF:
195                 st = OnOffType.OFF;
196                 break;
197             case JNA.CLibrary.TELLSTICK_DIM:
198                 BigDecimal dimValue = new BigDecimal(device.getStatevalue());
199                 if (dimValue.intValue() == 0) {
200                     st = OnOffType.OFF;
201                 } else if (dimValue.intValue() >= 255) {
202                     st = OnOffType.ON;
203                 } else {
204                     st = OnOffType.ON;
205                 }
206                 break;
207             default:
208                 logger.warn("Could not handle {} for {}", device.getState(), device);
209         }
210
211         return st;
212     }
213
214     @Override
215     public BigDecimal calcDimValue(Device device) {
216         BigDecimal dimValue = BigDecimal.ZERO;
217         switch (((TellstickLocalDeviceDTO) device).getState()) {
218             case JNA.CLibrary.TELLSTICK_TURNON:
219                 dimValue = new BigDecimal(100);
220                 break;
221             case JNA.CLibrary.TELLSTICK_TURNOFF:
222                 break;
223             case JNA.CLibrary.TELLSTICK_DIM:
224                 dimValue = new BigDecimal(((TellstickLocalDeviceDTO) device).getStatevalue());
225                 dimValue = dimValue.multiply(new BigDecimal(100));
226                 dimValue = dimValue.divide(new BigDecimal(255), 0, RoundingMode.HALF_UP);
227                 break;
228             default:
229                 logger.warn("Could not handle {} for {}", (((TellstickLocalDeviceDTO) device).getState()), device);
230         }
231         return dimValue;
232     }
233
234     public long getLastSend() {
235         return lastSend;
236     }
237
238     public void setLastSend(long currentTimeMillis) {
239         lastSend = currentTimeMillis;
240     }
241
242     @Override
243     public void onRequest(TellstickSensorEvent newDevices) {
244         setLastSend(newDevices.getTimestamp());
245     }
246
247     @Override
248     public void onRequest(TellstickDeviceEvent newDevices) {
249         setLastSend(newDevices.getTimestamp());
250     }
251
252     <T> T callRestMethod(String uri, Class<T> response) throws TelldusLocalException, InterruptedException {
253         T resultObj = null;
254         try {
255             for (int i = 0; i < MAX_RETRIES; i++) {
256                 try {
257                     resultObj = innerCallRest(localApiUrl + uri, response);
258                     break;
259                 } catch (TimeoutException e) {
260                     logger.warn("TimeoutException error in get");
261                 }
262             }
263         } catch (JsonSyntaxException e) {
264             throw new TelldusLocalException(e);
265         } catch (ExecutionException e) {
266             throw new TelldusLocalException(e);
267         }
268         return resultObj;
269     }
270
271     private <T> T innerCallRest(String uri, Class<T> json)
272             throws ExecutionException, InterruptedException, TimeoutException, JsonSyntaxException {
273         logger.trace("HTTP GET: {}", uri);
274
275         Request request = httpClient.newRequest(uri).method(HttpMethod.GET).timeout(REQUEST_TIMEOUT_MS,
276                 TimeUnit.MILLISECONDS);
277         request.header("Authorization", authorizationHeader);
278
279         ContentResponse response = request.send();
280         String content = response.getContentAsString();
281         logger.trace("API response: {}", content);
282
283         return gson.fromJson(content, json);
284     }
285 }