[daikin] Fix communication errors by retrying failed http requests (#12239)

* [daikin] Fix communication errors by retrying failed http requests

Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>
This commit is contained in:
jimtng 2022-02-16 16:35:19 +10:00 committed by GitHub
parent 85866c63c0
commit f18ee99b08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 155 additions and 159 deletions

View File

@ -12,13 +12,12 @@
*/ */
package org.openhab.binding.daikin.internal; package org.openhab.binding.daikin.internal;
import java.io.IOException; import java.io.EOFException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
@ -37,7 +36,6 @@ import org.openhab.binding.daikin.internal.api.airbase.AirbaseBasicInfo;
import org.openhab.binding.daikin.internal.api.airbase.AirbaseControlInfo; import org.openhab.binding.daikin.internal.api.airbase.AirbaseControlInfo;
import org.openhab.binding.daikin.internal.api.airbase.AirbaseModelInfo; import org.openhab.binding.daikin.internal.api.airbase.AirbaseModelInfo;
import org.openhab.binding.daikin.internal.api.airbase.AirbaseZoneInfo; import org.openhab.binding.daikin.internal.api.airbase.AirbaseZoneInfo;
import org.openhab.core.io.net.http.HttpUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -47,11 +45,12 @@ import org.slf4j.LoggerFactory;
* @author Tim Waterhouse - Initial Contribution * @author Tim Waterhouse - Initial Contribution
* @author Paul Smedley <paul@smedley.id.au> - Modifications to support Airbase Controllers * @author Paul Smedley <paul@smedley.id.au> - Modifications to support Airbase Controllers
* @author Jimmy Tanagra - Add support for https and Daikin's uuid authentication * @author Jimmy Tanagra - Add support for https and Daikin's uuid authentication
* Implement connection retry
* *
*/ */
@NonNullByDefault @NonNullByDefault
public class DaikinWebTargets { public class DaikinWebTargets {
private static final int TIMEOUT_MS = 30000; private static final int TIMEOUT_MS = 5000;
private String getBasicInfoUri; private String getBasicInfoUri;
private String setControlInfoUri; private String setControlInfoUri;
@ -183,73 +182,73 @@ public class DaikinWebTargets {
} }
private String invoke(String uri) throws DaikinCommunicationException { private String invoke(String uri) throws DaikinCommunicationException {
return invoke(uri, new HashMap<>()); return invoke(uri, null);
} }
private String invoke(String uri, Map<String, String> params) throws DaikinCommunicationException { private synchronized String invoke(String url, @Nullable Map<String, String> params)
String uriWithParams = uri + paramsToQueryString(params); throws DaikinCommunicationException {
logger.debug("Calling url: {}", uriWithParams); int attemptCount = 1;
String response;
synchronized (this) {
try {
if (httpClient != null) {
response = executeUrl(uriWithParams);
} else {
// a fall back method
logger.debug("Using HttpUtil fall scback");
response = HttpUtil.executeUrl("GET", uriWithParams, TIMEOUT_MS);
}
} catch (DaikinCommunicationException ex) {
throw ex;
} catch (IOException ex) {
// Response will also be set to null if parsing in executeUrl fails so we use null here to make the
// error check below consistent.
response = null;
}
}
if (response == null) {
throw new DaikinCommunicationException("Daikin controller returned error while invoking " + uriWithParams);
}
return response;
}
private String executeUrl(String url) throws DaikinCommunicationException {
try { try {
Request request = httpClient.newRequest(url).method(HttpMethod.GET).timeout(TIMEOUT_MS, while (true) {
TimeUnit.MILLISECONDS); try {
if (uuid != null) { String result = executeUrl(url, params);
request.header("X-Daikin-uuid", uuid); if (attemptCount > 1) {
logger.debug("Header: X-Daikin-uuid: {}", uuid); logger.debug("HTTP request successful on attempt #{}: {}", attemptCount, url);
}
return result;
} catch (ExecutionException | TimeoutException e) {
if (attemptCount >= 3) {
logger.debug("HTTP request failed after {} attempts: {}", attemptCount, url, e);
Throwable rootCause = getRootCause(e);
String message = rootCause.getMessage();
// EOFException message is too verbose/gibberish
if (message == null || rootCause instanceof EOFException) {
message = "Connection error";
}
throw new DaikinCommunicationException(message);
}
logger.debug("HTTP request error on attempt #{}: {} {}", attemptCount, url, e.getMessage());
Thread.sleep(500 * attemptCount);
attemptCount++;
}
} }
ContentResponse response = request.send();
if (response.getStatus() == HttpStatus.FORBIDDEN_403) {
throw new DaikinCommunicationForbiddenException("Daikin controller access denied. Check uuid/key.");
}
if (response.getStatus() != HttpStatus.OK_200) {
logger.debug("Daikin controller HTTP status: {} - {}", response.getStatus(), response.getReason());
}
return response.getContentAsString();
} catch (DaikinCommunicationException e) {
throw e;
} catch (ExecutionException | TimeoutException e) {
throw new DaikinCommunicationException("Daikin HTTP error", e);
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
throw new DaikinCommunicationException("Daikin HTTP interrupted", e); throw new DaikinCommunicationException("Execution interrupted");
} }
} }
private String paramsToQueryString(Map<String, String> params) { private String executeUrl(String url, @Nullable Map<String, String> params)
if (params.isEmpty()) { throws InterruptedException, TimeoutException, ExecutionException, DaikinCommunicationException {
return ""; Request request = httpClient.newRequest(url).method(HttpMethod.GET).timeout(TIMEOUT_MS, TimeUnit.MILLISECONDS);
if (uuid != null) {
request.header("X-Daikin-uuid", uuid);
logger.trace("Header: X-Daikin-uuid: {}", uuid);
}
if (params != null) {
params.forEach((key, value) -> request.param(key, value));
}
logger.trace("Calling url: {}", request.getURI());
ContentResponse response = request.send();
if (response.getStatus() != HttpStatus.OK_200) {
logger.debug("Daikin controller HTTP status: {} - {} {}", response.getStatus(), response.getReason(), url);
} }
return "?" + params.entrySet().stream().map(param -> param.getKey() + "=" + param.getValue()) if (response.getStatus() == HttpStatus.FORBIDDEN_403) {
.collect(Collectors.joining("&")); throw new DaikinCommunicationForbiddenException("Daikin controller access denied. Check uuid/key.");
}
return response.getContentAsString();
}
private Throwable getRootCause(Throwable exception) {
Throwable cause = exception.getCause();
while (cause != null) {
exception = cause;
cause = cause.getCause();
}
return exception;
} }
} }

