]> git.basschouten.com Git - openhab-addons.git/commitdiff
[daikin] Fix communication errors by retrying failed http requests (#12239)
authorjimtng <2554958+jimtng@users.noreply.github.com>
Wed, 16 Feb 2022 06:35:19 +0000 (16:35 +1000)
committerGitHub <noreply@github.com>
Wed, 16 Feb 2022 06:35:19 +0000 (07:35 +0100)
* [daikin] Fix communication errors by retrying failed http requests

Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>
15 files changed:
bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/DaikinWebTargets.java
bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/BasicInfo.java
bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/ControlInfo.java
bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/EnergyInfoDayAndWeek.java
bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/EnergyInfoYear.java
bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/InfoParser.java
bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/SensorInfo.java
bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseBasicInfo.java
bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseControlInfo.java
bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseModelInfo.java
bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/api/airbase/AirbaseZoneInfo.java
bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/discovery/DaikinACUnitDiscoveryService.java
bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/handler/DaikinAcUnitHandler.java
bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/handler/DaikinAirbaseUnitHandler.java
bundles/org.openhab.binding.daikin/src/main/java/org/openhab/binding/daikin/internal/handler/DaikinBaseHandler.java

index 7682c3f33124f3c8ecfe55a9ad694e0b17c63092..cc68a6b607287493d726c51453212427e470169d 100644 (file)
  */
 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 <paul@smedley.id.au> - 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<>());
-    }
-
-    private String invoke(String uri, Map<String, String> 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);
+        return invoke(uri, null);
+    }
+
+    private synchronized String invoke(String url, @Nullable Map<String, String> params)
+            throws DaikinCommunicationException {
+        int attemptCount = 1;
+        try {
+            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++;
                 }
-            } 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;
             }
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new DaikinCommunicationException("Execution interrupted");
         }
-
-        if (response == null) {
-            throw new DaikinCommunicationException("Daikin controller returned error while invoking " + uriWithParams);
-        }
-
-        return response;
     }
 
-    private String executeUrl(String url) throws DaikinCommunicationException {
-        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);
-            }
-            ContentResponse response = request.send();
+    private String executeUrl(String url, @Nullable Map<String, String> 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());
 
-            if (response.getStatus() == HttpStatus.FORBIDDEN_403) {
-                throw new DaikinCommunicationForbiddenException("Daikin controller access denied. Check uuid/key.");
-            }
+        ContentResponse response = request.send();
 
-            if (response.getStatus() != HttpStatus.OK_200) {
-                logger.debug("Daikin controller HTTP status: {} - {}", response.getStatus(), response.getReason());
-            }
+        if (response.getStatus() != HttpStatus.OK_200) {
+            logger.debug("Daikin controller HTTP status: {} - {} {}", response.getStatus(), response.getReason(), url);
+        }
 
-            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);
+        if (response.getStatus() == HttpStatus.FORBIDDEN_403) {
+            throw new DaikinCommunicationForbiddenException("Daikin controller access denied. Check uuid/key.");
         }
+
+        return response.getContentAsString();
     }
 
