diff --git a/bundles/org.openhab.binding.hdpowerview/README.md b/bundles/org.openhab.binding.hdpowerview/README.md index c777915c4681e..694e812983dab 100644 --- a/bundles/org.openhab.binding.hdpowerview/README.md +++ b/bundles/org.openhab.binding.hdpowerview/README.md @@ -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
(upper rail) | ***`secondary`*** | ▲ | Up | ***`CLOSED`*** | 0%1) | ![](doc/right.png) | | | | ▼ | Down | ***`OPEN`*** | 100%1) | ![](doc/left.png) | +| Blackout panel ('DuoLite') | ***`secondary`*** | ▲ | Up | `OPEN` | 0% | ▲ | +| | | ▼ | Down | `CLOSED` | 100% | ▼ | ***1) 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. diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/CoordinateSystem.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/CoordinateSystem.java index 46db0371a9998..236e468947820 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/CoordinateSystem.java +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/CoordinateSystem.java @@ -19,9 +19,9 @@ * 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; diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/ShadePosition.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/ShadePosition.java index 0c32427e4a61e..1c397d60bbac4 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/ShadePosition.java +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/api/ShadePosition.java @@ -77,13 +77,13 @@ public State getState(Capabilities shadeCapabilities, CoordinateSystem posKindCo */ 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 @@ private void setPosition1(Capabilities shadeCapabilities, CoordinateSystem posKi 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 @@ private void setPosition1(Capabilities shadeCapabilities, CoordinateSystem posKi */ 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 @@ private State getPosition1(Capabilities shadeCapabilities, CoordinateSystem posK 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 @@ private State getPosition1(Capabilities shadeCapabilities, CoordinateSystem posK */ 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 @@ private void setPosition2(Capabilities shadeCapabilities, CoordinateSystem posKi 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 @@ private void setPosition2(Capabilities shadeCapabilities, CoordinateSystem posKi 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 @@ private State getPosition2(Capabilities shadeCapabilities, CoordinateSystem posK } 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 @@ private State getPosition2(Capabilities shadeCapabilities, CoordinateSystem posK } break; - case SECONDARY_ZERO_IS_OPEN: + case SECONDARY_POSITION: /* * Secondary, upper rail of a dual action shade: => NOT INVERTED */ @@ -259,12 +274,8 @@ private State getPosition2(Capabilities shadeCapabilities, CoordinateSystem posK /* * 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 @@ private State getPosition2(Capabilities shadeCapabilities, CoordinateSystem posK * @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 boolean secondaryRailDetected() { * @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 ShadePosition setPosition(Capabilities shadeCapabilities, CoordinateSyste 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 ShadePosition setPosition(Capabilities shadeCapabilities, CoordinateSyste // 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); diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/database/ShadeCapabilitiesDatabase.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/database/ShadeCapabilitiesDatabase.java index 1fcb7a529b87e..a1f665c1aee5c 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/database/ShadeCapabilitiesDatabase.java +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/database/ShadeCapabilitiesDatabase.java @@ -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 static class Capabilities extends Base { 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 boolean supportsTiltOnClosed() { 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; + } } /** diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewShadeHandler.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewShadeHandler.java index 30ab73e700856..428cb5f895079 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewShadeHandler.java +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewShadeHandler.java @@ -192,9 +192,9 @@ private void handleShadeCommand(String channelId, Command command, HDPowerViewWe 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 @@ private void handleShadeCommand(String channelId, Command command, HDPowerViewWe 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 @@ private void updatePositionStates(ShadePosition shadePos) { 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) { diff --git a/bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/HDPowerViewJUnitTests.java b/bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/HDPowerViewJUnitTests.java index 229468ecb53d8..883124450d0cb 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/HDPowerViewJUnitTests.java +++ b/bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/HDPowerViewJUnitTests.java @@ -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 void testOnlineCommunication() { 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 void testOnlineCommunication() { 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 void testOnlineCommunication() { } } - /** - * 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 void duetteTopDownBottomUpShadeIsParsedCorrectly() throws JsonParseExcept 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 void duetteTopDownBottomUpShadeIsParsedCorrectly() throws JsonParseExcept 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 void duetteTopDownBottomUpShadeIsParsedCorrectly() throws JsonParseExcept } } } - - /** - * 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)); - } } diff --git a/bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/ShadePositionTest.java b/bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/ShadePositionTest.java new file mode 100644 index 0000000000000..818dc4a153f83 --- /dev/null +++ b/bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/ShadePositionTest.java @@ -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); + } +}