Skip to content

Commit

Permalink
[hdpowerview] Add Hub configuration option hardRefreshBatteryLevel (#…
Browse files Browse the repository at this point in the history
…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>
  • Loading branch information
jlaur committed Sep 18, 2021
1 parent 84ba4fc commit 805d56d
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 30 deletions.
5 changes: 5 additions & 0 deletions bundles/org.openhab.binding.hdpowerview/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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:

```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,22 +234,40 @@ 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
* @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 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
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@ public class HDPowerViewHubConfiguration {

public long refresh;
public long hardRefresh;
public long hardRefreshBatteryLevel;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
}

Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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);
}
}

Expand All @@ -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() {
Expand Down Expand Up @@ -304,13 +323,27 @@ private Map<String, Channel> getIdChannelMap() {
return ret;
}

private void requestRefreshShades() {
private void requestRefreshShadePositions() {
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).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).requestRefreshShade();
((HDPowerViewShadeHandler) handler).requestRefreshShadeBatteryLevel();
} else {
String shadeId = item.getValue();
logger.debug("Shade '{}' handler not initialized", shadeId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -85,7 +93,7 @@ public void initialize() {
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (RefreshType.REFRESH.equals(command)) {
requestRefreshShade();
requestRefreshShadePosition();
return;
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -287,6 +330,5 @@ private void doRefreshShade() {
} catch (HubMaintenanceException e) {
// exceptions are logged in HDPowerViewWebTargets
}
refreshFuture = null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,17 @@
<default>60000</default>
</parameter>
<parameter name="hardRefresh" type="integer" required="false">
<label>Hard Refresh Interval</label>
<description>The number of minutes between hard refreshes of the PowerView Hub (or 0 to disable)</description>
<label>Hard Position Refresh Interval</label>
<description>The number of minutes between hard refreshes of positions from the PowerView Hub (or 0 to disable)</description>
<default>180</default>
</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>
</bridge-type>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down

0 comments on commit 805d56d

Please sign in to comment.