From b223c7c7fcdb5aea06921b7547e76c5aec296174 Mon Sep 17 00:00:00 2001 From: jlaur Date: Sat, 18 Sep 2021 13:55:04 +0200 Subject: [PATCH] [hdpowerview] Add Hub configuration option hardRefreshBatteryLevel (#11260) * Add Hub configuration option hardRefreshBatteryLevel for refreshing battery status more frequently. * Explicitly update battery channels to Undefined when data is missing or invalid. Fixes #11259 Signed-off-by: Jacob Laursen Signed-off-by: Dave J Schoepel --- .../org.openhab.binding.hdpowerview/README.md | 5 ++ .../internal/HDPowerViewWebTargets.java | 24 ++++++- .../config/HDPowerViewHubConfiguration.java | 1 + .../handler/HDPowerViewHubHandler.java | 59 +++++++++++++---- .../handler/HDPowerViewShadeHandler.java | 64 +++++++++++++++---- .../resources/OH-INF/thing/thing-types.xml | 11 +++- .../hdpowerview/HDPowerViewJUnitTests.java | 2 +- 7 files changed, 136 insertions(+), 30 deletions(-) diff --git a/bundles/org.openhab.binding.hdpowerview/README.md b/bundles/org.openhab.binding.hdpowerview/README.md index 7f043983b74f..d387c46160d5 100644 --- a/bundles/org.openhab.binding.hdpowerview/README.md +++ b/bundles/org.openhab.binding.hdpowerview/README.md @@ -44,6 +44,7 @@ If in the future, you add additional shades or scenes to your system, the bindin | host | The host name or IP address of the hub on your network. | | refresh | The number of milli-seconds between fetches of the PowerView hub's shade state (default 60'000 one minute). | | hardRefresh | The number of minutes between hard refreshes of the PowerView hub's shade state (default 180 three hours). See [Refreshing the PowerView Hub Cache](#Refreshing-the-PowerView-Hub-Cache). | +| hardRefreshBatteryLevel | The number of hours between hard refreshes of battery levels from the PowerView Hub (or 0 to disable, defaulting to weekly). See [Refreshing the PowerView Hub Cache](#Refreshing-the-PowerView-Hub-Cache). | ### Thing Configuration for PowerView Shades @@ -135,6 +136,10 @@ The hub periodically does a _**"hard refresh"**_ in order to overcome this issue The time interval between hard refreshes is set in the `hardRefresh` configuration parameter. To disable periodic hard refreshes, set `hardRefresh` to zero. +Similarly, the battery level is transient and is only updated automatically by the hub once a week. +To change this interval, set `hardRefreshBatteryLevel` to number of hours between refreshes. +To use default hub behavior (weekly updates), set `hardRefreshBatteryLevel` to zero. + Note: You can also force the hub to refresh itself by sending a `REFRESH` command in a rule to an item that is connected to a channel in the hub as follows: ``` diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewWebTargets.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewWebTargets.java index 4d40d9927bee..eaa17cfbfe3a 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewWebTargets.java +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewWebTargets.java @@ -234,8 +234,8 @@ private synchronized String invoke(HttpMethod method, String url, @Nullable Quer /** * Instructs the hub to do a hard refresh (discovery on the hubs RF network) on - * a specific shade; fetches a JSON package that describes that shade, and wraps - * it in a Shade class instance + * a specific shade's position; fetches a JSON package that describes that shade, + * and wraps it in a Shade class instance * * @param shadeId id of the shade to be refreshed * @return Shade class instance @@ -243,13 +243,31 @@ private synchronized String invoke(HttpMethod method, String url, @Nullable Quer * @throws HubProcessingException if there is any processing error * @throws HubMaintenanceException if the hub is down for maintenance */ - public @Nullable Shade refreshShade(int shadeId) + public @Nullable Shade refreshShadePosition(int shadeId) throws JsonParseException, HubProcessingException, HubMaintenanceException { String json = invoke(HttpMethod.GET, shades + Integer.toString(shadeId), Query.of("refresh", Boolean.toString(true)), null); return gson.fromJson(json, Shade.class); } + /** + * Instructs the hub to do a hard refresh (discovery on the hubs RF network) on + * a specific shade's battery level; fetches a JSON package that describes that shade, + * and wraps it in a Shade class instance + * + * @param shadeId id of the shade to be refreshed + * @return Shade class instance + * @throws JsonParseException if there is a JSON parsing error + * @throws HubProcessingException if there is any processing error + * @throws HubMaintenanceException if the hub is down for maintenance + */ + public @Nullable Shade refreshShadeBatteryLevel(int shadeId) + throws JsonParseException, HubProcessingException, HubMaintenanceException { + String json = invoke(HttpMethod.GET, shades + Integer.toString(shadeId), + Query.of("updateBatteryLevel", Boolean.toString(true)), null); + return gson.fromJson(json, Shade.class); + } + /** * Tells the hub to stop movement of a specific shade * diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/config/HDPowerViewHubConfiguration.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/config/HDPowerViewHubConfiguration.java index c93df90aeec4..dd599c3265ca 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/config/HDPowerViewHubConfiguration.java +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/config/HDPowerViewHubConfiguration.java @@ -29,4 +29,5 @@ public class HDPowerViewHubConfiguration { public long refresh; public long hardRefresh; + public long hardRefreshBatteryLevel; } diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewHubHandler.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewHubHandler.java index 0827a682e31e..d617bb06cfbe 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewHubHandler.java +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewHubHandler.java @@ -67,11 +67,13 @@ public class HDPowerViewHubHandler extends BaseBridgeHandler { private final HttpClient httpClient; private long refreshInterval; - private long hardRefreshInterval; + private long hardRefreshPositionInterval; + private long hardRefreshBatteryLevelInterval; private @Nullable HDPowerViewWebTargets webTargets; private @Nullable ScheduledFuture pollFuture; - private @Nullable ScheduledFuture hardRefreshFuture; + private @Nullable ScheduledFuture hardRefreshPositionFuture; + private @Nullable ScheduledFuture hardRefreshBatteryLevelFuture; private final ChannelTypeUID sceneChannelTypeUID = new ChannelTypeUID(HDPowerViewBindingConstants.BINDING_ID, HDPowerViewBindingConstants.CHANNELTYPE_SCENE_ACTIVATE); @@ -84,7 +86,7 @@ public HDPowerViewHubHandler(Bridge bridge, HttpClient httpClient) { @Override public void handleCommand(ChannelUID channelUID, Command command) { if (RefreshType.REFRESH.equals(command)) { - requestRefreshShades(); + requestRefreshShadePositions(); return; } @@ -119,7 +121,8 @@ public void initialize() { webTargets = new HDPowerViewWebTargets(httpClient, host); refreshInterval = config.refresh; - hardRefreshInterval = config.hardRefresh; + hardRefreshPositionInterval = config.hardRefresh; + hardRefreshBatteryLevelInterval = config.hardRefreshBatteryLevel; schedulePoll(); } @@ -147,14 +150,24 @@ private void schedulePoll() { logger.debug("Scheduling poll for 5000ms out, then every {}ms", refreshInterval); this.pollFuture = scheduler.scheduleWithFixedDelay(this::poll, 5000, refreshInterval, TimeUnit.MILLISECONDS); - future = this.hardRefreshFuture; + future = this.hardRefreshPositionFuture; if (future != null) { future.cancel(false); } - if (hardRefreshInterval > 0) { - logger.debug("Scheduling hard refresh every {}minutes", hardRefreshInterval); - this.hardRefreshFuture = scheduler.scheduleWithFixedDelay(this::requestRefreshShades, 1, - hardRefreshInterval, TimeUnit.MINUTES); + if (hardRefreshPositionInterval > 0) { + logger.debug("Scheduling hard position refresh every {} minutes", hardRefreshPositionInterval); + this.hardRefreshPositionFuture = scheduler.scheduleWithFixedDelay(this::requestRefreshShadePositions, 1, + hardRefreshPositionInterval, TimeUnit.MINUTES); + } + + future = this.hardRefreshBatteryLevelFuture; + if (future != null) { + future.cancel(false); + } + if (hardRefreshBatteryLevelInterval > 0) { + logger.debug("Scheduling hard battery level refresh every {} hours", hardRefreshBatteryLevelInterval); + this.hardRefreshBatteryLevelFuture = scheduler.scheduleWithFixedDelay( + this::requestRefreshShadeBatteryLevels, 1, hardRefreshBatteryLevelInterval, TimeUnit.HOURS); } } @@ -165,11 +178,17 @@ private synchronized void stopPoll() { } this.pollFuture = null; - future = this.hardRefreshFuture; + future = this.hardRefreshPositionFuture; if (future != null) { future.cancel(true); } - this.hardRefreshFuture = null; + this.hardRefreshPositionFuture = null; + + future = this.hardRefreshBatteryLevelFuture; + if (future != null) { + future.cancel(true); + } + this.hardRefreshBatteryLevelFuture = null; } private synchronized void poll() { @@ -304,13 +323,27 @@ private Map getIdChannelMap() { return ret; } - private void requestRefreshShades() { + private void requestRefreshShadePositions() { + Map thingIdMap = getThingIdMap(); + for (Entry item : thingIdMap.entrySet()) { + Thing thing = item.getKey(); + ThingHandler handler = thing.getHandler(); + if (handler instanceof HDPowerViewShadeHandler) { + ((HDPowerViewShadeHandler) handler).requestRefreshShadePosition(); + } else { + String shadeId = item.getValue(); + logger.debug("Shade '{}' handler not initialized", shadeId); + } + } + } + + private void requestRefreshShadeBatteryLevels() { Map thingIdMap = getThingIdMap(); for (Entry item : thingIdMap.entrySet()) { Thing thing = item.getKey(); ThingHandler handler = thing.getHandler(); if (handler instanceof HDPowerViewShadeHandler) { - ((HDPowerViewShadeHandler) handler).requestRefreshShade(); + ((HDPowerViewShadeHandler) handler).requestRefreshShadeBatteryLevel(); } else { String shadeId = item.getValue(); logger.debug("Shade '{}' handler not initialized", shadeId); diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewShadeHandler.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewShadeHandler.java index 70bf65bdd75e..42514597bafb 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewShadeHandler.java +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewShadeHandler.java @@ -19,6 +19,8 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import javax.ws.rs.NotSupportedException; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.hdpowerview.internal.HDPowerViewWebTargets; @@ -57,10 +59,16 @@ @NonNullByDefault public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler { + private enum RefreshKind { + POSITION, + BATTERY_LEVEL + } + private final Logger logger = LoggerFactory.getLogger(HDPowerViewShadeHandler.class); private static final int REFRESH_DELAY_SEC = 10; - private @Nullable ScheduledFuture refreshFuture = null; + private @Nullable ScheduledFuture refreshPositionFuture = null; + private @Nullable ScheduledFuture refreshBatteryLevelFuture = null; public HDPowerViewShadeHandler(Thing thing) { super(thing); @@ -85,7 +93,7 @@ public void initialize() { @Override public void handleCommand(ChannelUID channelUID, Command command) { if (RefreshType.REFRESH.equals(command)) { - requestRefreshShade(); + requestRefreshShadePosition(); return; } @@ -138,7 +146,9 @@ protected void onReceiveUpdate(@Nullable ShadeData shadeData) { updateStatus(ThingStatus.ONLINE); updateBindingStates(shadeData.positions); updateBatteryLevel(shadeData.batteryStatus); - updateState(CHANNEL_SHADE_BATTERY_VOLTAGE, new QuantityType<>(shadeData.batteryStrength / 10, Units.VOLT)); + 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); @@ -171,6 +181,8 @@ private void updateBatteryLevel(int batteryStatus) { mappedValue = 100; break; default: // No status available (0) or invalid + updateState(CHANNEL_SHADE_LOW_BATTERY, UnDefType.UNDEF); + updateState(CHANNEL_SHADE_BATTERY_LEVEL, UnDefType.UNDEF); return; } updateState(CHANNEL_SHADE_LOW_BATTERY, batteryStatus == 1 ? OnOffType.ON : OnOffType.OFF); @@ -243,7 +255,7 @@ private void stopShade() { } int shadeId = getShadeId(); webTargets.stopShade(shadeId); - requestRefreshShade(); + requestRefreshShadePosition(); } catch (HubProcessingException | NumberFormatException e) { logger.warn("Unexpected error: {}", e.getMessage()); return; @@ -254,15 +266,36 @@ private void stopShade() { } /** - * Request that the shade shall undergo a 'hard' refresh + * Request that the shade shall undergo a 'hard' refresh for querying its current position + */ + protected synchronized void requestRefreshShadePosition() { + if (refreshPositionFuture == null) { + refreshPositionFuture = scheduler.schedule(this::doRefreshShadePosition, REFRESH_DELAY_SEC, + TimeUnit.SECONDS); + } + } + + /** + * Request that the shade shall undergo a 'hard' refresh for querying its battery level state */ - protected synchronized void requestRefreshShade() { - if (refreshFuture == null) { - refreshFuture = scheduler.schedule(this::doRefreshShade, REFRESH_DELAY_SEC, TimeUnit.SECONDS); + protected synchronized void requestRefreshShadeBatteryLevel() { + if (refreshBatteryLevelFuture == null) { + refreshBatteryLevelFuture = scheduler.schedule(this::doRefreshShadeBatteryLevel, REFRESH_DELAY_SEC, + TimeUnit.SECONDS); } } - private void doRefreshShade() { + private void doRefreshShadePosition() { + this.doRefreshShade(RefreshKind.POSITION); + refreshPositionFuture = null; + } + + private void doRefreshShadeBatteryLevel() { + this.doRefreshShade(RefreshKind.BATTERY_LEVEL); + refreshBatteryLevelFuture = null; + } + + private void doRefreshShade(RefreshKind kind) { try { HDPowerViewHubHandler bridge; if ((bridge = getBridgeHandler()) == null) { @@ -273,7 +306,17 @@ private void doRefreshShade() { throw new HubProcessingException("Web targets not initialized"); } int shadeId = getShadeId(); - Shade shade = webTargets.refreshShade(shadeId); + Shade shade; + switch (kind) { + case POSITION: + shade = webTargets.refreshShadePosition(shadeId); + break; + case BATTERY_LEVEL: + shade = webTargets.refreshShadeBatteryLevel(shadeId); + break; + default: + throw new NotSupportedException("Unsupported refresh kind " + kind.toString()); + } if (shade != null) { ShadeData shadeData = shade.shade; if (shadeData != null) { @@ -287,6 +330,5 @@ private void doRefreshShade() { } catch (HubMaintenanceException e) { // exceptions are logged in HDPowerViewWebTargets } - refreshFuture = null; } } diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/thing/thing-types.xml index 9794e8368aa5..c852a49b7529 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/thing/thing-types.xml @@ -26,10 +26,17 @@ 60000 - - The number of minutes between hard refreshes of the PowerView Hub (or 0 to disable) + + The number of minutes between hard refreshes of positions from the PowerView Hub (or 0 to disable) 180 + + + The number of hours between hard refreshes of battery levels from the PowerView Hub (or 0 to disable, + default is weekly) + true + 0 + diff --git a/bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/HDPowerViewJUnitTests.java b/bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/HDPowerViewJUnitTests.java index 8fd7e844c565..46240065fbaf 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/HDPowerViewJUnitTests.java +++ b/bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/HDPowerViewJUnitTests.java @@ -218,7 +218,7 @@ public void testOnlineCommunication() { Shade shade = null; try { assertNotEquals(0, shadeId); - shade = webTargets.refreshShade(shadeId); + shade = webTargets.refreshShadePosition(shadeId); assertNotNull(shade); } catch (HubProcessingException | HubMaintenanceException e) { fail(e.getMessage());