[hdpowerview] Added support for rear blackout panel (#12098)

* [hdpowerview] refactor enum constant names
* [hdpowerview] add support for blackout shades
* [hdpowerview] unit tests for capabilities 8 & 9
* [hdpowerview] delete no longer valid comment
* [hdpowerview] blackout shade position is never UNDEF
* [hdpowerview] updated read me
* [hdpowerview] refactor unit tests into two classes

Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
This commit is contained in:
Andrew Fiddian-Green
2022-01-25 19:09:14 +00:00
committed by GitHub
parent 8b32095681
commit 8a2d507b21
7 changed files with 388 additions and 207 deletions

View File

@@ -19,9 +19,9 @@ import org.eclipse.jdt.annotation.Nullable;
* Shade coordinate system (a.k.a. position kind), as returned by the HD PowerView hub.
*
* @param NONE a coordinate system that does not refer to any type of physical rail.
* @param PRIMARY_ZERO_IS_CLOSED primary rail, whose coordinate value 0 means shade is closed.
* @param SECONDARY_ZERO_IS_OPEN secondary rail, whose coordinate value 0 means shade is open.
* @param VANE_TILT_COORDS vane/tilt operator, whose coordinate system is for vanes.
* @param PRIMARY_POSITION primary rail, whose coordinate value 0 means shade is closed.
* @param SECONDARY_POSITION secondary rail, whose coordinate value 0 means shade is open.
* @param VANE_TILT_POSITION vane/tilt operator, whose coordinate system is for vanes.
* @param ERROR_UNKNOWN unsupported coordinate system.
*
* @author Andy Lintner - Initial contribution of the original enum called
@@ -77,9 +77,9 @@ public enum CoordinateSystem {
*
*/
NONE,
PRIMARY_ZERO_IS_CLOSED,
SECONDARY_ZERO_IS_OPEN,
VANE_TILT_COORDS,
PRIMARY_POSITION,
SECONDARY_POSITION,
VANE_TILT_POSITION,
ERROR_UNKNOWN;
public static final int MAX_SHADE = 65535;

View File

@@ -77,13 +77,13 @@ public class ShadePosition {
*/
private void setPosition1(Capabilities shadeCapabilities, CoordinateSystem posKindCoords, int percent) {
switch (posKindCoords) {
case PRIMARY_ZERO_IS_CLOSED:
case PRIMARY_POSITION:
/*
* Primary rail of a bottom-up shade, or lower rail of a dual action shade: => INVERTED
*/
if (shadeCapabilities.supportsPrimary() && shadeCapabilities.supportsSecondary()) {
// on dual rail shades constrain percent to not move the lower rail above the upper
State secondary = getState(shadeCapabilities, SECONDARY_ZERO_IS_OPEN);
State secondary = getState(shadeCapabilities, SECONDARY_POSITION);
if (secondary instanceof PercentType) {
int secPercent = ((PercentType) secondary).intValue();
if (percent < secPercent) {
@@ -95,15 +95,20 @@ public class ShadePosition {
position1 = MAX_SHADE - (int) Math.round((double) percent / 100 * MAX_SHADE);
break;
case SECONDARY_ZERO_IS_OPEN:
case SECONDARY_POSITION:
/*
* Secondary, blackout shade a 'Duolite' shade: => INVERTED
* Secondary, upper rail of a dual action shade: => NOT INVERTED
*/
posKind1 = posKindCoords.ordinal();
position1 = (int) Math.round((double) percent / 100 * MAX_SHADE);
if (shadeCapabilities.supportsBlackoutShade()) {
position1 = MAX_SHADE - (int) Math.round((double) percent / 100 * MAX_SHADE);
} else {
position1 = (int) Math.round((double) percent / 100 * MAX_SHADE);
}
break;
case VANE_TILT_COORDS:
case VANE_TILT_POSITION:
/*
* Vane angle of the primary rail of a bottom-up single action shade: => NOT INVERTED
*/
@@ -127,28 +132,38 @@ public class ShadePosition {
*/
private State getPosition1(Capabilities shadeCapabilities, CoordinateSystem posKindCoords) {
switch (posKindCoords) {
case PRIMARY_ZERO_IS_CLOSED:
case PRIMARY_POSITION:
/*
* Primary rail of a bottom-up shade, or lower rail of a dual action shade: => INVERTED
*/
if (posKindCoords.equals(posKind1)) {
return new PercentType(100 - (int) Math.round((double) position1 / MAX_SHADE * 100));
}
if (VANE_TILT_COORDS.equals(posKind1) && shadeCapabilities.supportsTiltOnClosed()) {
if (VANE_TILT_POSITION.equals(posKind1) && shadeCapabilities.supportsTiltOnClosed()) {
return PercentType.HUNDRED;
}
if (SECONDARY_POSITION.equals(posKind1) && shadeCapabilities.supportsBlackoutShade()) {
return PercentType.HUNDRED;
}
break;
case SECONDARY_ZERO_IS_OPEN:
case SECONDARY_POSITION:
/*
* Secondary, blackout shade a 'Duolite' shade: => INVERTED
* Secondary, upper rail of a dual action shade: => NOT INVERTED
*/
if (posKindCoords.equals(posKind1)) {
if (shadeCapabilities.supportsBlackoutShade()) {
return new PercentType(100 - (int) Math.round((double) position1 / MAX_SHADE * 100));
}
return new PercentType((int) Math.round((double) position1 / MAX_SHADE * 100));
}
if (PRIMARY_POSITION.equals(posKind1) && shadeCapabilities.supportsBlackoutShade()) {
return PercentType.ZERO;
}
break;
case VANE_TILT_COORDS:
case VANE_TILT_POSITION:
/*
* Vane angle of the primary rail of a bottom-up single action shade: => NOT INVERTED
*
@@ -164,7 +179,7 @@ public class ShadePosition {
int max = shadeCapabilities.supportsTilt180() ? MAX_SHADE : MAX_VANE;
return new PercentType((int) Math.round((double) Math.min(position1, max) / max * 100));
}
if (PRIMARY_ZERO_IS_CLOSED.equals(posKind1) && shadeCapabilities.supportsTiltOnClosed()) {
if (PRIMARY_POSITION.equals(posKind1) && shadeCapabilities.supportsTiltOnClosed()) {
return position1 != 0 ? UnDefType.UNDEF : PercentType.ZERO;
}
break;
@@ -185,7 +200,7 @@ public class ShadePosition {
*/
private void setPosition2(Capabilities shadeCapabilities, CoordinateSystem posKindCoords, int percent) {
switch (posKindCoords) {
case PRIMARY_ZERO_IS_CLOSED:
case PRIMARY_POSITION:
/*
* Primary rail of a bottom-up shade, or lower rail of a dual action shade: => INVERTED
*/
@@ -193,13 +208,13 @@ public class ShadePosition {
position2 = Integer.valueOf(MAX_SHADE - (int) Math.round((double) percent / 100 * MAX_SHADE));
break;
case SECONDARY_ZERO_IS_OPEN:
case SECONDARY_POSITION:
/*
* Secondary, upper rail of a dual action shade: => NOT INVERTED
*/
if (shadeCapabilities.supportsPrimary() && shadeCapabilities.supportsSecondary()) {
// on dual rail shades constrain percent to not move the upper rail below the lower
State primary = getState(shadeCapabilities, PRIMARY_ZERO_IS_CLOSED);
State primary = getState(shadeCapabilities, PRIMARY_POSITION);
if (primary instanceof PercentType) {
int primaryPercent = ((PercentType) primary).intValue();
if (percent > primaryPercent) {
@@ -211,7 +226,7 @@ public class ShadePosition {
position2 = Integer.valueOf((int) Math.round((double) percent / 100 * MAX_SHADE));
break;
case VANE_TILT_COORDS:
case VANE_TILT_POSITION:
posKind2 = posKindCoords.ordinal();
int max = shadeCapabilities.supportsTilt180() ? MAX_SHADE : MAX_VANE;
position2 = Integer.valueOf((int) Math.round((double) percent / 100 * max));
@@ -239,7 +254,7 @@ public class ShadePosition {
}
switch (posKindCoords) {
case PRIMARY_ZERO_IS_CLOSED:
case PRIMARY_POSITION:
/*
* Primary rail of a bottom-up shade, or lower rail of a dual action shade: => INVERTED
*/
@@ -248,7 +263,7 @@ public class ShadePosition {
}
break;
case SECONDARY_ZERO_IS_OPEN:
case SECONDARY_POSITION:
/*
* Secondary, upper rail of a dual action shade: => NOT INVERTED
*/
@@ -259,12 +274,8 @@ public class ShadePosition {
/*
* Vane angle of the primary rail of a bottom-up single action shade: => NOT INVERTED
*
* note: sometimes the hub may return a value of position1 > MAX_VANE (seems to
* be a bug in the hub) so we avoid an out of range exception via the Math.min()
* function below..
*/
case VANE_TILT_COORDS:
case VANE_TILT_POSITION:
if (posKindCoords.equals(posKind2)) {
int max = shadeCapabilities.supportsTilt180() ? MAX_SHADE : MAX_VANE;
return new PercentType((int) Math.round((double) Math.min(position2.intValue(), max) / max * 100));
@@ -284,7 +295,7 @@ public class ShadePosition {
* @return true if the ShadePosition supports a secondary rail.
*/
public boolean secondaryRailDetected() {
return SECONDARY_ZERO_IS_OPEN.equals(posKind1) || SECONDARY_ZERO_IS_OPEN.equals(posKind2);
return SECONDARY_POSITION.equals(posKind1) || SECONDARY_POSITION.equals(posKind2);
}
/**
@@ -294,8 +305,8 @@ public class ShadePosition {
* @return true if potential support for tilt anywhere functionality was detected.
*/
public boolean tiltAnywhereDetected() {
return ((PRIMARY_ZERO_IS_CLOSED.equals(posKind1)) && (VANE_TILT_COORDS.equals(posKind2))
|| ((PRIMARY_ZERO_IS_CLOSED.equals(posKind2) && (VANE_TILT_COORDS.equals(posKind1)))));
return ((PRIMARY_POSITION.equals(posKind1)) && (VANE_TILT_POSITION.equals(posKind2))
|| ((PRIMARY_POSITION.equals(posKind2) && (VANE_TILT_POSITION.equals(posKind1)))));
}
/**
@@ -310,7 +321,7 @@ public class ShadePosition {
logger.trace("setPosition(): capabilities={}, coords={}, percent={}", shadeCapabilities, posKindCoords,
percent);
// if necessary swap the order of position1 and position2
if (PRIMARY_ZERO_IS_CLOSED.equals(posKind2) && !PRIMARY_ZERO_IS_CLOSED.equals(posKind1)) {
if (PRIMARY_POSITION.equals(posKind2) && !PRIMARY_POSITION.equals(posKind1)) {
final Integer posKind2Temp = posKind2;
final Integer position2Temp = position2;
posKind2 = Integer.valueOf(posKind1);
@@ -327,23 +338,25 @@ public class ShadePosition {
// logic to set either position1 or position2
switch (posKindCoords) {
case PRIMARY_ZERO_IS_CLOSED:
case PRIMARY_POSITION:
if (shadeCapabilities.supportsPrimary()) {
setPosition1(shadeCapabilities, posKindCoords, percent);
}
break;
case SECONDARY_ZERO_IS_OPEN:
case SECONDARY_POSITION:
if (shadeCapabilities.supportsSecondary()) {
if (shadeCapabilities.supportsPrimary()) {
setPosition2(shadeCapabilities, posKindCoords, percent);
} else {
setPosition1(shadeCapabilities, posKindCoords, percent);
}
} else if (shadeCapabilities.supportsBlackoutShade()) {
setPosition1(shadeCapabilities, posKindCoords, percent);
}
break;
case VANE_TILT_COORDS:
case VANE_TILT_POSITION:
if (shadeCapabilities.supportsPrimary()) {
if (shadeCapabilities.supportsTiltOnClosed()) {
setPosition1(shadeCapabilities, posKindCoords, percent);

View File

@@ -48,8 +48,8 @@ public class ShadeCapabilitiesDatabase {
new Capabilities(5) .tiltAnywhere().tilt180() .text("Tilt Only 180°"),
new Capabilities(6).primary() .text("Top Down") .primaryStateInverted(),
new Capabilities(7).primary() .secondary().text("Top Down Bottom Up"),
new Capabilities(8).primary() .text("Duolite Lift"),
new Capabilities(9).primary().tiltAnywhere() .text("Duolite Lift and Tilt 90°"),
new Capabilities(8).primary() .text("Duolite Lift") .withBlackoutShade(),
new Capabilities(9).primary().tiltAnywhere() .text("Duolite Lift and Tilt 90°").withBlackoutShade(),
// @formatter:on
new Capabilities()).stream().collect(Collectors.toMap(Capabilities::getValue, Function.identity()));
@@ -148,12 +148,18 @@ public class ShadeCapabilitiesDatabase {
private boolean supportsSecondary;
private boolean supportsTiltOnClosed;
private boolean supportsTiltAnywhere;
private boolean supportsBlackoutShade;
private boolean primaryStateInverted;
private boolean tilt180Degrees;
public Capabilities() {
}
protected Capabilities withBlackoutShade() {
supportsBlackoutShade = true;
return this;
}
protected Capabilities(int capabilities) {
intValue = capabilities;
}
@@ -249,6 +255,15 @@ public class ShadeCapabilitiesDatabase {
public boolean supportsTilt180() {
return tilt180Degrees;
}
/**
* Check if the Capabilities class instance supports a secondary 'DuoLite' blackout shade.
*
* @return true if the primary shade supports a secondary blackout shade.
*/
public boolean supportsBlackoutShade() {
return supportsBlackoutShade;
}
}
/**

View File

@@ -192,9 +192,9 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
switch (channelId) {
case CHANNEL_SHADE_POSITION:
if (command instanceof PercentType) {
moveShade(PRIMARY_ZERO_IS_CLOSED, ((PercentType) command).intValue(), webTargets, shadeId);
moveShade(PRIMARY_POSITION, ((PercentType) command).intValue(), webTargets, shadeId);
} else if (command instanceof UpDownType) {
moveShade(PRIMARY_ZERO_IS_CLOSED, UpDownType.UP == command ? 0 : 100, webTargets, shadeId);
moveShade(PRIMARY_POSITION, UpDownType.UP == command ? 0 : 100, webTargets, shadeId);
} else if (command instanceof StopMoveType) {
if (StopMoveType.STOP == command) {
stopShade(webTargets, shadeId);
@@ -206,17 +206,17 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
case CHANNEL_SHADE_VANE:
if (command instanceof PercentType) {
moveShade(VANE_TILT_COORDS, ((PercentType) command).intValue(), webTargets, shadeId);
moveShade(VANE_TILT_POSITION, ((PercentType) command).intValue(), webTargets, shadeId);
} else if (command instanceof OnOffType) {
moveShade(VANE_TILT_COORDS, OnOffType.ON == command ? 100 : 0, webTargets, shadeId);
moveShade(VANE_TILT_POSITION, 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(), webTargets, shadeId);
moveShade(SECONDARY_POSITION, ((PercentType) command).intValue(), webTargets, shadeId);
} else if (command instanceof UpDownType) {
moveShade(SECONDARY_ZERO_IS_OPEN, UpDownType.UP == command ? 0 : 100, webTargets, shadeId);
moveShade(SECONDARY_POSITION, UpDownType.UP == command ? 0 : 100, webTargets, shadeId);
} else if (command instanceof StopMoveType) {
if (StopMoveType.STOP == command) {
stopShade(webTargets, shadeId);
@@ -392,9 +392,9 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
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));
updateState(CHANNEL_SHADE_POSITION, shadePos.getState(capabilities, PRIMARY_POSITION));
updateState(CHANNEL_SHADE_VANE, shadePos.getState(capabilities, VANE_TILT_POSITION));
updateState(CHANNEL_SHADE_SECONDARY_POSITION, shadePos.getState(capabilities, SECONDARY_POSITION));
}
private void updateBatteryLevelStates(int batteryStatus) {