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