2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.tellstick.internal.local;
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;
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;
47 import com.google.gson.Gson;
48 import com.google.gson.JsonSyntaxException;
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.
54 * @author Jan Gustafsson - Initial contribution
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;
75 public TelldusLocalDeviceController(TelldusLocalConfiguration configuration, HttpClient httpClient) {
76 this.httpClient = httpClient;
77 localApiUrl = "http://" + configuration.ipAddress + "/";
78 authorizationHeader = authorizationHeader + configuration.accessToken;
82 public void dispose() {
86 public void handleSendEvent(Device device, int resendCount, boolean isdimmer, Command command)
87 throws TellstickException {
88 logger.debug("Send {} to {}", command, device);
90 if (device instanceof TellstickLocalDeviceDTO) {
91 if (command == OnOffType.ON) {
93 } else if (command == OnOffType.OFF) {
95 } else if (command instanceof PercentType percentCommand) {
96 dim(device, percentCommand);
97 } else if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
98 increaseDecrease(device, increaseDecreaseCommand);
100 } else if (device instanceof SwitchableDevice) {
101 if (command == OnOffType.ON) {
103 logger.trace("Turn off first in case it is allready on");
107 } else if (command == OnOffType.OFF) {
111 logger.warn("Cannot send to {}", device);
113 } catch (InterruptedException e) {
114 logger.debug("OH is shut-down.");
118 private void increaseDecrease(Device dev, IncreaseDecreaseType increaseDecreaseType)
119 throws TellstickException, InterruptedException {
120 String strValue = ((TellstickDevice) dev).getData();
122 if (strValue != null) {
123 value = Double.valueOf(strValue);
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);
131 dim(dev, new PercentType(percent));
134 private void dim(Device dev, PercentType command) throws TellstickException, InterruptedException {
135 double value = command.doubleValue();
137 // 0 means OFF and 100 means ON
138 if (value == 0 && dev instanceof TellstickLocalDeviceDTO) {
140 } else if (value == 100 && dev instanceof TellstickLocalDeviceDTO) {
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);
149 throw new TelldusBindingException("Cannot send DIM to " + dev);
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);
159 throw new TelldusBindingException("Cannot send OFF to " + dev);
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());
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);
181 throw new TelldusBindingException("Cannot send ON to " + dev);
186 public State calcState(Device dev) {
187 TellstickLocalDeviceDTO device = (TellstickLocalDeviceDTO) dev;
190 switch (device.getState()) {
191 case JNA.CLibrary.TELLSTICK_TURNON:
194 case JNA.CLibrary.TELLSTICK_TURNOFF:
197 case JNA.CLibrary.TELLSTICK_DIM:
198 BigDecimal dimValue = new BigDecimal(device.getStatevalue());
199 if (dimValue.intValue() == 0) {
201 } else if (dimValue.intValue() >= 255) {
208 logger.warn("Could not handle {} for {}", device.getState(), device);
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);
221 case JNA.CLibrary.TELLSTICK_TURNOFF:
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);
229 logger.warn("Could not handle {} for {}", (((TellstickLocalDeviceDTO) device).getState()), device);
234 public long getLastSend() {
238 public void setLastSend(long currentTimeMillis) {
239 lastSend = currentTimeMillis;
243 public void onRequest(TellstickSensorEvent newDevices) {
244 setLastSend(newDevices.getTimestamp());
248 public void onRequest(TellstickDeviceEvent newDevices) {
249 setLastSend(newDevices.getTimestamp());
252 <T> T callRestMethod(String uri, Class<T> response) throws TelldusLocalException, InterruptedException {
255 for (int i = 0; i < MAX_RETRIES; i++) {
257 resultObj = innerCallRest(localApiUrl + uri, response);
259 } catch (TimeoutException e) {
260 logger.warn("TimeoutException error in get");
263 } catch (JsonSyntaxException e) {
264 throw new TelldusLocalException(e);
265 } catch (ExecutionException e) {
266 throw new TelldusLocalException(e);
271 private <T> T innerCallRest(String uri, Class<T> json)
272 throws ExecutionException, InterruptedException, TimeoutException, JsonSyntaxException {
273 logger.trace("HTTP GET: {}", uri);
275 Request request = httpClient.newRequest(uri).method(HttpMethod.GET).timeout(REQUEST_TIMEOUT_MS,
276 TimeUnit.MILLISECONDS);
277 request.header("Authorization", authorizationHeader);
279 ContentResponse response = request.send();
280 String content = response.getContentAsString();
281 logger.trace("API response: {}", content);
283 return gson.fromJson(content, json);