[hdpowerview] Add support for calibrating a shade (#12002)

* Add support for calibrating a shade.

Fixes #11767

* Fix startup problems by decoupling capabilities cache from updateSoftProperties.
* Minor refactoring of capabilities and shade id handling.
* Dispose faster/safer by killing any remaining tasks.
* Set shade thing status to UNKNOWN until we receive any data for shade.
* Fix position update glitch after setting position.
* Remove unneeded catch after shade id refactoring.
* Document return values in Javadoc.
* Avoid logging InterruptedException during dispose.
* Add calibration example item.
* Reduce nesting.
* Add myself as reviewer for binding.
* Add Andrew Fiddian-Green as reviewer for binding.
* Handle JsonParseException.
* Fix alphabetic order.

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
This commit is contained in:
Jacob Laursen
2022-01-14 22:59:01 +01:00
committed by GitHub
parent 843ca55c68
commit 2832567fc9
13 changed files with 319 additions and 176 deletions

View File

@@ -41,6 +41,7 @@ public class HDPowerViewBindingConstants {
public static final String CHANNEL_SHADE_POSITION = "position";
public static final String CHANNEL_SHADE_SECONDARY_POSITION = "secondary";
public static final String CHANNEL_SHADE_VANE = "vane";
public static final String CHANNEL_SHADE_CALIBRATE = "calibrate";
public static final String CHANNEL_SHADE_LOW_BATTERY = "lowBattery";
public static final String CHANNEL_SHADE_BATTERY_LEVEL = "batteryLevel";
public static final String CHANNEL_SHADE_BATTERY_VOLTAGE = "batteryVoltage";

View File

@@ -26,6 +26,7 @@ import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.openhab.binding.hdpowerview.internal.api.ShadePosition;
import org.openhab.binding.hdpowerview.internal.api.requests.ShadeCalibrate;
import org.openhab.binding.hdpowerview.internal.api.requests.ShadeMove;
import org.openhab.binding.hdpowerview.internal.api.requests.ShadeStop;
import org.openhab.binding.hdpowerview.internal.api.responses.FirmwareVersion;
@@ -169,12 +170,45 @@ public class HDPowerViewWebTargets {
*
* @param shadeId id of the shade to be moved
* @param position instance of ShadePosition containing the new position
* @return Shade class instance (with new position)
* @throws HubProcessingException if there is any processing error
* @throws HubMaintenanceException if the hub is down for maintenance
*/
public void moveShade(int shadeId, ShadePosition position) throws HubProcessingException, HubMaintenanceException {
String json = gson.toJson(new ShadeMove(shadeId, position));
invoke(HttpMethod.PUT, shades + Integer.toString(shadeId), null, json);
public @Nullable Shade moveShade(int shadeId, ShadePosition position)
throws JsonParseException, HubProcessingException, HubMaintenanceException {
String jsonRequest = gson.toJson(new ShadeMove(position));
String jsonResponse = invoke(HttpMethod.PUT, shades + Integer.toString(shadeId), null, jsonRequest);
return gson.fromJson(jsonResponse, Shade.class);
}
/**
* Instructs the hub to stop movement of a specific shade
*
* @param shadeId id of the shade to be stopped
* @return Shade class instance (new position cannot be relied upon)
* @throws HubProcessingException if there is any processing error
* @throws HubMaintenanceException if the hub is down for maintenance
*/
public @Nullable Shade stopShade(int shadeId)
throws JsonParseException, HubProcessingException, HubMaintenanceException {
String jsonRequest = gson.toJson(new ShadeStop());
String jsonResponse = invoke(HttpMethod.PUT, shades + Integer.toString(shadeId), null, jsonRequest);
return gson.fromJson(jsonResponse, Shade.class);
}
/**
* Instructs the hub to calibrate a specific shade
*
* @param shadeId id of the shade to be calibrated
* @return Shade class instance
* @throws HubProcessingException if there is any processing error
* @throws HubMaintenanceException if the hub is down for maintenance
*/
public @Nullable Shade calibrateShade(int shadeId)
throws JsonParseException, HubProcessingException, HubMaintenanceException {
String jsonRequest = gson.toJson(new ShadeCalibrate());
String jsonResponse = invoke(HttpMethod.PUT, shades + Integer.toString(shadeId), null, jsonRequest);
return gson.fromJson(jsonResponse, Shade.class);
}
/**
@@ -397,16 +431,4 @@ public class HDPowerViewWebTargets {
Query.of("updateBatteryLevel", Boolean.toString(true)), null);
return gson.fromJson(json, Shade.class);
}
/**
* Tells the hub to stop movement of a specific shade
*
* @param shadeId id of the shade to be stopped
* @throws HubProcessingException if there is any processing error
* @throws HubMaintenanceException if the hub is down for maintenance
*/
public void stopShade(int shadeId) throws HubProcessingException, HubMaintenanceException {
String json = gson.toJson(new ShadeStop(shadeId));
invoke(HttpMethod.PUT, shades + Integer.toString(shadeId), null, json);
}
}

View File

@@ -13,21 +13,18 @@
package org.openhab.binding.hdpowerview.internal.api.requests;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The motion "stop" directive for a shade
* A request to calibrate a shade
*
* @author Andrew Fiddian-Green - Initial contribution
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
class ShadeIdStop {
public class ShadeCalibrate {
int id;
public @Nullable String motion;
public ShadeMotion shade;
public ShadeIdStop(int id) {
this.id = id;
this.motion = "stop";
public ShadeCalibrate() {
this.shade = new ShadeMotion(ShadeMotion.Type.CALIBRATE);
}
}

View File

@@ -0,0 +1,45 @@
/**
* 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.api.requests;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* A motion directive for a shade
*
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
class ShadeMotion {
public enum Type {
STOP("stop"),
CALIBRATE("calibrate");
private String motion;
Type(String motion) {
this.motion = motion;
}
public String getMotion() {
return this.motion;
}
}
public String motion;
public ShadeMotion(Type motionType) {
this.motion = motionType.getMotion();
}
}

View File

@@ -13,7 +13,6 @@
package org.openhab.binding.hdpowerview.internal.api.requests;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.hdpowerview.internal.api.ShadePosition;
/**
@@ -24,9 +23,9 @@ import org.openhab.binding.hdpowerview.internal.api.ShadePosition;
@NonNullByDefault
public class ShadeMove {
public @Nullable ShadeIdPosition shade;
public ShadePositions shade;
public ShadeMove(int id, ShadePosition position) {
this.shade = new ShadeIdPosition(id, position);
public ShadeMove(ShadePosition position) {
this.shade = new ShadePositions(position);
}
}

View File

@@ -13,7 +13,6 @@
package org.openhab.binding.hdpowerview.internal.api.requests;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.hdpowerview.internal.api.ShadePosition;
/**
@@ -22,13 +21,11 @@ import org.openhab.binding.hdpowerview.internal.api.ShadePosition;
* @author Andy Lintner - Initial contribution
*/
@NonNullByDefault
class ShadeIdPosition {
class ShadePositions {
int id;
public @Nullable ShadePosition positions;
public ShadePosition positions;
public ShadeIdPosition(int id, ShadePosition position) {
this.id = id;
public ShadePositions(ShadePosition position) {
this.positions = position;
}
}

View File

@@ -13,7 +13,6 @@
package org.openhab.binding.hdpowerview.internal.api.requests;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* A request to stop the movement of a shade
@@ -23,9 +22,9 @@ import org.eclipse.jdt.annotation.Nullable;
@NonNullByDefault
public class ShadeStop {
public @Nullable ShadeIdStop shade;
public ShadeMotion shade;
public ShadeStop(int id) {
this.shade = new ShadeIdStop(id);
public ShadeStop() {
this.shade = new ShadeMotion(ShadeMotion.Type.STOP);
}
}

View File

@@ -92,7 +92,7 @@ public class ShadeCapabilitiesDatabase {
protected int intValue = -1;
protected String text = "-- not in database --";
protected Integer getValue() {
public Integer getValue() {
return intValue;
}

View File

@@ -54,6 +54,8 @@ import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonParseException;
/**
* Handles commands for an HD PowerView Shade
*
@@ -70,14 +72,14 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
}
private final Logger logger = LoggerFactory.getLogger(HDPowerViewShadeHandler.class);
private final ShadeCapabilitiesDatabase db = new ShadeCapabilitiesDatabase();
private static final int REFRESH_DELAY_SEC = 10;
private @Nullable ScheduledFuture<?> refreshPositionFuture = null;
private @Nullable ScheduledFuture<?> refreshSignalFuture = null;
private @Nullable ScheduledFuture<?> refreshBatteryLevelFuture = null;
private final ShadeCapabilitiesDatabase db = new ShadeCapabilitiesDatabase();
private int shadeCapabilities = -1;
private @Nullable Capabilities capabilities;
private int shadeId;
private boolean isDisposing;
public HDPowerViewShadeHandler(Thing thing) {
super(thing);
@@ -85,8 +87,10 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
@Override
public void initialize() {
logger.debug("Initializing shade handler");
isDisposing = false;
try {
getShadeId();
shadeId = getShadeId();
} catch (NumberFormatException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.conf-error.invalid-id");
@@ -104,12 +108,34 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
}
ThingStatus bridgeStatus = bridge.getStatus();
if (bridgeStatus == ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
updateStatus(ThingStatus.UNKNOWN);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
}
}
@Override
public void dispose() {
logger.debug("Disposing shade handler for shade {}", shadeId);
isDisposing = true;
ScheduledFuture<?> future = refreshPositionFuture;
if (future != null) {
future.cancel(true);
}
refreshPositionFuture = null;
future = refreshSignalFuture;
if (future != null) {
future.cancel(true);
}
refreshSignalFuture = null;
future = refreshBatteryLevelFuture;
if (future != null) {
future.cancel(true);
}
refreshBatteryLevelFuture = null;
capabilities = null;
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
String channelId = channelUID.getId();
@@ -133,15 +159,42 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
return;
}
HDPowerViewHubHandler bridge = getBridgeHandler();
if (bridge == null) {
logger.warn("Missing bridge handler");
return;
}
HDPowerViewWebTargets webTargets = bridge.getWebTargets();
if (webTargets == null) {
logger.warn("Web targets not initialized");
return;
}
try {
handleShadeCommand(channelId, command, webTargets, shadeId);
} catch (JsonParseException e) {
logger.warn("Bridge returned a bad JSON response: {}", e.getMessage());
} catch (HubProcessingException e) {
// ScheduledFutures will be cancelled by dispose(), naturally causing InterruptedException in invoke()
// for any ongoing requests. Logging this would only cause confusion.
if (!isDisposing) {
logger.warn("Unexpected error: {}", e.getMessage());
}
} catch (HubMaintenanceException e) {
// exceptions are logged in HDPowerViewWebTargets
}
}
private void handleShadeCommand(String channelId, Command command, HDPowerViewWebTargets webTargets, int shadeId)
throws HubProcessingException, HubMaintenanceException {
switch (channelId) {
case CHANNEL_SHADE_POSITION:
if (command instanceof PercentType) {
moveShade(PRIMARY_ZERO_IS_CLOSED, ((PercentType) command).intValue());
moveShade(PRIMARY_ZERO_IS_CLOSED, ((PercentType) command).intValue(), webTargets, shadeId);
} else if (command instanceof UpDownType) {
moveShade(PRIMARY_ZERO_IS_CLOSED, UpDownType.UP.equals(command) ? 0 : 100);
moveShade(PRIMARY_ZERO_IS_CLOSED, UpDownType.UP == command ? 0 : 100, webTargets, shadeId);
} else if (command instanceof StopMoveType) {
if (StopMoveType.STOP.equals(command)) {
stopShade();
if (StopMoveType.STOP == command) {
stopShade(webTargets, shadeId);
} else {
logger.warn("Unexpected StopMoveType command");
}
@@ -150,25 +203,31 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
case CHANNEL_SHADE_VANE:
if (command instanceof PercentType) {
moveShade(VANE_TILT_COORDS, ((PercentType) command).intValue());
moveShade(VANE_TILT_COORDS, ((PercentType) command).intValue(), webTargets, shadeId);
} else if (command instanceof OnOffType) {
moveShade(VANE_TILT_COORDS, OnOffType.ON.equals(command) ? 100 : 0);
moveShade(VANE_TILT_COORDS, OnOffType.ON == command ? 100 : 0, webTargets, shadeId);
}
break;
case CHANNEL_SHADE_SECONDARY_POSITION:
if (command instanceof PercentType) {
moveShade(SECONDARY_ZERO_IS_OPEN, ((PercentType) command).intValue());
moveShade(SECONDARY_ZERO_IS_OPEN, ((PercentType) command).intValue(), webTargets, shadeId);
} else if (command instanceof UpDownType) {
moveShade(SECONDARY_ZERO_IS_OPEN, UpDownType.UP.equals(command) ? 0 : 100);
moveShade(SECONDARY_ZERO_IS_OPEN, UpDownType.UP == command ? 0 : 100, webTargets, shadeId);
} else if (command instanceof StopMoveType) {
if (StopMoveType.STOP.equals(command)) {
stopShade();
if (StopMoveType.STOP == command) {
stopShade(webTargets, shadeId);
} else {
logger.warn("Unexpected StopMoveType command");
}
}
break;
case CHANNEL_SHADE_CALIBRATE:
if (OnOffType.ON == command) {
calibrateShade(webTargets, shadeId);
}
break;
}
}
@@ -180,10 +239,14 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
protected void onReceiveUpdate(@Nullable ShadeData shadeData) {
if (shadeData != null) {
updateStatus(ThingStatus.ONLINE);
updateCapabilities(shadeData);
updateSoftProperties(shadeData);
updateFirmwareProperties(shadeData);
updateBindingStates(shadeData.positions);
updateBatteryLevel(shadeData.batteryStatus);
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);
@@ -193,6 +256,29 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
}
}
private void updateCapabilities(ShadeData shade) {
if (capabilities != null) {
// Already cached.
return;
}
Integer value = shade.capabilities;
if (value != null) {
int valueAsInt = value.intValue();
logger.debug("Caching capabilities {} for shade {}", valueAsInt, shade.id);
capabilities = db.getCapabilities(valueAsInt);
} else {
logger.debug("Capabilities not included in shade response");
}
}
private Capabilities getCapabilitiesOrDefault() {
Capabilities capabilities = this.capabilities;
if (capabilities == null) {
return new Capabilities();
}
return capabilities;
}
/**
* Update the Thing's properties based on the contents of the provided ShadeData.
*
@@ -233,11 +319,6 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
}
}
// update shadeCapabilities field
if (capabilitiesVal >= 0) {
shadeCapabilities = capabilitiesVal;
}
if (propChanged && db.isCapabilitiesInDatabase(capabilitiesVal) && db.isTypeInDatabase(type)
&& (capabilitiesVal != db.getType(type).getCapabilities())) {
db.logCapabilitiesMismatch(type, capabilitiesVal);
@@ -268,57 +349,52 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
*/
private void updateHardProperties(ShadeData shadeData) {
final ShadePosition positions = shadeData.positions;
if (positions != null) {
final Map<String, String> properties = getThing().getProperties();
// update 'jsonHasSecondary' property
String propKey = HDPowerViewBindingConstants.PROPERTY_SECONDARY_RAIL_DETECTED;
String propOldVal = properties.getOrDefault(propKey, "");
boolean propNewBool = positions.secondaryRailDetected();
String propNewVal = String.valueOf(propNewBool);
if (!propNewVal.equals(propOldVal)) {
getThing().setProperty(propKey, propNewVal);
final Integer temp = shadeData.capabilities;
final int capabilities = temp != null ? temp.intValue() : -1;
if (propNewBool != db.getCapabilities(capabilities).supportsSecondary()) {
db.logPropertyMismatch(propKey, shadeData.type, capabilities, propNewBool);
}
}
// update 'jsonTiltAnywhere' property
propKey = HDPowerViewBindingConstants.PROPERTY_TILT_ANYWHERE_DETECTED;
propOldVal = properties.getOrDefault(propKey, "");
propNewBool = positions.tiltAnywhereDetected();
propNewVal = String.valueOf(propNewBool);
if (!propNewVal.equals(propOldVal)) {
getThing().setProperty(propKey, propNewVal);
final Integer temp = shadeData.capabilities;
final int capabilities = temp != null ? temp.intValue() : -1;
if (propNewBool != db.getCapabilities(capabilities).supportsTiltAnywhere()) {
db.logPropertyMismatch(propKey, shadeData.type, capabilities, propNewBool);
}
}
}
}
private void updateBindingStates(@Nullable ShadePosition shadePos) {
if (shadePos == null) {
logger.debug("The value of 'shadePosition' argument was null!");
} else if (shadeCapabilities < 0) {
logger.debug("The 'shadeCapabilities' field has not been initialized!");
} else {
Capabilities caps = db.getCapabilities(shadeCapabilities);
updateState(CHANNEL_SHADE_POSITION, shadePos.getState(caps, PRIMARY_ZERO_IS_CLOSED));
updateState(CHANNEL_SHADE_VANE, shadePos.getState(caps, VANE_TILT_COORDS));
updateState(CHANNEL_SHADE_SECONDARY_POSITION, shadePos.getState(caps, SECONDARY_ZERO_IS_OPEN));
if (positions == null) {
return;
}
updateState(CHANNEL_SHADE_POSITION, UnDefType.UNDEF);
updateState(CHANNEL_SHADE_VANE, UnDefType.UNDEF);
updateState(CHANNEL_SHADE_SECONDARY_POSITION, UnDefType.UNDEF);
Capabilities capabilities = getCapabilitiesOrDefault();
final Map<String, String> properties = getThing().getProperties();
// update 'secondary rail detected' property
String propKey = HDPowerViewBindingConstants.PROPERTY_SECONDARY_RAIL_DETECTED;
String propOldVal = properties.getOrDefault(propKey, "");
boolean propNewBool = positions.secondaryRailDetected();
String propNewVal = String.valueOf(propNewBool);
if (!propNewVal.equals(propOldVal)) {
getThing().setProperty(propKey, propNewVal);
if (propNewBool != capabilities.supportsSecondary()) {
db.logPropertyMismatch(propKey, shadeData.type, capabilities.getValue(), propNewBool);
}
}
// update 'tilt anywhere detected' property
propKey = HDPowerViewBindingConstants.PROPERTY_TILT_ANYWHERE_DETECTED;
propOldVal = properties.getOrDefault(propKey, "");
propNewBool = positions.tiltAnywhereDetected();
propNewVal = String.valueOf(propNewBool);
if (!propNewVal.equals(propOldVal)) {
getThing().setProperty(propKey, propNewVal);
if (propNewBool != capabilities.supportsTiltAnywhere()) {
db.logPropertyMismatch(propKey, shadeData.type, capabilities.getValue(), propNewBool);
}
}
}
private void updateBatteryLevel(int batteryStatus) {
private void updatePositionStates(ShadePosition shadePos) {
Capabilities capabilities = this.capabilities;
if (capabilities == null) {
logger.debug("The 'shadeCapabilities' field has not yet been initialized");
updateState(CHANNEL_SHADE_POSITION, UnDefType.UNDEF);
updateState(CHANNEL_SHADE_VANE, UnDefType.UNDEF);
updateState(CHANNEL_SHADE_SECONDARY_POSITION, UnDefType.UNDEF);
return;
}
updateState(CHANNEL_SHADE_POSITION, shadePos.getState(capabilities, PRIMARY_ZERO_IS_CLOSED));
updateState(CHANNEL_SHADE_VANE, shadePos.getState(capabilities, VANE_TILT_COORDS));
updateState(CHANNEL_SHADE_SECONDARY_POSITION, shadePos.getState(capabilities, SECONDARY_ZERO_IS_OPEN));
}
private void updateBatteryLevelStates(int batteryStatus) {
int mappedValue;
switch (batteryStatus) {
case 1: // Low
@@ -340,45 +416,60 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
updateState(CHANNEL_SHADE_BATTERY_LEVEL, new DecimalType(mappedValue));
}
private void moveShade(CoordinateSystem coordSys, int newPercent) {
try {
HDPowerViewHubHandler bridge;
if ((bridge = getBridgeHandler()) == null) {
throw new HubProcessingException("Missing bridge handler");
private void moveShade(CoordinateSystem coordSys, int newPercent, HDPowerViewWebTargets webTargets, int shadeId)
throws HubProcessingException, HubMaintenanceException {
ShadePosition newPosition = null;
// (try to) read the positions from the hub
Shade shade = webTargets.getShade(shadeId);
if (shade != null) {
ShadeData shadeData = shade.shade;
if (shadeData != null) {
updateCapabilities(shadeData);
newPosition = shadeData.positions;
}
HDPowerViewWebTargets webTargets = bridge.getWebTargets();
if (webTargets == null) {
throw new HubProcessingException("Web targets not initialized");
}
ShadePosition newPosition = null;
// (try to) read the positions from the hub
int shadeId = getShadeId();
Shade shade = webTargets.getShade(shadeId);
if (shade != null) {
ShadeData shadeData = shade.shade;
if (shadeData != null) {
newPosition = shadeData.positions;
}
}
// if no positions returned, then create a new position
if (newPosition == null) {
newPosition = new ShadePosition();
}
// set the new position value, and write the positions to the hub
webTargets.moveShade(shadeId,
newPosition.setPosition(db.getCapabilities(shadeCapabilities), coordSys, newPercent));
// update the Channels to match the new position
final ShadePosition finalPosition = newPosition;
scheduler.submit(() -> {
updateBindingStates(finalPosition);
});
} catch (HubProcessingException | NumberFormatException e) {
logger.warn("Unexpected error: {}", e.getMessage());
return;
} catch (HubMaintenanceException e) {
// exceptions are logged in HDPowerViewWebTargets
}
// if no positions returned, then create a new position
if (newPosition == null) {
newPosition = new ShadePosition();
}
Capabilities capabilities = getCapabilitiesOrDefault();
// set the new position value, and write the positions to the hub
shade = webTargets.moveShade(shadeId, newPosition.setPosition(capabilities, coordSys, newPercent));
if (shade != null) {
updateShadePositions(shade);
}
}
private void stopShade(HDPowerViewWebTargets webTargets, int shadeId)
throws HubProcessingException, HubMaintenanceException {
Shade shade = webTargets.stopShade(shadeId);
if (shade != null) {
updateShadePositions(shade);
}
// Positions in response from stop motion is not updated to to actual positions yet,
// so we need to request hard refresh.
requestRefreshShadePosition();
}
private void calibrateShade(HDPowerViewWebTargets webTargets, int shadeId)
throws HubProcessingException, HubMaintenanceException {
Shade shade = webTargets.calibrateShade(shadeId);
if (shade != null) {
updateShadePositions(shade);
}
}
private void updateShadePositions(Shade shade) {
ShadeData shadeData = shade.shade;
if (shadeData == null) {
return;
}
ShadePosition shadePosition = shadeData.positions;
if (shadePosition == null) {
return;
}
updateCapabilities(shadeData);
updatePositionStates(shadePosition);
}
private int getShadeId() throws NumberFormatException {
@@ -389,35 +480,12 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
return Integer.parseInt(str);
}
private void stopShade() {
try {
HDPowerViewHubHandler bridge;
if ((bridge = getBridgeHandler()) == null) {
throw new HubProcessingException("Missing bridge handler");
}
HDPowerViewWebTargets webTargets = bridge.getWebTargets();
if (webTargets == null) {
throw new HubProcessingException("Web targets not initialized");
}
int shadeId = getShadeId();
webTargets.stopShade(shadeId);
requestRefreshShadePosition();
} catch (HubProcessingException | NumberFormatException e) {
logger.warn("Unexpected error: {}", e.getMessage());
return;
} catch (HubMaintenanceException e) {
// exceptions are logged in HDPowerViewWebTargets
return;
}
}
/**
* 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);
refreshPositionFuture = scheduler.schedule(this::doRefreshShadePosition, 0, TimeUnit.SECONDS);
}
}
@@ -426,7 +494,7 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
*/
protected synchronized void requestRefreshShadeSurvey() {
if (refreshSignalFuture == null) {
refreshSignalFuture = scheduler.schedule(this::doRefreshShadeSignal, REFRESH_DELAY_SEC, TimeUnit.SECONDS);
refreshSignalFuture = scheduler.schedule(this::doRefreshShadeSignal, 0, TimeUnit.SECONDS);
}
}
@@ -435,8 +503,7 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
*/
protected synchronized void requestRefreshShadeBatteryLevel() {
if (refreshBatteryLevelFuture == null) {
refreshBatteryLevelFuture = scheduler.schedule(this::doRefreshShadeBatteryLevel, REFRESH_DELAY_SEC,
TimeUnit.SECONDS);
refreshBatteryLevelFuture = scheduler.schedule(this::doRefreshShadeBatteryLevel, 0, TimeUnit.SECONDS);
}
}
@@ -465,7 +532,6 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
if (webTargets == null) {
throw new HubProcessingException("Web targets not initialized");
}
int shadeId = getShadeId();
Shade shade;
switch (kind) {
case POSITION:
@@ -491,12 +557,17 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
if (Boolean.TRUE.equals(shadeData.timedOut)) {
logger.warn("Shade {} wireless refresh time out", shadeId);
} else if (kind == RefreshKind.POSITION) {
updateShadePositions(shade);
updateHardProperties(shadeData);
}
}
}
} catch (HubProcessingException | NumberFormatException e) {
logger.warn("Unexpected error: {}", e.getMessage());
} catch (HubProcessingException e) {
// ScheduledFutures will be cancelled by dispose(), naturally causing InterruptedException in invoke()
// for any ongoing requests. Logging this would only cause confusion.
if (!isDisposing) {
logger.warn("Unexpected error: {}", e.getMessage());
}
} catch (HubMaintenanceException e) {
// exceptions are logged in HDPowerViewWebTargets
}

View File

@@ -29,6 +29,8 @@ thing-type.config.hdpowerview.shade.id.description = The numeric ID of the Power
channel-type.hdpowerview.battery-voltage.label = Battery Voltage
channel-type.hdpowerview.battery-voltage.description = Battery voltage reported by the shade
channel-type.hdpowerview.shade-calibrate.label = Calibrate
channel-type.hdpowerview.shade-calibrate.description = Perform calibration of the shade
channel-type.hdpowerview.shade-position.label = Position
channel-type.hdpowerview.shade-position.description = The vertical position of the shade
channel-type.hdpowerview.shade-vane.label = Vane

View File

@@ -60,6 +60,7 @@
<description>The secondary vertical position (on top-down/bottom-up shades)</description>
</channel>
<channel id="vane" typeId="shade-vane"/>
<channel id="calibrate" typeId="shade-calibrate"/>
<channel id="lowBattery" typeId="system.low-battery"/>
<channel id="batteryLevel" typeId="system.battery-level"/>
<channel id="batteryVoltage" typeId="battery-voltage"/>
@@ -96,6 +97,12 @@
<description>The opening of the slats in the shade</description>
</channel-type>
<channel-type id="shade-calibrate" advanced="true">
<item-type>Switch</item-type>
<label>Calibrate</label>
<description>Perform calibration of the shade</description>
</channel-type>
<channel-type id="scene-activate">
<item-type>Switch</item-type>
<label>Activate</label>