]> git.basschouten.com Git - openhab-addons.git/commitdiff
Improve error handling for unknown shades and timeouts. (#12181)
authorJacob Laursen <jacob-github@vindvejr.dk>
Thu, 3 Feb 2022 22:45:41 +0000 (23:45 +0100)
committerGitHub <noreply@github.com>
Thu, 3 Feb 2022 22:45:41 +0000 (23:45 +0100)
Fixes #12180

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewWebTargets.java
bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/exceptions/HubShadeTimeoutException.java [new file with mode: 0644]
bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewHubHandler.java
bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewShadeHandler.java
bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/i18n/hdpowerview.properties

index 99c96eded31e12635345d70b951c4a1c56d2a671..aa7dd2f3ccc909221493562739bdc615a442dd5f 100644 (file)
@@ -50,6 +50,7 @@ import org.openhab.binding.hdpowerview.internal.api.responses.Survey;
 import org.openhab.binding.hdpowerview.internal.exceptions.HubInvalidResponseException;
 import org.openhab.binding.hdpowerview.internal.exceptions.HubMaintenanceException;
 import org.openhab.binding.hdpowerview.internal.exceptions.HubProcessingException;
+import org.openhab.binding.hdpowerview.internal.exceptions.HubShadeTimeoutException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -208,15 +209,16 @@ public class HDPowerViewWebTargets {
      * @throws HubInvalidResponseException if response is invalid
      * @throws HubProcessingException if there is any processing error
      * @throws HubMaintenanceException if the hub is down for maintenance
+     * @throws HubShadeTimeoutException if the shade did not respond to a request
      */
-    public ShadeData moveShade(int shadeId, ShadePosition position)
-            throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+    public ShadeData moveShade(int shadeId, ShadePosition position) throws HubInvalidResponseException,
+            HubProcessingException, HubMaintenanceException, HubShadeTimeoutException {
         String jsonRequest = gson.toJson(new ShadeMove(position));
         String jsonResponse = invoke(HttpMethod.PUT, shades + Integer.toString(shadeId), null, jsonRequest);
         return shadeDataFromJson(jsonResponse);
     }
 
-    private ShadeData shadeDataFromJson(String json) throws HubInvalidResponseException {
+    private ShadeData shadeDataFromJson(String json) throws HubInvalidResponseException, HubShadeTimeoutException {
         try {
             Shade shade = gson.fromJson(json, Shade.class);
             if (shade == null) {
@@ -226,6 +228,9 @@ public class HDPowerViewWebTargets {
             if (shadeData == null) {
                 throw new HubInvalidResponseException("Missing 'shade.shade' element");
             }
+            if (Boolean.TRUE.equals(shadeData.timedOut)) {
+                throw new HubShadeTimeoutException("Timeout when sending request to the shade");
+            }
             return shadeData;
         } catch (JsonParseException e) {
             throw new HubInvalidResponseException("Error parsing shade response", e);
@@ -240,9 +245,10 @@ public class HDPowerViewWebTargets {
      * @throws HubInvalidResponseException if response is invalid
      * @throws HubProcessingException if there is any processing error
      * @throws HubMaintenanceException if the hub is down for maintenance
+     * @throws HubShadeTimeoutException if the shade did not respond to a request
      */
-    public ShadeData stopShade(int shadeId)
-            throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+    public ShadeData stopShade(int shadeId) throws HubInvalidResponseException, HubProcessingException,
+            HubMaintenanceException, HubShadeTimeoutException {
         String jsonRequest = gson.toJson(new ShadeStop());
         String jsonResponse = invoke(HttpMethod.PUT, shades + Integer.toString(shadeId), null, jsonRequest);
         return shadeDataFromJson(jsonResponse);
@@ -256,9 +262,10 @@ public class HDPowerViewWebTargets {
      * @throws HubInvalidResponseException if response is invalid
      * @throws HubProcessingException if there is any processing error
      * @throws HubMaintenanceException if the hub is down for maintenance
+     * @throws HubShadeTimeoutException if the shade did not respond to a request
      */
-    public ShadeData jogShade(int shadeId)
-            throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+    public ShadeData jogShade(int shadeId) throws HubInvalidResponseException, HubProcessingException,
+            HubMaintenanceException, HubShadeTimeoutException {
         String jsonRequest = gson.toJson(new ShadeJog());
         String jsonResponse = invoke(HttpMethod.PUT, shades + Integer.toString(shadeId), null, jsonRequest);
         return shadeDataFromJson(jsonResponse);
@@ -272,9 +279,10 @@ public class HDPowerViewWebTargets {
      * @throws HubInvalidResponseException if response is invalid
      * @throws HubProcessingException if there is any processing error
      * @throws HubMaintenanceException if the hub is down for maintenance
+     * @throws HubShadeTimeoutException if the shade did not respond to a request
      */
-    public ShadeData calibrateShade(int shadeId)
-            throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+    public ShadeData calibrateShade(int shadeId) throws HubInvalidResponseException, HubProcessingException,
+            HubMaintenanceException, HubShadeTimeoutException {
         String jsonRequest = gson.toJson(new ShadeCalibrate());
         String jsonResponse = invoke(HttpMethod.PUT, shades + Integer.toString(shadeId), null, jsonRequest);
         return shadeDataFromJson(jsonResponse);
@@ -574,9 +582,10 @@ public class HDPowerViewWebTargets {
      * @throws HubInvalidResponseException if response is invalid
      * @throws HubProcessingException if there is any processing error
      * @throws HubMaintenanceException if the hub is down for maintenance
+     * @throws HubShadeTimeoutException if the shade did not respond to a request
      */
-    public ShadeData getShade(int shadeId)
-            throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+    public ShadeData getShade(int shadeId) throws HubInvalidResponseException, HubProcessingException,
+            HubMaintenanceException, HubShadeTimeoutException {
         String jsonResponse = invoke(HttpMethod.GET, shades + Integer.toString(shadeId), null, null);
         return shadeDataFromJson(jsonResponse);
     }
@@ -591,9 +600,10 @@ public class HDPowerViewWebTargets {
      * @throws HubInvalidResponseException if response is invalid
      * @throws HubProcessingException if there is any processing error
      * @throws HubMaintenanceException if the hub is down for maintenance
+     * @throws HubShadeTimeoutException if the shade did not respond to a request
      */
     public ShadeData refreshShadePosition(int shadeId)
-            throws JsonParseException, HubProcessingException, HubMaintenanceException {
+            throws JsonParseException, HubProcessingException, HubMaintenanceException, HubShadeTimeoutException {
         String jsonResponse = invoke(HttpMethod.GET, shades + Integer.toString(shadeId),
                 Query.of("refresh", Boolean.toString(true)), null);
         return shadeDataFromJson(jsonResponse);
@@ -636,9 +646,10 @@ public class HDPowerViewWebTargets {
      * @throws HubInvalidResponseException if response is invalid
      * @throws HubProcessingException if there is any processing error
      * @throws HubMaintenanceException if the hub is down for maintenance
+     * @throws HubShadeTimeoutException if the shade did not respond to a request
      */
-    public ShadeData refreshShadeBatteryLevel(int shadeId)
-            throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+    public ShadeData refreshShadeBatteryLevel(int shadeId) throws HubInvalidResponseException, HubProcessingException,
+            HubMaintenanceException, HubShadeTimeoutException {
         String jsonResponse = invoke(HttpMethod.GET, shades + Integer.toString(shadeId),
                 Query.of("updateBatteryLevel", Boolean.toString(true)), null);
         return shadeDataFromJson(jsonResponse);
diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/exceptions/HubShadeTimeoutException.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/exceptions/HubShadeTimeoutException.java
new file mode 100644 (file)
index 0000000..3dcedc6
--- /dev/null
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.hdpowerview.internal.exceptions;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link HubShadeTimeoutException} is a custom exception for the HD PowerView Hub
+ * which is thrown when a shade does not respond to a request.
+ *
+ * @author @author Jacob Laursen - Initial contribution
+ */
+@NonNullByDefault
+public class HubShadeTimeoutException extends HubException {
+
+    private static final long serialVersionUID = -362347489903471011L;
+
+    public HubShadeTimeoutException(String message) {
+        super(message);
+    }
+}
index 3f6d1747515c3a03c9690c0e34796657c189aa76..cfea061f28e0e9488b8f80d13788ce7a1e2049b0 100644 (file)
@@ -58,6 +58,7 @@ import org.openhab.core.thing.ChannelUID;
 import org.openhab.core.thing.Thing;
 import org.openhab.core.thing.ThingStatus;
 import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.ThingStatusInfo;
 import org.openhab.core.thing.ThingUID;
 import org.openhab.core.thing.binding.BaseBridgeHandler;
 import org.openhab.core.thing.binding.ThingHandler;
@@ -204,7 +205,11 @@ public class HDPowerViewHubHandler extends BaseBridgeHandler {
         if (childHandler instanceof HDPowerViewShadeHandler) {
             ShadeData shadeData = pendingShadeInitializations.remove(childThing.getUID());
             if (shadeData != null) {
-                updateShadeThing(shadeData.id, childThing, shadeData);
+                if (shadeData.id > 0) {
+                    updateShadeThing(shadeData.id, childThing, shadeData);
+                } else {
+                    updateUnknownShadeThing(childThing);
+                }
             }
         }
         super.childHandlerInitialized(childHandler, childThing);
@@ -354,15 +359,15 @@ public class HDPowerViewHubHandler extends BaseBridgeHandler {
             Thing thing = item.getKey();
             int shadeId = item.getValue();
             ShadeData shadeData = idShadeDataMap.get(shadeId);
-            updateShadeThing(shadeId, thing, shadeData);
+            if (shadeData != null) {
+                updateShadeThing(shadeId, thing, shadeData);
+            } else {
+                updateUnknownShadeThing(thing);
+            }
         }
     }
 
-    private void updateShadeThing(int shadeId, Thing thing, @Nullable ShadeData shadeData) {
-        if (shadeData == null) {
-            logger.debug("Shade '{}' has no data in hub", shadeId);
-            return;
-        }
+    private void updateShadeThing(int shadeId, Thing thing, ShadeData shadeData) {
         HDPowerViewShadeHandler thingHandler = ((HDPowerViewShadeHandler) thing.getHandler());
         if (thingHandler == null) {
             logger.debug("Shade '{}' handler not initialized", shadeId);
@@ -390,6 +395,36 @@ public class HDPowerViewHubHandler extends BaseBridgeHandler {
         }
     }
 
+    private void updateUnknownShadeThing(Thing thing) {
+        String shadeId = thing.getUID().getId();
+        logger.debug("Shade '{}' has no data in hub", shadeId);
+        HDPowerViewShadeHandler thingHandler = ((HDPowerViewShadeHandler) thing.getHandler());
+        if (thingHandler == null) {
+            logger.debug("Shade '{}' handler not initialized", shadeId);
+            pendingShadeInitializations.put(thing.getUID(), new ShadeData());
+            return;
+        }
+        ThingStatus thingStatus = thingHandler.getThing().getStatus();
+        switch (thingStatus) {
+            case UNKNOWN:
+            case ONLINE:
+            case OFFLINE:
+                thing.setStatusInfo(new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.GONE,
+                        "@text/offline.gone.shade-unknown-to-hub"));
+                break;
+            case UNINITIALIZED:
+            case INITIALIZING:
+                logger.debug("Shade '{}' handler not yet ready; status: {}", shadeId, thingStatus);
+                pendingShadeInitializations.put(thing.getUID(), new ShadeData());
+                break;
+            case REMOVING:
+            case REMOVED:
+            default:
+                logger.debug("Ignoring shade status update for shade '{}' in status {}", shadeId, thingStatus);
+                break;
+        }
+    }
+
     private List<Scene> fetchScenes()
             throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
         HDPowerViewWebTargets webTargets = this.webTargets;
@@ -601,6 +636,11 @@ public class HDPowerViewHubHandler extends BaseBridgeHandler {
         Map<Thing, Integer> thingIdMap = getShadeThingIdMap();
         for (Entry<Thing, Integer> item : thingIdMap.entrySet()) {
             Thing thing = item.getKey();
+            if (thing.getStatusInfo().getStatusDetail() == ThingStatusDetail.GONE) {
+                // Skip shades unknown to the Hub.
+                logger.debug("Shade '{}' is unknown, skipping position refresh", item.getValue());
+                continue;
+            }
             ThingHandler handler = thing.getHandler();
             if (handler instanceof HDPowerViewShadeHandler) {
                 ((HDPowerViewShadeHandler) handler).requestRefreshShadePosition();
@@ -615,6 +655,11 @@ public class HDPowerViewHubHandler extends BaseBridgeHandler {
         Map<Thing, Integer> thingIdMap = getShadeThingIdMap();
         for (Entry<Thing, Integer> item : thingIdMap.entrySet()) {
             Thing thing = item.getKey();
+            if (thing.getStatusInfo().getStatusDetail() == ThingStatusDetail.GONE) {
+                // Skip shades unknown to the Hub.
+                logger.debug("Shade '{}' is unknown, skipping battery level refresh", item.getValue());
+                continue;
+            }
             ThingHandler handler = thing.getHandler();
             if (handler instanceof HDPowerViewShadeHandler) {
                 ((HDPowerViewShadeHandler) handler).requestRefreshShadeBatteryLevel();
index 8118dfd39b1d83dde59a8d91db46ed747e227405..5b0cb829a543ad80d8b666418e775e45c7f53ac1 100644 (file)
@@ -37,6 +37,7 @@ import org.openhab.binding.hdpowerview.internal.exceptions.HubException;
 import org.openhab.binding.hdpowerview.internal.exceptions.HubInvalidResponseException;
 import org.openhab.binding.hdpowerview.internal.exceptions.HubMaintenanceException;
 import org.openhab.binding.hdpowerview.internal.exceptions.HubProcessingException;
+import org.openhab.binding.hdpowerview.internal.exceptions.HubShadeTimeoutException;
 import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.OnOffType;
 import org.openhab.core.library.types.PercentType;
@@ -177,6 +178,8 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
             }
         } catch (HubMaintenanceException e) {
             // exceptions are logged in HDPowerViewWebTargets
+        } catch (HubShadeTimeoutException e) {
+            logger.warn("Shade {} timeout when sending command {}", shadeId, command);
         } catch (HubException e) {
             // ScheduledFutures will be cancelled by dispose(), naturally causing InterruptedException in invoke()
             // for any ongoing requests. Logging this would only cause confusion.
@@ -187,7 +190,8 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
     }
 
     private void handleShadeCommand(String channelId, Command command, HDPowerViewWebTargets webTargets, int shadeId)
-            throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+            throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException,
+            HubShadeTimeoutException {
         switch (channelId) {
             case CHANNEL_SHADE_POSITION:
                 if (command instanceof PercentType) {
@@ -244,26 +248,19 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
     /**
      * Update the state of the channels based on the ShadeData provided.
      *
-     * @param shadeData the ShadeData to be used; may be null.
+     * @param shadeData the ShadeData to be used.
      */
-    protected void onReceiveUpdate(@Nullable ShadeData shadeData) {
-        if (shadeData != null) {
-            updateStatus(ThingStatus.ONLINE);
-            updateCapabilities(shadeData);
-            updateSoftProperties(shadeData);
-            updateFirmwareProperties(shadeData);
-            ShadePosition shadePosition = shadeData.positions;
-            if (shadePosition != null) {
-                updatePositionStates(shadePosition);
-            }
-            updateBatteryLevelStates(shadeData.batteryStatus);
-            updateState(CHANNEL_SHADE_BATTERY_VOLTAGE,
-                    shadeData.batteryStrength > 0 ? new QuantityType<>(shadeData.batteryStrength / 10, Units.VOLT)
-                            : UnDefType.UNDEF);
-            updateState(CHANNEL_SHADE_SIGNAL_STRENGTH, new DecimalType(shadeData.signalStrength));
-        } else {
-            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
+    protected void onReceiveUpdate(ShadeData shadeData) {
+        updateStatus(ThingStatus.ONLINE);
+        updateCapabilities(shadeData);
+        updateSoftProperties(shadeData);
+        updateFirmwareProperties(shadeData);
+        ShadePosition shadePosition = shadeData.positions;
+        if (shadePosition != null) {
+            updatePositionStates(shadePosition);
         }
+        updateBatteryStates(shadeData.batteryStatus, shadeData.batteryStrength);
+        updateState(CHANNEL_SHADE_SIGNAL_STRENGTH, new DecimalType(shadeData.signalStrength));
     }
 
     private void updateCapabilities(ShadeData shade) {
@@ -404,6 +401,12 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
         updateState(CHANNEL_SHADE_SECONDARY_POSITION, shadePos.getState(capabilities, SECONDARY_POSITION));
     }
 
+    private void updateBatteryStates(int batteryStatus, double batteryStrength) {
+        updateBatteryLevelStates(batteryStatus);
+        updateState(CHANNEL_SHADE_BATTERY_VOLTAGE,
+                batteryStrength > 0 ? new QuantityType<>(batteryStrength / 10, Units.VOLT) : UnDefType.UNDEF);
+    }
+
     private void updateBatteryLevelStates(int batteryStatus) {
         int mappedValue;
         switch (batteryStatus) {
@@ -427,7 +430,8 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
     }
 
     private void moveShade(CoordinateSystem coordSys, int newPercent, HDPowerViewWebTargets webTargets, int shadeId)
-            throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+            throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException,
+            HubShadeTimeoutException {
         ShadePosition newPosition = null;
         // (try to) read the positions from the hub
         ShadeData shadeData = webTargets.getShade(shadeId);
@@ -443,21 +447,21 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
         updateShadePositions(shadeData);
     }
 
-    private void stopShade(HDPowerViewWebTargets webTargets, int shadeId)
-            throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+    private void stopShade(HDPowerViewWebTargets webTargets, int shadeId) throws HubInvalidResponseException,
+            HubProcessingException, HubMaintenanceException, HubShadeTimeoutException {
         updateShadePositions(webTargets.stopShade(shadeId));
         // Positions in response from stop motion is not updated to to actual positions yet,
         // so we need to request hard refresh.
         requestRefreshShadePosition();
     }
 
-    private void identifyShade(HDPowerViewWebTargets webTargets, int shadeId)
-            throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+    private void identifyShade(HDPowerViewWebTargets webTargets, int shadeId) throws HubInvalidResponseException,
+            HubProcessingException, HubMaintenanceException, HubShadeTimeoutException {
         updateShadePositions(webTargets.jogShade(shadeId));
     }
 
-    private void calibrateShade(HDPowerViewWebTargets webTargets, int shadeId)
-            throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+    private void calibrateShade(HDPowerViewWebTargets webTargets, int shadeId) throws HubInvalidResponseException,
+            HubProcessingException, HubMaintenanceException, HubShadeTimeoutException {
         updateShadePositions(webTargets.calibrateShade(shadeId));
     }
 
@@ -526,6 +530,8 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
             switch (kind) {
                 case POSITION:
                     shadeData = webTargets.refreshShadePosition(shadeId);
+                    updateShadePositions(shadeData);
+                    updateHardProperties(shadeData);
                     break;
                 case SURVEY:
                     Survey survey = webTargets.getShadeSurvey(shadeId);
@@ -534,19 +540,14 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
                     } else {
                         logger.warn("No response from shade {} survey", shadeId);
                     }
-                    return;
+                    break;
                 case BATTERY_LEVEL:
                     shadeData = webTargets.refreshShadeBatteryLevel(shadeId);
+                    updateBatteryStates(shadeData.batteryStatus, shadeData.batteryStrength);
                     break;
                 default:
                     throw new NotSupportedException("Unsupported refresh kind " + kind.toString());
             }
-            if (Boolean.TRUE.equals(shadeData.timedOut)) {
-                logger.warn("Shade {} wireless refresh time out", shadeId);
-            } else if (kind == RefreshKind.POSITION) {
-                updateShadePositions(shadeData);
-                updateHardProperties(shadeData);
-            }
         } catch (HubInvalidResponseException e) {
             Throwable cause = e.getCause();
             if (cause == null) {
@@ -556,6 +557,8 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
             }
         } catch (HubMaintenanceException e) {
             // exceptions are logged in HDPowerViewWebTargets
+        } catch (HubShadeTimeoutException e) {
+            logger.info("Shade {} wireless refresh time out", shadeId);
         } catch (HubException e) {
             // ScheduledFutures will be cancelled by dispose(), naturally causing InterruptedException in invoke()
             // for any ongoing requests. Logging this would only cause confusion.
index ef907e9262961902cec503e7bafec87c6908b69a..d09d2fd988953518a26da670e0c4ea480ce19a8c 100644 (file)
@@ -52,6 +52,7 @@ channel-type.hdpowerview.shade-vane.description = The opening of the slats in th
 offline.conf-error.no-host-address = Host address must be set
 offline.conf-error.invalid-id = Configuration 'id' not a valid integer
 offline.conf-error.invalid-bridge-handler = Invalid bridge handler
+offline.gone.shade-unknown-to-hub = Shade is unknown to Hub
 
 # dynamic channels