View File

@ -23,7 +23,7 @@ import org.slf4j.LoggerFactory;
/** /**
* Holds information from the basic_info call. * Holds information from the basic_info call.
* *
* @author Jimy Tanagra - Initial contribution * @author Jimmy Tanagra - Initial contribution
* *
*/ */
@NonNullByDefault @NonNullByDefault
@ -38,7 +38,7 @@ public class BasicInfo {
} }
public static BasicInfo parse(String response) { public static BasicInfo parse(String response) {
LOGGER.debug("Parsing string: \"{}\"", response); LOGGER.trace("Parsing string: \"{}\"", response);
Map<String, String> responseMap = InfoParser.parse(response); Map<String, String> responseMap = InfoParser.parse(response);

View File

@ -50,7 +50,7 @@ public class ControlInfo {
} }
public static ControlInfo parse(String response) { public static ControlInfo parse(String response) {
LOGGER.debug("Parsing string: \"{}\"", response); LOGGER.trace("Parsing string: \"{}\"", response);
Map<String, String> responseMap = InfoParser.parse(response); Map<String, String> responseMap = InfoParser.parse(response);

View File

@ -12,10 +12,8 @@
*/ */
package org.openhab.binding.daikin.internal.api; package org.openhab.binding.daikin.internal.api;
import java.util.Arrays;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -42,22 +40,14 @@ public class EnergyInfoDayAndWeek {
} }
public static EnergyInfoDayAndWeek parse(String response) { public static EnergyInfoDayAndWeek parse(String response) {
EnergyInfoDayAndWeek info = new EnergyInfoDayAndWeek();
LOGGER.trace("Parsing string: \"{}\"", response); LOGGER.trace("Parsing string: \"{}\"", response);
// /aircon/get_week_power_ex // /aircon/get_week_power_ex
// ret=OK,s_dayw=0,week_heat=1/1/1/1/1/5/2/1/1/1/1/2/1/1,week_cool=0/0/0/0/0/0/0/0/0/0/0/0/0/0 // ret=OK,s_dayw=0,week_heat=1/1/1/1/1/5/2/1/1/1/1/2/1/1,week_cool=0/0/0/0/0/0/0/0/0/0/0/0/0/0
// week_heat=<today>/<today-1>/<today-2>/<today-3>/... // week_heat=<today>/<today-1>/<today-2>/<today-3>/...
Map<String, String> responseMap = Arrays.asList(response.split(",")).stream().filter(kv -> kv.contains("=")) Map<String, String> responseMap = InfoParser.parse(response);
.map(kv -> { EnergyInfoDayAndWeek info = new EnergyInfoDayAndWeek();
String[] keyValue = kv.split("="); if ("OK".equals(responseMap.get("ret"))) {
String key = keyValue[0];
String value = keyValue.length > 1 ? keyValue[1] : "";
return new String[] { key, value };
}).collect(Collectors.toMap(x -> x[0], x -> x[1]));
if (responseMap.get("ret") != null && ("OK".equals(responseMap.get("ret")))) {
Optional<Integer> dayOfWeek = Optional.ofNullable(responseMap.get("s_dayw")) Optional<Integer> dayOfWeek = Optional.ofNullable(responseMap.get("s_dayw"))
.flatMap(value -> InfoParser.parseInt(value)); .flatMap(value -> InfoParser.parseInt(value));
@ -94,7 +84,7 @@ public class EnergyInfoDayAndWeek {
info.energyCoolingLastWeek = Optional.of(previousWeekEnergy / 10); info.energyCoolingLastWeek = Optional.of(previousWeekEnergy / 10);
} }
} else { } else {
LOGGER.debug("did not receive 'ret=OK' from adapter"); LOGGER.debug("EnergyInfoDayAndWeek::parse() did not receive 'ret=OK' from adapter");
} }
return info; return info;
} }

View File

@ -43,10 +43,10 @@ public class EnergyInfoYear {
EnergyInfoYear info = new EnergyInfoYear(); EnergyInfoYear info = new EnergyInfoYear();
info.energyHeatingThisYear = Optional.ofNullable(responseMap.get("curr_year_heat")) info.energyHeatingThisYear = Optional.ofNullable(responseMap.get("curr_year_heat"))
.flatMap(value -> InfoParser.parseArrayofInt(value, 12)); .flatMap(value -> InfoParser.parseArrayOfInt(value, 12));
info.energyCoolingThisYear = Optional.ofNullable(responseMap.get("curr_year_cool")) info.energyCoolingThisYear = Optional.ofNullable(responseMap.get("curr_year_cool"))
.flatMap(value -> InfoParser.parseArrayofInt(value, 12)); .flatMap(value -> InfoParser.parseArrayOfInt(value, 12));
return info; return info;
} }

