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.draytonwiser.internal.api;
15 import static org.openhab.binding.draytonwiser.internal.DraytonWiserBindingConstants.*;
17 import java.util.concurrent.ExecutionException;
18 import java.util.concurrent.TimeUnit;
19 import java.util.concurrent.TimeoutException;
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.eclipse.jetty.client.HttpClient;
24 import org.eclipse.jetty.client.api.ContentResponse;
25 import org.eclipse.jetty.client.util.StringContentProvider;
26 import org.eclipse.jetty.http.HttpMethod;
27 import org.eclipse.jetty.http.HttpStatus;
28 import org.openhab.binding.draytonwiser.internal.handler.HeatHubConfiguration;
29 import org.openhab.binding.draytonwiser.internal.model.DomainDTO;
30 import org.openhab.binding.draytonwiser.internal.model.StationDTO;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
34 import com.google.gson.FieldNamingPolicy;
35 import com.google.gson.Gson;
36 import com.google.gson.GsonBuilder;
37 import com.google.gson.JsonSyntaxException;
40 * Class with api specific call code.
42 * @author Andrew Schofield - Initial contribution
43 * @author Hilbrand Bouwkamp - Moved Api specific code to it's own class
46 public class DraytonWiserApi {
48 public static final Gson GSON = new GsonBuilder().setFieldNamingStrategy(FieldNamingPolicy.UPPER_CAMEL_CASE)
51 private final Logger logger = LoggerFactory.getLogger(DraytonWiserApi.class);
52 private final HttpClient httpClient;
54 private HeatHubConfiguration configuration = new HeatHubConfiguration();
55 private int failCount;
57 public DraytonWiserApi(final HttpClient httpClient) {
58 this.httpClient = httpClient;
61 public void setConfiguration(final HeatHubConfiguration configuration) {
62 this.configuration = configuration;
65 public @Nullable StationDTO getStation() throws DraytonWiserApiException {
66 final ContentResponse response = sendMessageToHeatHub(STATION_ENDPOINT, HttpMethod.GET);
68 return response == null ? null : GSON.fromJson(response.getContentAsString(), StationDTO.class);
71 public @Nullable DomainDTO getDomain() throws DraytonWiserApiException {
72 final ContentResponse response = sendMessageToHeatHub(DOMAIN_ENDPOINT, HttpMethod.GET);
74 if (response == null) {
79 return GSON.fromJson(response.getContentAsString(), DomainDTO.class);
80 } catch (final JsonSyntaxException e) {
81 logger.debug("Could not parse Json content: {}", e.getMessage(), e);
86 public void setRoomSetPoint(final int roomId, final int setPoint) throws DraytonWiserApiException {
87 final String payload = "{\"RequestOverride\":{\"Type\":\"Manual\", \"SetPoint\":" + setPoint + "}}";
89 sendMessageToHeatHub(ROOMS_ENDPOINT + roomId, "PATCH", payload);
92 public void setRoomManualMode(final int roomId, final boolean manualMode) throws DraytonWiserApiException {
93 String payload = "{\"Mode\":\"" + (manualMode ? "Manual" : "Auto") + "\"}";
94 sendMessageToHeatHub(ROOMS_ENDPOINT + roomId, "PATCH", payload);
95 payload = "{\"RequestOverride\":{\"Type\":\"None\",\"Originator\" :\"App\",\"DurationMinutes\":0,\"SetPoint\":0}}";
96 sendMessageToHeatHub(ROOMS_ENDPOINT + roomId, "PATCH", payload);
99 public void setRoomWindowStateDetection(final int roomId, final boolean windowStateDetection)
100 throws DraytonWiserApiException {
101 final String payload = windowStateDetection ? "true" : "false";
102 sendMessageToHeatHub(ROOMS_ENDPOINT + roomId + "/WindowDetectionActive", "PATCH", payload);
105 public void setRoomBoostActive(final int roomId, final int setPoint, final int duration)
106 throws DraytonWiserApiException {
107 final String payload = "{\"RequestOverride\":{\"Type\":\"Manual\",\"Originator\" :\"App\",\"DurationMinutes\":"
108 + duration + ",\"SetPoint\":" + setPoint + "}}";
109 sendMessageToHeatHub(ROOMS_ENDPOINT + roomId, "PATCH", payload);
112 public void setRoomBoostInactive(final int roomId) throws DraytonWiserApiException {
113 final String payload = "{\"RequestOverride\":{\"Type\":\"None\",\"Originator\" :\"App\",\"DurationMinutes\":0,\"SetPoint\":0}}";
114 sendMessageToHeatHub(ROOMS_ENDPOINT + roomId, "PATCH", payload);
117 public void setHotWaterManualMode(final boolean manualMode) throws DraytonWiserApiException {
118 String payload = "{\"Mode\":\"" + (manualMode ? "Manual" : "Auto") + "\"}";
119 sendMessageToHeatHub(HOTWATER_ENDPOINT + "2", "PATCH", payload);
120 payload = "{\"RequestOverride\":{\"Type\":\"None\",\"Originator\" :\"App\",\"DurationMinutes\":0,\"SetPoint\":0}}";
121 sendMessageToHeatHub(HOTWATER_ENDPOINT + "2", "PATCH", payload);
124 public void setHotWaterSetPoint(final int setPoint) throws DraytonWiserApiException {
125 final String payload = "{\"RequestOverride\":{\"Type\":\"Manual\", \"SetPoint\":" + setPoint + "}}";
126 sendMessageToHeatHub(HOTWATER_ENDPOINT + "2", "PATCH", payload);
129 public void setHotWaterBoostActive(final int duration) throws DraytonWiserApiException {
130 final String payload = "{\"RequestOverride\":{\"Type\":\"Manual\",\"Originator\" :\"App\",\"DurationMinutes\":"
131 + duration + ",\"SetPoint\":1100}}";
132 sendMessageToHeatHub(HOTWATER_ENDPOINT + "2", "PATCH", payload);
135 public void setHotWaterBoostInactive() throws DraytonWiserApiException {
136 final String payload = "{\"RequestOverride\":{\"Type\":\"None\",\"Originator\" :\"App\",\"DurationMinutes\":0,\"SetPoint\":0}}";
137 sendMessageToHeatHub(HOTWATER_ENDPOINT + "2", "PATCH", payload);
140 public void setAwayMode(final boolean awayMode) throws DraytonWiserApiException {
141 final int setPoint = configuration.awaySetPoint * 10;
143 String payload = "{\"Type\":" + (awayMode ? "2" : "0") + ", \"setPoint\":" + (awayMode ? setPoint : "0") + "}";
144 sendMessageToHeatHub(SYSTEM_ENDPOINT + "RequestOverride", "PATCH", payload);
145 payload = "{\"Type\":" + (awayMode ? "2" : "0") + ", \"setPoint\":" + (awayMode ? "-200" : "0") + "}";
146 sendMessageToHeatHub(HOTWATER_ENDPOINT + "2/RequestOverride", "PATCH", payload);
149 public void setDeviceLocked(final int deviceId, final boolean locked) throws DraytonWiserApiException {
150 final String payload = locked ? "true" : "false";
151 sendMessageToHeatHub(DEVICE_ENDPOINT + deviceId + "/DeviceLockEnabled", "PATCH", payload);
154 public void setEcoMode(final boolean ecoMode) throws DraytonWiserApiException {
155 final String payload = "{\"EcoModeEnabled\":" + ecoMode + "}";
156 sendMessageToHeatHub(SYSTEM_ENDPOINT, "PATCH", payload);
159 public void setSmartPlugManualMode(final int id, final boolean manualMode) throws DraytonWiserApiException {
160 final String payload = "{\"Mode\":\"" + (manualMode ? "Manual" : "Auto") + "\"}";
161 sendMessageToHeatHub(SMARTPLUG_ENDPOINT + id, "PATCH", payload);
164 public void setSmartPlugOutputState(final int id, final boolean outputState) throws DraytonWiserApiException {
165 final String payload = "{\"RequestOutput\":\"" + (outputState ? "On" : "Off") + "\"}";
166 sendMessageToHeatHub(SMARTPLUG_ENDPOINT + id, "PATCH", payload);
169 public void setSmartPlugAwayAction(final int id, final boolean awayAction) throws DraytonWiserApiException {
170 final String payload = "{\"AwayAction\":\"" + (awayAction ? "Off" : "NoChange") + "\"}";
171 sendMessageToHeatHub(SMARTPLUG_ENDPOINT + id, "PATCH", payload);
174 public void setComfortMode(final boolean comfortMode) throws DraytonWiserApiException {
175 final String payload = "{\"ComfortModeEnabled\":" + comfortMode + "}";
176 sendMessageToHeatHub(SYSTEM_ENDPOINT, "PATCH", payload);
179 private synchronized @Nullable ContentResponse sendMessageToHeatHub(final String path, final HttpMethod method)
180 throws DraytonWiserApiException {
181 return sendMessageToHeatHub(path, method.asString(), "");
184 private synchronized @Nullable ContentResponse sendMessageToHeatHub(final String path, final String method,
185 final String content) throws DraytonWiserApiException {
186 // we need to keep track of the number of times that the heat hub has "failed" to respond.
187 // we only actually report a failure if we hit an error state 3 or more times
189 logger.debug("Sending message to heathub: {}", path);
190 final StringContentProvider contentProvider = new StringContentProvider(content);
191 final ContentResponse response = httpClient
192 .newRequest("http://" + configuration.networkAddress + "/" + path).method(method)
193 .header("SECRET", configuration.secret).content(contentProvider).timeout(10, TimeUnit.SECONDS)
196 if (logger.isTraceEnabled()) {
197 logger.trace("Reponse (Status:{}): {}", response.getStatus(), response.getContentAsString());
199 if (response.getStatus() == HttpStatus.OK_200) {
202 } else if (response.getStatus() == HttpStatus.UNAUTHORIZED_401) {
205 throw new DraytonWiserApiException("Invalid authorization token");
210 throw new DraytonWiserApiException("Heathub didn't repond after " + failCount + " retries");
213 } catch (final TimeoutException e) {
216 logger.debug("Heathub didn't repond in time: {}", e.getMessage());
217 throw new DraytonWiserApiException("Heathub didn't repond in time", e);
219 } catch (final InterruptedException e) {
220 Thread.currentThread().interrupt();
221 } catch (final ExecutionException e) {
222 logger.debug("Execution Exception: {}", e.getMessage(), e);
223 throw new DraytonWiserApiException(e.getMessage(), e);
224 } catch (final RuntimeException e) {
225 logger.debug("Unexpected error: {}", e.getMessage(), e);
226 throw new DraytonWiserApiException(e.getMessage(), e);