[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 <jacob-github@vindvejr.dk>
This commit is contained in:
jlaur 2021-09-18 13:55:04 +02:00 committed by GitHub
parent 84ba4fc333
commit 805d56dc08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 136 additions and 30 deletions

View File

@ -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. | | 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). | | 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). | | 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 ### 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. The time interval between hard refreshes is set in the `hardRefresh` configuration parameter.
To disable periodic hard refreshes, set `hardRefresh` to zero. 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: 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:
``` ```

View File

@ -234,8 +234,8 @@ public class HDPowerViewWebTargets {
/** /**
* Instructs the hub to do a hard refresh (discovery on the hubs RF network) on * 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 * a specific shade's position; fetches a JSON package that describes that shade,
* it in a Shade class instance * and wraps it in a Shade class instance
* *
* @param shadeId id of the shade to be refreshed * @param shadeId id of the shade to be refreshed
* @return Shade class instance * @return Shade class instance
@ -243,13 +243,31 @@ public class HDPowerViewWebTargets {
* @throws HubProcessingException if there is any processing error * @throws HubProcessingException if there is any processing error
* @throws HubMaintenanceException if the hub is down for maintenance * @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 { throws JsonParseException, HubProcessingException, HubMaintenanceException {
String json = invoke(HttpMethod.GET, shades + Integer.toString(shadeId), String json = invoke(HttpMethod.GET, shades + Integer.toString(shadeId),
Query.of("refresh", Boolean.toString(true)), null); Query.of("refresh", Boolean.toString(true)), null);
return gson.fromJson(json, Shade.class); 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 * Tells the hub to stop movement of a specific shade
* *

View File

@ -29,4 +29,5 @@ public class HDPowerViewHubConfiguration {
public long refresh; public long refresh;
public long hardRefresh; public long hardRefresh;
public long hardRefreshBatteryLevel;
} }

View File

@ -67,11 +67,13 @@ public class HDPowerViewHubHandler extends BaseBridgeHandler {
private final HttpClient httpClient; private final HttpClient httpClient;
private long refreshInterval; private long refreshInterval;
private long hardRefreshInterval; private long hardRefreshPositionInterval;
private long hardRefreshBatteryLevelInterval;
private @Nullable HDPowerViewWebTargets webTargets; private @Nullable HDPowerViewWebTargets webTargets;
private @Nullable ScheduledFuture<?> pollFuture; 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, private final ChannelTypeUID sceneChannelTypeUID = new ChannelTypeUID(HDPowerViewBindingConstants.BINDING_ID,
HDPowerViewBindingConstants.CHANNELTYPE_SCENE_ACTIVATE); HDPowerViewBindingConstants.CHANNELTYPE_SCENE_ACTIVATE);
@ -84,7 +86,7 @@ public class HDPowerViewHubHandler extends BaseBridgeHandler {
@Override @Override
public void handleCommand(ChannelUID channelUID, Command command) { public void handleCommand(ChannelUID channelUID, Command command) {
if (RefreshType.REFRESH.equals(command)) { if (RefreshType.REFRESH.equals(command)) {
requestRefreshShades(); requestRefreshShadePositions();
return; return;
} }
@ -119,7 +121,8 @@ public class HDPowerViewHubHandler extends BaseBridgeHandler {
webTargets = new HDPowerViewWebTargets(httpClient, host); webTargets = new HDPowerViewWebTargets(httpClient, host);
refreshInterval = config.refresh; refreshInterval = config.refresh;
hardRefreshInterval = config.hardRefresh; hardRefreshPositionInterval = config.hardRefresh;
hardRefreshBatteryLevelInterval = config.hardRefreshBatteryLevel;
schedulePoll(); schedulePoll();
} }
@ -147,14 +150,24 @@ public class HDPowerViewHubHandler extends BaseBridgeHandler {
logger.debug("Scheduling poll for 5000ms out, then every {}ms", refreshInterval); logger.debug("Scheduling poll for 5000ms out, then every {}ms", refreshInterval);
this.pollFuture = scheduler.scheduleWithFixedDelay(this::poll, 5000, refreshInterval, TimeUnit.MILLISECONDS); this.pollFuture = scheduler.scheduleWithFixedDelay(this::poll, 5000, refreshInterval, TimeUnit.MILLISECONDS);
future = this.hardRefreshFuture; future = this.hardRefreshPositionFuture;
if (future != null) { if (future != null) {
future.cancel(false); future.cancel(false);
} }
if (hardRefreshInterval > 0) { if (hardRefreshPositionInterval > 0) {
logger.debug("Scheduling hard refresh every {}minutes", hardRefreshInterval); logger.debug("Scheduling hard position refresh every {} minutes", hardRefreshPositionInterval);
this.hardRefreshFuture = scheduler.scheduleWithFixedDelay(this::requestRefreshShades, 1, this.hardRefreshPositionFuture = scheduler.scheduleWithFixedDelay(this::requestRefreshShadePositions, 1,
hardRefreshInterval, TimeUnit.MINUTES); 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 @@ public class HDPowerViewHubHandler extends BaseBridgeHandler {
} }
this.pollFuture = null; this.pollFuture = null;
future = this.hardRefreshFuture; future = this.hardRefreshPositionFuture;
if (future != null) { if (future != null) {
future.cancel(true); 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() { private synchronized void poll() {
@ -304,13 +323,27 @@ public class HDPowerViewHubHandler extends BaseBridgeHandler {
return ret; return ret;
} }
private void requestRefreshShades() { private void requestRefreshShadePositions() {
Map<Thing, String> thingIdMap = getThingIdMap(); Map<Thing, String> thingIdMap = getThingIdMap();
for (Entry<Thing, String> item : thingIdMap.entrySet()) { for (Entry<Thing, String> item : thingIdMap.entrySet()) {
Thing thing = item.getKey(); Thing thing = item.getKey();
ThingHandler handler = thing.getHandler(); ThingHandler handler = thing.getHandler();
if (handler instanceof HDPowerViewShadeHandler) { if (handler instanceof HDPowerViewShadeHandler) {
((HDPowerViewShadeHandler) handler).requestRefreshShade(); ((HDPowerViewShadeHandler) handler).requestRefreshShadePosition();
} else {
String shadeId = item.getValue();
logger.debug("Shade '{}' handler not initialized", shadeId);
}
}
}
private void requestRefreshShadeBatteryLevels() {
Map<Thing, String> thingIdMap = getThingIdMap();
for (Entry<Thing, String> item : thingIdMap.entrySet()) {
Thing thing = item.getKey();
ThingHandler handler = thing.getHandler();
if (handler instanceof HDPowerViewShadeHandler) {
((HDPowerViewShadeHandler) handler).requestRefreshShadeBatteryLevel();
} else { } else {
String shadeId = item.getValue(); String shadeId = item.getValue();
logger.debug("Shade '{}' handler not initialized", shadeId); logger.debug("Shade '{}' handler not initialized", shadeId);

View File

@ -19,6 +19,8 @@ import static org.openhab.binding.hdpowerview.internal.api.CoordinateSystem.*;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.ws.rs.NotSupportedException;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.hdpowerview.internal.HDPowerViewWebTargets; import org.openhab.binding.hdpowerview.internal.HDPowerViewWebTargets;
@ -57,10 +59,16 @@ import org.slf4j.LoggerFactory;
@NonNullByDefault @NonNullByDefault
public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler { public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
private enum RefreshKind {
POSITION,
BATTERY_LEVEL
}
private final Logger logger = LoggerFactory.getLogger(HDPowerViewShadeHandler.class); private final Logger logger = LoggerFactory.getLogger(HDPowerViewShadeHandler.class);
private static final int REFRESH_DELAY_SEC = 10; 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) { public HDPowerViewShadeHandler(Thing thing) {
super(thing); super(thing);
@ -85,7 +93,7 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
@Override @Override
public void handleCommand(ChannelUID channelUID, Command command) { public void handleCommand(ChannelUID channelUID, Command command) {
if (RefreshType.REFRESH.equals(command)) { if (RefreshType.REFRESH.equals(command)) {
requestRefreshShade(); requestRefreshShadePosition();
return; return;
} }
@ -138,7 +146,9 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
updateStatus(ThingStatus.ONLINE); updateStatus(ThingStatus.ONLINE);
updateBindingStates(shadeData.positions); updateBindingStates(shadeData.positions);
updateBatteryLevel(shadeData.batteryStatus); 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)); updateState(CHANNEL_SHADE_SIGNAL_STRENGTH, new DecimalType(shadeData.signalStrength));
} else { } else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
@ -171,6 +181,8 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
mappedValue = 100; mappedValue = 100;
break; break;
default: // No status available (0) or invalid default: // No status available (0) or invalid
updateState(CHANNEL_SHADE_LOW_BATTERY, UnDefType.UNDEF);
updateState(CHANNEL_SHADE_BATTERY_LEVEL, UnDefType.UNDEF);
return; return;
} }
updateState(CHANNEL_SHADE_LOW_BATTERY, batteryStatus == 1 ? OnOffType.ON : OnOffType.OFF); updateState(CHANNEL_SHADE_LOW_BATTERY, batteryStatus == 1 ? OnOffType.ON : OnOffType.OFF);
@ -243,7 +255,7 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
} }
int shadeId = getShadeId(); int shadeId = getShadeId();
webTargets.stopShade(shadeId); webTargets.stopShade(shadeId);
requestRefreshShade(); requestRefreshShadePosition();
} catch (HubProcessingException | NumberFormatException e) { } catch (HubProcessingException | NumberFormatException e) {
logger.warn("Unexpected error: {}", e.getMessage()); logger.warn("Unexpected error: {}", e.getMessage());
return; return;
@ -254,15 +266,36 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
} }
/** /**
* 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 requestRefreshShade() { protected synchronized void requestRefreshShadePosition() {
if (refreshFuture == null) { if (refreshPositionFuture == null) {
refreshFuture = scheduler.schedule(this::doRefreshShade, REFRESH_DELAY_SEC, TimeUnit.SECONDS); refreshPositionFuture = scheduler.schedule(this::doRefreshShadePosition, REFRESH_DELAY_SEC,
TimeUnit.SECONDS);
} }
} }
private void doRefreshShade() { /**
* Request that the shade shall undergo a 'hard' refresh for querying its battery level state
*/
protected synchronized void requestRefreshShadeBatteryLevel() {
if (refreshBatteryLevelFuture == null) {
refreshBatteryLevelFuture = scheduler.schedule(this::doRefreshShadeBatteryLevel, REFRESH_DELAY_SEC,
TimeUnit.SECONDS);
}
}
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 { try {
HDPowerViewHubHandler bridge; HDPowerViewHubHandler bridge;
if ((bridge = getBridgeHandler()) == null) { if ((bridge = getBridgeHandler()) == null) {
@ -273,7 +306,17 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
throw new HubProcessingException("Web targets not initialized"); throw new HubProcessingException("Web targets not initialized");
} }
int shadeId = getShadeId(); 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) { if (shade != null) {
ShadeData shadeData = shade.shade; ShadeData shadeData = shade.shade;
if (shadeData != null) { if (shadeData != null) {
@ -287,6 +330,5 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
} catch (HubMaintenanceException e) { } catch (HubMaintenanceException e) {
// exceptions are logged in HDPowerViewWebTargets // exceptions are logged in HDPowerViewWebTargets
} }
refreshFuture = null;
} }
} }

