From f18ee99b08a6afc69b03a6b7bab0ef933b93029c Mon Sep 17 00:00:00 2001 From: jimtng <2554958+jimtng@users.noreply.github.com> Date: Wed, 16 Feb 2022 16:35:19 +1000 Subject: [PATCH] [daikin] Fix communication errors by retrying failed http requests (#12239) * [daikin] Fix communication errors by retrying failed http requests Signed-off-by: Jimmy Tanagra --- .../daikin/internal/DaikinWebTargets.java | 121 +++++++++--------- .../daikin/internal/api/BasicInfo.java | 4 +- .../daikin/internal/api/ControlInfo.java | 2 +- .../internal/api/EnergyInfoDayAndWeek.java | 18 +-- .../daikin/internal/api/EnergyInfoYear.java | 4 +- .../daikin/internal/api/InfoParser.java | 25 +++- .../daikin/internal/api/SensorInfo.java | 2 +- .../api/airbase/AirbaseBasicInfo.java | 2 +- .../api/airbase/AirbaseControlInfo.java | 2 +- .../api/airbase/AirbaseModelInfo.java | 2 +- .../internal/api/airbase/AirbaseZoneInfo.java | 16 ++- .../DaikinACUnitDiscoveryService.java | 10 +- .../internal/handler/DaikinAcUnitHandler.java | 12 +- .../handler/DaikinAirbaseUnitHandler.java | 77 +++++------ .../internal/handler/DaikinBaseHandler.java | 17 ++- 15 files changed, 155 insertions(+), 159 deletions(-) diff --git a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/DaikinWebTargets.java b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/DaikinWebTargets.java index 7682c3f3312..cc68a6b6072 100644 --- a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/DaikinWebTargets.java +++ b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/DaikinWebTargets.java @@ -12,13 +12,12 @@ */ package org.openhab.binding.daikin.internal; -import java.io.IOException; +import java.io.EOFException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; 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.AirbaseModelInfo; import org.openhab.binding.daikin.internal.api.airbase.AirbaseZoneInfo; -import org.openhab.core.io.net.http.HttpUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,11 +45,12 @@ import org.slf4j.LoggerFactory; * @author Tim Waterhouse - Initial Contribution * @author Paul Smedley - Modifications to support Airbase Controllers * @author Jimmy Tanagra - Add support for https and Daikin's uuid authentication + * Implement connection retry * */ @NonNullByDefault public class DaikinWebTargets { - private static final int TIMEOUT_MS = 30000; + private static final int TIMEOUT_MS = 5000; private String getBasicInfoUri; private String setControlInfoUri; @@ -183,73 +182,73 @@ public class DaikinWebTargets { } private String invoke(String uri) throws DaikinCommunicationException { - return invoke(uri, new HashMap<>()); + return invoke(uri, null); } - private String invoke(String uri, Map params) throws DaikinCommunicationException { - String uriWithParams = uri + paramsToQueryString(params); - logger.debug("Calling url: {}", uriWithParams); - 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 { + private synchronized String invoke(String url, @Nullable Map params) + throws DaikinCommunicationException { + int attemptCount = 1; try { - Request request = httpClient.newRequest(url).method(HttpMethod.GET).timeout(TIMEOUT_MS, - TimeUnit.MILLISECONDS); - if (uuid != null) { - request.header("X-Daikin-uuid", uuid); - logger.debug("Header: X-Daikin-uuid: {}", uuid); + while (true) { + try { + String result = executeUrl(url, params); + if (attemptCount > 1) { + 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) { Thread.currentThread().interrupt(); - throw new DaikinCommunicationException("Daikin HTTP interrupted", e); + throw new DaikinCommunicationException("Execution interrupted"); } } - private String paramsToQueryString(Map params) { - if (params.isEmpty()) { - return ""; + private String executeUrl(String url, @Nullable Map params) + throws InterruptedException, TimeoutException, ExecutionException, DaikinCommunicationException { + 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()) - .collect(Collectors.joining("&")); + if (response.getStatus() == HttpStatus.FORBIDDEN_403) { + 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; } } diff --git a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/BasicInfo.java b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/BasicInfo.java index 6ca4db8637e..73849b754a9 100644 --- a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/BasicInfo.java +++ b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/BasicInfo.java @@ -23,7 +23,7 @@ import org.slf4j.LoggerFactory; /** * Holds information from the basic_info call. * - * @author Jimy Tanagra - Initial contribution + * @author Jimmy Tanagra - Initial contribution * */ @NonNullByDefault @@ -38,7 +38,7 @@ public class BasicInfo { } public static BasicInfo parse(String response) { - LOGGER.debug("Parsing string: \"{}\"", response); + LOGGER.trace("Parsing string: \"{}\"", response); Map responseMap = InfoParser.parse(response); diff --git a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/ControlInfo.java b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/ControlInfo.java index 7e568db9a10..96da99e413b 100644 --- a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/ControlInfo.java +++ b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/ControlInfo.java @@ -50,7 +50,7 @@ public class ControlInfo { } public static ControlInfo parse(String response) { - LOGGER.debug("Parsing string: \"{}\"", response); + LOGGER.trace("Parsing string: \"{}\"", response); Map responseMap = InfoParser.parse(response); diff --git a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/EnergyInfoDayAndWeek.java b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/EnergyInfoDayAndWeek.java index e21d6169374..cb24464dfdc 100644 --- a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/EnergyInfoDayAndWeek.java +++ b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/EnergyInfoDayAndWeek.java @@ -12,10 +12,8 @@ */ package org.openhab.binding.daikin.internal.api; -import java.util.Arrays; import java.util.Map; import java.util.Optional; -import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; import org.slf4j.Logger; @@ -42,22 +40,14 @@ public class EnergyInfoDayAndWeek { } public static EnergyInfoDayAndWeek parse(String response) { - EnergyInfoDayAndWeek info = new EnergyInfoDayAndWeek(); - LOGGER.trace("Parsing string: \"{}\"", response); // /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 // week_heat=////... - Map responseMap = Arrays.asList(response.split(",")).stream().filter(kv -> kv.contains("=")) - .map(kv -> { - String[] keyValue = kv.split("="); - 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")))) { + Map responseMap = InfoParser.parse(response); + EnergyInfoDayAndWeek info = new EnergyInfoDayAndWeek(); + if ("OK".equals(responseMap.get("ret"))) { Optional dayOfWeek = Optional.ofNullable(responseMap.get("s_dayw")) .flatMap(value -> InfoParser.parseInt(value)); @@ -94,7 +84,7 @@ public class EnergyInfoDayAndWeek { info.energyCoolingLastWeek = Optional.of(previousWeekEnergy / 10); } } else { - LOGGER.debug("did not receive 'ret=OK' from adapter"); + LOGGER.debug("EnergyInfoDayAndWeek::parse() did not receive 'ret=OK' from adapter"); } return info; } diff --git a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/EnergyInfoYear.java b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/EnergyInfoYear.java index 799b4606132..f8ef77b09e1 100644 --- a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/EnergyInfoYear.java +++ b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/EnergyInfoYear.java @@ -43,10 +43,10 @@ public class EnergyInfoYear { EnergyInfoYear info = new EnergyInfoYear(); 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")) - .flatMap(value -> InfoParser.parseArrayofInt(value, 12)); + .flatMap(value -> InfoParser.parseArrayOfInt(value, 12)); return info; } diff --git a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/InfoParser.java b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/InfoParser.java index d69e72c794b..f8da25502e0 100644 --- a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/InfoParser.java +++ b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/InfoParser.java @@ -12,21 +12,29 @@ */ 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.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; 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. * * @author Jimmy Tanagra - Initial Contribution + * urldecode the parsed value * */ @NonNullByDefault public class InfoParser { + private static final Logger logger = LoggerFactory.getLogger(InfoParser.class); + private InfoParser() { } @@ -34,7 +42,7 @@ public class InfoParser { return Stream.of(response.split(",")).filter(kv -> kv.contains("=")).map(kv -> { String[] keyValue = kv.split("="); String key = keyValue[0]; - String value = keyValue.length > 1 ? keyValue[1] : ""; + String value = keyValue.length > 1 ? urldecode(keyValue[1]) : ""; return new String[] { key, value }; }).collect(Collectors.toMap(x -> x[0], x -> x[1])); } @@ -61,7 +69,7 @@ public class InfoParser { } } - public static Optional parseArrayofInt(String value) { + public static Optional parseArrayOfInt(String value) { if ("-".equals(value)) { return Optional.empty(); } @@ -72,11 +80,20 @@ public class InfoParser { } } - public static Optional parseArrayofInt(String value, int expectedArraySize) { - Optional result = parseArrayofInt(value); + public static Optional parseArrayOfInt(String value, int expectedArraySize) { + Optional result = parseArrayOfInt(value); if (result.isPresent() && result.get().length == expectedArraySize) { return result; } 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; + } + } } diff --git a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/SensorInfo.java b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/SensorInfo.java index f1331c2992a..3a64a101753 100644 --- a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/SensorInfo.java +++ b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/SensorInfo.java @@ -38,7 +38,7 @@ public class SensorInfo { } public static SensorInfo parse(String response) { - LOGGER.debug("Parsing string: \"{}\"", response); + LOGGER.trace("Parsing string: \"{}\"", response); Map responseMap = InfoParser.parse(response); diff --git a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseBasicInfo.java b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseBasicInfo.java index 23f5b3836dc..7d854a2a20f 100644 --- a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseBasicInfo.java +++ b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseBasicInfo.java @@ -39,7 +39,7 @@ public class AirbaseBasicInfo { } public static AirbaseBasicInfo parse(String response) { - LOGGER.debug("Parsing string: \"{}\"", response); + LOGGER.trace("Parsing string: \"{}\"", response); Map responseMap = InfoParser.parse(response); diff --git a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseControlInfo.java b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseControlInfo.java index 7b602deab60..68f4484ffd0 100644 --- a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseControlInfo.java +++ b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseControlInfo.java @@ -49,7 +49,7 @@ public class AirbaseControlInfo { } public static AirbaseControlInfo parse(String response) { - LOGGER.debug("Parsing string: \"{}\"", response); + LOGGER.trace("Parsing string: \"{}\"", response); Map responseMap = InfoParser.parse(response); diff --git a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseModelInfo.java b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseModelInfo.java index 8908f06a51c..9c35be9ec15 100644 --- a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseModelInfo.java +++ b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseModelInfo.java @@ -43,7 +43,7 @@ public class AirbaseModelInfo { } public static AirbaseModelInfo parse(String response) { - LOGGER.debug("Parsing string: \"{}\"", response); + LOGGER.trace("Parsing string: \"{}\"", response); Map responseMap = InfoParser.parse(response); diff --git a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseZoneInfo.java b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseZoneInfo.java index 062b93f8511..cea8dfae331 100644 --- a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseZoneInfo.java +++ b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseZoneInfo.java @@ -27,6 +27,7 @@ import org.slf4j.LoggerFactory; * Holds information from the basic_info call. * * @author Paul Smedley - Initial contribution + * @author Jimmy Tanagra - Refactor zone array to 0-based * */ @NonNullByDefault @@ -34,13 +35,13 @@ public class AirbaseZoneInfo { private static final Logger LOGGER = LoggerFactory.getLogger(AirbaseZoneInfo.class); public String zonenames = ""; - public boolean zone[] = new boolean[9]; + public boolean zone[] = new boolean[8]; private AirbaseZoneInfo() { } public static AirbaseZoneInfo parse(String response) { - LOGGER.debug("Parsing string: \"{}\"", response); + LOGGER.trace("Parsing string: \"{}\"", response); Map responseMap = InfoParser.parse(response); @@ -48,18 +49,19 @@ public class AirbaseZoneInfo { info.zonenames = Optional.ofNullable(responseMap.get("zone_name")).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++) { - info.zone[i] = "1".equals(zones[i - 1]); + int count = Math.min(info.zone.length, zones.length); + for (int i = 0; i < count; i++) { + info.zone[i] = "1".equals(zones[i]); } return info; } public Map getParamString() { Map params = new LinkedHashMap<>(); - String onoffstring = IntStream.range(1, zone.length).mapToObj(idx -> zone[idx] ? "1" : "0") - .collect(Collectors.joining("%3b")); + String onoffstring = IntStream.range(0, zone.length).mapToObj(idx -> zone[idx] ? "1" : "0") + .collect(Collectors.joining(";")); params.put("zone_name", zonenames); params.put("zone_onoff", onoffstring); diff --git a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/discovery/DaikinACUnitDiscoveryService.java b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/discovery/DaikinACUnitDiscoveryService.java index 76959196555..eb2f89ce2b3 100644 --- a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/discovery/DaikinACUnitDiscoveryService.java +++ b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/discovery/DaikinACUnitDiscoveryService.java @@ -77,7 +77,7 @@ public class DaikinACUnitDiscoveryService extends AbstractDiscoveryService { @Override protected void startBackgroundDiscovery() { - logger.debug("Starting background discovery"); + logger.trace("Starting background discovery"); if (backgroundFuture != null && !backgroundFuture.isDone()) { backgroundFuture.cancel(true); @@ -100,7 +100,7 @@ public class DaikinACUnitDiscoveryService extends AbstractDiscoveryService { return () -> { long timestampOfLastScan = getTimestampOfLastScan(); for (InetAddress broadcastAddress : getBroadcastAddresses()) { - logger.debug("Starting broadcast for {}", broadcastAddress.toString()); + logger.trace("Starting broadcast for {}", broadcastAddress.toString()); try (DatagramSocket socket = new DatagramSocket()) { socket.setBroadcast(true); @@ -133,7 +133,7 @@ public class DaikinACUnitDiscoveryService extends AbstractDiscoveryService { String host = incomingPacket.getAddress().toString().substring(1); 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 parsedData = InfoParser.parse(data); Boolean secure = "1".equals(parsedData.get("en_secure")); @@ -165,7 +165,7 @@ public class DaikinACUnitDiscoveryService extends AbstractDiscoveryService { } DiscoveryResult result = resultBuilder.build(); - logger.debug("Successfully discovered host {}", host); + logger.trace("Successfully discovered host {}", host); thingDiscovered(result); return true; } @@ -176,7 +176,7 @@ public class DaikinACUnitDiscoveryService extends AbstractDiscoveryService { .withProperty(DaikinConfiguration.HOST, host).withLabel("Daikin Airbase AC Unit (" + host + ")") .withRepresentationProperty(DaikinConfiguration.HOST).build(); - logger.debug("Successfully discovered host {}", host); + logger.trace("Successfully discovered host {}", host); thingDiscovered(result); return true; } diff --git a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/handler/DaikinAcUnitHandler.java b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/handler/DaikinAcUnitHandler.java index c9a3774e776..a96dd1d952f 100644 --- a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/handler/DaikinAcUnitHandler.java +++ b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/handler/DaikinAcUnitHandler.java @@ -12,7 +12,6 @@ */ package org.openhab.binding.daikin.internal.handler; -import java.io.IOException; import java.math.BigDecimal; import java.util.Optional; 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.thing.ChannelUID; import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingStatus; import org.openhab.core.types.Command; import org.openhab.core.types.State; import org.openhab.core.types.UnDefType; @@ -66,12 +64,11 @@ public class DaikinAcUnitHandler extends DaikinBaseHandler { } @Override - protected void pollStatus() throws IOException { - DaikinWebTargets webTargets = this.webTargets; - if (webTargets == null) { - return; - } + protected void pollStatus() throws DaikinCommunicationException { 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); 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. logger.debug("getEnergyInfoDayAndWeek() error: {}", e.getMessage()); } - updateStatus(ThingStatus.ONLINE); } @Override diff --git a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/handler/DaikinAirbaseUnitHandler.java b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/handler/DaikinAirbaseUnitHandler.java index 72ed5e53262..3be46e0d05b 100644 --- a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/handler/DaikinAirbaseUnitHandler.java +++ b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/handler/DaikinAirbaseUnitHandler.java @@ -12,7 +12,6 @@ */ package org.openhab.binding.daikin.internal.handler; -import java.io.IOException; import java.util.ArrayList; import java.util.EnumSet; 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.thing.ChannelUID; import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingStatus; import org.openhab.core.types.Command; import org.openhab.core.types.StateOption; import org.slf4j.Logger; @@ -75,46 +73,40 @@ public class DaikinAirbaseUnitHandler extends DaikinBaseHandler { } @Override - protected void pollStatus() throws IOException { - AirbaseControlInfo controlInfo = webTargets.getAirbaseControlInfo(); - + protected void pollStatus() throws DaikinCommunicationException { if (airbaseModelInfo == null || !"OK".equals(airbaseModelInfo.ret)) { airbaseModelInfo = webTargets.getAirbaseModelInfo(); updateChannelStateDescriptions(); } - if (controlInfo != null) { - updateState(DaikinBindingConstants.CHANNEL_AC_POWER, controlInfo.power ? OnOffType.ON : OnOffType.OFF); - updateTemperatureChannel(DaikinBindingConstants.CHANNEL_AC_TEMP, controlInfo.temp); - updateState(DaikinBindingConstants.CHANNEL_AC_MODE, new StringType(controlInfo.mode.name())); - updateState(DaikinBindingConstants.CHANNEL_AIRBASE_AC_FAN_SPEED, - new StringType(controlInfo.fanSpeed.name())); + AirbaseControlInfo controlInfo = webTargets.getAirbaseControlInfo(); + if (!"OK".equals(controlInfo.ret)) { + throw new DaikinCommunicationException("Invalid response from host"); + } - if (!controlInfo.power) { - updateState(DaikinBindingConstants.CHANNEL_AC_HOMEKITMODE, new StringType(HomekitMode.OFF.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())); - } + updateState(DaikinBindingConstants.CHANNEL_AC_POWER, OnOffType.from(controlInfo.power)); + updateTemperatureChannel(DaikinBindingConstants.CHANNEL_AC_TEMP, controlInfo.temp); + 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_HOMEKITMODE, new StringType(HomekitMode.OFF.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(); - if (sensorInfo != null) { - updateTemperatureChannel(DaikinBindingConstants.CHANNEL_INDOOR_TEMP, sensorInfo.indoortemp); - - updateTemperatureChannel(DaikinBindingConstants.CHANNEL_OUTDOOR_TEMP, sensorInfo.outdoortemp); - } + updateTemperatureChannel(DaikinBindingConstants.CHANNEL_INDOOR_TEMP, sensorInfo.indoortemp); + updateTemperatureChannel(DaikinBindingConstants.CHANNEL_OUTDOOR_TEMP, sensorInfo.outdoortemp); AirbaseZoneInfo zoneInfo = webTargets.getAirbaseZoneInfo(); - if (zoneInfo != null) { - IntStream.range(0, zoneInfo.zone.length) - .forEach(idx -> updateState(DaikinBindingConstants.CHANNEL_AIRBASE_AC_ZONE + idx, - OnOffType.from(zoneInfo.zone[idx]))); - } - updateStatus(ThingStatus.ONLINE); + IntStream.range(0, zoneInfo.zone.length) + .forEach(idx -> updateState(DaikinBindingConstants.CHANNEL_AIRBASE_AC_ZONE + (idx + 1), + OnOffType.from(zoneInfo.zone[idx]))); } @Override @@ -177,14 +169,21 @@ public class DaikinAirbaseUnitHandler extends DaikinBaseHandler { 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 { AirbaseZoneInfo zoneInfo = webTargets.getAirbaseZoneInfo(); - long commonZones = 0; long maxZones = zoneInfo.zone.length; if (airbaseModelInfo != null) { - maxZones = Math.min(maxZones - 1, airbaseModelInfo.zonespresent); - commonZones = airbaseModelInfo.commonzone; + maxZones = Math.min(maxZones, airbaseModelInfo.zonespresent); } if (zone <= 0 || zone > maxZones) { 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; } - long openZones = IntStream.range(0, zoneInfo.zone.length).filter(idx -> zoneInfo.zone[idx]).count() - + commonZones; - logger.debug("Number of open zones: \"{}\"", openZones); - - if (openZones >= 1) { - zoneInfo.zone[zone] = command; - webTargets.setAirbaseZoneInfo(zoneInfo); - } + zoneInfo.zone[zone - 1] = command; + webTargets.setAirbaseZoneInfo(zoneInfo); } @Override diff --git a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/handler/DaikinBaseHandler.java b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/handler/DaikinBaseHandler.java index a3620b51052..53e487813f0 100644 --- a/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/handler/DaikinBaseHandler.java +++ b/bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/handler/DaikinBaseHandler.java @@ -12,7 +12,6 @@ */ package org.openhab.binding.daikin.internal.handler; -import java.io.IOException; import java.util.Optional; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -68,7 +67,7 @@ public abstract class DaikinBaseHandler extends BaseThingHandler { private boolean uuidRegistrationAttempted = false; // 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; @@ -131,8 +130,8 @@ public abstract class DaikinBaseHandler extends BaseThingHandler { } logger.debug("Received command ({}) of wrong type for thing '{}' on channel {}", command, thing.getUID().getAsString(), channelUID.getId()); - } catch (DaikinCommunicationException ex) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, ex.getMessage()); + } catch (DaikinCommunicationException e) { + 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); refreshInterval = config.refresh; - schedulePoll(); } } @@ -182,8 +180,11 @@ public abstract class DaikinBaseHandler extends BaseThingHandler { private synchronized void poll() { try { - logger.debug("Polling for state"); + logger.trace("Polling for state"); pollStatus(); + if (getThing().getStatus() != ThingStatus.ONLINE) { + updateStatus(ThingStatus.ONLINE); + } } catch (DaikinCommunicationForbiddenException e) { if (!uuidRegistrationAttempted && config.key != null && config.uuid != null) { 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."); logger.warn("{} access denied by adapter. Check uuid/key.", thing.getUID()); } - } catch (IOException e) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); - } catch (RuntimeException e) { + } catch (DaikinCommunicationException e) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } }