[hdpowerview] Fix secondary position bug. Add shade database and properties. (#11698)
Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
This commit is contained in:
committed by
GitHub
parent
0988c040a6
commit
d45bcdb7aa
@@ -54,6 +54,11 @@ public class HDPowerViewBindingConstants {
|
||||
public static final String CHANNELTYPE_SCENE_GROUP_ACTIVATE = "scene-group-activate";
|
||||
public static final String CHANNELTYPE_AUTOMATION_ENABLED = "automation-enabled";
|
||||
|
||||
public static final String PROPERTY_SHADE_TYPE = "type";
|
||||
public static final String PROPERTY_SHADE_CAPABILITIES = "capabilities";
|
||||
public static final String PROPERTY_SECONDARY_RAIL_DETECTED = "secondaryRailDetected";
|
||||
public static final String PROPERTY_TILT_ANYWHERE_DETECTED = "tiltAnywhereDetected";
|
||||
|
||||
public static final List<String> NETBIOS_NAMES = Arrays.asList("PDBU-Hub3.0", "PowerView-Hub");
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = new HashSet<>();
|
||||
|
||||
@@ -97,6 +97,11 @@ public class HDPowerViewWebTargets {
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("?%s=%s", key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -247,7 +252,11 @@ public class HDPowerViewWebTargets {
|
||||
private synchronized String invoke(HttpMethod method, String url, @Nullable Query query,
|
||||
@Nullable String jsonCommand) throws HubMaintenanceException, HubProcessingException {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("API command {} {}", method, url);
|
||||
if (query != null) {
|
||||
logger.trace("API command {} {}{}", method, url, query);
|
||||
} else {
|
||||
logger.trace("API command {} {}", method, url);
|
||||
}
|
||||
if (jsonCommand != null) {
|
||||
logger.trace("JSON command = {}", jsonCommand);
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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.internal.api;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Actuator class; all shades have a PRIMARY class actuator, plus double action
|
||||
* shades also have a SECONDARY class actuator
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum ActuatorClass {
|
||||
PRIMARY_ACTUATOR,
|
||||
SECONDARY_ACTUATOR;
|
||||
}
|
||||
@@ -13,18 +13,20 @@
|
||||
package org.openhab.binding.hdpowerview.internal.api;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Shade coordinate system, as returned by the HD PowerView hub
|
||||
*
|
||||
* @param ZERO_IS_CLOSED coordinate value 0 means shade is closed
|
||||
* @param ZERO_IS_OPEN coordinate value 0 means shade is open
|
||||
* @param VANE_COORDS coordinate system for vanes
|
||||
* @param ERROR_UNKNOWN unsupported coordinate system
|
||||
* 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 ERROR_UNKNOWN unsupported coordinate system.
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution of the original enum called
|
||||
* ShadePositionKind
|
||||
*
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Rewritten as a new enum called
|
||||
* CoordinateSystem to support secondary rail positions and be more
|
||||
* explicit on coordinate directions and ranges
|
||||
@@ -41,71 +43,69 @@ public enum CoordinateSystem {
|
||||
* like the top-down and the bottom-up, it operates in two where the top
|
||||
* (middle) rail closed value is 0 and the bottom (primary) rail closed position
|
||||
* is also 0 and fully open for both is 65535
|
||||
*
|
||||
*
|
||||
* The position element can take on multiple states depending on the family of
|
||||
* shade under control.
|
||||
*
|
||||
* The ranges of position integer values are
|
||||
* The ranges of position integer values are
|
||||
* shades: 0..65535
|
||||
* vanes: 0..32767
|
||||
*
|
||||
* Shade fully up: (top-down: open, bottom-up: closed)
|
||||
* posKind: 1 {ZERO_IS_CLOSED}
|
||||
*
|
||||
* Shade fully up: (top-down: open, bottom-up: closed)
|
||||
* posKind: 1 {ZERO_IS_CLOSED}
|
||||
* position: 65535
|
||||
*
|
||||
* Shade and vane fully down: (top-down: closed, bottom-up: open)
|
||||
* Shade and vane fully down: (top-down: closed, bottom-up: open)
|
||||
* posKind: 1 {ZERO_IS_CLOSED}
|
||||
* position1: 0
|
||||
*
|
||||
*
|
||||
* ALTERNATE: Shade and vane fully down: (top-down: closed, bottom-up: open)
|
||||
* posKind: 3 {VANE_COORDS}
|
||||
* position: 0
|
||||
*
|
||||
* Shade fully down (closed) and vane fully up (open):
|
||||
* Shade fully down (closed) and vane fully up (open):
|
||||
* posKind: 3 {VANE_COORDS}
|
||||
* position: 32767
|
||||
*
|
||||
* Dual action, secondary top-down shade fully up (closed):
|
||||
* Dual action, secondary top-down shade fully up (closed):
|
||||
* posKind: 2 {ZERO_IS_OPEN}
|
||||
* position: 0
|
||||
*
|
||||
* Dual action, secondary top-down shade fully down (open):
|
||||
*
|
||||
* Dual action, secondary top-down shade fully down (open):
|
||||
* posKind: 2 {ZERO_IS_OPEN}
|
||||
* position: 65535
|
||||
*
|
||||
*
|
||||
*/
|
||||
ZERO_IS_CLOSED,
|
||||
ZERO_IS_OPEN,
|
||||
VANE_COORDS,
|
||||
NONE,
|
||||
PRIMARY_ZERO_IS_CLOSED,
|
||||
SECONDARY_ZERO_IS_OPEN,
|
||||
VANE_TILT_COORDS,
|
||||
ERROR_UNKNOWN;
|
||||
|
||||
public static final int MAX_SHADE = 65535;
|
||||
public static final int MAX_VANE = 32767;
|
||||
|
||||
/**
|
||||
* Converts an HD PowerView posKind integer value to a CoordinateSystem enum value
|
||||
*
|
||||
* @param posKind input integer value
|
||||
* @return corresponding CoordinateSystem enum
|
||||
* Converts an HD PowerView posKind integer value to a CoordinateSystem enum value.
|
||||
*
|
||||
* @param posKind input integer value.
|
||||
* @return corresponding CoordinateSystem enum.
|
||||
*/
|
||||
public static CoordinateSystem fromPosKind(int posKind) {
|
||||
switch (posKind) {
|
||||
case 1:
|
||||
return ZERO_IS_CLOSED;
|
||||
case 2:
|
||||
return ZERO_IS_OPEN;
|
||||
case 3:
|
||||
return VANE_COORDS;
|
||||
try {
|
||||
return CoordinateSystem.values()[posKind];
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
return ERROR_UNKNOWN;
|
||||
}
|
||||
return ERROR_UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a CoordinateSystem enum to an HD PowerView posKind integer value
|
||||
*
|
||||
* @return the posKind integer value
|
||||
* Check if the coordinate system matches the given posKind.
|
||||
*
|
||||
* @param posKind
|
||||
* @return true if equal.
|
||||
*/
|
||||
public int toPosKind() {
|
||||
return ordinal() + 1;
|
||||
public boolean equals(@Nullable Integer posKind) {
|
||||
return (posKind != null) && (posKind.intValue() == ordinal());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,12 @@ import static org.openhab.binding.hdpowerview.internal.api.CoordinateSystem.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
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;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The position of a single shade, as returned by the HD PowerView hub
|
||||
@@ -28,159 +31,126 @@ import org.openhab.core.types.UnDefType;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShadePosition {
|
||||
|
||||
private final transient Logger logger = LoggerFactory.getLogger(ShadePosition.class);
|
||||
|
||||
/**
|
||||
* Primary actuator position
|
||||
* Primary actuator position.
|
||||
*/
|
||||
private int posKind1;
|
||||
private int position1;
|
||||
|
||||
/**
|
||||
* Secondary actuator position
|
||||
* Secondary actuator position.
|
||||
*
|
||||
* here we have to use Integer objects rather than just int primitives because
|
||||
* these are secondary optional position elements in the JSON payload, so the
|
||||
* GSON de-serializer might leave them as null
|
||||
* Here we have to use Integer objects rather than just int primitives because these are secondary optional position
|
||||
* elements in the JSON payload, so the GSON de-serializer might leave them as null.
|
||||
*/
|
||||
private @Nullable Integer posKind2 = null;
|
||||
private @Nullable Integer position2 = null;
|
||||
|
||||
/**
|
||||
* Create a ShadePosition position instance with just a primary actuator
|
||||
* position
|
||||
*
|
||||
* @param coordSys the Coordinate System to be used
|
||||
* @param percent the percentage position within that Coordinate System
|
||||
* @return the ShadePosition instance
|
||||
*/
|
||||
public static ShadePosition create(CoordinateSystem coordSys, int percent) {
|
||||
return new ShadePosition(coordSys, percent, null, null);
|
||||
public ShadePosition() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a ShadePosition position instance with both a primary and a secondary
|
||||
* actuator position
|
||||
* Get the shade's State for the given actuator class resp. coordinate system.
|
||||
*
|
||||
* @param primaryCoordSys the Coordinate System to be used for the primary
|
||||
* position
|
||||
* @param primaryPercent the percentage position for primary position
|
||||
* @param secondaryCoordSys the Coordinate System to be used for the secondary
|
||||
* position
|
||||
* @param secondaryPercent the percentage position for secondary position
|
||||
* @return the ShadePosition instance
|
||||
* @param shadeCapabilities the shade Thing capabilities.
|
||||
* @param posKindCoords the actuator class (coordinate system) whose state is to be returned.
|
||||
* @return the current state.
|
||||
*/
|
||||
public static ShadePosition create(CoordinateSystem primaryCoordSys, int primaryPercent,
|
||||
@Nullable CoordinateSystem secondaryCoordSys, @Nullable Integer secondaryPercent) {
|
||||
return new ShadePosition(primaryCoordSys, primaryPercent, secondaryCoordSys, secondaryPercent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for ShadePosition position with both a primary and a secondary
|
||||
* actuator position
|
||||
*
|
||||
* @param primaryCoordSys the Coordinate System to be used for the primary
|
||||
* position
|
||||
* @param primaryPercent the percentage position for primary position
|
||||
* @param secondaryCoordSys the Coordinate System to be used for the secondary
|
||||
* position
|
||||
* @param secondaryPercent the percentage position for secondary position
|
||||
*/
|
||||
ShadePosition(CoordinateSystem primaryCoordSys, int primaryPercent, @Nullable CoordinateSystem secondaryCoordSys,
|
||||
@Nullable Integer secondaryPercent) {
|
||||
setPosition1(primaryCoordSys, primaryPercent);
|
||||
setPosition2(secondaryCoordSys, secondaryPercent);
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given Actuator Class and Coordinate System, map the ShadePosition's
|
||||
* state to an OpenHAB State
|
||||
*
|
||||
* @param actuatorClass the requested Actuator Class
|
||||
* @param coordSys the requested Coordinate System
|
||||
* @return the corresponding OpenHAB State
|
||||
*/
|
||||
public State getState(ActuatorClass actuatorClass, CoordinateSystem coordSys) {
|
||||
switch (actuatorClass) {
|
||||
case PRIMARY_ACTUATOR:
|
||||
return getPosition1(coordSys);
|
||||
case SECONDARY_ACTUATOR:
|
||||
return getPosition2(coordSys);
|
||||
default:
|
||||
return UnDefType.UNDEF;
|
||||
public State getState(Capabilities shadeCapabilities, CoordinateSystem posKindCoords) {
|
||||
State result = getPosition1(shadeCapabilities, posKindCoords);
|
||||
if (result == UnDefType.UNDEF) {
|
||||
result = getPosition2(shadeCapabilities, posKindCoords);
|
||||
}
|
||||
logger.trace("getState(): capabilities={}, coords={} => result={}", shadeCapabilities, posKindCoords, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the Coordinate System used for the given Actuator Class (if any)
|
||||
* Set the shade's position1 value for the given actuator class resp. coordinate system.
|
||||
*
|
||||
* @param actuatorClass the requested Actuator Class
|
||||
* @return the Coordinate System used for that Actuator Class, or ERROR_UNKNOWN
|
||||
* if the Actuator Class is not implemented
|
||||
* @param shadeCapabilities the shade Thing capabilities.
|
||||
* @param posKindCoords the actuator class (coordinate system) whose state is to be changed.
|
||||
* @param percent the new position value.
|
||||
*/
|
||||
public CoordinateSystem getCoordinateSystem(ActuatorClass actuatorClass) {
|
||||
switch (actuatorClass) {
|
||||
case PRIMARY_ACTUATOR:
|
||||
return fromPosKind(posKind1);
|
||||
case SECONDARY_ACTUATOR:
|
||||
Integer posKind2 = this.posKind2;
|
||||
if (posKind2 != null) {
|
||||
return fromPosKind(posKind2.intValue());
|
||||
}
|
||||
default:
|
||||
return ERROR_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
private void setPosition1(CoordinateSystem coordSys, int percent) {
|
||||
posKind1 = coordSys.toPosKind();
|
||||
switch (coordSys) {
|
||||
case ZERO_IS_CLOSED:
|
||||
/*-
|
||||
* Primary rail of a single action bottom-up shade, or
|
||||
* Primary, lower, bottom-up, rail of a dual action shade
|
||||
*/
|
||||
case ZERO_IS_OPEN:
|
||||
/*-
|
||||
* Primary rail of a single action top-down shade
|
||||
*
|
||||
* All these types use the same coordinate system; which is inverted in relation
|
||||
* to that of OpenHAB
|
||||
*/
|
||||
position1 = MAX_SHADE - (int) Math.round(percent / 100d * MAX_SHADE);
|
||||
break;
|
||||
case VANE_COORDS:
|
||||
private void setPosition1(Capabilities shadeCapabilities, CoordinateSystem posKindCoords, int percent) {
|
||||
switch (posKindCoords) {
|
||||
case PRIMARY_ZERO_IS_CLOSED:
|
||||
/*
|
||||
* Vane angle of the primary rail of a bottom-up single action shade
|
||||
* Primary rail of a bottom-up shade, or lower rail of a dual action shade: => INVERTED
|
||||
*/
|
||||
position1 = (int) Math.round(percent / 100d * MAX_VANE);
|
||||
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);
|
||||
if (secondary instanceof PercentType) {
|
||||
int secPercent = ((PercentType) secondary).intValue();
|
||||
if (percent < secPercent) {
|
||||
percent = secPercent;
|
||||
}
|
||||
}
|
||||
}
|
||||
posKind1 = posKindCoords.ordinal();
|
||||
position1 = MAX_SHADE - (int) Math.round((double) percent / 100 * MAX_SHADE);
|
||||
break;
|
||||
|
||||
case SECONDARY_ZERO_IS_OPEN:
|
||||
/*
|
||||
* Secondary, upper rail of a dual action shade: => NOT INVERTED
|
||||
*/
|
||||
posKind1 = posKindCoords.ordinal();
|
||||
position1 = (int) Math.round((double) percent / 100 * MAX_SHADE);
|
||||
break;
|
||||
|
||||
case VANE_TILT_COORDS:
|
||||
/*
|
||||
* Vane angle of the primary rail of a bottom-up single action shade: => NOT INVERTED
|
||||
*/
|
||||
posKind1 = posKindCoords.ordinal();
|
||||
int max = shadeCapabilities.supportsTilt180() ? MAX_SHADE : MAX_VANE;
|
||||
position1 = (int) Math.round((double) percent / 100 * max);
|
||||
break;
|
||||
|
||||
default:
|
||||
posKind1 = CoordinateSystem.NONE.ordinal();
|
||||
position1 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private State getPosition1(CoordinateSystem coordSys) {
|
||||
switch (coordSys) {
|
||||
case ZERO_IS_CLOSED:
|
||||
/*-
|
||||
* Primary rail of a single action bottom-up shade, or
|
||||
* Primary, lower, bottom-up, rail of a dual action shade
|
||||
*/
|
||||
case ZERO_IS_OPEN:
|
||||
/**
|
||||
* Get the shade's position1 State for the given actuator class resp. coordinate system.
|
||||
*
|
||||
* @param shadeCapabilities the shade Thing capabilities.
|
||||
* @param posKindCoords the actuator class (coordinate system) whose state is to be returned.
|
||||
* @return the State (or UNDEF if not available).
|
||||
*/
|
||||
private State getPosition1(Capabilities shadeCapabilities, CoordinateSystem posKindCoords) {
|
||||
switch (posKindCoords) {
|
||||
case PRIMARY_ZERO_IS_CLOSED:
|
||||
/*
|
||||
* Primary rail of a single action top-down shade
|
||||
*
|
||||
* All these types use the same coordinate system; which is inverted in relation
|
||||
* to that of OpenHAB
|
||||
*
|
||||
* If the slats have a defined position then the shade position must by
|
||||
* definition be 100%
|
||||
* Primary rail of a bottom-up shade, or lower rail of a dual action shade: => INVERTED
|
||||
*/
|
||||
return posKind1 == 3 ? PercentType.HUNDRED
|
||||
: new PercentType(100 - (int) Math.round((double) position1 / MAX_SHADE * 100));
|
||||
if (posKindCoords.equals(posKind1)) {
|
||||
return new PercentType(100 - (int) Math.round((double) position1 / MAX_SHADE * 100));
|
||||
}
|
||||
if (VANE_TILT_COORDS.equals(posKind1) && shadeCapabilities.supportsTiltOnClosed()) {
|
||||
return PercentType.HUNDRED;
|
||||
}
|
||||
break;
|
||||
|
||||
case VANE_COORDS:
|
||||
case SECONDARY_ZERO_IS_OPEN:
|
||||
/*
|
||||
* Vane angle of the primary rail of a bottom-up single action shade
|
||||
* Secondary, upper rail of a dual action shade: => NOT INVERTED
|
||||
*/
|
||||
if (posKindCoords.equals(posKind1)) {
|
||||
return new PercentType((int) Math.round((double) position1 / MAX_SHADE * 100));
|
||||
}
|
||||
break;
|
||||
|
||||
case VANE_TILT_COORDS:
|
||||
/*
|
||||
* Vane angle of the primary rail of a bottom-up single action shade: => NOT INVERTED
|
||||
*
|
||||
* If the shades are not open, the vane position is undefined; if the the shades
|
||||
* are exactly open then the vanes are at zero; otherwise return the actual vane
|
||||
@@ -190,56 +160,205 @@ public class ShadePosition {
|
||||
* be a bug in the hub) so we avoid an out of range exception via the Math.min()
|
||||
* function below..
|
||||
*/
|
||||
return posKind1 != 3 ? (position1 != 0 ? UnDefType.UNDEF : PercentType.ZERO)
|
||||
: new PercentType((int) Math.round((double) Math.min(position1, MAX_VANE) / MAX_VANE * 100));
|
||||
|
||||
default:
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
}
|
||||
|
||||
private void setPosition2(@Nullable CoordinateSystem coordSys, @Nullable Integer percent) {
|
||||
if (coordSys == null || percent == null) {
|
||||
return;
|
||||
}
|
||||
posKind2 = Integer.valueOf(coordSys.toPosKind());
|
||||
switch (coordSys) {
|
||||
case ZERO_IS_CLOSED:
|
||||
case ZERO_IS_OPEN:
|
||||
/*
|
||||
* Secondary, upper, top-down rail of a dual action shade
|
||||
*
|
||||
* Uses a coordinate system that is NOT inverted in relation to OpenHAB
|
||||
*/
|
||||
position2 = Integer.valueOf((int) Math.round(percent.doubleValue() / 100 * MAX_SHADE));
|
||||
if (posKindCoords.equals(posKind1)) {
|
||||
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()) {
|
||||
return position1 != 0 ? UnDefType.UNDEF : PercentType.ZERO;
|
||||
}
|
||||
break;
|
||||
|
||||
case ERROR_UNKNOWN:
|
||||
case NONE:
|
||||
// fall through, return UNDEF
|
||||
}
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the shade's position2 value for the given actuator class resp. coordinate system.
|
||||
*
|
||||
* @param shadeCapabilities the shade Thing capabilities.
|
||||
* @param posKindCoords the actuator class (coordinate system) whose state is to be changed.
|
||||
* @param percent the new position value.
|
||||
*/
|
||||
private void setPosition2(Capabilities shadeCapabilities, CoordinateSystem posKindCoords, int percent) {
|
||||
switch (posKindCoords) {
|
||||
case PRIMARY_ZERO_IS_CLOSED:
|
||||
/*
|
||||
* 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:
|
||||
/*
|
||||
* 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);
|
||||
if (primary instanceof PercentType) {
|
||||
int primaryPercent = ((PercentType) primary).intValue();
|
||||
if (percent > primaryPercent) {
|
||||
percent = primaryPercent;
|
||||
}
|
||||
}
|
||||
}
|
||||
posKind2 = posKindCoords.ordinal();
|
||||
position2 = Integer.valueOf((int) Math.round((double) percent / 100 * MAX_SHADE));
|
||||
break;
|
||||
|
||||
case VANE_TILT_COORDS:
|
||||
posKind2 = posKindCoords.ordinal();
|
||||
int max = shadeCapabilities.supportsTilt180() ? MAX_SHADE : MAX_VANE;
|
||||
position2 = Integer.valueOf((int) Math.round((double) percent / 100 * max));
|
||||
break;
|
||||
|
||||
default:
|
||||
position2 = Integer.valueOf(0);
|
||||
posKind2 = null;
|
||||
position2 = null;
|
||||
}
|
||||
}
|
||||
|
||||
private State getPosition2(CoordinateSystem coordSys) {
|
||||
/**
|
||||
* Get the shade's position2 State for the given actuator class resp. coordinate system.
|
||||
*
|
||||
* @param shadeCapabilities the shade Thing capabilities.
|
||||
* @param posKindCoords the actuator class (coordinate system) whose state is to be returned.
|
||||
* @return the State (or UNDEF if not available).
|
||||
*/
|
||||
private State getPosition2(Capabilities shadeCapabilities, CoordinateSystem posKindCoords) {
|
||||
Integer posKind2 = this.posKind2;
|
||||
Integer position2 = this.position2;
|
||||
|
||||
if (position2 == null || posKind2 == null) {
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
switch (coordSys) {
|
||||
case ZERO_IS_CLOSED:
|
||||
|
||||
switch (posKindCoords) {
|
||||
case PRIMARY_ZERO_IS_CLOSED:
|
||||
/*
|
||||
* This case should never occur; but return a value anyway just in case
|
||||
* Primary rail of a bottom-up shade, or lower rail of a dual action shade: => INVERTED
|
||||
*/
|
||||
case ZERO_IS_OPEN:
|
||||
if (posKindCoords.equals(posKind2)) {
|
||||
return new PercentType(100 - (int) Math.round(position2.doubleValue() / MAX_SHADE * 100));
|
||||
}
|
||||
break;
|
||||
|
||||
case SECONDARY_ZERO_IS_OPEN:
|
||||
/*
|
||||
* Secondary, upper, top-down rail of a dual action shade
|
||||
*
|
||||
* Uses a coordinate system that is NOT inverted in relation to OpenHAB
|
||||
* Secondary, upper rail of a dual action shade: => NOT INVERTED
|
||||
*/
|
||||
if (posKind2.intValue() != 3) {
|
||||
if (posKindCoords.equals(posKind2)) {
|
||||
return new PercentType((int) Math.round(position2.doubleValue() / MAX_SHADE * 100));
|
||||
}
|
||||
default:
|
||||
return UnDefType.UNDEF;
|
||||
break;
|
||||
|
||||
/*
|
||||
* 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:
|
||||
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));
|
||||
}
|
||||
break;
|
||||
|
||||
case ERROR_UNKNOWN:
|
||||
case NONE:
|
||||
// fall through, return UNDEF
|
||||
}
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if the ShadePosition has a posKindN value indicating potential support for a secondary rail.
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if the ShadePosition has both a posKindN value indicating potential support for tilt, AND a posKindN
|
||||
* indicating support for a primary rail. i.e. it potentially supports tilt anywhere functionality.
|
||||
*
|
||||
* @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)))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the shade's position for the given actuator class resp. coordinate system.
|
||||
*
|
||||
* @param shadeCapabilities the shade Thing capabilities.
|
||||
* @param posKindCoords the actuator class (coordinate system) whose state is to be changed.
|
||||
* @param percent the new position value.
|
||||
* @return this object.
|
||||
*/
|
||||
public ShadePosition setPosition(Capabilities shadeCapabilities, CoordinateSystem posKindCoords, int percent) {
|
||||
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)) {
|
||||
final Integer posKind2Temp = posKind2;
|
||||
final Integer position2Temp = position2;
|
||||
posKind2 = Integer.valueOf(posKind1);
|
||||
position2 = Integer.valueOf(position1);
|
||||
posKind1 = posKind2Temp != null ? posKind2Temp.intValue() : NONE.ordinal();
|
||||
position1 = position2Temp != null ? position2Temp.intValue() : 0;
|
||||
}
|
||||
|
||||
// delete position2 if it has an invalid position kind
|
||||
if (ERROR_UNKNOWN.equals(posKind2) || NONE.equals(posKind2)) {
|
||||
posKind2 = null;
|
||||
position2 = null;
|
||||
}
|
||||
|
||||
// logic to set either position1 or position2
|
||||
switch (posKindCoords) {
|
||||
case PRIMARY_ZERO_IS_CLOSED:
|
||||
if (shadeCapabilities.supportsPrimary()) {
|
||||
setPosition1(shadeCapabilities, posKindCoords, percent);
|
||||
}
|
||||
break;
|
||||
|
||||
case SECONDARY_ZERO_IS_OPEN:
|
||||
if (shadeCapabilities.supportsSecondary()) {
|
||||
if (shadeCapabilities.supportsPrimary()) {
|
||||
setPosition2(shadeCapabilities, posKindCoords, percent);
|
||||
} else {
|
||||
setPosition1(shadeCapabilities, posKindCoords, percent);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case VANE_TILT_COORDS:
|
||||
if (shadeCapabilities.supportsPrimary()) {
|
||||
if (shadeCapabilities.supportsTiltOnClosed()) {
|
||||
setPosition1(shadeCapabilities, posKindCoords, percent);
|
||||
} else if (shadeCapabilities.supportsTiltAnywhere()) {
|
||||
setPosition2(shadeCapabilities, posKindCoords, percent);
|
||||
}
|
||||
} else if (shadeCapabilities.supportsTiltAnywhere()) {
|
||||
setPosition1(shadeCapabilities, posKindCoords, percent);
|
||||
}
|
||||
break;
|
||||
|
||||
case ERROR_UNKNOWN:
|
||||
case NONE:
|
||||
// fall through, do nothing
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ public class Shades {
|
||||
public @Nullable ShadePosition positions;
|
||||
public @Nullable Boolean timedOut;
|
||||
public int signalStrength;
|
||||
public @Nullable Integer capabilities;
|
||||
|
||||
public String getName() {
|
||||
return new String(Base64.getDecoder().decode(name));
|
||||
|
||||
@@ -0,0 +1,339 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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.internal.database;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Class containing the database of all known shade 'types' and their respective 'capabilities'.
|
||||
*
|
||||
* If user systems detect shade types that are not in the database, then this class can issue logger warning messages
|
||||
* indicating such absence, and prompting the user to report it to developers so that the database and the respective
|
||||
* binding functionality can (hopefully) be extended over time.
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Initial Contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShadeCapabilitiesDatabase {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ShadeCapabilitiesDatabase.class);
|
||||
|
||||
/*
|
||||
* Database of known shade capabilities.
|
||||
*/
|
||||
private static final Map<Integer, Capabilities> CAPABILITIES_DATABASE = Arrays.asList(
|
||||
// @formatter:off
|
||||
new Capabilities(0).primary().tiltOnClosed() .text("Bottom Up"),
|
||||
new Capabilities(1).primary().tiltAnywhere() .text("Bottom Up Tilt 90°"),
|
||||
new Capabilities(2).primary().tiltAnywhere().tilt180() .text("Bottom Up Tilt 180°"),
|
||||
new Capabilities(3).primary().tiltOnClosed() .text("Vertical"),
|
||||
new Capabilities(4).primary().tiltAnywhere().tilt180() .text("Vertical Tilt 180°"),
|
||||
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°"),
|
||||
// @formatter:on
|
||||
new Capabilities()).stream().collect(Collectors.toMap(Capabilities::getValue, Function.identity()));
|
||||
|
||||
/*
|
||||
* Database of known shade types and corresponding capabilities.
|
||||
*/
|
||||
private static final Map<Integer, Type> TYPE_DATABASE = Arrays.asList(
|
||||
// @formatter:off
|
||||
new Type( 4).capabilities(0).text("Roman"),
|
||||
new Type( 5).capabilities(0).text("Bottom Up"),
|
||||
new Type( 6).capabilities(0).text("Duette"),
|
||||
new Type( 7).capabilities(6).text("Top Down"),
|
||||
new Type( 8).capabilities(7).text("Duette Top Down Bottom Up"),
|
||||
new Type( 9).capabilities(7).text("Duette DuoLite Top Down Bottom Up"),
|
||||
new Type(23).capabilities(1).text("Silhouette"),
|
||||
new Type(42).capabilities(0).text("M25T Roller Blind"),
|
||||
new Type(43).capabilities(1).text("Facette"),
|
||||
new Type(44).capabilities(0).text("Twist"),
|
||||
new Type(47).capabilities(7).text("Pleated Top Down Bottom Up"),
|
||||
new Type(49).capabilities(0).text("AC Roller"),
|
||||
new Type(51).capabilities(2).text("Venetian"),
|
||||
new Type(54).capabilities(3).text("Vertical Slats Left Stack"),
|
||||
new Type(55).capabilities(3).text("Vertical Slats Right Stack"),
|
||||
new Type(56).capabilities(3).text("Vertical Slats Split Stack"),
|
||||
new Type(62).capabilities(2).text("Venetian"),
|
||||
new Type(69).capabilities(3).text("Curtain Left Stack"),
|
||||
new Type(70).capabilities(3).text("Curtain Right Stack"),
|
||||
new Type(71).capabilities(3).text("Curtain Split Stack"),
|
||||
new Type(79).capabilities(8).text("Duolite Lift"),
|
||||
// @formatter:on
|
||||
new Type()).stream().collect(Collectors.toMap(Type::getValue, Function.identity()));
|
||||
|
||||
/**
|
||||
* Base class that is extended by Type and Capabilities classes.
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Initial Contribution
|
||||
*/
|
||||
private static class Base {
|
||||
protected int intValue = -1;
|
||||
protected String text = "-- not in database --";
|
||||
|
||||
protected Integer getValue() {
|
||||
return intValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s (%d)", text, intValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes a shade type entry in the database; implements 'capabilities' parameter.
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Initial Contribution
|
||||
*/
|
||||
public static class Type extends Base {
|
||||
private int capabilities = -1;
|
||||
|
||||
protected Type() {
|
||||
}
|
||||
|
||||
protected Type(int type) {
|
||||
intValue = type;
|
||||
}
|
||||
|
||||
protected Type text(String text) {
|
||||
this.text = text;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected Type capabilities(int capabilities) {
|
||||
this.capabilities = capabilities;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get shade types's 'capabilities'.
|
||||
*
|
||||
* @return 'capabilities'.
|
||||
*/
|
||||
public int getCapabilities() {
|
||||
return capabilities;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes a shade 'capabilities' entry in the database; adds properties indicating its supported functionality.
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Initial Contribution
|
||||
*/
|
||||
public static class Capabilities extends Base {
|
||||
private boolean supportsPrimary;
|
||||
private boolean supportsSecondary;
|
||||
private boolean supportsTiltOnClosed;
|
||||
private boolean supportsTiltAnywhere;
|
||||
private boolean primaryStateInverted;
|
||||
private boolean tilt180Degrees;
|
||||
|
||||
public Capabilities() {
|
||||
}
|
||||
|
||||
protected Capabilities(int capabilities) {
|
||||
intValue = capabilities;
|
||||
}
|
||||
|
||||
protected Capabilities text(String text) {
|
||||
this.text = text;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected Capabilities primary() {
|
||||
supportsPrimary = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected Capabilities tiltOnClosed() {
|
||||
supportsTiltOnClosed = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected Capabilities secondary() {
|
||||
supportsSecondary = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected Capabilities tiltAnywhere() {
|
||||
supportsTiltAnywhere = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected Capabilities primaryStateInverted() {
|
||||
primaryStateInverted = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected Capabilities tilt180() {
|
||||
tilt180Degrees = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the Capabilities class instance supports a primary shade.
|
||||
*
|
||||
* @return true if it supports a primary shade.
|
||||
*/
|
||||
public boolean supportsPrimary() {
|
||||
return supportsPrimary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the Capabilities class instance supports a vane/tilt function (by means of a second motor).
|
||||
*
|
||||
* @return true if it supports a vane/tilt function (by means of a second motor).
|
||||
*/
|
||||
public boolean supportsTiltAnywhere() {
|
||||
return supportsTiltAnywhere;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the Capabilities class instance supports a secondary shade.
|
||||
*
|
||||
* @return true if it supports a secondary shade.
|
||||
*/
|
||||
public boolean supportsSecondary() {
|
||||
return supportsSecondary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the Capabilities class instance supports a secondary shade.
|
||||
*
|
||||
* @return true if the primary shade is inverted.
|
||||
*/
|
||||
public boolean isPrimaryStateInverted() {
|
||||
return primaryStateInverted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the Capabilities class instance supports 'tilt when closed'.
|
||||
*
|
||||
* Note: Simple bottom up or vertical shades that do not have independent vane controls, can be tilted in a
|
||||
* simple way, only when they are fully closed, by moving the shade motor a bit further.
|
||||
*
|
||||
* @return true if the primary shade is inverted.
|
||||
*/
|
||||
public boolean supportsTiltOnClosed() {
|
||||
return supportsTiltOnClosed && !supportsTiltAnywhere;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the Capabilities class instance supports 180 degrees tilt.
|
||||
*
|
||||
* @return true if the primary shade supports 180 degrees.
|
||||
*/
|
||||
public boolean supportsTilt180() {
|
||||
return tilt180Degrees;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a given shade 'type' is in the database.
|
||||
*
|
||||
* @param type the shade 'type' parameter.
|
||||
* @return true if the shade 'type' is known.
|
||||
*/
|
||||
public boolean isTypeInDatabase(int type) {
|
||||
return TYPE_DATABASE.containsKey(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a given 'capabilities' value is in the database.
|
||||
*
|
||||
* @param capabilities the shade 'capabilities' parameter
|
||||
* @return true if the 'capabilities' value is known
|
||||
*/
|
||||
public boolean isCapabilitiesInDatabase(int capabilities) {
|
||||
return CAPABILITIES_DATABASE.containsKey(capabilities);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Type class instance that corresponds to the given 'type' parameter.
|
||||
*
|
||||
* @param type the shade 'type' parameter.
|
||||
* @return corresponding instance of Type class.
|
||||
*/
|
||||
public Type getType(int type) {
|
||||
return TYPE_DATABASE.getOrDefault(type, new Type());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Capabilities class instance that corresponds to the given 'capabilities' parameter.
|
||||
*
|
||||
* @param capabilities the shade 'capabilities' parameter.
|
||||
* @return corresponding instance of Capabilities class.
|
||||
*/
|
||||
public Capabilities getCapabilities(int capabilities) {
|
||||
return CAPABILITIES_DATABASE.getOrDefault(capabilities, new Capabilities());
|
||||
}
|
||||
|
||||
private static final String REQUEST_DEVELOPERS_TO_UPDATE = " => Please request developers to update the database!";
|
||||
|
||||
/**
|
||||
* Log a message indicating that 'type' is not in database.
|
||||
*
|
||||
* @param type
|
||||
*/
|
||||
public void logTypeNotInDatabase(int type) {
|
||||
logger.warn("The shade 'type:{}' is not in the database!{}", type, REQUEST_DEVELOPERS_TO_UPDATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a message indicating that 'capabilities' is not in database.
|
||||
*
|
||||
* @param capabilities
|
||||
*/
|
||||
public void logCapabilitiesNotInDatabase(int type, int capabilities) {
|
||||
logger.warn("The 'capabilities:{}' for shade 'type:{}' are not in the database!{}", capabilities, type,
|
||||
REQUEST_DEVELOPERS_TO_UPDATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a message indicating the type's capabilities and the passed capabilities are not equal.
|
||||
*
|
||||
* @param type
|
||||
* @param capabilities
|
||||
*/
|
||||
public void logCapabilitiesMismatch(int type, int capabilities) {
|
||||
logger.warn("The 'capabilities:{}' reported by shade 'type:{}' don't match the database!{}", capabilities, type,
|
||||
REQUEST_DEVELOPERS_TO_UPDATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a message indicating that a shade's secondary/vanes support, as observed via its actual JSON payload, does
|
||||
* not match the expected value as declared in its 'type' and 'capabilities'.
|
||||
*
|
||||
* @param propertyKey
|
||||
* @param type
|
||||
* @param capabilities
|
||||
* @param propertyValue
|
||||
*/
|
||||
public void logPropertyMismatch(String propertyKey, int type, int capabilities, boolean propertyValue) {
|
||||
logger.warn(
|
||||
"The '{}:{}' property actually reported by shade 'type:{}' is different "
|
||||
+ "than expected from its 'capabilities:{}' in the database!{}",
|
||||
propertyKey, propertyValue, type, capabilities, REQUEST_DEVELOPERS_TO_UPDATE);
|
||||
}
|
||||
}
|
||||
@@ -26,9 +26,10 @@ import org.openhab.binding.hdpowerview.internal.HubProcessingException;
|
||||
import org.openhab.binding.hdpowerview.internal.api.responses.Shades;
|
||||
import org.openhab.binding.hdpowerview.internal.api.responses.Shades.ShadeData;
|
||||
import org.openhab.binding.hdpowerview.internal.config.HDPowerViewShadeConfiguration;
|
||||
import org.openhab.binding.hdpowerview.internal.database.ShadeCapabilitiesDatabase;
|
||||
import org.openhab.binding.hdpowerview.internal.database.ShadeCapabilitiesDatabase.Capabilities;
|
||||
import org.openhab.binding.hdpowerview.internal.handler.HDPowerViewHubHandler;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.slf4j.Logger;
|
||||
@@ -48,6 +49,7 @@ public class HDPowerViewShadeDiscoveryService extends AbstractDiscoveryService {
|
||||
private final HDPowerViewHubHandler hub;
|
||||
private final Runnable scanner;
|
||||
private @Nullable ScheduledFuture<?> backgroundFuture;
|
||||
private final ShadeCapabilitiesDatabase db = new ShadeCapabilitiesDatabase();
|
||||
|
||||
public HDPowerViewShadeDiscoveryService(HDPowerViewHubHandler hub) {
|
||||
super(Collections.singleton(HDPowerViewBindingConstants.THING_TYPE_SHADE), 600, true);
|
||||
@@ -96,12 +98,20 @@ public class HDPowerViewShadeDiscoveryService extends AbstractDiscoveryService {
|
||||
String id = Integer.toString(shadeData.id);
|
||||
ThingUID thingUID = new ThingUID(HDPowerViewBindingConstants.THING_TYPE_SHADE,
|
||||
bridgeUID, id);
|
||||
DiscoveryResult result = DiscoveryResultBuilder.create(thingUID)
|
||||
Integer caps = shadeData.capabilities;
|
||||
Capabilities capabilities = db.getCapabilities((caps != null) ? caps.intValue() : -1);
|
||||
|
||||
DiscoveryResultBuilder builder = DiscoveryResultBuilder.create(thingUID)
|
||||
.withLabel(shadeData.getName()).withBridge(bridgeUID)
|
||||
.withProperty(HDPowerViewShadeConfiguration.ID, id)
|
||||
.withRepresentationProperty(HDPowerViewShadeConfiguration.ID)
|
||||
.withLabel(shadeData.getName()).withBridge(bridgeUID).build();
|
||||
.withProperty(HDPowerViewBindingConstants.PROPERTY_SHADE_TYPE,
|
||||
db.getType(shadeData.type).toString())
|
||||
.withProperty(HDPowerViewBindingConstants.PROPERTY_SHADE_CAPABILITIES,
|
||||
capabilities.toString())
|
||||
.withRepresentationProperty(HDPowerViewShadeConfiguration.ID);
|
||||
|
||||
logger.debug("Hub discovered shade '{}'", id);
|
||||
thingDiscovered(result);
|
||||
thingDiscovered(builder.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
package org.openhab.binding.hdpowerview.internal.handler;
|
||||
|
||||
import static org.openhab.binding.hdpowerview.internal.HDPowerViewBindingConstants.*;
|
||||
import static org.openhab.binding.hdpowerview.internal.api.ActuatorClass.*;
|
||||
import static org.openhab.binding.hdpowerview.internal.api.CoordinateSystem.*;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -23,16 +23,18 @@ import javax.ws.rs.NotSupportedException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.hdpowerview.internal.HDPowerViewBindingConstants;
|
||||
import org.openhab.binding.hdpowerview.internal.HDPowerViewWebTargets;
|
||||
import org.openhab.binding.hdpowerview.internal.HubMaintenanceException;
|
||||
import org.openhab.binding.hdpowerview.internal.HubProcessingException;
|
||||
import org.openhab.binding.hdpowerview.internal.api.ActuatorClass;
|
||||
import org.openhab.binding.hdpowerview.internal.api.CoordinateSystem;
|
||||
import org.openhab.binding.hdpowerview.internal.api.ShadePosition;
|
||||
import org.openhab.binding.hdpowerview.internal.api.responses.Shade;
|
||||
import org.openhab.binding.hdpowerview.internal.api.responses.Shades.ShadeData;
|
||||
import org.openhab.binding.hdpowerview.internal.api.responses.Survey;
|
||||
import org.openhab.binding.hdpowerview.internal.config.HDPowerViewShadeConfiguration;
|
||||
import org.openhab.binding.hdpowerview.internal.database.ShadeCapabilitiesDatabase;
|
||||
import org.openhab.binding.hdpowerview.internal.database.ShadeCapabilitiesDatabase.Capabilities;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
@@ -47,7 +49,6 @@ import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -74,6 +75,9 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
|
||||
private @Nullable ScheduledFuture<?> refreshSignalFuture = null;
|
||||
private @Nullable ScheduledFuture<?> refreshBatteryLevelFuture = null;
|
||||
|
||||
private final ShadeCapabilitiesDatabase db = new ShadeCapabilitiesDatabase();
|
||||
private int shadeCapabilities = -1;
|
||||
|
||||
public HDPowerViewShadeHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
@@ -131,9 +135,9 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
|
||||
switch (channelId) {
|
||||
case CHANNEL_SHADE_POSITION:
|
||||
if (command instanceof PercentType) {
|
||||
moveShade(PRIMARY_ACTUATOR, ZERO_IS_CLOSED, ((PercentType) command).intValue());
|
||||
moveShade(PRIMARY_ZERO_IS_CLOSED, ((PercentType) command).intValue());
|
||||
} else if (command instanceof UpDownType) {
|
||||
moveShade(PRIMARY_ACTUATOR, ZERO_IS_CLOSED, UpDownType.UP.equals(command) ? 0 : 100);
|
||||
moveShade(PRIMARY_ZERO_IS_CLOSED, UpDownType.UP.equals(command) ? 0 : 100);
|
||||
} else if (command instanceof StopMoveType) {
|
||||
if (StopMoveType.STOP.equals(command)) {
|
||||
stopShade();
|
||||
@@ -145,17 +149,17 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
|
||||
|
||||
case CHANNEL_SHADE_VANE:
|
||||
if (command instanceof PercentType) {
|
||||
moveShade(PRIMARY_ACTUATOR, VANE_COORDS, ((PercentType) command).intValue());
|
||||
moveShade(VANE_TILT_COORDS, ((PercentType) command).intValue());
|
||||
} else if (command instanceof OnOffType) {
|
||||
moveShade(PRIMARY_ACTUATOR, VANE_COORDS, OnOffType.ON.equals(command) ? 100 : 0);
|
||||
moveShade(VANE_TILT_COORDS, OnOffType.ON.equals(command) ? 100 : 0);
|
||||
}
|
||||
break;
|
||||
|
||||
case CHANNEL_SHADE_SECONDARY_POSITION:
|
||||
if (command instanceof PercentType) {
|
||||
moveShade(SECONDARY_ACTUATOR, ZERO_IS_OPEN, ((PercentType) command).intValue());
|
||||
moveShade(SECONDARY_ZERO_IS_OPEN, ((PercentType) command).intValue());
|
||||
} else if (command instanceof UpDownType) {
|
||||
moveShade(SECONDARY_ACTUATOR, ZERO_IS_OPEN, UpDownType.UP.equals(command) ? 0 : 100);
|
||||
moveShade(SECONDARY_ZERO_IS_OPEN, UpDownType.UP.equals(command) ? 0 : 100);
|
||||
} else if (command instanceof StopMoveType) {
|
||||
if (StopMoveType.STOP.equals(command)) {
|
||||
stopShade();
|
||||
@@ -168,13 +172,14 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the state of the channels based on the ShadeData provided
|
||||
* Update the state of the channels based on the ShadeData provided.
|
||||
*
|
||||
* @param shadeData the ShadeData to be used; may be null
|
||||
* @param shadeData the ShadeData to be used; may be null.
|
||||
*/
|
||||
protected void onReceiveUpdate(@Nullable ShadeData shadeData) {
|
||||
if (shadeData != null) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
updateSoftProperties(shadeData);
|
||||
updateBindingStates(shadeData.positions);
|
||||
updateBatteryLevel(shadeData.batteryStatus);
|
||||
updateState(CHANNEL_SHADE_BATTERY_VOLTAGE,
|
||||
@@ -186,16 +191,116 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateBindingStates(@Nullable ShadePosition shadePos) {
|
||||
if (shadePos != null) {
|
||||
updateState(CHANNEL_SHADE_POSITION, shadePos.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED));
|
||||
updateState(CHANNEL_SHADE_VANE, shadePos.getState(PRIMARY_ACTUATOR, VANE_COORDS));
|
||||
updateState(CHANNEL_SHADE_SECONDARY_POSITION, shadePos.getState(SECONDARY_ACTUATOR, ZERO_IS_OPEN));
|
||||
} else {
|
||||
updateState(CHANNEL_SHADE_POSITION, UnDefType.UNDEF);
|
||||
updateState(CHANNEL_SHADE_VANE, UnDefType.UNDEF);
|
||||
updateState(CHANNEL_SHADE_SECONDARY_POSITION, UnDefType.UNDEF);
|
||||
/**
|
||||
* Update the Thing's properties based on the contents of the provided ShadeData.
|
||||
*
|
||||
* Checks the database of known Shade 'types' and 'capabilities' and logs any unknown or incompatible values, so
|
||||
* that developers can be kept updated about the potential need to add support for that type resp. capabilities.
|
||||
*
|
||||
* @param shadeData
|
||||
*/
|
||||
private void updateSoftProperties(ShadeData shadeData) {
|
||||
final Map<String, String> properties = getThing().getProperties();
|
||||
boolean propChanged = false;
|
||||
|
||||
// update 'type' property
|
||||
final int type = shadeData.type;
|
||||
String propKey = HDPowerViewBindingConstants.PROPERTY_SHADE_TYPE;
|
||||
String propOldVal = properties.getOrDefault(propKey, "");
|
||||
String propNewVal = db.getType(type).toString();
|
||||
if (!propNewVal.equals(propOldVal)) {
|
||||
propChanged = true;
|
||||
getThing().setProperty(propKey, propNewVal);
|
||||
if ((type > 0) && !db.isTypeInDatabase(type)) {
|
||||
db.logTypeNotInDatabase(type);
|
||||
}
|
||||
}
|
||||
|
||||
// update 'capabilities' property
|
||||
final Integer temp = shadeData.capabilities;
|
||||
final int capabilitiesVal = temp != null ? temp.intValue() : -1;
|
||||
Capabilities capabilities = db.getCapabilities(capabilitiesVal);
|
||||
propKey = HDPowerViewBindingConstants.PROPERTY_SHADE_CAPABILITIES;
|
||||
propOldVal = properties.getOrDefault(propKey, "");
|
||||
propNewVal = capabilities.toString();
|
||||
if (!propNewVal.equals(propOldVal)) {
|
||||
propChanged = true;
|
||||
getThing().setProperty(propKey, propNewVal);
|
||||
if ((capabilitiesVal >= 0) && !db.isCapabilitiesInDatabase(capabilitiesVal)) {
|
||||
db.logCapabilitiesNotInDatabase(type, capabilitiesVal);
|
||||
}
|
||||
}
|
||||
|
||||
// update shadeCapabilities field
|
||||
if (capabilitiesVal >= 0) {
|
||||
shadeCapabilities = capabilitiesVal;
|
||||
}
|
||||
|
||||
if (propChanged && db.isCapabilitiesInDatabase(capabilitiesVal) && db.isTypeInDatabase(type)
|
||||
&& (capabilitiesVal != db.getType(type).getCapabilities())) {
|
||||
db.logCapabilitiesMismatch(type, capabilitiesVal);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* After a hard refresh, update the Thing's properties based on the contents of the provided ShadeData.
|
||||
*
|
||||
* Checks if the secondary support capabilities in the database of known Shade 'types' and 'capabilities' matches
|
||||
* that implied by the ShadeData and logs any incompatible values, so that developers can be kept updated about the
|
||||
* potential need to add support for that type resp. capabilities.
|
||||
*
|
||||
* @param shadeData
|
||||
*/
|
||||
private void updateHardProperties(ShadeData shadeData) {
|
||||
final ShadePosition positions = shadeData.positions;
|
||||
if (positions != null) {
|
||||
final Map<String, String> properties = getThing().getProperties();
|
||||
|
||||
// update 'jsonHasSecondary' property
|
||||
String propKey = HDPowerViewBindingConstants.PROPERTY_SECONDARY_RAIL_DETECTED;
|
||||
String propOldVal = properties.getOrDefault(propKey, "");
|
||||
boolean propNewBool = positions.secondaryRailDetected();
|
||||
String propNewVal = String.valueOf(propNewBool);
|
||||
if (!propNewVal.equals(propOldVal)) {
|
||||
getThing().setProperty(propKey, propNewVal);
|
||||
final Integer temp = shadeData.capabilities;
|
||||
final int capabilities = temp != null ? temp.intValue() : -1;
|
||||
if (propNewBool != db.getCapabilities(capabilities).supportsSecondary()) {
|
||||
db.logPropertyMismatch(propKey, shadeData.type, capabilities, propNewBool);
|
||||
}
|
||||
}
|
||||
|
||||
// update 'jsonTiltAnywhere' property
|
||||
propKey = HDPowerViewBindingConstants.PROPERTY_TILT_ANYWHERE_DETECTED;
|
||||
propOldVal = properties.getOrDefault(propKey, "");
|
||||
propNewBool = positions.tiltAnywhereDetected();
|
||||
propNewVal = String.valueOf(propNewBool);
|
||||
if (!propNewVal.equals(propOldVal)) {
|
||||
getThing().setProperty(propKey, propNewVal);
|
||||
final Integer temp = shadeData.capabilities;
|
||||
final int capabilities = temp != null ? temp.intValue() : -1;
|
||||
if (propNewBool != db.getCapabilities(capabilities).supportsTiltAnywhere()) {
|
||||
db.logPropertyMismatch(propKey, shadeData.type, capabilities, propNewBool);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateBindingStates(@Nullable ShadePosition shadePos) {
|
||||
if (shadePos == null) {
|
||||
logger.debug("The value of 'shadePosition' argument was null!");
|
||||
} else if (shadeCapabilities < 0) {
|
||||
logger.debug("The 'shadeCapabilities' field has not been initialized!");
|
||||
} else {
|
||||
Capabilities caps = db.getCapabilities(shadeCapabilities);
|
||||
updateState(CHANNEL_SHADE_POSITION, shadePos.getState(caps, PRIMARY_ZERO_IS_CLOSED));
|
||||
updateState(CHANNEL_SHADE_VANE, shadePos.getState(caps, VANE_TILT_COORDS));
|
||||
updateState(CHANNEL_SHADE_SECONDARY_POSITION, shadePos.getState(caps, SECONDARY_ZERO_IS_OPEN));
|
||||
return;
|
||||
}
|
||||
updateState(CHANNEL_SHADE_POSITION, UnDefType.UNDEF);
|
||||
updateState(CHANNEL_SHADE_VANE, UnDefType.UNDEF);
|
||||
updateState(CHANNEL_SHADE_SECONDARY_POSITION, UnDefType.UNDEF);
|
||||
}
|
||||
|
||||
private void updateBatteryLevel(int batteryStatus) {
|
||||
@@ -220,7 +325,7 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
|
||||
updateState(CHANNEL_SHADE_BATTERY_LEVEL, new DecimalType(mappedValue));
|
||||
}
|
||||
|
||||
private void moveShade(ActuatorClass actuatorClass, CoordinateSystem coordSys, int newPercent) {
|
||||
private void moveShade(CoordinateSystem coordSys, int newPercent) {
|
||||
try {
|
||||
HDPowerViewHubHandler bridge;
|
||||
if ((bridge = getBridgeHandler()) == null) {
|
||||
@@ -230,33 +335,28 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
|
||||
if (webTargets == null) {
|
||||
throw new HubProcessingException("Web targets not initialized");
|
||||
}
|
||||
ShadePosition newPosition = null;
|
||||
// (try to) read the positions from the hub
|
||||
int shadeId = getShadeId();
|
||||
|
||||
switch (actuatorClass) {
|
||||
case PRIMARY_ACTUATOR:
|
||||
// write the new primary position
|
||||
webTargets.moveShade(shadeId, ShadePosition.create(coordSys, newPercent));
|
||||
break;
|
||||
case SECONDARY_ACTUATOR:
|
||||
// read the current primary position; default value 100%
|
||||
int primaryPercent = 100;
|
||||
Shade shade = webTargets.getShade(shadeId);
|
||||
if (shade != null) {
|
||||
ShadeData shadeData = shade.shade;
|
||||
if (shadeData != null) {
|
||||
ShadePosition shadePos = shadeData.positions;
|
||||
if (shadePos != null) {
|
||||
State primaryState = shadePos.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED);
|
||||
if (primaryState instanceof PercentType) {
|
||||
primaryPercent = ((PercentType) primaryState).intValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// write the current primary position, plus the new secondary position
|
||||
webTargets.moveShade(shadeId,
|
||||
ShadePosition.create(ZERO_IS_CLOSED, primaryPercent, ZERO_IS_OPEN, newPercent));
|
||||
Shade shade = webTargets.getShade(shadeId);
|
||||
if (shade != null) {
|
||||
ShadeData shadeData = shade.shade;
|
||||
if (shadeData != null) {
|
||||
newPosition = shadeData.positions;
|
||||
}
|
||||
}
|
||||
// if no positions returned, then create a new position
|
||||
if (newPosition == null) {
|
||||
newPosition = new ShadePosition();
|
||||
}
|
||||
// set the new position value, and write the positions to the hub
|
||||
webTargets.moveShade(shadeId,
|
||||
newPosition.setPosition(db.getCapabilities(shadeCapabilities), coordSys, newPercent));
|
||||
// update the Channels to match the new position
|
||||
final ShadePosition finalPosition = newPosition;
|
||||
scheduler.submit(() -> {
|
||||
updateBindingStates(finalPosition);
|
||||
});
|
||||
} catch (HubProcessingException | NumberFormatException e) {
|
||||
logger.warn("Unexpected error: {}", e.getMessage());
|
||||
return;
|
||||
@@ -375,6 +475,8 @@ public class HDPowerViewShadeHandler extends AbstractHubbedThingHandler {
|
||||
if (shadeData != null) {
|
||||
if (Boolean.TRUE.equals(shadeData.timedOut)) {
|
||||
logger.warn("Shade {} wireless refresh time out", shadeId);
|
||||
} else if (kind == RefreshKind.POSITION) {
|
||||
updateHardProperties(shadeData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,10 @@
|
||||
<properties>
|
||||
<property name="vendor">Hunter Douglas (Luxaflex)</property>
|
||||
<property name="modelId">PowerView Motorized Shade</property>
|
||||
<property name="type"></property>
|
||||
<property name="capabilities"></property>
|
||||
<property name="secondaryRailDetected"></property>
|
||||
<property name="tiltAnywhereDetected"></property>
|
||||
</properties>
|
||||
<representation-property>id</representation-property>
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
package org.openhab.binding.hdpowerview;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.openhab.binding.hdpowerview.internal.api.ActuatorClass.*;
|
||||
import static org.openhab.binding.hdpowerview.internal.api.CoordinateSystem.*;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -24,13 +23,11 @@ import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.binding.hdpowerview.internal.HDPowerViewWebTargets;
|
||||
import org.openhab.binding.hdpowerview.internal.HubMaintenanceException;
|
||||
import org.openhab.binding.hdpowerview.internal.HubProcessingException;
|
||||
import org.openhab.binding.hdpowerview.internal.api.CoordinateSystem;
|
||||
import org.openhab.binding.hdpowerview.internal.api.ShadePosition;
|
||||
import org.openhab.binding.hdpowerview.internal.api.responses.SceneCollections;
|
||||
import org.openhab.binding.hdpowerview.internal.api.responses.SceneCollections.SceneCollection;
|
||||
@@ -39,6 +36,8 @@ import org.openhab.binding.hdpowerview.internal.api.responses.Scenes.Scene;
|
||||
import org.openhab.binding.hdpowerview.internal.api.responses.Shade;
|
||||
import org.openhab.binding.hdpowerview.internal.api.responses.Shades;
|
||||
import org.openhab.binding.hdpowerview.internal.api.responses.Shades.ShadeData;
|
||||
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;
|
||||
@@ -47,7 +46,7 @@ import com.google.gson.Gson;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
/**
|
||||
* Unit tests for HD PowerView binding
|
||||
* Unit tests for HD PowerView binding.
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Initial contribution
|
||||
* @author Jacob Laursen - Add support for scene groups
|
||||
@@ -58,8 +57,10 @@ 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
|
||||
* load a test JSON string from a file.
|
||||
*/
|
||||
private String loadJson(String fileName) {
|
||||
try {
|
||||
@@ -72,7 +73,7 @@ public class HDPowerViewJUnitTests {
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a series of ONLINE tests on the communication with a hub
|
||||
* Run a series of ONLINE tests on the communication with a hub.
|
||||
*
|
||||
* @param hubIPAddress must be a valid hub IP address to run the
|
||||
* tests on; or an INVALID IP address to
|
||||
@@ -111,79 +112,36 @@ public class HDPowerViewJUnitTests {
|
||||
HDPowerViewWebTargets webTargets = new HDPowerViewWebTargets(client, hubIPAddress);
|
||||
assertNotNull(webTargets);
|
||||
|
||||
// ==== exercise some code ====
|
||||
ShadePosition test;
|
||||
State pos;
|
||||
|
||||
// shade fully up
|
||||
test = ShadePosition.create(ZERO_IS_CLOSED, 0);
|
||||
assertNotNull(test);
|
||||
pos = test.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED);
|
||||
assertEquals(PercentType.class, pos.getClass());
|
||||
assertEquals(0, ((PercentType) pos).intValue());
|
||||
pos = test.getState(PRIMARY_ACTUATOR, VANE_COORDS);
|
||||
assertTrue(UnDefType.UNDEF.equals(pos));
|
||||
|
||||
// shade fully down (method 1)
|
||||
test = ShadePosition.create(ZERO_IS_CLOSED, 100);
|
||||
assertNotNull(test);
|
||||
pos = test.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED);
|
||||
assertEquals(PercentType.class, pos.getClass());
|
||||
assertEquals(100, ((PercentType) pos).intValue());
|
||||
pos = test.getState(PRIMARY_ACTUATOR, VANE_COORDS);
|
||||
assertEquals(PercentType.class, pos.getClass());
|
||||
assertEquals(0, ((PercentType) pos).intValue());
|
||||
|
||||
// shade fully down (method 2)
|
||||
test = ShadePosition.create(VANE_COORDS, 0);
|
||||
assertNotNull(test);
|
||||
pos = test.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED);
|
||||
assertEquals(PercentType.class, pos.getClass());
|
||||
assertEquals(100, ((PercentType) pos).intValue());
|
||||
pos = test.getState(PRIMARY_ACTUATOR, VANE_COORDS);
|
||||
assertEquals(PercentType.class, pos.getClass());
|
||||
assertEquals(0, ((PercentType) pos).intValue());
|
||||
|
||||
// shade fully down (method 2) and vane fully open
|
||||
test = ShadePosition.create(VANE_COORDS, 100);
|
||||
assertNotNull(test);
|
||||
pos = test.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED);
|
||||
assertEquals(PercentType.class, pos.getClass());
|
||||
assertEquals(100, ((PercentType) pos).intValue());
|
||||
pos = test.getState(PRIMARY_ACTUATOR, VANE_COORDS);
|
||||
assertEquals(PercentType.class, pos.getClass());
|
||||
assertEquals(100, ((PercentType) pos).intValue());
|
||||
|
||||
int shadeId = 0;
|
||||
@Nullable
|
||||
ShadePosition shadePos = null;
|
||||
@Nullable
|
||||
Shades shadesX = null;
|
||||
|
||||
// ==== get all shades ====
|
||||
try {
|
||||
shadesX = webTargets.getShades();
|
||||
assertNotNull(shadesX);
|
||||
@Nullable
|
||||
List<ShadeData> shadesData = shadesX.shadeData;
|
||||
assertNotNull(shadesData);
|
||||
assertTrue(!shadesData.isEmpty());
|
||||
@Nullable
|
||||
ShadeData shadeData;
|
||||
shadeData = shadesData.get(0);
|
||||
assertNotNull(shadeData);
|
||||
assertTrue(shadeData.getName().length() > 0);
|
||||
shadePos = shadeData.positions;
|
||||
assertNotNull(shadePos);
|
||||
@Nullable
|
||||
ShadeData shadeZero = shadesData.get(0);
|
||||
assertNotNull(shadeZero);
|
||||
shadeId = shadeZero.id;
|
||||
assertNotEquals(0, shadeId);
|
||||
if (shadesX != null) {
|
||||
List<ShadeData> shadesData = shadesX.shadeData;
|
||||
assertNotNull(shadesData);
|
||||
|
||||
for (ShadeData shadexData : shadesData) {
|
||||
String shadeName = shadexData.getName();
|
||||
assertNotNull(shadeName);
|
||||
if (shadesData != null) {
|
||||
assertTrue(!shadesData.isEmpty());
|
||||
ShadeData shadeData;
|
||||
shadeData = shadesData.get(0);
|
||||
assertNotNull(shadeData);
|
||||
assertTrue(shadeData.getName().length() > 0);
|
||||
shadePos = shadeData.positions;
|
||||
assertNotNull(shadePos);
|
||||
ShadeData shadeZero = shadesData.get(0);
|
||||
assertNotNull(shadeZero);
|
||||
shadeId = shadeZero.id;
|
||||
assertNotEquals(0, shadeId);
|
||||
|
||||
for (ShadeData shadexData : shadesData) {
|
||||
String shadeName = shadexData.getName();
|
||||
assertNotNull(shadeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (JsonParseException | HubProcessingException | HubMaintenanceException e) {
|
||||
fail(e.getMessage());
|
||||
@@ -194,26 +152,29 @@ public class HDPowerViewJUnitTests {
|
||||
try {
|
||||
Scenes scenes = webTargets.getScenes();
|
||||
assertNotNull(scenes);
|
||||
@Nullable
|
||||
List<Scene> scenesData = scenes.sceneData;
|
||||
assertNotNull(scenesData);
|
||||
assertTrue(!scenesData.isEmpty());
|
||||
@Nullable
|
||||
Scene sceneZero = scenesData.get(0);
|
||||
assertNotNull(sceneZero);
|
||||
sceneId = sceneZero.id;
|
||||
assertTrue(sceneId > 0);
|
||||
|
||||
for (Scene scene : scenesData) {
|
||||
String sceneName = scene.getName();
|
||||
assertNotNull(sceneName);
|
||||
if (scenes != null) {
|
||||
List<Scene> scenesData = scenes.sceneData;
|
||||
assertNotNull(scenesData);
|
||||
|
||||
if (scenesData != null) {
|
||||
assertTrue(!scenesData.isEmpty());
|
||||
Scene sceneZero = scenesData.get(0);
|
||||
assertNotNull(sceneZero);
|
||||
sceneId = sceneZero.id;
|
||||
assertTrue(sceneId > 0);
|
||||
|
||||
for (Scene scene : scenesData) {
|
||||
String sceneName = scene.getName();
|
||||
assertNotNull(sceneName);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (JsonParseException | HubProcessingException | HubMaintenanceException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
// ==== refresh a specific shade ====
|
||||
@Nullable
|
||||
Shade shade = null;
|
||||
try {
|
||||
assertNotEquals(0, shadeId);
|
||||
@@ -227,28 +188,53 @@ public class HDPowerViewJUnitTests {
|
||||
try {
|
||||
assertNotEquals(0, shadeId);
|
||||
assertNotNull(shade);
|
||||
@Nullable
|
||||
ShadeData shadeData = shade.shade;
|
||||
assertNotNull(shadeData);
|
||||
ShadePosition positions = shadeData.positions;
|
||||
assertNotNull(positions);
|
||||
CoordinateSystem coordSys = positions.getCoordinateSystem(PRIMARY_ACTUATOR);
|
||||
assertNotNull(coordSys);
|
||||
if (shade != null) {
|
||||
ShadeData shadeData = shade.shade;
|
||||
assertNotNull(shadeData);
|
||||
|
||||
pos = positions.getState(PRIMARY_ACTUATOR, coordSys);
|
||||
assertEquals(PercentType.class, pos.getClass());
|
||||
if (shadeData != null) {
|
||||
ShadePosition positions = shadeData.positions;
|
||||
assertNotNull(positions);
|
||||
|
||||
pos = positions.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED);
|
||||
assertEquals(PercentType.class, pos.getClass());
|
||||
if (positions != null) {
|
||||
Integer capabilitiesValue = shadeData.capabilities;
|
||||
assertNotNull(capabilitiesValue);
|
||||
|
||||
int position = ((PercentType) pos).intValue();
|
||||
position = position + ((position <= 10) ? 5 : -5);
|
||||
if (capabilitiesValue != null) {
|
||||
Capabilities capabilities = db.getCapabilities(capabilitiesValue.intValue());
|
||||
|
||||
ShadePosition newPos = ShadePosition.create(ZERO_IS_CLOSED, position);
|
||||
assertNotNull(newPos);
|
||||
State pos = positions.getState(capabilities, PRIMARY_ZERO_IS_CLOSED);
|
||||
assertEquals(PercentType.class, pos.getClass());
|
||||
|
||||
if (allowShadeMovementCommands) {
|
||||
webTargets.moveShade(shadeId, newPos);
|
||||
int position = ((PercentType) pos).intValue();
|
||||
position = position + ((position <= 10) ? 5 : -5);
|
||||
|
||||
ShadePosition targetPosition = new ShadePosition().setPosition(capabilities,
|
||||
PRIMARY_ZERO_IS_CLOSED, position);
|
||||
assertNotNull(targetPosition);
|
||||
|
||||
if (allowShadeMovementCommands) {
|
||||
webTargets.moveShade(shadeId, targetPosition);
|
||||
|
||||
Shade newShade = webTargets.getShade(shadeId);
|
||||
assertNotNull(newShade);
|
||||
if (newShade != null) {
|
||||
ShadeData newData = newShade.shade;
|
||||
assertNotNull(newData);
|
||||
if (newData != null) {
|
||||
ShadePosition actualPosition = newData.positions;
|
||||
assertNotNull(actualPosition);
|
||||
if (actualPosition != null) {
|
||||
assertEquals(
|
||||
targetPosition.getState(capabilities, PRIMARY_ZERO_IS_CLOSED),
|
||||
actualPosition.getState(capabilities, PRIMARY_ZERO_IS_CLOSED));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (HubProcessingException | HubMaintenanceException e) {
|
||||
fail(e.getMessage());
|
||||
@@ -286,12 +272,78 @@ public class HDPowerViewJUnitTests {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test generic JSON shades response
|
||||
* 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.
|
||||
*/
|
||||
@Test
|
||||
public void shadeResponseIsParsedCorrectly() throws JsonParseException {
|
||||
final Gson gson = new Gson();
|
||||
@Nullable
|
||||
Shades shades;
|
||||
String json = loadJson("shades");
|
||||
assertNotEquals("", json);
|
||||
@@ -300,7 +352,7 @@ public class HDPowerViewJUnitTests {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test generic JSON scene response
|
||||
* Test generic JSON scene response.
|
||||
*/
|
||||
@Test
|
||||
public void sceneResponseIsParsedCorrectly() throws JsonParseException {
|
||||
@@ -308,23 +360,22 @@ public class HDPowerViewJUnitTests {
|
||||
String json = loadJson("scenes");
|
||||
assertNotEquals("", json);
|
||||
|
||||
@Nullable
|
||||
Scenes scenes = gson.fromJson(json, Scenes.class);
|
||||
assertNotNull(scenes);
|
||||
|
||||
@Nullable
|
||||
List<Scene> sceneData = scenes.sceneData;
|
||||
assertNotNull(sceneData);
|
||||
|
||||
assertEquals(4, sceneData.size());
|
||||
@Nullable
|
||||
Scene scene = sceneData.get(0);
|
||||
assertEquals("Door Open", scene.getName());
|
||||
assertEquals(18097, scene.id);
|
||||
if (scenes != null) {
|
||||
List<Scene> sceneData = scenes.sceneData;
|
||||
assertNotNull(sceneData);
|
||||
if (sceneData != null) {
|
||||
assertEquals(4, sceneData.size());
|
||||
Scene scene = sceneData.get(0);
|
||||
assertEquals("Door Open", scene.getName());
|
||||
assertEquals(18097, scene.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test generic JSON scene collection response
|
||||
* Test generic JSON scene collection response.
|
||||
*/
|
||||
@Test
|
||||
public void sceneCollectionResponseIsParsedCorrectly() throws JsonParseException {
|
||||
@@ -332,22 +383,24 @@ public class HDPowerViewJUnitTests {
|
||||
String json = loadJson("sceneCollections");
|
||||
assertNotEquals("", json);
|
||||
|
||||
@Nullable
|
||||
SceneCollections sceneCollections = gson.fromJson(json, SceneCollections.class);
|
||||
assertNotNull(sceneCollections);
|
||||
@Nullable
|
||||
List<SceneCollection> sceneCollectionData = sceneCollections.sceneCollectionData;
|
||||
assertNotNull(sceneCollectionData);
|
||||
|
||||
assertEquals(1, sceneCollectionData.size());
|
||||
@Nullable
|
||||
SceneCollection sceneCollection = sceneCollectionData.get(0);
|
||||
assertEquals("Børn op", sceneCollection.getName());
|
||||
assertEquals(27119, sceneCollection.id);
|
||||
if (sceneCollections != null) {
|
||||
List<SceneCollection> sceneCollectionData = sceneCollections.sceneCollectionData;
|
||||
assertNotNull(sceneCollectionData);
|
||||
if (sceneCollectionData != null) {
|
||||
assertEquals(1, sceneCollectionData.size());
|
||||
|
||||
SceneCollection sceneCollection = sceneCollectionData.get(0);
|
||||
assertEquals("Børn op", sceneCollection.getName());
|
||||
assertEquals(27119, sceneCollection.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the JSON parsing for a duette top down bottom up shade
|
||||
* Test the JSON parsing for a duette top down bottom up shade.
|
||||
*/
|
||||
@Test
|
||||
public void duetteTopDownBottomUpShadeIsParsedCorrectly() throws JsonParseException {
|
||||
@@ -355,38 +408,157 @@ public class HDPowerViewJUnitTests {
|
||||
String json = loadJson("duette");
|
||||
assertNotEquals("", json);
|
||||
|
||||
@Nullable
|
||||
Shades shades = gson.fromJson(json, Shades.class);
|
||||
assertNotNull(shades);
|
||||
@Nullable
|
||||
List<ShadeData> shadesData = shades.shadeData;
|
||||
assertNotNull(shadesData);
|
||||
if (shades != null) {
|
||||
List<ShadeData> shadesData = shades.shadeData;
|
||||
assertNotNull(shadesData);
|
||||
|
||||
assertEquals(1, shadesData.size());
|
||||
@Nullable
|
||||
ShadeData shadeData = shadesData.get(0);
|
||||
assertNotNull(shadeData);
|
||||
if (shadesData != null) {
|
||||
assertEquals(1, shadesData.size());
|
||||
ShadeData shadeData = shadesData.get(0);
|
||||
assertNotNull(shadeData);
|
||||
|
||||
assertEquals("Gardin 1", shadeData.getName());
|
||||
assertEquals(63778, shadeData.id);
|
||||
assertEquals("Gardin 1", shadeData.getName());
|
||||
assertEquals(63778, shadeData.id);
|
||||
|
||||
ShadePosition shadePos = shadeData.positions;
|
||||
assertNotNull(shadePos);
|
||||
assertEquals(ZERO_IS_CLOSED, shadePos.getCoordinateSystem(PRIMARY_ACTUATOR));
|
||||
ShadePosition shadePos = shadeData.positions;
|
||||
assertNotNull(shadePos);
|
||||
|
||||
State pos = shadePos.getState(PRIMARY_ACTUATOR, ZERO_IS_CLOSED);
|
||||
assertEquals(PercentType.class, pos.getClass());
|
||||
assertEquals(59, ((PercentType) pos).intValue());
|
||||
if (shadePos != null) {
|
||||
Integer capabilitiesValue = shadeData.capabilities;
|
||||
assertNotNull(capabilitiesValue);
|
||||
if (capabilitiesValue != null) {
|
||||
assertEquals(7, capabilitiesValue.intValue());
|
||||
|
||||
pos = shadePos.getState(SECONDARY_ACTUATOR, ZERO_IS_OPEN);
|
||||
assertEquals(PercentType.class, pos.getClass());
|
||||
assertEquals(35, ((PercentType) pos).intValue());
|
||||
Capabilities capabilities = db.getCapabilities(capabilitiesValue);
|
||||
|
||||
pos = shadePos.getState(PRIMARY_ACTUATOR, VANE_COORDS);
|
||||
assertEquals(UnDefType.class, pos.getClass());
|
||||
State pos = shadePos.getState(capabilities, PRIMARY_ZERO_IS_CLOSED);
|
||||
assertEquals(PercentType.class, pos.getClass());
|
||||
assertEquals(59, ((PercentType) pos).intValue());
|
||||
|
||||
assertEquals(3, shadeData.batteryStatus);
|
||||
pos = shadePos.getState(capabilities, SECONDARY_ZERO_IS_OPEN);
|
||||
assertEquals(PercentType.class, pos.getClass());
|
||||
assertEquals(35, ((PercentType) pos).intValue());
|
||||
|
||||
assertEquals(4, shadeData.signalStrength);
|
||||
pos = shadePos.getState(capabilities, VANE_TILT_COORDS);
|
||||
assertEquals(UnDefType.class, pos.getClass());
|
||||
|
||||
assertEquals(3, shadeData.batteryStatus);
|
||||
|
||||
assertEquals(4, shadeData.signalStrength);
|
||||
|
||||
assertEquals(8, shadeData.type);
|
||||
|
||||
assertTrue(db.isTypeInDatabase(shadeData.type));
|
||||
assertTrue(db.isCapabilitiesInDatabase(capabilitiesValue.intValue()));
|
||||
|
||||
assertEquals(db.getType(shadeData.type).getCapabilities(), capabilitiesValue.intValue());
|
||||
|
||||
assertTrue(db.getCapabilities(capabilitiesValue.intValue()).supportsSecondary());
|
||||
assertNotEquals(db.getType(shadeData.type).getCapabilities(), capabilitiesValue.intValue() + 1);
|
||||
|
||||
// ==== when changing position1, position2 value is not changed (vice-versa) ====
|
||||
ShadePosition shadePosition = shadeData.positions;
|
||||
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);
|
||||
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);
|
||||
assertEquals(PercentType.class, position1Old.getClass());
|
||||
assertEquals(PercentType.class, position1New.getClass());
|
||||
assertEquals(((PercentType) position1Old).intValue(),
|
||||
((PercentType) position1New).intValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user