View File

@ -12,21 +12,29 @@
*/ */
package org.openhab.binding.daikin.internal.api; package org.openhab.binding.daikin.internal.api;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* Class for parsing the comma separated values and array values returned by the Daikin Controller. * Class for parsing the comma separated values and array values returned by the Daikin Controller.
* *
* @author Jimmy Tanagra - Initial Contribution * @author Jimmy Tanagra - Initial Contribution
* urldecode the parsed value
* *
*/ */
@NonNullByDefault @NonNullByDefault
public class InfoParser { public class InfoParser {
private static final Logger logger = LoggerFactory.getLogger(InfoParser.class);
private InfoParser() { private InfoParser() {
} }
@ -34,7 +42,7 @@ public class InfoParser {
return Stream.of(response.split(",")).filter(kv -> kv.contains("=")).map(kv -> { return Stream.of(response.split(",")).filter(kv -> kv.contains("=")).map(kv -> {
String[] keyValue = kv.split("="); String[] keyValue = kv.split("=");
String key = keyValue[0]; String key = keyValue[0];
String value = keyValue.length > 1 ? keyValue[1] : ""; String value = keyValue.length > 1 ? urldecode(keyValue[1]) : "";
return new String[] { key, value }; return new String[] { key, value };
}).collect(Collectors.toMap(x -> x[0], x -> x[1])); }).collect(Collectors.toMap(x -> x[0], x -> x[1]));
} }
@ -61,7 +69,7 @@ public class InfoParser {
} }
} }
public static Optional<Integer[]> parseArrayofInt(String value) { public static Optional<Integer[]> parseArrayOfInt(String value) {
if ("-".equals(value)) { if ("-".equals(value)) {
return Optional.empty(); return Optional.empty();
} }
@ -72,11 +80,20 @@ public class InfoParser {
} }
} }
public static Optional<Integer[]> parseArrayofInt(String value, int expectedArraySize) { public static Optional<Integer[]> parseArrayOfInt(String value, int expectedArraySize) {
Optional<Integer[]> result = parseArrayofInt(value); Optional<Integer[]> result = parseArrayOfInt(value);
if (result.isPresent() && result.get().length == expectedArraySize) { if (result.isPresent() && result.get().length == expectedArraySize) {
return result; return result;
} }
return Optional.empty(); return Optional.empty();
} }
public static String urldecode(String value) {
try {
return URLDecoder.decode(value, StandardCharsets.UTF_8.toString());
} catch (UnsupportedEncodingException e) {
logger.warn("Unsupported encoding error in '{}'", value, e);
return value;
}
}
} }