-    private String paramsToQueryString(Map<String, String> params) {
-        if (params.isEmpty()) {
-            return "";
+    private Throwable getRootCause(Throwable exception) {
+        Throwable cause = exception.getCause();
+        while (cause != null) {
+            exception = cause;
+            cause = cause.getCause();
         }
-
-        return "?" + params.entrySet().stream().map(param -> param.getKey() + "=" + param.getValue())
-                .collect(Collectors.joining("&"));
+        return exception;
     }
 }
index 6ca4db8637e4e9c7a2d8c996574d361a85c80bfc..73849b754a91a4193365b189f56dafcb0ff3be64 100644 (file)
@@ -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<String, String> responseMap = InfoParser.parse(response);
 
index 7e568db9a10a8f6ab4c23e6d35797a537b1ea118..96da99e413ba1245a87dcd3155a311bfaa86348d 100644 (file)
@@ -50,7 +50,7 @@ public class ControlInfo {
     }
 
     public static ControlInfo parse(String response) {
-        LOGGER.debug("Parsing string: \"{}\"", response);
+        LOGGER.trace("Parsing string: \"{}\"", response);
 
         Map<String, String> responseMap = InfoParser.parse(response);
 
index e21d6169374771ac5bac8170f502432bf9c02f37..cb24464dfdce17b4f42c2bac2f8e129e709a8d08 100644 (file)
  */
 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=<today>/<today-1>/<today-2>/<today-3>/...
-        Map<String, String> 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<String, String> responseMap = InfoParser.parse(response);
+        EnergyInfoDayAndWeek info = new EnergyInfoDayAndWeek();
+        if ("OK".equals(responseMap.get("ret"))) {
             Optional<Integer> 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;
     }
index 799b4606132c81d36c7c938da7fadbeca230183a..f8ef77b09e1fc9b0152e99658f0958bf0e342910 100644 (file)
@@ -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;
     }
index d69e72c794b583a5dae60f17f2b05dda83627289..f8da25502e03621471dfcffa949af5101347aec8 100644 (file)
  */
 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<Integer[]> parseArrayofInt(String value) {
+    public static Optional<Integer[]> parseArrayOfInt(String value) {
         if ("-".equals(value)) {
             return Optional.empty();
         }
@@ -72,11 +80,20 @@ public class InfoParser {
         }
     }
 
-    public static Optional<Integer[]> parseArrayofInt(String value, int expectedArraySize) {
-        Optional<Integer[]> result = parseArrayofInt(value);
+    public static Optional<Integer[]> parseArrayOfInt(String value, int expectedArraySize) {
+        Optional<Integer[]> 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;
+        }
+    }
 }
index f1331c2992a91f6d430e175ddc7f74500d76d3be..3a64a101753be1ee9ef06865bfea69d072d25754 100644 (file)
@@ -38,7 +38,7 @@ public class SensorInfo {
     }
 
     public static SensorInfo parse(String response) {
-        LOGGER.debug("Parsing string: \"{}\"", response);
+        LOGGER.trace("Parsing string: \"{}\"", response);
 
         Map<String, String> responseMap = InfoParser.parse(response);
 
index 23f5b3836dc23e281b8c002f1b46913a157ceebb..7d854a2a20fa649f752c445cabc0574bc965fda7 100644 (file)
@@ -39,7 +39,7 @@ public class AirbaseBasicInfo {
     }
 
     public static AirbaseBasicInfo parse(String response) {
-        LOGGER.debug("Parsing string: \"{}\"", response);
+        LOGGER.trace("Parsing string: \"{}\"", response);
 
         Map<String, String> responseMap = InfoParser.parse(response);
 
index 7b602deab609ac64e29e1594918f8bf3ffbf5fe6..68f4484ffd0d1188986e9ad51c3e6ba6398429f9 100644 (file)
@@ -49,7 +49,7 @@ public class AirbaseControlInfo {
     }
 
     public static AirbaseControlInfo parse(String response) {
-        LOGGER.debug("Parsing string: \"{}\"", response);
+        LOGGER.trace("Parsing string: \"{}\"", response);
 
         Map<String, String> responseMap = InfoParser.parse(response);
 
index 8908f06a51c24e48d72a1d48b967949bbd0dc33a..9c35be9ec151a9c6b9b0d6a2169eee6e3de45281 100644 (file)
@@ -43,7 +43,7 @@ public class AirbaseModelInfo {
     }
 
     public static AirbaseModelInfo parse(String response) {
-        LOGGER.debug("Parsing string: \"{}\"", response);
+        LOGGER.trace("Parsing string: \"{}\"", response);
 
         Map<String, String> responseMap = InfoParser.parse(response);
 
index 062b93f851165aae215140f9e558d97fedf55a92..cea8dfae3313cdbab0b89a2d35804a6c2561966d 100644 (file)
@@ -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<String, String> 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<String, String> getParamString() {
         Map<String, String> 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);
 
index 7695919655578021755cdbfd8608b273b7dca868..eb2f89ce2b3211e7ff70ae01d0779460bed2eac7 100644 (file)
@@ -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<String, String> 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;
             }
index c9a3774e77632ba7c3fa4ba0010822d49af4e470..a96dd1d952f9eb07c24608e293b8c1169c16fbcc 100644 (file)
@@ -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
index 72ed5e5326272ca8367bfeff890590a701ec491a..3be46e0d05bedd2c61f79d640e4d57fa78aaf3af 100644 (file)
@@ -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()));
-
-            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()));
-            }
+        AirbaseControlInfo controlInfo = webTargets.getAirbaseControlInfo();
+        if (!"OK".equals(controlInfo.ret)) {
+            throw new DaikinCommunicationException("Invalid response from host");
         }
 
-        SensorInfo sensorInfo = webTargets.getAirbaseSensorInfo();
-        if (sensorInfo != null) {
-            updateTemperatureChannel(DaikinBindingConstants.CHANNEL_INDOOR_TEMP, sensorInfo.indoortemp);
+        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()));
 
-            updateTemperatureChannel(DaikinBindingConstants.CHANNEL_OUTDOOR_TEMP, sensorInfo.outdoortemp);
+        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();
+        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
index a3620b51052bc2aa622c155e7509bd733350675e..53e487813f0d14d9b8ff663d41d1205e2cc52373 100644 (file)
@@ -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());
         }
     }