[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) {

View File

@@ -57,8 +57,6 @@ public class HDPowerViewJUnitTests {
private static final Pattern VALID_IP_V4_ADDRESS = Pattern
.compile("\\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.|$)){4}\\b");
private final ShadeCapabilitiesDatabase db = new ShadeCapabilitiesDatabase();
/*
* load a test JSON string from a file.
*/
@@ -190,16 +188,17 @@ public class HDPowerViewJUnitTests {
assertNotNull(capabilitiesValue);
if (positions != null && capabilitiesValue != null) {
Capabilities capabilities = db.getCapabilities(capabilitiesValue.intValue());
Capabilities capabilities = new ShadeCapabilitiesDatabase()
.getCapabilities(capabilitiesValue.intValue());
State pos = positions.getState(capabilities, PRIMARY_ZERO_IS_CLOSED);
State pos = positions.getState(capabilities, PRIMARY_POSITION);
assertEquals(PercentType.class, pos.getClass());
int position = ((PercentType) pos).intValue();
position = position + ((position <= 10) ? 5 : -5);
ShadePosition targetPosition = new ShadePosition().setPosition(capabilities,
PRIMARY_ZERO_IS_CLOSED, position);
ShadePosition targetPosition = new ShadePosition().setPosition(capabilities, PRIMARY_POSITION,
position);
assertNotNull(targetPosition);
if (allowShadeMovementCommands) {
@@ -209,8 +208,8 @@ public class HDPowerViewJUnitTests {
ShadePosition actualPosition = newData.positions;
assertNotNull(actualPosition);
if (actualPosition != null) {
assertEquals(targetPosition.getState(capabilities, PRIMARY_ZERO_IS_CLOSED),
actualPosition.getState(capabilities, PRIMARY_ZERO_IS_CLOSED));
assertEquals(targetPosition.getState(capabilities, PRIMARY_POSITION),
actualPosition.getState(capabilities, PRIMARY_POSITION));
}
}
}
@@ -250,73 +249,6 @@ public class HDPowerViewJUnitTests {
}
}
/**
* Test parsing of ShadePosition (shade fully up).
*
*/
@Test
public void testShadePositionParsingFullyUp() {
Capabilities capabilities = db.getCapabilities(0);
ShadePosition test = new ShadePosition().setPosition(capabilities, PRIMARY_ZERO_IS_CLOSED, 0);
assertNotNull(test);
State pos = test.getState(capabilities, PRIMARY_ZERO_IS_CLOSED);
assertEquals(PercentType.class, pos.getClass());
assertEquals(0, ((PercentType) pos).intValue());
pos = test.getState(capabilities, VANE_TILT_COORDS);
assertTrue(UnDefType.UNDEF.equals(pos));
}
/**
* Test parsing of ShadePosition (shade fully down (method 1)).
*
*/
@Test
public void testShadePositionParsingShadeFullyDown1() {
Capabilities capabilities = db.getCapabilities(0);
ShadePosition test = new ShadePosition().setPosition(capabilities, PRIMARY_ZERO_IS_CLOSED, 100);
assertNotNull(test);
State pos = test.getState(capabilities, PRIMARY_ZERO_IS_CLOSED);
assertEquals(PercentType.class, pos.getClass());
assertEquals(100, ((PercentType) pos).intValue());
pos = test.getState(capabilities, VANE_TILT_COORDS);
assertEquals(PercentType.class, pos.getClass());
assertEquals(0, ((PercentType) pos).intValue());
}
/**
* Test parsing of ShadePosition (shade fully down (method 2)).
*
*/
@Test
public void testShadePositionParsingShadeFullyDown2() {
Capabilities capabilities = db.getCapabilities(0);
ShadePosition test = new ShadePosition().setPosition(capabilities, VANE_TILT_COORDS, 0);
assertNotNull(test);
State pos = test.getState(capabilities, PRIMARY_ZERO_IS_CLOSED);
assertEquals(PercentType.class, pos.getClass());
assertEquals(100, ((PercentType) pos).intValue());
pos = test.getState(capabilities, VANE_TILT_COORDS);
assertEquals(PercentType.class, pos.getClass());
assertEquals(0, ((PercentType) pos).intValue());
}
/**
* Test parsing of ShadePosition (shade fully down (method 2) and vane fully open).
*
*/
@Test
public void testShadePositionParsingShadeFullyDownVaneOpen() {
Capabilities capabilities = db.getCapabilities(0);
ShadePosition test = new ShadePosition().setPosition(capabilities, VANE_TILT_COORDS, 100);
assertNotNull(test);
State pos = test.getState(capabilities, PRIMARY_ZERO_IS_CLOSED);
assertEquals(PercentType.class, pos.getClass());
assertEquals(100, ((PercentType) pos).intValue());
pos = test.getState(capabilities, VANE_TILT_COORDS);
assertEquals(PercentType.class, pos.getClass());
assertEquals(100, ((PercentType) pos).intValue());
}
/**
* Test generic JSON shades response.
*/
@@ -409,18 +341,18 @@ public class HDPowerViewJUnitTests {
assertNotNull(capabilitiesValue);
if (capabilitiesValue != null) {
assertEquals(7, capabilitiesValue.intValue());
ShadeCapabilitiesDatabase db = new ShadeCapabilitiesDatabase();
Capabilities capabilities = db.getCapabilities(capabilitiesValue);
State pos = shadePos.getState(capabilities, PRIMARY_ZERO_IS_CLOSED);
State pos = shadePos.getState(capabilities, PRIMARY_POSITION);
assertEquals(PercentType.class, pos.getClass());
assertEquals(59, ((PercentType) pos).intValue());
pos = shadePos.getState(capabilities, SECONDARY_ZERO_IS_OPEN);
pos = shadePos.getState(capabilities, SECONDARY_POSITION);
assertEquals(PercentType.class, pos.getClass());
assertEquals(35, ((PercentType) pos).intValue());
pos = shadePos.getState(capabilities, VANE_TILT_COORDS);
pos = shadePos.getState(capabilities, VANE_TILT_POSITION);
assertEquals(UnDefType.class, pos.getClass());
assertEquals(3, shadeData.batteryStatus);
@@ -442,18 +374,18 @@ public class HDPowerViewJUnitTests {
assertNotNull(shadePosition);
if (shadePosition != null) {
// ==== position2 ====
State position2Old = shadePosition.getState(capabilities, SECONDARY_ZERO_IS_OPEN);
shadePosition.setPosition(capabilities, PRIMARY_ZERO_IS_CLOSED, 99);
State position2New = shadePosition.getState(capabilities, SECONDARY_ZERO_IS_OPEN);
State position2Old = shadePosition.getState(capabilities, SECONDARY_POSITION);
shadePosition.setPosition(capabilities, PRIMARY_POSITION, 99);
State position2New = shadePosition.getState(capabilities, SECONDARY_POSITION);
assertEquals(PercentType.class, position2Old.getClass());
assertEquals(PercentType.class, position2New.getClass());
assertEquals(((PercentType) position2Old).intValue(),
((PercentType) position2New).intValue());
// ==== position2 ====
State position1Old = shadePosition.getState(capabilities, PRIMARY_ZERO_IS_CLOSED);
shadePosition.setPosition(capabilities, SECONDARY_ZERO_IS_OPEN, 99);
State position1New = shadePosition.getState(capabilities, PRIMARY_ZERO_IS_CLOSED);
State position1Old = shadePosition.getState(capabilities, PRIMARY_POSITION);
shadePosition.setPosition(capabilities, SECONDARY_POSITION, 99);
State position1New = shadePosition.getState(capabilities, PRIMARY_POSITION);
assertEquals(PercentType.class, position1Old.getClass());
assertEquals(PercentType.class, position1New.getClass());
assertEquals(((PercentType) position1Old).intValue(),
@@ -464,80 +396,4 @@ public class HDPowerViewJUnitTests {
}
}
}
/**
* General tests of the database of known types.
*/
@Test
public void testKnownTypesDatabase() {
assertTrue(db.isTypeInDatabase(4));
assertTrue(db.isCapabilitiesInDatabase(0));
assertTrue(db.getCapabilities(6).isPrimaryStateInverted());
assertTrue(db.getCapabilities(7).supportsSecondary());
assertEquals(db.getType(4).getCapabilities(), 0);
assertEquals(db.getType(-1).getCapabilities(), -1);
assertFalse(db.isTypeInDatabase(99));
assertFalse(db.isCapabilitiesInDatabase(99));
assertFalse(db.getCapabilities(0).isPrimaryStateInverted());
assertFalse(db.getCapabilities(-1).isPrimaryStateInverted());
assertFalse(db.getCapabilities(99).isPrimaryStateInverted());
assertFalse(db.getCapabilities(0).supportsSecondary());
assertFalse(db.getCapabilities(-1).supportsSecondary());
assertFalse(db.getCapabilities(99).supportsSecondary());
}
/**
* On dual rail shades, it should not be possible to drive the upper rail below the lower rail, or vice-versa. So
* the binding code applies constraints on setting such positions. This test checks that the constraint code is
* working.
*/
@Test
public void testDualRailConstraints() {
ShadePosition shade = new ShadePosition();
Capabilities caps = db.getCapabilities(7);
// ==== OK !! primary at bottom, secondary at top ====
shade.setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 100).setPosition(caps, SECONDARY_ZERO_IS_OPEN, 0);
assertEquals(PercentType.HUNDRED, shade.getState(caps, PRIMARY_ZERO_IS_CLOSED));
assertEquals(PercentType.ZERO, shade.getState(caps, SECONDARY_ZERO_IS_OPEN));
// ==== OK !! primary at middle, secondary at top ====
shade.setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 50).setPosition(caps, SECONDARY_ZERO_IS_OPEN, 0);
assertEquals(new PercentType(50), shade.getState(caps, PRIMARY_ZERO_IS_CLOSED));
assertEquals(PercentType.ZERO, shade.getState(caps, SECONDARY_ZERO_IS_OPEN));
// ==== OK !! primary at middle, secondary at middle ====
shade.setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 50).setPosition(caps, SECONDARY_ZERO_IS_OPEN, 50);
assertEquals(new PercentType(50), shade.getState(caps, PRIMARY_ZERO_IS_CLOSED));
assertEquals(new PercentType(50), shade.getState(caps, SECONDARY_ZERO_IS_OPEN));
// ==== IMPOSSIBLE !! secondary at middle, primary above => test the constraining code ====
shade.setPosition(caps, SECONDARY_ZERO_IS_OPEN, 0).setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 100);
shade.setPosition(caps, SECONDARY_ZERO_IS_OPEN, 40).setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 25);
assertEquals(new PercentType(40), shade.getState(caps, SECONDARY_ZERO_IS_OPEN));
assertEquals(new PercentType(40), shade.getState(caps, PRIMARY_ZERO_IS_CLOSED));
// ==== OK !! secondary at middle, primary below ====
shade.setPosition(caps, SECONDARY_ZERO_IS_OPEN, 0).setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 100);
shade.setPosition(caps, SECONDARY_ZERO_IS_OPEN, 50).setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 75);
assertEquals(new PercentType(50), shade.getState(caps, SECONDARY_ZERO_IS_OPEN));
assertEquals(new PercentType(75), shade.getState(caps, PRIMARY_ZERO_IS_CLOSED));
// ==== IMPOSSIBLE !! primary at middle, secondary below => test the constraining code ====
shade.setPosition(caps, SECONDARY_ZERO_IS_OPEN, 0).setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 100);
shade.setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 60).setPosition(caps, SECONDARY_ZERO_IS_OPEN, 75);
assertEquals(new PercentType(60), shade.getState(caps, PRIMARY_ZERO_IS_CLOSED));
assertEquals(new PercentType(60), shade.getState(caps, SECONDARY_ZERO_IS_OPEN));
// ==== OK !! primary at middle, secondary above ====
shade.setPosition(caps, SECONDARY_ZERO_IS_OPEN, 0).setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 100);
shade.setPosition(caps, PRIMARY_ZERO_IS_CLOSED, 60).setPosition(caps, SECONDARY_ZERO_IS_OPEN, 25);
assertEquals(new PercentType(60), shade.getState(caps, PRIMARY_ZERO_IS_CLOSED));
assertEquals(new PercentType(25), shade.getState(caps, SECONDARY_ZERO_IS_OPEN));
}
}