View File

@ -38,7 +38,7 @@ public class SensorInfo {
} }
public static SensorInfo parse(String response) { public static SensorInfo parse(String response) {
LOGGER.debug("Parsing string: \"{}\"", response); LOGGER.trace("Parsing string: \"{}\"", response);
Map<String, String> responseMap = InfoParser.parse(response); Map<String, String> responseMap = InfoParser.parse(response);

View File

@ -39,7 +39,7 @@ public class AirbaseBasicInfo {
} }
public static AirbaseBasicInfo parse(String response) { public static AirbaseBasicInfo parse(String response) {
LOGGER.debug("Parsing string: \"{}\"", response); LOGGER.trace("Parsing string: \"{}\"", response);
Map<String, String> responseMap = InfoParser.parse(response); Map<String, String> responseMap = InfoParser.parse(response);

View File

@ -49,7 +49,7 @@ public class AirbaseControlInfo {
} }
public static AirbaseControlInfo parse(String response) { public static AirbaseControlInfo parse(String response) {
LOGGER.debug("Parsing string: \"{}\"", response); LOGGER.trace("Parsing string: \"{}\"", response);
Map<String, String> responseMap = InfoParser.parse(response); Map<String, String> responseMap = InfoParser.parse(response);

View File

@ -43,7 +43,7 @@ public class AirbaseModelInfo {
} }
public static AirbaseModelInfo parse(String response) { public static AirbaseModelInfo parse(String response) {
LOGGER.debug("Parsing string: \"{}\"", response); LOGGER.trace("Parsing string: \"{}\"", response);
Map<String, String> responseMap = InfoParser.parse(response); Map<String, String> responseMap = InfoParser.parse(response);

View File

@ -27,6 +27,7 @@ import org.slf4j.LoggerFactory;
* Holds information from the basic_info call. * Holds information from the basic_info call.
* *
* @author Paul Smedley - Initial contribution * @author Paul Smedley - Initial contribution
* @author Jimmy Tanagra - Refactor zone array to 0-based
* *
*/ */
@NonNullByDefault @NonNullByDefault
@ -34,13 +35,13 @@ public class AirbaseZoneInfo {
private static final Logger LOGGER = LoggerFactory.getLogger(AirbaseZoneInfo.class); private static final Logger LOGGER = LoggerFactory.getLogger(AirbaseZoneInfo.class);
public String zonenames = ""; public String zonenames = "";
public boolean zone[] = new boolean[9]; public boolean zone[] = new boolean[8];
private AirbaseZoneInfo() { private AirbaseZoneInfo() {
} }
public static AirbaseZoneInfo parse(String response) { public static AirbaseZoneInfo parse(String response) {
LOGGER.debug("Parsing string: \"{}\"", response); LOGGER.trace("Parsing string: \"{}\"", response);
Map<String, String> responseMap = InfoParser.parse(response); Map<String, String> responseMap = InfoParser.parse(response);
@ -48,18 +49,19 @@ public class AirbaseZoneInfo {
info.zonenames = Optional.ofNullable(responseMap.get("zone_name")).orElse(""); info.zonenames = Optional.ofNullable(responseMap.get("zone_name")).orElse("");
String zoneinfo = Optional.ofNullable(responseMap.get("zone_onoff")).orElse(""); String zoneinfo = Optional.ofNullable(responseMap.get("zone_onoff")).orElse("");
String[] zones = zoneinfo.split("%3b"); String[] zones = zoneinfo.split(";");
for (int i = 1; i < 9; i++) { int count = Math.min(info.zone.length, zones.length);
info.zone[i] = "1".equals(zones[i - 1]); for (int i = 0; i < count; i++) {
info.zone[i] = "1".equals(zones[i]);
} }
return info; return info;
} }
public Map<String, String> getParamString() { public Map<String, String> getParamString() {
Map<String, String> params = new LinkedHashMap<>(); Map<String, String> params = new LinkedHashMap<>();
String onoffstring = IntStream.range(1, zone.length).mapToObj(idx -> zone[idx] ? "1" : "0") String onoffstring = IntStream.range(0, zone.length).mapToObj(idx -> zone[idx] ? "1" : "0")
.collect(Collectors.joining("%3b")); .collect(Collectors.joining(";"));
params.put("zone_name", zonenames); params.put("zone_name", zonenames);
params.put("zone_onoff", onoffstring); params.put("zone_onoff", onoffstring);

View File

@ -77,7 +77,7 @@ public class DaikinACUnitDiscoveryService extends AbstractDiscoveryService {
@Override @Override
protected void startBackgroundDiscovery() { protected void startBackgroundDiscovery() {
logger.debug("Starting background discovery"); logger.trace("Starting background discovery");
if (backgroundFuture != null && !backgroundFuture.isDone()) { if (backgroundFuture != null && !backgroundFuture.isDone()) {
backgroundFuture.cancel(true); backgroundFuture.cancel(true);
@ -100,7 +100,7 @@ public class DaikinACUnitDiscoveryService extends AbstractDiscoveryService {
return () -> { return () -> {
long timestampOfLastScan = getTimestampOfLastScan(); long timestampOfLastScan = getTimestampOfLastScan();
for (InetAddress broadcastAddress : getBroadcastAddresses()) { for (InetAddress broadcastAddress : getBroadcastAddresses()) {
logger.debug("Starting broadcast for {}", broadcastAddress.toString()); logger.trace("Starting broadcast for {}", broadcastAddress.toString());
try (DatagramSocket socket = new DatagramSocket()) { try (DatagramSocket socket = new DatagramSocket()) {
socket.setBroadcast(true); socket.setBroadcast(true);
@ -133,7 +133,7 @@ public class DaikinACUnitDiscoveryService extends AbstractDiscoveryService {
String host = incomingPacket.getAddress().toString().substring(1); String host = incomingPacket.getAddress().toString().substring(1);
String data = new String(incomingPacket.getData(), 0, incomingPacket.getLength(), "US-ASCII"); String data = new String(incomingPacket.getData(), 0, incomingPacket.getLength(), "US-ASCII");
logger.debug("Received packet from {}: {}", host, data); logger.trace("Received packet from {}: {}", host, data);
Map<String, String> parsedData = InfoParser.parse(data); Map<String, String> parsedData = InfoParser.parse(data);
Boolean secure = "1".equals(parsedData.get("en_secure")); Boolean secure = "1".equals(parsedData.get("en_secure"));
@ -165,7 +165,7 @@ public class DaikinACUnitDiscoveryService extends AbstractDiscoveryService {
} }
DiscoveryResult result = resultBuilder.build(); DiscoveryResult result = resultBuilder.build();
logger.debug("Successfully discovered host {}", host); logger.trace("Successfully discovered host {}", host);
thingDiscovered(result); thingDiscovered(result);
return true; return true;
} }
@ -176,7 +176,7 @@ public class DaikinACUnitDiscoveryService extends AbstractDiscoveryService {
.withProperty(DaikinConfiguration.HOST, host).withLabel("Daikin Airbase AC Unit (" + host + ")") .withProperty(DaikinConfiguration.HOST, host).withLabel("Daikin Airbase AC Unit (" + host + ")")
.withRepresentationProperty(DaikinConfiguration.HOST).build(); .withRepresentationProperty(DaikinConfiguration.HOST).build();
logger.debug("Successfully discovered host {}", host); logger.trace("Successfully discovered host {}", host);
thingDiscovered(result); thingDiscovered(result);
return true; return true;
} }

View File

@ -12,7 +12,6 @@
*/ */
package org.openhab.binding.daikin.internal.handler; package org.openhab.binding.daikin.internal.handler;
import java.io.IOException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Optional; import java.util.Optional;
import java.util.stream.IntStream; import java.util.stream.IntStream;
@ -40,7 +39,6 @@ import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.Units; import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.types.Command; import org.openhab.core.types.Command;
import org.openhab.core.types.State; import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType; import org.openhab.core.types.UnDefType;
@ -66,12 +64,11 @@ public class DaikinAcUnitHandler extends DaikinBaseHandler {
} }
@Override @Override
protected void pollStatus() throws IOException { protected void pollStatus() throws DaikinCommunicationException {
DaikinWebTargets webTargets = this.webTargets;
if (webTargets == null) {
return;
}
ControlInfo controlInfo = webTargets.getControlInfo(); ControlInfo controlInfo = webTargets.getControlInfo();
if (!"OK".equals(controlInfo.ret)) {
throw new DaikinCommunicationException("Invalid response from host");
}
updateState(DaikinBindingConstants.CHANNEL_AC_POWER, controlInfo.power ? OnOffType.ON : OnOffType.OFF); updateState(DaikinBindingConstants.CHANNEL_AC_POWER, controlInfo.power ? OnOffType.ON : OnOffType.OFF);
updateTemperatureChannel(DaikinBindingConstants.CHANNEL_AC_TEMP, controlInfo.temp); updateTemperatureChannel(DaikinBindingConstants.CHANNEL_AC_TEMP, controlInfo.temp);
@ -152,7 +149,6 @@ public class DaikinAcUnitHandler extends DaikinBaseHandler {
// Suppress any error if energy info is not supported. // Suppress any error if energy info is not supported.
logger.debug("getEnergyInfoDayAndWeek() error: {}", e.getMessage()); logger.debug("getEnergyInfoDayAndWeek() error: {}", e.getMessage());
} }
updateStatus(ThingStatus.ONLINE);
} }
@Override @Override

View File

@ -12,7 +12,6 @@
*/ */
package org.openhab.binding.daikin.internal.handler; package org.openhab.binding.daikin.internal.handler;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.List;
@ -37,7 +36,6 @@ import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType; import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.types.Command; import org.openhab.core.types.Command;
import org.openhab.core.types.StateOption; import org.openhab.core.types.StateOption;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -75,46 +73,40 @@ public class DaikinAirbaseUnitHandler extends DaikinBaseHandler {
} }
@Override @Override
protected void pollStatus() throws IOException { protected void pollStatus() throws DaikinCommunicationException {
AirbaseControlInfo controlInfo = webTargets.getAirbaseControlInfo();
if (airbaseModelInfo == null || !"OK".equals(airbaseModelInfo.ret)) { if (airbaseModelInfo == null || !"OK".equals(airbaseModelInfo.ret)) {
airbaseModelInfo = webTargets.getAirbaseModelInfo(); airbaseModelInfo = webTargets.getAirbaseModelInfo();
updateChannelStateDescriptions(); updateChannelStateDescriptions();
} }
if (controlInfo != null) { AirbaseControlInfo controlInfo = webTargets.getAirbaseControlInfo();
updateState(DaikinBindingConstants.CHANNEL_AC_POWER, controlInfo.power ? OnOffType.ON : OnOffType.OFF); if (!"OK".equals(controlInfo.ret)) {
updateTemperatureChannel(DaikinBindingConstants.CHANNEL_AC_TEMP, controlInfo.temp); throw new DaikinCommunicationException("Invalid response from host");
updateState(DaikinBindingConstants.CHANNEL_AC_MODE, new StringType(controlInfo.mode.name())); }
updateState(DaikinBindingConstants.CHANNEL_AIRBASE_AC_FAN_SPEED,
new StringType(controlInfo.fanSpeed.name()));
if (!controlInfo.power) { updateState(DaikinBindingConstants.CHANNEL_AC_POWER, OnOffType.from(controlInfo.power));
updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.OFF.getValue())); updateTemperatureChannel(DaikinBindingConstants.CHANNEL_AC_TEMP, controlInfo.temp);
} else if (controlInfo.mode == AirbaseMode.COLD) { updateState(DaikinBindingConstants.CHANNEL_AC_MODE, new StringType(controlInfo.mode.name()));
updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.COOL.getValue())); updateState(DaikinBindingConstants.CHANNEL_AIRBASE_AC_FAN_SPEED, new StringType(controlInfo.fanSpeed.name()));
} else if (controlInfo.mode == AirbaseMode.HEAT) {
updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.HEAT.getValue())); if (!controlInfo.power) {
} else if (controlInfo.mode == AirbaseMode.AUTO) { updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.OFF.getValue()));
updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.AUTO.getValue())); } else if (controlInfo.mode == AirbaseMode.COLD) {
} updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.COOL.getValue()));
} else if (controlInfo.mode == AirbaseMode.HEAT) {
updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.HEAT.getValue()));
} else if (controlInfo.mode == AirbaseMode.AUTO) {
updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.AUTO.getValue()));
} }
SensorInfo sensorInfo = webTargets.getAirbaseSensorInfo(); SensorInfo sensorInfo = webTargets.getAirbaseSensorInfo();
if (sensorInfo != null) { updateTemperatureChannel(DaikinBindingConstants.CHANNEL_INDOOR_TEMP, sensorInfo.indoortemp);
updateTemperatureChannel(DaikinBindingConstants.CHANNEL_INDOOR_TEMP, sensorInfo.indoortemp); updateTemperatureChannel(DaikinBindingConstants.CHANNEL_OUTDOOR_TEMP, sensorInfo.outdoortemp);
updateTemperatureChannel(DaikinBindingConstants.CHANNEL_OUTDOOR_TEMP, sensorInfo.outdoortemp);
}
AirbaseZoneInfo zoneInfo = webTargets.getAirbaseZoneInfo(); AirbaseZoneInfo zoneInfo = webTargets.getAirbaseZoneInfo();
if (zoneInfo != null) { IntStream.range(0, zoneInfo.zone.length)
IntStream.range(0, zoneInfo.zone.length) .forEach(idx -> updateState(DaikinBindingConstants.CHANNEL_AIRBASE_AC_ZONE + (idx + 1),
.forEach(idx -> updateState(DaikinBindingConstants.CHANNEL_AIRBASE_AC_ZONE + idx, OnOffType.from(zoneInfo.zone[idx])));
OnOffType.from(zoneInfo.zone[idx])));
}
updateStatus(ThingStatus.ONLINE);
} }
@Override @Override
@ -177,14 +169,21 @@ public class DaikinAirbaseUnitHandler extends DaikinBaseHandler {
webTargets.setAirbaseControlInfo(info); webTargets.setAirbaseControlInfo(info);
} }
/**
*
* Turn the zone on/off
* The Airbase controller allows turning off all zones, so we allow it here too
*
* @param zone the zone number starting from 1
* @param command true to turn on the zone, false to turn it off
*
*/
protected void changeZone(int zone, boolean command) throws DaikinCommunicationException { protected void changeZone(int zone, boolean command) throws DaikinCommunicationException {
AirbaseZoneInfo zoneInfo = webTargets.getAirbaseZoneInfo(); AirbaseZoneInfo zoneInfo = webTargets.getAirbaseZoneInfo();
long commonZones = 0;
long maxZones = zoneInfo.zone.length; long maxZones = zoneInfo.zone.length;
if (airbaseModelInfo != null) { if (airbaseModelInfo != null) {
maxZones = Math.min(maxZones - 1, airbaseModelInfo.zonespresent); maxZones = Math.min(maxZones, airbaseModelInfo.zonespresent);
commonZones = airbaseModelInfo.commonzone;
} }
if (zone <= 0 || zone > maxZones) { if (zone <= 0 || zone > maxZones) {
logger.warn("The given zone number ({}) is outside the number of zones supported by the controller ({})", logger.warn("The given zone number ({}) is outside the number of zones supported by the controller ({})",
@ -192,14 +191,8 @@ public class DaikinAirbaseUnitHandler extends DaikinBaseHandler {
return; return;
} }
long openZones = IntStream.range(0, zoneInfo.zone.length).filter(idx -> zoneInfo.zone[idx]).count() zoneInfo.zone[zone - 1] = command;
+ commonZones; webTargets.setAirbaseZoneInfo(zoneInfo);
logger.debug("Number of open zones: \"{}\"", openZones);
if (openZones >= 1) {
zoneInfo.zone[zone] = command;
webTargets.setAirbaseZoneInfo(zoneInfo);
}
} }
@Override @Override

View File

@ -12,7 +12,6 @@
*/ */
package org.openhab.binding.daikin.internal.handler; package org.openhab.binding.daikin.internal.handler;
import java.io.IOException;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -68,7 +67,7 @@ public abstract class DaikinBaseHandler extends BaseThingHandler {
private boolean uuidRegistrationAttempted = false; private boolean uuidRegistrationAttempted = false;
// Abstract methods to be overridden by specific Daikin implementation class // Abstract methods to be overridden by specific Daikin implementation class
protected abstract void pollStatus() throws IOException; protected abstract void pollStatus() throws DaikinCommunicationException;
protected abstract void changePower(boolean power) throws DaikinCommunicationException; protected abstract void changePower(boolean power) throws DaikinCommunicationException;
@ -131,8 +130,8 @@ public abstract class DaikinBaseHandler extends BaseThingHandler {
} }
logger.debug("Received command ({}) of wrong type for thing '{}' on channel {}", command, logger.debug("Received command ({}) of wrong type for thing '{}' on channel {}", command,
thing.getUID().getAsString(), channelUID.getId()); thing.getUID().getAsString(), channelUID.getId());
} catch (DaikinCommunicationException ex) { } catch (DaikinCommunicationException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, ex.getMessage()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
} }
} }
@ -148,7 +147,6 @@ public abstract class DaikinBaseHandler extends BaseThingHandler {
} }
webTargets = new DaikinWebTargets(httpClient, config.host, config.secure, config.uuid); webTargets = new DaikinWebTargets(httpClient, config.host, config.secure, config.uuid);
refreshInterval = config.refresh; refreshInterval = config.refresh;
schedulePoll(); schedulePoll();
} }
} }
@ -182,8 +180,11 @@ public abstract class DaikinBaseHandler extends BaseThingHandler {
private synchronized void poll() { private synchronized void poll() {
try { try {
logger.debug("Polling for state"); logger.trace("Polling for state");
pollStatus(); pollStatus();
if (getThing().getStatus() != ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
}
} catch (DaikinCommunicationForbiddenException e) { } catch (DaikinCommunicationForbiddenException e) {
if (!uuidRegistrationAttempted && config.key != null && config.uuid != null) { if (!uuidRegistrationAttempted && config.key != null && config.uuid != null) {
logger.debug("poll: Attempting to register uuid {} with key {}", config.uuid, config.key); logger.debug("poll: Attempting to register uuid {} with key {}", config.uuid, config.key);
@ -194,9 +195,7 @@ public abstract class DaikinBaseHandler extends BaseThingHandler {
"Access denied. Check uuid/key."); "Access denied. Check uuid/key.");
logger.warn("{} access denied by adapter. Check uuid/key.", thing.getUID()); logger.warn("{} access denied by adapter. Check uuid/key.", thing.getUID());
} }
} catch (IOException e) { } catch (DaikinCommunicationException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
} catch (RuntimeException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
} }
} }