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.TimeoutException;
20 import org.eclipse.jetty.client.HttpClient;
21 import org.eclipse.jetty.client.api.ContentResponse;
22 import org.eclipse.jetty.client.api.Request;
23 import org.eclipse.jetty.http.HttpMethod;
24 import org.openhab.binding.tellstick.internal.TelldusBindingException;
25 import org.openhab.binding.tellstick.internal.conf.TelldusLocalConfiguration;
26 import org.openhab.binding.tellstick.internal.handler.TelldusDeviceController;
27 import org.openhab.binding.tellstick.internal.local.dto.TelldusLocalResponseDTO;
28 import org.openhab.binding.tellstick.internal.local.dto.TellstickLocalDeviceDTO;
29 import org.openhab.core.library.types.IncreaseDecreaseType;
30 import org.openhab.core.library.types.OnOffType;
31 import org.openhab.core.library.types.PercentType;
32 import org.openhab.core.types.Command;
33 import org.openhab.core.types.State;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36 import org.tellstick.JNA;
37 import org.tellstick.device.TellstickDevice;
38 import org.tellstick.device.TellstickDeviceEvent;
39 import org.tellstick.device.TellstickException;
40 import org.tellstick.device.TellstickSensorEvent;
41 import org.tellstick.device.iface.Device;
42 import org.tellstick.device.iface.DeviceChangeListener;
43 import org.tellstick.device.iface.SensorListener;
44 import org.tellstick.device.iface.SwitchableDevice;
46 import com.google.gson.Gson;
47 import com.google.gson.JsonSyntaxException;
50 * {@link TelldusLocalDeviceController} handles the communication with Telldus Local API (Tellstick ZNET v1/v2)
51 * This controller uses JSON based Rest API to communicate with Telldus Local API.
53 * @author Jan Gustafsson - Initial contribution
55 public class TelldusLocalDeviceController implements DeviceChangeListener, SensorListener, TelldusDeviceController {
56 private final Logger logger = LoggerFactory.getLogger(TelldusLocalDeviceController.class);
57 private long lastSend = 0;
58 public static final long DEFAULT_INTERVAL_BETWEEN_SEND_SEC = 250;
59 private final HttpClient httpClient;
60 private final Gson gson = new Gson();
61 private String localApiUrl;
62 private String authorizationHeader = "Bearer ";
63 static final String HTTP_LOCAL_API = "api/";
64 static final String HTTP_LOCAL_API_DEVICES = HTTP_LOCAL_API + "devices/list?supportedMethods=19&includeIgnored=0";
65 static final String HTTP_LOCAL_API_SENSORS = HTTP_LOCAL_API
66 + "sensors/list?includeValues=1&includeScale=1&includeUnit=1&includeIgnored=0";
67 static final String HTTP_LOCAL_API_SENSOR_INFO = HTTP_LOCAL_API + "sensor/info";
68 static final String HTTP_LOCAL_API_DEVICE_DIM = HTTP_LOCAL_API + "device/dim?id=%d&level=%d";
69 static final String HTTP_LOCAL_API_DEVICE_TURNOFF = HTTP_LOCAL_API + "device/turnOff?id=%d";
70 static final String HTTP_LOCAL_DEVICE_TURNON = HTTP_LOCAL_API + "device/turnOn?id=%d";
71 private static final int MAX_RETRIES = 3;
73 public TelldusLocalDeviceController(TelldusLocalConfiguration configuration, HttpClient httpClient) {
74 this.httpClient = httpClient;
75 localApiUrl = "http://" + configuration.ipAddress + "/";
76 authorizationHeader = authorizationHeader + configuration.accessToken;
80 public void dispose() {
84 public void handleSendEvent(Device device, int resendCount, boolean isdimmer, Command command)
85 throws TellstickException {
86 logger.debug("Send {} to {}", command, device);
88 if (device instanceof TellstickLocalDeviceDTO) {
89 if (command == OnOffType.ON) {
91 } else if (command == OnOffType.OFF) {
93 } else if (command instanceof PercentType percentCommand) {
94 dim(device, percentCommand);
95 } else if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
96 increaseDecrease(device, increaseDecreaseCommand);
98 } else if (device instanceof SwitchableDevice) {
99 if (command == OnOffType.ON) {
101 logger.trace("Turn off first in case it is allready on");
105 } else if (command == OnOffType.OFF) {
109 logger.warn("Cannot send to {}", device);
111 } catch (InterruptedException e) {
112 logger.debug("OH is shut-down.");
116 private void increaseDecrease(Device dev, IncreaseDecreaseType increaseDecreaseType)
117 throws TellstickException, InterruptedException {
118 String strValue = ((TellstickDevice) dev).getData();
120 if (strValue != null) {
121 value = Double.valueOf(strValue);
123 int percent = (int) Math.round((value / 255) * 100);
124 if (IncreaseDecreaseType.INCREASE == increaseDecreaseType) {
125 percent = Math.min(percent + 10, 100);
126 } else if (IncreaseDecreaseType.DECREASE == increaseDecreaseType) {
127 percent = Math.max(percent - 10, 0);
129 dim(dev, new PercentType(percent));
132 private void dim(Device dev, PercentType command) throws TellstickException, InterruptedException {
133 double value = command.doubleValue();
135 // 0 means OFF and 100 means ON
136 if (value == 0 && dev instanceof TellstickLocalDeviceDTO) {
138 } else if (value == 100 && dev instanceof TellstickLocalDeviceDTO) {
140 } else if (dev instanceof TellstickLocalDeviceDTO device
141 && (device.getMethods() & JNA.CLibrary.TELLSTICK_DIM) > 0) {
142 long tdVal = Math.round((value / 100) * 255);
143 TelldusLocalResponseDTO response = callRestMethod(
144 String.format(HTTP_LOCAL_API_DEVICE_DIM, dev.getId(), tdVal), TelldusLocalResponseDTO.class);
145 handleResponse(device, response);
147 throw new TelldusBindingException("Cannot send DIM to " + dev);
151 private void turnOff(Device dev) throws TellstickException, InterruptedException {
152 if (dev instanceof TellstickLocalDeviceDTO device) {
153 TelldusLocalResponseDTO response = callRestMethod(String.format(HTTP_LOCAL_API_DEVICE_TURNOFF, dev.getId()),
154 TelldusLocalResponseDTO.class);
155 handleResponse(device, response);
157 throw new TelldusBindingException("Cannot send OFF to " + dev);
161 private void handleResponse(TellstickLocalDeviceDTO device, TelldusLocalResponseDTO response)
162 throws TellstickException {
163 if (response == null || (response.getStatus() == null && response.getError() == null)) {
164 throw new TelldusBindingException("No response " + response);
165 } else if (response.getError() != null) {
166 device.setUpdated(true);
167 throw new TelldusBindingException("Error " + response.getError());
168 } else if (!"success".equals(response.getStatus().trim())) {
169 throw new TelldusBindingException("Response " + response.getStatus());
173 private void turnOn(Device dev) throws TellstickException, InterruptedException {
174 if (dev instanceof TellstickLocalDeviceDTO device) {
175 TelldusLocalResponseDTO response = callRestMethod(String.format(HTTP_LOCAL_DEVICE_TURNON, dev.getId()),
176 TelldusLocalResponseDTO.class);
177 handleResponse(device, response);
179 throw new TelldusBindingException("Cannot send ON to " + dev);
184 public State calcState(Device dev) {
185 TellstickLocalDeviceDTO device = (TellstickLocalDeviceDTO) dev;
188 switch (device.getState()) {
189 case JNA.CLibrary.TELLSTICK_TURNON:
192 case JNA.CLibrary.TELLSTICK_TURNOFF:
195 case JNA.CLibrary.TELLSTICK_DIM:
196 BigDecimal dimValue = new BigDecimal(device.getStatevalue());
197 if (dimValue.intValue() == 0) {
199 } else if (dimValue.intValue() >= 255) {
206 logger.warn("Could not handle {} for {}", device.getState(), device);
213 public BigDecimal calcDimValue(Device device) {
214 BigDecimal dimValue = BigDecimal.ZERO;
215 switch (((TellstickLocalDeviceDTO) device).getState()) {
216 case JNA.CLibrary.TELLSTICK_TURNON:
217 dimValue = new BigDecimal(100);
219 case JNA.CLibrary.TELLSTICK_TURNOFF:
221 case JNA.CLibrary.TELLSTICK_DIM:
222 dimValue = new BigDecimal(((TellstickLocalDeviceDTO) device).getStatevalue());
223 dimValue = dimValue.multiply(new BigDecimal(100));
224 dimValue = dimValue.divide(new BigDecimal(255), 0, RoundingMode.HALF_UP);
227 logger.warn("Could not handle {} for {}", (((TellstickLocalDeviceDTO) device).getState()), device);
232 public long getLastSend() {
236 public void setLastSend(long currentTimeMillis) {
237 lastSend = currentTimeMillis;
241 public void onRequest(TellstickSensorEvent newDevices) {
242 setLastSend(newDevices.getTimestamp());
246 public void onRequest(TellstickDeviceEvent newDevices) {
247 setLastSend(newDevices.getTimestamp());
250 <T> T callRestMethod(String uri, Class<T> response) throws TelldusLocalException, InterruptedException {
253 for (int i = 0; i < MAX_RETRIES; i++) {
255 resultObj = innerCallRest(localApiUrl + uri, response);
257 } catch (TimeoutException e) {
258 logger.warn("TimeoutException error in get");
261 } catch (JsonSyntaxException e) {
262 throw new TelldusLocalException(e);
263 } catch (ExecutionException e) {
264 throw new TelldusLocalException(e);
269 private <T> T innerCallRest(String uri, Class<T> json)
270 throws ExecutionException, InterruptedException, TimeoutException, JsonSyntaxException {
271 logger.trace("HTTP GET: {}", uri);
273 Request request = httpClient.newRequest(uri).method(HttpMethod.GET);
274 request.header("Authorization", authorizationHeader);
276 ContentResponse response = request.send();
277 String content = response.getContentAsString();
278 logger.trace("API response: {}", content);
280 return gson.fromJson(content, json);