View File

@@ -0,0 +1,292 @@
/**
* 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;
import static org.junit.jupiter.api.Assertions.*;
import static org.openhab.binding.hdpowerview.internal.api.CoordinateSystem.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.hdpowerview.internal.api.ShadePosition;
import org.openhab.binding.hdpowerview.internal.database.ShadeCapabilitiesDatabase;
import org.openhab.binding.hdpowerview.internal.database.ShadeCapabilitiesDatabase.Capabilities;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
/**
* Unit tests for Shade Position setting and getting.
*
* @author Andrew Fiddian-Green - Initial contribution
*/
@NonNullByDefault
public class ShadePositionTest {
private final ShadeCapabilitiesDatabase db = new ShadeCapabilitiesDatabase();
/**
* General tests of the database of known types.
*/
@Test
public void testKnownTypesDatabase() {
assertTrue(db.isTypeInDatabase(4));
assertTrue(db.isCapabilitiesInDatabase(0));
assertTrue(db.getCapabilities(0).supportsPrimary());
assertTrue(db.getCapabilities(0).supportsTiltOnClosed());
assertTrue(db.getCapabilities(1).supportsTiltAnywhere());
assertTrue(db.getCapabilities(2).supportsTilt180());
assertTrue(db.getCapabilities(3).supportsTiltOnClosed());
assertTrue(db.getCapabilities(4).supportsTilt180());
assertTrue(db.getCapabilities(5).supportsTilt180());
assertFalse(db.getCapabilities(5).supportsPrimary());
assertTrue(db.getCapabilities(6).isPrimaryStateInverted());
assertTrue(db.getCapabilities(7).supportsSecondary());
assertTrue(db.getCapabilities(8).supportsBlackoutShade());
assertTrue(db.getCapabilities(9).supportsBlackoutShade());
assertEquals(db.getType(4).getCapabilities(), 0);
assertEquals(db.getType(-1).getCapabilities(), -1);
assertFalse(db.isTypeInDatabase(99));
assertFalse(db.isCapabilitiesInDatabase(99));
assertFalse(db.getCapabilities(0).isPrimaryStateInverted());
assertFalse(db.getCapabilities(-1).isPrimaryStateInverted());
assertFalse(db.getCapabilities(99).isPrimaryStateInverted());
assertFalse(db.getCapabilities(0).supportsSecondary());
assertFalse(db.getCapabilities(-1).supportsSecondary());
assertFalse(db.getCapabilities(99).supportsSecondary());
}
/**
* Helper method; test if shade position is a PercentType and that its value is correct.
*
* @param position the shade position
* @param value the test value to compare with
*/
private void assertShadePosition(State position, int value) {
assertEquals(PercentType.class, position.getClass());
assertEquals(value, ((PercentType) position).intValue());
}
/**
* Test parsing of ShadePosition (shade fully up).
*
*/
@Test
public void testShadePositionParsingFullyUp() {
Capabilities capabilities = db.getCapabilities(0);
ShadePosition test = new ShadePosition().setPosition(capabilities, PRIMARY_POSITION, 0);
assertNotNull(test);
State pos = test.getState(capabilities, PRIMARY_POSITION);
assertShadePosition(pos, 0);
pos = test.getState(capabilities, VANE_TILT_POSITION);
assertTrue(UnDefType.UNDEF.equals(pos));
}
/**
* Test parsing of ShadePosition (shade fully down (method 1)).
*
*/
@Test
public void testShadePositionParsingShadeFullyDown1() {
Capabilities capabilities = db.getCapabilities(0);
ShadePosition test = new ShadePosition().setPosition(capabilities, PRIMARY_POSITION, 100);
assertNotNull(test);
assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), 0);
}
/**
* Test parsing of ShadePosition (shade fully down (method 2)).
*
*/
@Test
public void testShadePositionParsingShadeFullyDown2() {
Capabilities capabilities = db.getCapabilities(0);
ShadePosition test = new ShadePosition().setPosition(capabilities, VANE_TILT_POSITION, 0);
assertNotNull(test);
assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), 0);
}
/**
* Test parsing of ShadePosition (shade fully down (method 2) and vane fully open).
*
*/
@Test
public void testShadePositionParsingShadeFullyDownVaneOpen() {
Capabilities capabilities = db.getCapabilities(0);
ShadePosition test = new ShadePosition().setPosition(capabilities, VANE_TILT_POSITION, 100);
assertNotNull(test);
assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), 100);
}
/**
* On dual rail shades, it should not be possible to drive the upper rail below the lower rail, or vice-versa. So
* the binding code applies constraints on setting such positions. This test checks that the constraint code is
* working.
*/
@Test
public void testDualRailConstraints() {
Capabilities capabilities = db.getCapabilities(7);
ShadePosition test = new ShadePosition();
// ==== OK !! primary at bottom, secondary at top ====
test.setPosition(capabilities, PRIMARY_POSITION, 100).setPosition(capabilities, SECONDARY_POSITION, 0);
assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 0);
// ==== OK !! primary at middle, secondary at top ====
test.setPosition(capabilities, PRIMARY_POSITION, 50).setPosition(capabilities, SECONDARY_POSITION, 0);
assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 50);
assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 0);
// ==== OK !! primary at middle, secondary at middle ====
test.setPosition(capabilities, PRIMARY_POSITION, 50).setPosition(capabilities, SECONDARY_POSITION, 50);
assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 50);
assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 50);
// ==== IMPOSSIBLE !! secondary at middle, primary above => test the constraining code ====
test.setPosition(capabilities, SECONDARY_POSITION, 0).setPosition(capabilities, PRIMARY_POSITION, 100);
test.setPosition(capabilities, SECONDARY_POSITION, 40).setPosition(capabilities, PRIMARY_POSITION, 25);
assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 40);
assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 40);
// ==== OK !! secondary at middle, primary below ====
test.setPosition(capabilities, SECONDARY_POSITION, 0).setPosition(capabilities, PRIMARY_POSITION, 100);
test.setPosition(capabilities, SECONDARY_POSITION, 50).setPosition(capabilities, PRIMARY_POSITION, 75);
assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 75);
assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 50);
// ==== IMPOSSIBLE !! primary at middle, secondary below => test the constraining code ====
test.setPosition(capabilities, SECONDARY_POSITION, 0).setPosition(capabilities, PRIMARY_POSITION, 100);
test.setPosition(capabilities, PRIMARY_POSITION, 60).setPosition(capabilities, SECONDARY_POSITION, 75);
assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 60);
assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 60);
// ==== OK !! primary at middle, secondary above ====
test.setPosition(capabilities, SECONDARY_POSITION, 0).setPosition(capabilities, PRIMARY_POSITION, 100);
test.setPosition(capabilities, PRIMARY_POSITION, 60).setPosition(capabilities, SECONDARY_POSITION, 25);
assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 60);
assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 25);
}
/**
* Test parsing of DuoLite shades having a secondary blackout shade.
*
*/
@Test
public void testDuoliteShadePositionParsing() {
// blackout shades have capabilities 8
Capabilities capabilities = db.getCapabilities(8);
ShadePosition test;
// both shades up
test = new ShadePosition().setPosition(capabilities, PRIMARY_POSITION, 0);
assertNotNull(test);
assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 0);
assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 0);
// front shade 50% down
test = new ShadePosition().setPosition(capabilities, PRIMARY_POSITION, 50);
assertNotNull(test);
assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 50);
assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 0);
// front shade 100% down, back shade 0% down
test = new ShadePosition().setPosition(capabilities, PRIMARY_POSITION, 100);
assertNotNull(test);
assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 0);
// front shade 100% down, back shade 0% down (ALTERNATE)
test = new ShadePosition().setPosition(capabilities, SECONDARY_POSITION, 0);
assertNotNull(test);
assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 0);
// front shade 100% down, back shade 50% down
test = new ShadePosition().setPosition(capabilities, SECONDARY_POSITION, 50);
assertNotNull(test);
assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 50);
// front shade 100% down, back shade 100% down
test = new ShadePosition().setPosition(capabilities, SECONDARY_POSITION, 100);
assertNotNull(test);
assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 100);
}
/**
* Test parsing of DuoLite shades having both a secondary blackout shade, and tilt anywhere functionality.
*
*/
@Test
public void testDuoliteTiltShadePositionParsing() {
// blackout shades with tilt have capabilities 9
Capabilities capabilities = db.getCapabilities(9);
ShadePosition test;
// both shades up, tilt 0%
test = new ShadePosition().setPosition(capabilities, PRIMARY_POSITION, 0).setPosition(capabilities,
VANE_TILT_POSITION, 0);
assertNotNull(test);
assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 0);
assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 0);
// front shade 50% down, tilt 30%
test = new ShadePosition().setPosition(capabilities, PRIMARY_POSITION, 50).setPosition(capabilities,
VANE_TILT_POSITION, 30);
assertNotNull(test);
assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 50);
assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 0);
assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), 30);
// front shade 100% down, back shade 0% down, tilt 30%
test = new ShadePosition().setPosition(capabilities, PRIMARY_POSITION, 100).setPosition(capabilities,
VANE_TILT_POSITION, 30);
assertNotNull(test);
assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 0);
assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), 30);
// front shade 100% down, back shade 0% down, tilt 30% (ALTERNATE)
test = new ShadePosition().setPosition(capabilities, SECONDARY_POSITION, 0).setPosition(capabilities,
VANE_TILT_POSITION, 30);
assertNotNull(test);
assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 0);
assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), 30);
// front shade 100% down, back shade 50% down, tilt 30%
test = new ShadePosition().setPosition(capabilities, SECONDARY_POSITION, 50).setPosition(capabilities,
VANE_TILT_POSITION, 30);
assertNotNull(test);
assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 50);
assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), 30);
// front shade 100% down, back shade 100% down, tilt 70%
test = new ShadePosition().setPosition(capabilities, SECONDARY_POSITION, 100).setPosition(capabilities,
VANE_TILT_POSITION, 70);
assertNotNull(test);
assertShadePosition(test.getState(capabilities, PRIMARY_POSITION), 100);
assertShadePosition(test.getState(capabilities, SECONDARY_POSITION), 100);
assertShadePosition(test.getState(capabilities, VANE_TILT_POSITION), 70);
}
}