]> git.basschouten.com Git - openhab-addons.git/blob
7682c3f33124f3c8ecfe55a9ad694e0b17c63092
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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.daikin.internal;
14
15 import java.io.IOException;
16 import java.util.HashMap;
17 import java.util.Map;
18 import java.util.concurrent.ExecutionException;
19 import java.util.concurrent.TimeUnit;
20 import java.util.concurrent.TimeoutException;
21 import java.util.stream.Collectors;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.eclipse.jetty.client.HttpClient;
26 import org.eclipse.jetty.client.api.ContentResponse;
27 import org.eclipse.jetty.client.api.Request;
28 import org.eclipse.jetty.http.HttpMethod;
29 import org.eclipse.jetty.http.HttpStatus;
30 import org.openhab.binding.daikin.internal.api.BasicInfo;
31 import org.openhab.binding.daikin.internal.api.ControlInfo;
32 import org.openhab.binding.daikin.internal.api.EnergyInfoDayAndWeek;
33 import org.openhab.binding.daikin.internal.api.EnergyInfoYear;
34 import org.openhab.binding.daikin.internal.api.Enums.SpecialModeKind;
35 import org.openhab.binding.daikin.internal.api.SensorInfo;
36 import org.openhab.binding.daikin.internal.api.airbase.AirbaseBasicInfo;
37 import org.openhab.binding.daikin.internal.api.airbase.AirbaseControlInfo;
38 import org.openhab.binding.daikin.internal.api.airbase.AirbaseModelInfo;
39 import org.openhab.binding.daikin.internal.api.airbase.AirbaseZoneInfo;
40 import org.openhab.core.io.net.http.HttpUtil;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 /**
45  * Handles performing the actual HTTP requests for communicating with Daikin air conditioning units.
46  *
47  * @author Tim Waterhouse - Initial Contribution
48  * @author Paul Smedley <paul@smedley.id.au> - Modifications to support Airbase Controllers
49  * @author Jimmy Tanagra - Add support for https and Daikin's uuid authentication
50  *
51  */
52 @NonNullByDefault
53 public class DaikinWebTargets {
54     private static final int TIMEOUT_MS = 30000;
55
56     private String getBasicInfoUri;
57     private String setControlInfoUri;
58     private String getControlInfoUri;
59     private String getSensorInfoUri;
60     private String registerUuidUri;
61     private String getEnergyInfoYearUri;
62     private String getEnergyInfoWeekUri;
63     private String setSpecialModeUri;
64
65     private String setAirbaseControlInfoUri;
66     private String getAirbaseControlInfoUri;
67     private String getAirbaseSensorInfoUri;
68     private String getAirbaseBasicInfoUri;
69     private String getAirbaseModelInfoUri;
70     private String getAirbaseZoneInfoUri;
71     private String setAirbaseZoneInfoUri;
72
73     private @Nullable String uuid;
74     private final @Nullable HttpClient httpClient;
75
76     private Logger logger = LoggerFactory.getLogger(DaikinWebTargets.class);
77
78     public DaikinWebTargets(@Nullable HttpClient httpClient, @Nullable String host, @Nullable Boolean secure,
79             @Nullable String uuid) {
80         this.httpClient = httpClient;
81         this.uuid = uuid;
82
83         String baseUri = (secure != null && secure.booleanValue() ? "https://" : "http://") + host + "/";
84         getBasicInfoUri = baseUri + "common/basic_info";
85         setControlInfoUri = baseUri + "aircon/set_control_info";
86         getControlInfoUri = baseUri + "aircon/get_control_info";
87         getSensorInfoUri = baseUri + "aircon/get_sensor_info";
88         registerUuidUri = baseUri + "common/register_terminal";
89         getEnergyInfoYearUri = baseUri + "aircon/get_year_power_ex";
90         getEnergyInfoWeekUri = baseUri + "aircon/get_week_power_ex";
91         setSpecialModeUri = baseUri + "aircon/set_special_mode";
92
93         // Daikin Airbase API
94         getAirbaseBasicInfoUri = baseUri + "skyfi/common/basic_info";
95         setAirbaseControlInfoUri = baseUri + "skyfi/aircon/set_control_info";
96         getAirbaseControlInfoUri = baseUri + "skyfi/aircon/get_control_info";
97         getAirbaseSensorInfoUri = baseUri + "skyfi/aircon/get_sensor_info";
98         getAirbaseModelInfoUri = baseUri + "skyfi/aircon/get_model_info";
99         getAirbaseZoneInfoUri = baseUri + "skyfi/aircon/get_zone_setting";
100         setAirbaseZoneInfoUri = baseUri + "skyfi/aircon/set_zone_setting";
101     }
102
103     // Standard Daikin API
104     public BasicInfo getBasicInfo() throws DaikinCommunicationException {
105         String response = invoke(getBasicInfoUri);
106         return BasicInfo.parse(response);
107     }
108
109     public ControlInfo getControlInfo() throws DaikinCommunicationException {
110         String response = invoke(getControlInfoUri);
111         return ControlInfo.parse(response);
112     }
113
114     public void setControlInfo(ControlInfo info) throws DaikinCommunicationException {
115         Map<String, String> queryParams = info.getParamString();
116         invoke(setControlInfoUri, queryParams);
117     }
118
119     public SensorInfo getSensorInfo() throws DaikinCommunicationException {
120         String response = invoke(getSensorInfoUri);
121         return SensorInfo.parse(response);
122     }
123
124     public void registerUuid(String key) throws DaikinCommunicationException {
125         Map<String, String> params = new HashMap<>();
126         params.put("key", key);
127         String response = invoke(registerUuidUri, params);
128         logger.debug("registerUuid result: {}", response);
129     }
130
131     public EnergyInfoYear getEnergyInfoYear() throws DaikinCommunicationException {
132         String response = invoke(getEnergyInfoYearUri);
133         return EnergyInfoYear.parse(response);
134     }
135
136     public EnergyInfoDayAndWeek getEnergyInfoDayAndWeek() throws DaikinCommunicationException {
137         String response = invoke(getEnergyInfoWeekUri);
138         return EnergyInfoDayAndWeek.parse(response);
139     }
140
141     public boolean setSpecialMode(SpecialModeKind specialModeKind, boolean state) throws DaikinCommunicationException {
142         Map<String, String> queryParams = new HashMap<>();
143         queryParams.put("spmode_kind", String.valueOf(specialModeKind.getValue()));
144         queryParams.put("set_spmode", state ? "1" : "0");
145         String response = invoke(setSpecialModeUri, queryParams);
146         return !response.contains("ret=OK");
147     }
148
149     // Daikin Airbase API
150     public AirbaseControlInfo getAirbaseControlInfo() throws DaikinCommunicationException {
151         String response = invoke(getAirbaseControlInfoUri);
152         return AirbaseControlInfo.parse(response);
153     }
154
155     public void setAirbaseControlInfo(AirbaseControlInfo info) throws DaikinCommunicationException {
156         Map<String, String> queryParams = info.getParamString();
157         invoke(setAirbaseControlInfoUri, queryParams);
158     }
159
160     public SensorInfo getAirbaseSensorInfo() throws DaikinCommunicationException {
161         String response = invoke(getAirbaseSensorInfoUri);
162         return SensorInfo.parse(response);
163     }
164
165     public AirbaseBasicInfo getAirbaseBasicInfo() throws DaikinCommunicationException {
166         String response = invoke(getAirbaseBasicInfoUri);
167         return AirbaseBasicInfo.parse(response);
168     }
169
170     public AirbaseModelInfo getAirbaseModelInfo() throws DaikinCommunicationException {
171         String response = invoke(getAirbaseModelInfoUri);
172         return AirbaseModelInfo.parse(response);
173     }
174
175     public AirbaseZoneInfo getAirbaseZoneInfo() throws DaikinCommunicationException {
176         String response = invoke(getAirbaseZoneInfoUri);
177         return AirbaseZoneInfo.parse(response);
178     }
179
180     public void setAirbaseZoneInfo(AirbaseZoneInfo zoneinfo) throws DaikinCommunicationException {
181         Map<String, String> queryParams = zoneinfo.getParamString();
182         invoke(setAirbaseZoneInfoUri, queryParams);
183     }
184
185     private String invoke(String uri) throws DaikinCommunicationException {
186         return invoke(uri, new HashMap<>());
187     }
188
189     private String invoke(String uri, Map<String, String> params) throws DaikinCommunicationException {
190         String uriWithParams = uri + paramsToQueryString(params);
191         logger.debug("Calling url: {}", uriWithParams);
192         String response;
193         synchronized (this) {
194             try {
195                 if (httpClient != null) {
196                     response = executeUrl(uriWithParams);
197                 } else {
198                     // a fall back method
199                     logger.debug("Using HttpUtil fall scback");
200                     response = HttpUtil.executeUrl("GET", uriWithParams, TIMEOUT_MS);
201                 }
202             } catch (DaikinCommunicationException ex) {
203                 throw ex;
204             } catch (IOException ex) {
205                 // Response will also be set to null if parsing in executeUrl fails so we use null here to make the
206                 // error check below consistent.
207                 response = null;
208             }
209         }
210
211         if (response == null) {
212             throw new DaikinCommunicationException("Daikin controller returned error while invoking " + uriWithParams);
213         }
214
215         return response;
216     }
217
218     private String executeUrl(String url) throws DaikinCommunicationException {
219         try {
220             Request request = httpClient.newRequest(url).method(HttpMethod.GET).timeout(TIMEOUT_MS,
221                     TimeUnit.MILLISECONDS);
222             if (uuid != null) {
223                 request.header("X-Daikin-uuid", uuid);
224                 logger.debug("Header: X-Daikin-uuid: {}", uuid);
225             }
226             ContentResponse response = request.send();
227
228             if (response.getStatus() == HttpStatus.FORBIDDEN_403) {
229                 throw new DaikinCommunicationForbiddenException("Daikin controller access denied. Check uuid/key.");
230             }
231
232             if (response.getStatus() != HttpStatus.OK_200) {
233                 logger.debug("Daikin controller HTTP status: {} - {}", response.getStatus(), response.getReason());
234             }
235
236             return response.getContentAsString();
237         } catch (DaikinCommunicationException e) {
238             throw e;
239         } catch (ExecutionException | TimeoutException e) {
240             throw new DaikinCommunicationException("Daikin HTTP error", e);
241         } catch (InterruptedException e) {
242             Thread.currentThread().interrupt();
243             throw new DaikinCommunicationException("Daikin HTTP interrupted", e);
244         }
245     }
246
247     private String paramsToQueryString(Map<String, String> params) {
248         if (params.isEmpty()) {
249             return "";
250         }
251
252         return "?" + params.entrySet().stream().map(param -> param.getKey() + "=" + param.getValue())
253                 .collect(Collectors.joining("&"));
254     }
255 }