[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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user