Skip to content

Commit

Permalink
[hdpowerview] Added support for rear blackout panel (openhab#12098)
Browse files Browse the repository at this point in the history
* [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 <[email protected]>
  • Loading branch information
andrewfg authored and andan67 committed Nov 5, 2022
1 parent 3568d41 commit c3a3e23
Show file tree
Hide file tree
Showing 7 changed files with 388 additions and 207 deletions.
7 changes: 6 additions & 1 deletion bundles/org.openhab.binding.hdpowerview/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
|-----------------------------|-------------------|-----------------------|------------------|----------------|-------------------|----------------------|
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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
*/
Expand All @@ -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
*
Expand All @@ -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;
Expand All @@ -185,21 +200,21 @@ 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
*/
posKind2 = posKindCoords.ordinal();
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) {
Expand All @@ -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));
Expand Down Expand Up @@ -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
*/
Expand All @@ -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
*/
Expand All @@ -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));
Expand All @@ -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);
}

/**
Expand All @@ -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)))));
}

/**
Expand All @@ -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);
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()));

Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
}

/**
Expand Down
Loading

0 comments on commit c3a3e23

Please sign in to comment.