View File

@ -26,10 +26,17 @@
<default>60000</default> <default>60000</default>
</parameter> </parameter>
<parameter name="hardRefresh" type="integer" required="false"> <parameter name="hardRefresh" type="integer" required="false">
<label>Hard Refresh Interval</label> <label>Hard Position Refresh Interval</label>
<description>The number of minutes between hard refreshes of the PowerView Hub (or 0 to disable)</description> <description>The number of minutes between hard refreshes of positions from the PowerView Hub (or 0 to disable)</description>
<default>180</default> <default>180</default>
</parameter> </parameter>
<parameter name="hardRefreshBatteryLevel" type="integer" required="false">
<label>Hard Battery Level Refresh Interval</label>
<description>The number of hours between hard refreshes of battery levels from the PowerView Hub (or 0 to disable,
default is weekly)</description>
<advanced>true</advanced>
<default>0</default>
</parameter>
</config-description> </config-description>
</bridge-type> </bridge-type>

View File

@ -218,7 +218,7 @@ public class HDPowerViewJUnitTests {
Shade shade = null; Shade shade = null;
try { try {
assertNotEquals(0, shadeId); assertNotEquals(0, shadeId);
shade = webTargets.refreshShade(shadeId); shade = webTargets.refreshShadePosition(shadeId);
assertNotNull(shade); assertNotNull(shade);
} catch (HubProcessingException | HubMaintenanceException e) { } catch (HubProcessingException | HubMaintenanceException e) {
fail(e.getMessage()); fail(e.getMessage());