2 * Copyright (c) 2010-2021 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.daikin.internal;
15 import java.io.IOException;
16 import java.util.HashMap;
18 import java.util.concurrent.ExecutionException;
19 import java.util.concurrent.TimeUnit;
20 import java.util.concurrent.TimeoutException;
21 import java.util.stream.Collectors;
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;
45 * Handles performing the actual HTTP requests for communicating with Daikin air conditioning units.
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
53 public class DaikinWebTargets {
54 private static final int TIMEOUT_MS = 30000;
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;
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;
73 private @Nullable String uuid;
74 private final @Nullable HttpClient httpClient;
76 private Logger logger = LoggerFactory.getLogger(DaikinWebTargets.class);
78 public DaikinWebTargets(@Nullable HttpClient httpClient, @Nullable String host, @Nullable Boolean secure,
79 @Nullable String uuid) {
80 this.httpClient = httpClient;
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";
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";
103 // Standard Daikin API
104 public BasicInfo getBasicInfo() throws DaikinCommunicationException {
105 String response = invoke(getBasicInfoUri);
106 return BasicInfo.parse(response);
109 public ControlInfo getControlInfo() throws DaikinCommunicationException {
110 String response = invoke(getControlInfoUri);
111 return ControlInfo.parse(response);
114 public void setControlInfo(ControlInfo info) throws DaikinCommunicationException {
115 Map<String, String> queryParams = info.getParamString();
116 invoke(setControlInfoUri, queryParams);
119 public SensorInfo getSensorInfo() throws DaikinCommunicationException {
120 String response = invoke(getSensorInfoUri);
121 return SensorInfo.parse(response);
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);
131 public EnergyInfoYear getEnergyInfoYear() throws DaikinCommunicationException {
132 String response = invoke(getEnergyInfoYearUri);
133 return EnergyInfoYear.parse(response);
136 public EnergyInfoDayAndWeek getEnergyInfoDayAndWeek() throws DaikinCommunicationException {
137 String response = invoke(getEnergyInfoWeekUri);
138 return EnergyInfoDayAndWeek.parse(response);
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");
149 // Daikin Airbase API
150 public AirbaseControlInfo getAirbaseControlInfo() throws DaikinCommunicationException {
151 String response = invoke(getAirbaseControlInfoUri);
152 return AirbaseControlInfo.parse(response);
155 public void setAirbaseControlInfo(AirbaseControlInfo info) throws DaikinCommunicationException {
156 Map<String, String> queryParams = info.getParamString();
157 invoke(setAirbaseControlInfoUri, queryParams);
160 public SensorInfo getAirbaseSensorInfo() throws DaikinCommunicationException {
161 String response = invoke(getAirbaseSensorInfoUri);
162 return SensorInfo.parse(response);
165 public AirbaseBasicInfo getAirbaseBasicInfo() throws DaikinCommunicationException {
166 String response = invoke(getAirbaseBasicInfoUri);
167 return AirbaseBasicInfo.parse(response);
170 public AirbaseModelInfo getAirbaseModelInfo() throws DaikinCommunicationException {
171 String response = invoke(getAirbaseModelInfoUri);
172 return AirbaseModelInfo.parse(response);
175 public AirbaseZoneInfo getAirbaseZoneInfo() throws DaikinCommunicationException {
176 String response = invoke(getAirbaseZoneInfoUri);
177 return AirbaseZoneInfo.parse(response);
180 public void setAirbaseZoneInfo(AirbaseZoneInfo zoneinfo) throws DaikinCommunicationException {
181 Map<String, String> queryParams = zoneinfo.getParamString();
182 invoke(setAirbaseZoneInfoUri, queryParams);
185 private String invoke(String uri) throws DaikinCommunicationException {
186 return invoke(uri, new HashMap<>());
189 private String invoke(String uri, Map<String, String> params) throws DaikinCommunicationException {
190 String uriWithParams = uri + paramsToQueryString(params);
191 logger.debug("Calling url: {}", uriWithParams);
193 synchronized (this) {
195 if (httpClient != null) {
196 response = executeUrl(uriWithParams);
198 // a fall back method
199 logger.debug("Using HttpUtil fall scback");
200 response = HttpUtil.executeUrl("GET", uriWithParams, TIMEOUT_MS);
202 } catch (DaikinCommunicationException 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.
211 if (response == null) {
212 throw new DaikinCommunicationException("Daikin controller returned error while invoking " + uriWithParams);
218 private String executeUrl(String url) throws DaikinCommunicationException {
220 Request request = httpClient.newRequest(url).method(HttpMethod.GET).timeout(TIMEOUT_MS,
221 TimeUnit.MILLISECONDS);
223 request.header("X-Daikin-uuid", uuid);
224 logger.debug("Header: X-Daikin-uuid: {}", uuid);
226 ContentResponse response = request.send();
228 if (response.getStatus() == HttpStatus.FORBIDDEN_403) {
229 throw new DaikinCommunicationForbiddenException("Daikin controller access denied. Check uuid/key.");
232 if (response.getStatus() != HttpStatus.OK_200) {
233 logger.debug("Daikin controller HTTP status: {} - {}", response.getStatus(), response.getReason());
236 return response.getContentAsString();
237 } catch (DaikinCommunicationException 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);
247 private String paramsToQueryString(Map<String, String> params) {
248 if (params.isEmpty()) {
252 return "?" + params.entrySet().stream().map(param -> param.getKey() + "=" + param.getValue())
253 .collect(Collectors.joining("&"));