[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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 388 additions and 207 deletions

View File

@ -111,7 +111,7 @@ The `position` and `secondary` channels are Rollershutter types.
For vertical shades, the binding maps the vertical position of the "rail" to the Rollershutter ▲ / ▼ commands, and its respective percent value.
And for horizontal shades, it maps the horizontal position of the "truck" to the Rollershutter ▲ / ▼ commands, and its respective percent value.
Depending on whether the shade is a top-down, bottom-up, left-right, right-left, or dual action shade, the `OPEN` and `CLOSED` position of the shades may differ from the ▲ / ▼ commands follows..
Depending on whether the shade is a top-down, bottom-up, left-right, right-left, dual action shade, or, a shade with a secondary blackout panel, the `OPEN` and `CLOSED` position of the shades may differ from the ▲ / ▼ commands follows..
| Type of Shade | Channel | Rollershutter Command | Motion direction | Shade State | Percent | Pebble Remote Button |
|-----------------------------|-------------------|-----------------------|------------------|----------------|-------------------|----------------------|
@ -127,6 +127,8 @@ Depending on whether the shade is a top-down, bottom-up, left-right, right-left,
| | | ▼ | Down | `CLOSED` | 100% | ▼ |
| Dual action<br>(upper rail) | ***`secondary`*** | ▲ | Up | ***`CLOSED`*** | 0%<sup>1)</sup> | ![](doc/right.png) |
| | | ▼ | Down | ***`OPEN`*** | 100%<sup>1)</sup> | ![](doc/left.png) |
| Blackout panel ('DuoLite') | ***`secondary`*** | ▲ | Up | `OPEN` | 0% | ▲ |
| | | ▼ | Down | `CLOSED` | 100% | ▼ |
***<sup>1)</sup> BUG NOTE***: In openHAB versions v3.1.x and earlier, there was a bug in the handling of the position percent value of the `secondary` shade.
Although the RollerShutter Up/Down commands functioned properly as described in the table above, the percent state values (e.g. displayed on a slider control), did not.
@ -159,6 +161,9 @@ On dual action shades, the top rail cannot move below the bottom rail, nor can t
So the value of `secondary` is constrained by the prior value of `position`.
And the value of `position` is constrained by the prior value of `secondary`.
On shades with a secondary blackout panel 'DuoLite', the secondary blackout panel cannot be moved unless the main shade panel is already down.
In this case, the position of the secondary blackout panel is reported as 0%.
## Refreshing the PowerView Hub Cache
The hub maintains a cache of the last known state of its shades, and this binding delivers those values.

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);
}
}