mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[velux] Add support for vane/slat position (#12618)
Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
This commit is contained in:
parent
ec5794739c
commit
b9782484c6
@ -38,6 +38,10 @@ The binding will automatically discover Velux Bridges within the local network,
|
||||
Once a Velux Bridge has been discovered, you will need to enter the `password` Configuration Parameter (see below) before the binding can communicate with it.
|
||||
And once the Velux Bridge is fully configured, the binding will automatically discover all its respective scenes and actuators (like windows and rollershutters), and place them in the Inbox.
|
||||
|
||||
Note: When the KLF200 hub is started it provides a temporary private Wi-Fi Access Point for initial configuration.
|
||||
And if any device connects to this AP, it disables the normal LAN connection, thus preventing the binding from connecting.
|
||||
So make sure this AP is not permanently on (the default setting is that the AP will turn off after some time).
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
### Thing Configuration for "bridge"
|
||||
@ -135,7 +139,7 @@ The supported Channels and their associated channel types are shown below.
|
||||
| downtime | Number | Time interval (sec) between last successful and most recent device interaction. |
|
||||
| doDetection | Switch | Command to activate bridge detection mode. |
|
||||
|
||||
### Channels for "window" / "rollershutter" Things
|
||||
### Channels for "window" Things
|
||||
|
||||
The supported Channels and their associated channel types are shown below.
|
||||
|
||||
@ -154,6 +158,23 @@ The `position` Channel indicates the open/close state of the window (resp. rolle
|
||||
- In case of errors (e.g. window jammed) the display is `UNDEF`.
|
||||
- If a Somfy actuator is commanded to its 'favorite' position via a Somfy remote control, under some circumstances the display is `UNDEF`. See also Rules below.
|
||||
|
||||
### Channels for "rollershutter" Things
|
||||
|
||||
The supported Channels and their associated channel types are shown below.
|
||||
|
||||
| Channel | Data Type | Description |
|
||||
|--------------|---------------|-------------------------------------------------|
|
||||
| position | Rollershutter | Actual position of the window or device. |
|
||||
| limitMinimum | Rollershutter | Minimum limit position of the window or device. |
|
||||
| limitMaximum | Rollershutter | Maximum limit position of the window or device. |
|
||||
| vanePosition | Dimmer | Vane position of a Venetian blind. |
|
||||
|
||||
The `position`, `limitMinimum`, and `limitMaximum` are the same as described above for "window" Things.
|
||||
|
||||
The `vanePosition` Channel only applies to Venetian blinds that have tiltable slats.
|
||||
It can only have a valid position value if the main `position` of the Thing is fully down.
|
||||
So, if `vanePosition` is commanded to a new value, this will automatically cause the main `position` to move to the fully down position.
|
||||
|
||||
### Channels for "actuator" Things
|
||||
|
||||
The supported Channels and their associated channel types are shown below.
|
||||
|
@ -142,6 +142,7 @@ public class VeluxBindingConstants {
|
||||
public static final String CHANNEL_ACTUATOR_SILENTMODE = "silentMode";
|
||||
public static final String CHANNEL_ACTUATOR_LIMIT_MINIMUM = "limitMinimum";
|
||||
public static final String CHANNEL_ACTUATOR_LIMIT_MAXIMUM = "limitMaximum";
|
||||
public static final String CHANNEL_VANE_POSITION = "vanePosition";
|
||||
|
||||
// List of all virtual shutter channel ids
|
||||
public static final String CHANNEL_VSHUTTER_POSITION = "vposition";
|
||||
|
@ -102,6 +102,7 @@ public enum VeluxItemType {
|
||||
ROLLERSHUTTER_POSITION(VeluxBindingConstants.THING_TYPE_VELUX_ROLLERSHUTTER,VeluxBindingConstants.CHANNEL_ACTUATOR_POSITION, TypeFlavor.MANIPULATOR_SHUTTER),
|
||||
ROLLERSHUTTER_LIMIT_MINIMUM(VeluxBindingConstants.THING_TYPE_VELUX_ROLLERSHUTTER,VeluxBindingConstants.CHANNEL_ACTUATOR_LIMIT_MINIMUM,TypeFlavor.MANIPULATOR_SHUTTER),
|
||||
ROLLERSHUTTER_LIMIT_MAXIMUM(VeluxBindingConstants.THING_TYPE_VELUX_ROLLERSHUTTER,VeluxBindingConstants.CHANNEL_ACTUATOR_LIMIT_MAXIMUM,TypeFlavor.MANIPULATOR_SHUTTER),
|
||||
ROLLERSHUTTER_VANE_POSITION(VeluxBindingConstants.THING_TYPE_VELUX_ROLLERSHUTTER,VeluxBindingConstants.CHANNEL_VANE_POSITION, TypeFlavor.MANIPULATOR_SHUTTER),
|
||||
//
|
||||
WINDOW_POSITION(VeluxBindingConstants.THING_TYPE_VELUX_WINDOW, VeluxBindingConstants.CHANNEL_ACTUATOR_POSITION, TypeFlavor.MANIPULATOR_SHUTTER),
|
||||
WINDOW_LIMIT_MINIMUM(VeluxBindingConstants.THING_TYPE_VELUX_WINDOW, VeluxBindingConstants.CHANNEL_ACTUATOR_LIMIT_MINIMUM,TypeFlavor.MANIPULATOR_SHUTTER),
|
||||
|
@ -1,63 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.velux.internal.bridge;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.velux.internal.bridge.common.RunProductCommand;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProductPosition;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link VeluxBridgeRunProductCommand} represents a complete set of transactions
|
||||
* for executing a scene defined on the <B>Velux</B> bridge.
|
||||
* <P>
|
||||
* It provides a method {@link VeluxBridgeRunProductCommand#sendCommand} for sending a parameter change command.
|
||||
* Any parameters are controlled by {@link org.openhab.binding.velux.internal.config.VeluxBridgeConfiguration}.
|
||||
*
|
||||
* @see VeluxBridgeProvider
|
||||
*
|
||||
* @author Guenther Schreiner - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VeluxBridgeRunProductCommand {
|
||||
private final Logger logger = LoggerFactory.getLogger(VeluxBridgeRunProductCommand.class);
|
||||
|
||||
// Class access methods
|
||||
|
||||
/**
|
||||
* Login into bridge, instruct the bridge to pass a command towards an actuator based
|
||||
* on a well-prepared environment of a {@link VeluxBridgeProvider}.
|
||||
*
|
||||
* @param bridge Initialized Velux bridge handler.
|
||||
* @param nodeId Number of Actuator to be modified.
|
||||
* @param value Target value for Actuator main parameter.
|
||||
* @return true if successful, and false otherwise.
|
||||
*/
|
||||
public boolean sendCommand(VeluxBridge bridge, int nodeId, VeluxProductPosition value) {
|
||||
logger.trace("sendCommand(nodeId={},value={}) called.", nodeId, value);
|
||||
|
||||
boolean success = false;
|
||||
RunProductCommand bcp = bridge.bridgeAPI().runProductCommand();
|
||||
if (bcp != null) {
|
||||
int veluxValue = value.getPositionAsVeluxType();
|
||||
|
||||
bcp.setNodeAndMainParameter(nodeId, veluxValue);
|
||||
if (bridge.bridgeCommunicate(bcp) && bcp.isCommunicationSuccessful()) {
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
logger.debug("sendCommand() finished {}.", (success ? "successfully" : "with failure"));
|
||||
return success;
|
||||
}
|
||||
}
|
@ -107,4 +107,7 @@ public interface BridgeAPI {
|
||||
|
||||
@Nullable
|
||||
RunReboot runReboot();
|
||||
|
||||
@Nullable
|
||||
GetProduct getProductStatus();
|
||||
}
|
||||
|
@ -13,6 +13,9 @@
|
||||
package org.openhab.binding.velux.internal.bridge.common;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.velux.internal.bridge.slip.FunctionalParameters;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProductPosition;
|
||||
|
||||
/**
|
||||
* <B>Common bridge communication message scheme supported by the </B><I>Velux</I><B> bridge.</B>
|
||||
@ -22,7 +25,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
* In addition to the common methods defined by {@link BridgeCommunicationProtocol}
|
||||
* each protocol-specific implementation has to provide the following methods:
|
||||
* <UL>
|
||||
* <LI>{@link #setNodeAndMainParameter} for defining the intended node and the main parameter value.
|
||||
* <LI>{@link #setNodeIdAndParameters} for defining the intended node and the main parameter value.
|
||||
* </UL>
|
||||
*
|
||||
* @see BridgeCommunicationProtocol
|
||||
@ -35,9 +38,11 @@ public abstract class RunProductCommand implements BridgeCommunicationProtocol {
|
||||
/**
|
||||
* Modifies the state of an actuator
|
||||
*
|
||||
* @param actuatorId Gateway internal actuator identifier (zero to 199).
|
||||
* @param parameterValue target device state.
|
||||
* @return reference to the class instance.
|
||||
* @param nodeId Gateway internal actuator identifier (zero to 199).
|
||||
* @param mainParameter target device state.
|
||||
* @param functionalParameters the target Functional Parameters.
|
||||
* @return true if the method succeeds
|
||||
*/
|
||||
public abstract RunProductCommand setNodeAndMainParameter(int actuatorId, int parameterValue);
|
||||
public abstract boolean setNodeIdAndParameters(int nodeId, @Nullable VeluxProductPosition mainParameter,
|
||||
@Nullable FunctionalParameters functionalParameters);
|
||||
}
|
||||
|
@ -211,4 +211,9 @@ class JsonBridgeAPI implements BridgeAPI {
|
||||
public @Nullable RunReboot runReboot() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable GetProduct getProductStatus() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,207 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.velux.internal.bridge.slip;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.velux.internal.bridge.slip.utils.Packet;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProductPosition;
|
||||
|
||||
/**
|
||||
* Implementation of API Functional Parameters.
|
||||
* Supports an array of of four Functional Parameter values { FP1 .. FP4 }
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Initial contribution.
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class FunctionalParameters {
|
||||
private static final int FUNCTIONAL_PARAMETER_COUNT = 4;
|
||||
|
||||
private final int[] values;
|
||||
|
||||
/**
|
||||
* Private constructor to create a FunctionalParameters instance with all empty values.
|
||||
*/
|
||||
private FunctionalParameters() {
|
||||
values = new int[FUNCTIONAL_PARAMETER_COUNT];
|
||||
Arrays.fill(values, VeluxProductPosition.VPP_VELUX_UNKNOWN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Public constructor to create a FunctionalParameters instance from one specific value at one specific index.
|
||||
*/
|
||||
public FunctionalParameters(int index, int newValue) {
|
||||
this();
|
||||
values[index] = newValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionalParameters clone() {
|
||||
FunctionalParameters result = new FunctionalParameters();
|
||||
System.arraycopy(values, 0, result.values, 0, FUNCTIONAL_PARAMETER_COUNT);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("{0x%04X, 0x%04X, 0x%04X, 0x%04X}", values[0], values[1], values[2], values[3]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the functional parameter value at index.
|
||||
*
|
||||
* @return the value at the index.
|
||||
*/
|
||||
public int getValue(int index) {
|
||||
return values[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Functional Parameters instance from the merger of the data in 'foundationFunctionalParameters' and
|
||||
* 'substituteFunctionalParameters'. Invalid parameter values are not merged. If either
|
||||
* 'foundationFunctionalParameters' or 'substituteFunctionalParameters' is null, the merge includes only the data
|
||||
* from the non null parameter set. And if both sets of parameters are null then the result is also null.
|
||||
*
|
||||
* @param foundationFunctionalParameters the Functional Parameters to be used as the foundation for the merge.
|
||||
* @param substituteFunctionalParameters the Functional Parameters to substituted on top (if they can be).
|
||||
* @return a new Functional Parameters class instance containing the merged data.
|
||||
*/
|
||||
public static @Nullable FunctionalParameters createMergeSubstitute(
|
||||
@Nullable FunctionalParameters foundationFunctionalParameters,
|
||||
@Nullable FunctionalParameters substituteFunctionalParameters) {
|
||||
if (foundationFunctionalParameters == null && substituteFunctionalParameters == null) {
|
||||
return null;
|
||||
}
|
||||
FunctionalParameters result = new FunctionalParameters();
|
||||
if (foundationFunctionalParameters != null) {
|
||||
for (int i = 0; i < FUNCTIONAL_PARAMETER_COUNT; i++) {
|
||||
if (isNormalPosition(foundationFunctionalParameters.values[i])) {
|
||||
result.values[i] = foundationFunctionalParameters.values[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (substituteFunctionalParameters != null) {
|
||||
for (int i = 0; i < FUNCTIONAL_PARAMETER_COUNT; i++) {
|
||||
if (isNormalPosition(substituteFunctionalParameters.values[i])) {
|
||||
result.values[i] = substituteFunctionalParameters.values[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given parameter value is a normal actuator position value (i.e. 0x0000 .. 0xC800).
|
||||
*
|
||||
* @param paramValue the value to be checked.
|
||||
* @return true if it is a normal actuator position value.
|
||||
*/
|
||||
public static boolean isNormalPosition(int paramValue) {
|
||||
return (paramValue >= VeluxProductPosition.VPP_VELUX_MIN) && (paramValue <= VeluxProductPosition.VPP_VELUX_MAX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a FunctionalParameters instance from the given Packet. Where the parameters are packed into an array of
|
||||
* two byte integer values.
|
||||
*
|
||||
* @param responseData the Packet to read from.
|
||||
* @param startPosition the read starting position.
|
||||
* @return this object.
|
||||
*/
|
||||
public static @Nullable FunctionalParameters readArray(Packet responseData, int startPosition) {
|
||||
int pos = startPosition;
|
||||
boolean isValid = false;
|
||||
FunctionalParameters result = new FunctionalParameters();
|
||||
for (int i = 0; i < FUNCTIONAL_PARAMETER_COUNT; i++) {
|
||||
int value = responseData.getTwoByteValue(pos);
|
||||
if (isNormalPosition(value)) {
|
||||
result.values[i] = value;
|
||||
isValid = true;
|
||||
}
|
||||
pos = pos + 2;
|
||||
}
|
||||
return isValid ? result : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a FunctionalParameters instance from the given Packet. Where the parameters are packed into an array of
|
||||
* three byte records each comprising a one byte index followed by a two byte integer value.
|
||||
*
|
||||
* @param responseData the Packet to read from.
|
||||
* @param startPosition the read starting position.
|
||||
* @return this object.
|
||||
*/
|
||||
public static @Nullable FunctionalParameters readArrayIndexed(Packet responseData, int startPosition) {
|
||||
int pos = startPosition;
|
||||
boolean isValid = false;
|
||||
FunctionalParameters result = new FunctionalParameters();
|
||||
for (int i = 0; i < FUNCTIONAL_PARAMETER_COUNT; i++) {
|
||||
int index = responseData.getOneByteValue(pos) - 1;
|
||||
pos++;
|
||||
if ((index >= 0) && (index < FUNCTIONAL_PARAMETER_COUNT)) {
|
||||
int value = responseData.getTwoByteValue(pos);
|
||||
if (isNormalPosition(value)) {
|
||||
result.values[index] = value;
|
||||
isValid = true;
|
||||
}
|
||||
}
|
||||
pos = pos + 2;
|
||||
}
|
||||
return isValid ? result : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the Functional Parameters to the given packet. Only writes normal valid position values.
|
||||
*
|
||||
* @param requestData the Packet to write to.
|
||||
* @param startPosition the write starting position.
|
||||
* @return fpIndex a bit map that indicates which of the written Functional Parameters contains a normal valid
|
||||
* position value.
|
||||
*/
|
||||
public int writeArray(Packet requestData, int startPosition) {
|
||||
int bitMask = 0b10000000;
|
||||
int pos = startPosition;
|
||||
int fpIndex = 0;
|
||||
for (int i = 0; i < FUNCTIONAL_PARAMETER_COUNT; i++) {
|
||||
if (isNormalPosition(values[i])) {
|
||||
fpIndex |= bitMask;
|
||||
requestData.setTwoByteValue(pos, values[i]);
|
||||
}
|
||||
pos = pos + 2;
|
||||
bitMask = bitMask >>> 1;
|
||||
}
|
||||
return fpIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (!(obj instanceof FunctionalParameters)) {
|
||||
return false;
|
||||
}
|
||||
FunctionalParameters other = (FunctionalParameters) obj;
|
||||
for (int i = 0; i < FUNCTIONAL_PARAMETER_COUNT; i++) {
|
||||
if (values[i] != other.values[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(values);
|
||||
};
|
||||
}
|
@ -19,6 +19,9 @@ import org.openhab.binding.velux.internal.bridge.slip.utils.KLF200Response;
|
||||
import org.openhab.binding.velux.internal.bridge.slip.utils.Packet;
|
||||
import org.openhab.binding.velux.internal.things.VeluxKLFAPI.Command;
|
||||
import org.openhab.binding.velux.internal.things.VeluxKLFAPI.CommandNumber;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProduct;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProduct.ProductBridgeIndex;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProductName;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -49,7 +52,8 @@ import org.slf4j.LoggerFactory;
|
||||
* @author Guenther Schreiner - Initial contribution.
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class SCgetHouseStatus extends GetHouseStatus implements BridgeCommunicationProtocol, SlipBridgeCommunicationProtocol {
|
||||
public class SCgetHouseStatus extends GetHouseStatus
|
||||
implements BridgeCommunicationProtocol, SlipBridgeCommunicationProtocol {
|
||||
private final Logger logger = LoggerFactory.getLogger(SCgetHouseStatus.class);
|
||||
|
||||
private static final String DESCRIPTION = "Retrieve House Status";
|
||||
@ -71,10 +75,8 @@ class SCgetHouseStatus extends GetHouseStatus implements BridgeCommunicationProt
|
||||
private boolean success = false;
|
||||
private boolean finished = false;
|
||||
|
||||
private int ntfNodeID;
|
||||
private int ntfState;
|
||||
private int ntfCurrentPosition;
|
||||
private int ntfTarget;
|
||||
private Command creatorCommand = Command.UNDEFTYPE;
|
||||
private VeluxProduct product = VeluxProduct.UNKNOWN;
|
||||
|
||||
/*
|
||||
* ===========================================================
|
||||
@ -103,32 +105,37 @@ class SCgetHouseStatus extends GetHouseStatus implements BridgeCommunicationProt
|
||||
success = false;
|
||||
finished = true;
|
||||
Packet responseData = new Packet(thisResponseData);
|
||||
switch (Command.get(responseCommand)) {
|
||||
Command responseCmd = Command.get(responseCommand);
|
||||
switch (responseCmd) {
|
||||
case GW_NODE_STATE_POSITION_CHANGED_NTF:
|
||||
if (!KLF200Response.isLengthValid(logger, responseCommand, thisResponseData, 20)) {
|
||||
break;
|
||||
}
|
||||
ntfNodeID = responseData.getOneByteValue(0);
|
||||
ntfState = responseData.getOneByteValue(1);
|
||||
ntfCurrentPosition = responseData.getTwoByteValue(2);
|
||||
ntfTarget = responseData.getTwoByteValue(4);
|
||||
@SuppressWarnings("unused")
|
||||
int ntfFP1CurrentPosition = responseData.getTwoByteValue(6);
|
||||
@SuppressWarnings("unused")
|
||||
int ntfFP2CurrentPosition = responseData.getTwoByteValue(8);
|
||||
@SuppressWarnings("unused")
|
||||
int ntfFP3CurrentPosition = responseData.getTwoByteValue(10);
|
||||
@SuppressWarnings("unused")
|
||||
int ntfFP4CurrentPosition = responseData.getTwoByteValue(12);
|
||||
int ntfNodeID = responseData.getOneByteValue(0);
|
||||
int ntfState = responseData.getOneByteValue(1);
|
||||
int ntfCurrentPosition = responseData.getTwoByteValue(2);
|
||||
int ntfTarget = responseData.getTwoByteValue(4);
|
||||
FunctionalParameters ntfFunctionalParameters = FunctionalParameters.readArray(responseData, 6);
|
||||
int ntfRemainingTime = responseData.getTwoByteValue(14);
|
||||
int ntfTimeStamp = responseData.getFourByteValue(16);
|
||||
// Extracting information items
|
||||
logger.trace("setResponse(): ntfNodeID={}.", ntfNodeID);
|
||||
logger.trace("setResponse(): ntfState={}.", ntfState);
|
||||
logger.trace("setResponse(): ntfCurrentPosition={}.", ntfCurrentPosition);
|
||||
logger.trace("setResponse(): ntfTarget={}.", ntfTarget);
|
||||
logger.trace("setResponse(): ntfRemainingTime={}.", ntfRemainingTime);
|
||||
logger.trace("setResponse(): ntfTimeStamp={}.", ntfTimeStamp);
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("setResponse(): ntfNodeID={}.", ntfNodeID);
|
||||
logger.trace("setResponse(): ntfState={}.", ntfState);
|
||||
logger.trace("setResponse(): ntfCurrentPosition={}.", String.format("0x%04X", ntfCurrentPosition));
|
||||
logger.trace("setResponse(): ntfTarget={}.", String.format("0x%04X", ntfTarget));
|
||||
logger.trace("setResponse(): ntfFunctionalParameters={} (returns null).", ntfFunctionalParameters);
|
||||
logger.trace("setResponse(): ntfRemainingTime={}.", ntfRemainingTime);
|
||||
logger.trace("setResponse(): ntfTimeStamp={}.", ntfTimeStamp);
|
||||
}
|
||||
|
||||
// this BCP returns wrong functional parameters on some (e.g. Somfy) devices so return null instead
|
||||
ntfFunctionalParameters = null;
|
||||
|
||||
// create notification product with the returned values
|
||||
product = new VeluxProduct(VeluxProductName.UNKNOWN, new ProductBridgeIndex(ntfNodeID), ntfState,
|
||||
ntfCurrentPosition, ntfTarget, ntfFunctionalParameters, creatorCommand);
|
||||
|
||||
success = true;
|
||||
break;
|
||||
|
||||
@ -153,31 +160,19 @@ class SCgetHouseStatus extends GetHouseStatus implements BridgeCommunicationProt
|
||||
* Methods in addition to the interface {@link BridgeCommunicationProtocol}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @return <b>ntfNodeID</b> returns the Actuator Id as int.
|
||||
*/
|
||||
public int getNtfNodeID() {
|
||||
return ntfNodeID;
|
||||
public VeluxProduct getProduct() {
|
||||
logger.trace("getProduct(): returning {}.", product);
|
||||
return product;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <b>ntfState</b> returns the state of the Actuator as int.
|
||||
* Change the command id that identifies the API on which 'product' will be created.
|
||||
*
|
||||
* @param creatorCommand the API that will be used to create the product instance.
|
||||
* @return this
|
||||
*/
|
||||
public int getNtfState() {
|
||||
return ntfState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <b>ntfCurrentPosition</b> returns the current position of the Actuator as int.
|
||||
*/
|
||||
public int getNtfCurrentPosition() {
|
||||
return ntfCurrentPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <b>ntfTarget</b> returns the target position of the Actuator as int.
|
||||
*/
|
||||
public int getNtfTarget() {
|
||||
return ntfTarget;
|
||||
public SCgetHouseStatus setCreatorCommand(Command creatorCommand) {
|
||||
this.creatorCommand = creatorCommand;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import org.openhab.binding.velux.internal.things.VeluxProduct.ProductBridgeIndex
|
||||
import org.openhab.binding.velux.internal.things.VeluxProductName;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProductSerialNo;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProductType;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProductType.ActuatorType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -50,7 +51,7 @@ import org.slf4j.LoggerFactory;
|
||||
* @author Guenther Schreiner - Initial contribution.
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class SCgetProduct extends GetProduct implements SlipBridgeCommunicationProtocol {
|
||||
public class SCgetProduct extends GetProduct implements SlipBridgeCommunicationProtocol {
|
||||
private final Logger logger = LoggerFactory.getLogger(SCgetProduct.class);
|
||||
|
||||
private static final String DESCRIPTION = "Retrieve Product";
|
||||
@ -151,62 +152,59 @@ class SCgetProduct extends GetProduct implements SlipBridgeCommunicationProtocol
|
||||
}
|
||||
// Extracting information items
|
||||
int ntfNodeID = responseData.getOneByteValue(0);
|
||||
logger.trace("setResponse(): ntfNodeID={}.", ntfNodeID);
|
||||
int ntfOrder = responseData.getTwoByteValue(1);
|
||||
logger.trace("setResponse(): ntfOrder={}.", ntfOrder);
|
||||
int ntfPlacement = responseData.getOneByteValue(3);
|
||||
logger.trace("setResponse(): ntfPlacement={}.", ntfPlacement);
|
||||
String ntfName = responseData.getString(4, 64);
|
||||
logger.trace("setResponse(): ntfName={}.", ntfName);
|
||||
int ntfVelocity = responseData.getOneByteValue(68);
|
||||
logger.trace("setResponse(): ntfVelocity={}.", ntfVelocity);
|
||||
int ntfNodeTypeSubType = responseData.getTwoByteValue(69);
|
||||
logger.trace("setResponse(): ntfNodeTypeSubType={} ({}).", ntfNodeTypeSubType,
|
||||
VeluxProductType.get(ntfNodeTypeSubType));
|
||||
logger.trace("setResponse(): derived product description={}.",
|
||||
VeluxProductType.toString(ntfNodeTypeSubType));
|
||||
int ntfProductGroup = responseData.getTwoByteValue(71);
|
||||
logger.trace("setResponse(): ntfProductGroup={}.", ntfProductGroup);
|
||||
int ntfProductType = responseData.getOneByteValue(72);
|
||||
logger.trace("setResponse(): ntfProductType={}.", ntfProductType);
|
||||
int ntfNodeVariation = responseData.getOneByteValue(73);
|
||||
logger.trace("setResponse(): ntfNodeVariation={}.", ntfNodeVariation);
|
||||
int ntfPowerMode = responseData.getOneByteValue(74);
|
||||
logger.trace("setResponse(): ntfPowerMode={}.", ntfPowerMode);
|
||||
int ntfBuildNumber = responseData.getOneByteValue(75);
|
||||
logger.trace("setResponse(): ntfBuildNumber={}.", ntfBuildNumber);
|
||||
byte[] ntfSerialNumber = responseData.getByteArray(76, 8);
|
||||
logger.trace("setResponse(): ntfSerialNumber={}.", ntfSerialNumber);
|
||||
int ntfState = responseData.getOneByteValue(84);
|
||||
logger.trace("setResponse(): ntfState={}.", ntfState);
|
||||
int ntfCurrentPosition = responseData.getTwoByteValue(85);
|
||||
logger.trace("setResponse(): ntfCurrentPosition={}.", ntfCurrentPosition);
|
||||
int ntfTarget = responseData.getTwoByteValue(87);
|
||||
logger.trace("setResponse(): ntfTarget={}.", ntfTarget);
|
||||
int ntfFP1CurrentPosition = responseData.getTwoByteValue(89);
|
||||
logger.trace("setResponse(): ntfFP1CurrentPosition={}.", ntfFP1CurrentPosition);
|
||||
int ntfFP2CurrentPosition = responseData.getTwoByteValue(91);
|
||||
logger.trace("setResponse(): ntfFP2CurrentPosition={}.", ntfFP2CurrentPosition);
|
||||
int ntfFP3CurrentPosition = responseData.getTwoByteValue(93);
|
||||
logger.trace("setResponse(): ntfFP3CurrentPosition={}.", ntfFP3CurrentPosition);
|
||||
int ntfFP4CurrentPosition = responseData.getTwoByteValue(95);
|
||||
logger.trace("setResponse(): ntfFP4CurrentPosition={}.", ntfFP4CurrentPosition);
|
||||
FunctionalParameters ntfFunctionalParameters = FunctionalParameters.readArray(responseData, 89);
|
||||
int ntfRemainingTime = responseData.getFourByteValue(97);
|
||||
logger.trace("setResponse(): ntfRemainingTime={}.", ntfRemainingTime);
|
||||
int ntfTimeStamp = responseData.getFourByteValue(99);
|
||||
logger.trace("setResponse(): ntfTimeStamp={}.", ntfTimeStamp);
|
||||
int ntfNbrOfAlias = responseData.getOneByteValue(103);
|
||||
logger.trace("setResponse(): ntfNbrOfAlias={}.", ntfNbrOfAlias);
|
||||
int ntfAliasOne = responseData.getFourByteValue(104);
|
||||
logger.trace("setResponse(): ntfAliasOne={}.", ntfAliasOne);
|
||||
int ntfAliasTwo = responseData.getFourByteValue(108);
|
||||
logger.trace("setResponse(): ntfAliasTwo={}.", ntfAliasTwo);
|
||||
int ntfAliasThree = responseData.getFourByteValue(112);
|
||||
logger.trace("setResponse(): ntfAliasThree={}.", ntfAliasThree);
|
||||
int ntfAliasFour = responseData.getFourByteValue(116);
|
||||
logger.trace("setResponse(): ntfAliasFour={}.", ntfAliasFour);
|
||||
int ntfAliasFive = responseData.getFourByteValue(120);
|
||||
logger.trace("setResponse(): ntfAliasFive={}.", ntfAliasFive);
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("setResponse(): ntfNodeID={}.", ntfNodeID);
|
||||
logger.trace("setResponse(): ntfOrder={}.", ntfOrder);
|
||||
logger.trace("setResponse(): ntfPlacement={}.", ntfPlacement);
|
||||
logger.trace("setResponse(): ntfName={}.", ntfName);
|
||||
logger.trace("setResponse(): ntfVelocity={}.", ntfVelocity);
|
||||
logger.trace("setResponse(): ntfNodeTypeSubType={} ({}).", ntfNodeTypeSubType,
|
||||
VeluxProductType.get(ntfNodeTypeSubType));
|
||||
logger.trace("setResponse(): derived product description={}.",
|
||||
VeluxProductType.toString(ntfNodeTypeSubType));
|
||||
logger.trace("setResponse(): ntfProductGroup={}.", ntfProductGroup);
|
||||
logger.trace("setResponse(): ntfProductType={}.", ntfProductType);
|
||||
logger.trace("setResponse(): ntfNodeVariation={}.", ntfNodeVariation);
|
||||
logger.trace("setResponse(): ntfPowerMode={}.", ntfPowerMode);
|
||||
logger.trace("setResponse(): ntfBuildNumber={}.", ntfBuildNumber);
|
||||
logger.trace("setResponse(): ntfSerialNumber={}.", VeluxProductSerialNo.toString(ntfSerialNumber));
|
||||
logger.trace("setResponse(): ntfState={}.", ntfState);
|
||||
logger.trace("setResponse(): ntfCurrentPosition={}.", String.format("0x%04X", ntfCurrentPosition));
|
||||
logger.trace("setResponse(): ntfTarget={}.", String.format("0x%04X", ntfTarget));
|
||||
logger.trace("setResponse(): ntfFunctionalParameters={} (returns null).", ntfFunctionalParameters);
|
||||
logger.trace("setResponse(): ntfRemainingTime={}.", ntfRemainingTime);
|
||||
logger.trace("setResponse(): ntfTimeStamp={}.", ntfTimeStamp);
|
||||
logger.trace("setResponse(): ntfNbrOfAlias={}.", ntfNbrOfAlias);
|
||||
logger.trace("setResponse(): ntfAliasOne={}.", ntfAliasOne);
|
||||
logger.trace("setResponse(): ntfAliasTwo={}.", ntfAliasTwo);
|
||||
logger.trace("setResponse(): ntfAliasThree={}.", ntfAliasThree);
|
||||
logger.trace("setResponse(): ntfAliasFour={}.", ntfAliasFour);
|
||||
logger.trace("setResponse(): ntfAliasFive={}.", ntfAliasFive);
|
||||
}
|
||||
|
||||
if (!KLF200Response.check4matchingNodeID(logger, reqNodeID, ntfNodeID)) {
|
||||
break;
|
||||
@ -216,17 +214,24 @@ class SCgetProduct extends GetProduct implements SlipBridgeCommunicationProtocol
|
||||
ntfName = "#".concat(String.valueOf(ntfNodeID));
|
||||
logger.debug("setResponse(): device provided invalid name, using '{}' instead.", ntfName);
|
||||
}
|
||||
String commonSerialNumber = VeluxProductSerialNo.toString(ntfSerialNumber);
|
||||
|
||||
String ntfSerialNumberString = VeluxProductSerialNo.toString(ntfSerialNumber);
|
||||
if (VeluxProductSerialNo.isInvalid(ntfSerialNumber)) {
|
||||
commonSerialNumber = new String(ntfName);
|
||||
ntfSerialNumberString = new String(ntfName);
|
||||
logger.debug("setResponse(): device provided invalid serial number, using name '{}' instead.",
|
||||
commonSerialNumber);
|
||||
ntfSerialNumberString);
|
||||
}
|
||||
|
||||
// this BCP returns wrong functional parameters on some (e.g. Somfy) devices so return null instead
|
||||
ntfFunctionalParameters = null;
|
||||
|
||||
// create notification product with the returned values
|
||||
product = new VeluxProduct(new VeluxProductName(ntfName), VeluxProductType.get(ntfNodeTypeSubType),
|
||||
new ProductBridgeIndex(ntfNodeID), ntfOrder, ntfPlacement, ntfVelocity, ntfNodeVariation,
|
||||
ntfPowerMode, commonSerialNumber, ntfState, ntfCurrentPosition, ntfTarget, ntfRemainingTime,
|
||||
ntfTimeStamp);
|
||||
ActuatorType.get(ntfNodeTypeSubType), new ProductBridgeIndex(ntfNodeID), ntfOrder, ntfPlacement,
|
||||
ntfVelocity, ntfNodeVariation, ntfPowerMode, ntfSerialNumberString, ntfState,
|
||||
ntfCurrentPosition, ntfTarget, ntfFunctionalParameters, ntfRemainingTime, ntfTimeStamp,
|
||||
COMMAND);
|
||||
|
||||
success = true;
|
||||
break;
|
||||
|
||||
@ -255,13 +260,12 @@ class SCgetProduct extends GetProduct implements SlipBridgeCommunicationProtocol
|
||||
@Override
|
||||
public void setProductId(int nodeId) {
|
||||
logger.trace("setProductId({}) called.", nodeId);
|
||||
this.reqNodeID = nodeId;
|
||||
return;
|
||||
reqNodeID = nodeId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VeluxProduct getProduct() {
|
||||
logger.trace("getProduct(): returning product {}.", product);
|
||||
logger.trace("getProduct(): returning {}.", product);
|
||||
return product;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,284 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.velux.internal.bridge.slip;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.velux.internal.bridge.common.GetProduct;
|
||||
import org.openhab.binding.velux.internal.bridge.slip.utils.KLF200Response;
|
||||
import org.openhab.binding.velux.internal.bridge.slip.utils.Packet;
|
||||
import org.openhab.binding.velux.internal.things.VeluxKLFAPI.Command;
|
||||
import org.openhab.binding.velux.internal.things.VeluxKLFAPI.CommandNumber;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProduct;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProduct.ProductBridgeIndex;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProductName;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProductPosition;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Protocol specific bridge communication supported by the Velux bridge:
|
||||
* <B>Retrieve Product Status</B>
|
||||
* <p>
|
||||
* This implements an alternate API set (vs. the API set used by ScgetProduct) for retrieving a product's status. This
|
||||
* alternate API set was added to the code base because, when using ScgetProduct, some products (e.g. Somfy) would
|
||||
* produce buggy values in their Functional Parameters when reporting their Vane Position.
|
||||
* <p>
|
||||
* This API set is the one used (for example) by Home Assistant.
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Initial contribution.
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SCgetProductStatus extends GetProduct implements SlipBridgeCommunicationProtocol {
|
||||
private final Logger logger = LoggerFactory.getLogger(SCgetProductStatus.class);
|
||||
|
||||
private static final String DESCRIPTION = "Retrieve Product Status";
|
||||
private static final Command COMMAND = Command.GW_STATUS_REQUEST_REQ;
|
||||
|
||||
/*
|
||||
* RunStatus and StatusReply parameter values (from KLF200 API specification)
|
||||
*/
|
||||
private static final int EXECUTION_COMPLETED = 0;// Execution is completed with no errors.
|
||||
private static final int EXECUTION_FAILED = 1; // Execution has failed. (Get specifics in the following error code)
|
||||
private static final int EXECUTION_ACTIVE = 2;// Execution is still active
|
||||
private static final int UNKNOWN_STATUS_REPLY = 0x00; // Used to indicate unknown reply.
|
||||
private static final int COMMAND_COMPLETED_OK = 0x01;
|
||||
|
||||
/*
|
||||
* ===========================================================
|
||||
* Message Content Parameters
|
||||
*/
|
||||
|
||||
private int reqSessionID = 0; // The session id
|
||||
private final int reqIndexArrayCount = 1; // One node will be addressed
|
||||
private int reqNodeId = 1; // This is the node id
|
||||
private final int reqStatusType = 1; // The current value
|
||||
private final int reqFPI1 = 0xF0; // Functional Parameter Indicator 1 bit map (set to fetch { FP1 .. FP4 }
|
||||
private final int reqFPI2 = 0; // Functional Parameter Indicator 2 bit map.
|
||||
|
||||
/*
|
||||
* ===========================================================
|
||||
* Message Objects
|
||||
*/
|
||||
|
||||
private byte[] requestData = new byte[0];
|
||||
|
||||
/*
|
||||
* ===========================================================
|
||||
* Result Objects
|
||||
*/
|
||||
|
||||
private boolean success = false;
|
||||
private boolean finished = false;
|
||||
|
||||
private VeluxProduct product = VeluxProduct.UNKNOWN;
|
||||
|
||||
public SCgetProductStatus() {
|
||||
logger.debug("SCgetProductStatus(Constructor) called.");
|
||||
Random rand = new Random();
|
||||
reqSessionID = rand.nextInt(0x0fff);
|
||||
logger.debug("SCgetProductStatus(): starting session with the random number {}.", reqSessionID);
|
||||
}
|
||||
|
||||
/*
|
||||
* ===========================================================
|
||||
* Methods required for interface {@link BridgeCommunicationProtocol}.
|
||||
*/
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return DESCRIPTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandNumber getRequestCommand() {
|
||||
success = false;
|
||||
finished = false;
|
||||
logger.debug("getRequestCommand() returns {} ({}).", COMMAND.name(), COMMAND.getCommand());
|
||||
return COMMAND.getCommand();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getRequestDataAsArrayOfBytes() {
|
||||
logger.trace("getRequestDataAsArrayOfBytes() returns data for retrieving node with id {}.", reqNodeId);
|
||||
reqSessionID = (reqSessionID + 1) & 0xffff;
|
||||
Packet request = new Packet(new byte[26]);
|
||||
request.setTwoByteValue(0, reqSessionID);
|
||||
request.setOneByteValue(2, reqIndexArrayCount);
|
||||
request.setOneByteValue(3, reqNodeId);
|
||||
request.setOneByteValue(23, reqStatusType);
|
||||
request.setOneByteValue(24, reqFPI1);
|
||||
request.setOneByteValue(25, reqFPI2);
|
||||
requestData = request.toByteArray();
|
||||
return requestData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResponse(short responseCommand, byte[] thisResponseData, boolean isSequentialEnforced) {
|
||||
KLF200Response.introLogging(logger, responseCommand, thisResponseData);
|
||||
success = false;
|
||||
finished = false;
|
||||
Packet responseData = new Packet(thisResponseData);
|
||||
Command responseCmd = Command.get(responseCommand);
|
||||
switch (responseCmd) {
|
||||
case GW_STATUS_REQUEST_CFM:
|
||||
if (!KLF200Response.isLengthValid(logger, responseCommand, thisResponseData, 3)) {
|
||||
finished = true;
|
||||
break;
|
||||
}
|
||||
int cfmSessionID = responseData.getTwoByteValue(0);
|
||||
int cfmStatus = responseData.getOneByteValue(2);
|
||||
switch (cfmStatus) {
|
||||
case 0:
|
||||
logger.info("setResponse(): returned status: Error – Command rejected.");
|
||||
finished = true;
|
||||
break;
|
||||
case 1:
|
||||
logger.debug("setResponse(): returned status: OK - Command is accepted.");
|
||||
if (!KLF200Response.check4matchingSessionID(logger, cfmSessionID, reqSessionID)) {
|
||||
finished = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logger.warn("setResponse(): returned status={} (not defined).", cfmStatus);
|
||||
finished = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case GW_STATUS_REQUEST_NTF:
|
||||
if (!KLF200Response.isLengthValid(logger, responseCommand, thisResponseData, 59)) {
|
||||
finished = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Extracting information items
|
||||
int ntfSessionID = responseData.getTwoByteValue(0);
|
||||
int ntfStatusID = responseData.getOneByteValue(2);
|
||||
int ntfNodeID = responseData.getOneByteValue(3);
|
||||
int ntfRunStatus = responseData.getOneByteValue(4);
|
||||
int ntfStatusReply = responseData.getOneByteValue(5);
|
||||
int ntfStatusType = responseData.getOneByteValue(6);
|
||||
int ntfStatusCount = responseData.getOneByteValue(7);
|
||||
int ntfFirstParameterIndex = responseData.getOneByteValue(8);
|
||||
int ntfFirstParameter = responseData.getTwoByteValue(9);
|
||||
FunctionalParameters ntfFunctionalParameters = FunctionalParameters.readArrayIndexed(responseData, 11);
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("setResponse(): ntfSessionID={}.", ntfSessionID);
|
||||
logger.trace("setResponse(): ntfStatusID={}.", ntfStatusID);
|
||||
logger.trace("setResponse(): ntfNodeID={}.", ntfNodeID);
|
||||
logger.trace("setResponse(): ntfRunStatus={}.", ntfRunStatus);
|
||||
logger.trace("setResponse(): ntfStatusReply={}.", ntfStatusReply);
|
||||
logger.trace("setResponse(): ntfStatusType={}.", ntfStatusType);
|
||||
logger.trace("setResponse(): ntfStatusCount={}.", ntfStatusCount);
|
||||
logger.trace("setResponse(): ntfFirstParameterIndex={}.", ntfFirstParameterIndex);
|
||||
logger.trace("setResponse(): ntfFirstParameter={}.", String.format("0x%04X", ntfFirstParameter));
|
||||
logger.trace("setResponse(): ntfFunctionalParameters={}.", ntfFunctionalParameters);
|
||||
}
|
||||
|
||||
if (!KLF200Response.check4matchingNodeID(logger, reqNodeId, ntfNodeID)) {
|
||||
break;
|
||||
}
|
||||
|
||||
int ntfCurrentPosition;
|
||||
if ((ntfStatusCount > 0) && (ntfFirstParameterIndex == 0)) {
|
||||
ntfCurrentPosition = ntfFirstParameter;
|
||||
} else {
|
||||
ntfCurrentPosition = VeluxProductPosition.VPP_VELUX_UNKNOWN;
|
||||
}
|
||||
|
||||
int ntfState;
|
||||
switch (ntfRunStatus) {
|
||||
case EXECUTION_ACTIVE:
|
||||
ntfState = VeluxProduct.ProductState.EXECUTING.value;
|
||||
break;
|
||||
case EXECUTION_COMPLETED:
|
||||
ntfState = VeluxProduct.ProductState.DONE.value;
|
||||
break;
|
||||
case EXECUTION_FAILED:
|
||||
default:
|
||||
switch (ntfStatusReply) {
|
||||
case UNKNOWN_STATUS_REPLY:
|
||||
ntfState = VeluxProduct.ProductState.UNKNOWN.value;
|
||||
break;
|
||||
case COMMAND_COMPLETED_OK:
|
||||
ntfState = VeluxProduct.ProductState.DONE.value;
|
||||
break;
|
||||
default:
|
||||
ntfState = VeluxProduct.ProductState.ERROR.value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// create notification product with the returned values
|
||||
product = new VeluxProduct(VeluxProductName.UNKNOWN, new ProductBridgeIndex(ntfNodeID), ntfState,
|
||||
ntfCurrentPosition, VeluxProductPosition.VPP_VELUX_IGNORE, ntfFunctionalParameters, COMMAND);
|
||||
|
||||
success = true;
|
||||
if (!isSequentialEnforced) {
|
||||
logger.trace(
|
||||
"setResponse(): skipping wait for more packets as sequential processing is not enforced.");
|
||||
finished = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case GW_SESSION_FINISHED_NTF:
|
||||
finished = true;
|
||||
if (!KLF200Response.isLengthValid(logger, responseCommand, thisResponseData, 2)) {
|
||||
break;
|
||||
}
|
||||
int finishedNtfSessionID = responseData.getTwoByteValue(0);
|
||||
if (!KLF200Response.check4matchingSessionID(logger, finishedNtfSessionID, reqSessionID)) {
|
||||
break;
|
||||
}
|
||||
logger.debug("setResponse(): finishedNtfSessionID={}.", finishedNtfSessionID);
|
||||
success = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
KLF200Response.errorLogging(logger, responseCommand);
|
||||
finished = true;
|
||||
}
|
||||
KLF200Response.outroLogging(logger, success, finished);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCommunicationFinished() {
|
||||
return finished;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCommunicationSuccessful() {
|
||||
return success;
|
||||
}
|
||||
|
||||
/*
|
||||
* ===========================================================
|
||||
* Methods in addition to the interface {@link BridgeCommunicationProtocol}
|
||||
* and the abstract class {@link GetProduct}
|
||||
*/
|
||||
|
||||
@Override
|
||||
public void setProductId(int nodeId) {
|
||||
logger.trace("setProductId({}) called.", nodeId);
|
||||
reqNodeId = nodeId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VeluxProduct getProduct() {
|
||||
logger.trace("getProduct(): returning {}.", product);
|
||||
return product;
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@ import org.openhab.binding.velux.internal.things.VeluxProduct.ProductBridgeIndex
|
||||
import org.openhab.binding.velux.internal.things.VeluxProductName;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProductSerialNo;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProductType;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProductType.ActuatorType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -150,62 +151,59 @@ class SCgetProducts extends GetProducts implements SlipBridgeCommunicationProtoc
|
||||
}
|
||||
// Extracting information items
|
||||
int ntfNodeID = responseData.getOneByteValue(0);
|
||||
logger.trace("setResponse(): ntfNodeID={}.", ntfNodeID);
|
||||
int ntfOrder = responseData.getTwoByteValue(1);
|
||||
logger.trace("setResponse(): ntfOrder={}.", ntfOrder);
|
||||
int ntfPlacement = responseData.getOneByteValue(3);
|
||||
logger.trace("setResponse(): ntfPlacement={}.", ntfPlacement);
|
||||
String ntfName = responseData.getString(4, 64);
|
||||
logger.trace("setResponse(): ntfName={}.", ntfName);
|
||||
int ntfVelocity = responseData.getOneByteValue(68);
|
||||
logger.trace("setResponse(): ntfVelocity={}.", ntfVelocity);
|
||||
int ntfNodeTypeSubType = responseData.getTwoByteValue(69);
|
||||
logger.trace("setResponse(): ntfNodeTypeSubType={} ({}).", ntfNodeTypeSubType,
|
||||
VeluxProductType.get(ntfNodeTypeSubType));
|
||||
logger.trace("setResponse(): derived product description={}.",
|
||||
VeluxProductType.toString(ntfNodeTypeSubType));
|
||||
int ntfProductGroup = responseData.getOneByteValue(71);
|
||||
logger.trace("setResponse(): ntfProductGroup={}.", ntfProductGroup);
|
||||
int ntfProductType = responseData.getOneByteValue(72);
|
||||
logger.trace("setResponse(): ntfProductType={}.", ntfProductType);
|
||||
int ntfNodeVariation = responseData.getOneByteValue(73);
|
||||
logger.trace("setResponse(): ntfNodeVariation={}.", ntfNodeVariation);
|
||||
int ntfPowerMode = responseData.getOneByteValue(74);
|
||||
logger.trace("setResponse(): ntfPowerMode={}.", ntfPowerMode);
|
||||
int ntfBuildNumber = responseData.getOneByteValue(75);
|
||||
logger.trace("setResponse(): ntfBuildNumber={}.", ntfBuildNumber);
|
||||
byte[] ntfSerialNumber = responseData.getByteArray(76, 8);
|
||||
logger.trace("setResponse(): ntfSerialNumber={}.", ntfSerialNumber);
|
||||
int ntfState = responseData.getOneByteValue(84);
|
||||
logger.trace("setResponse(): ntfState={}.", ntfState);
|
||||
int ntfCurrentPosition = responseData.getTwoByteValue(85);
|
||||
logger.trace("setResponse(): ntfCurrentPosition={}.", ntfCurrentPosition);
|
||||
int ntfTarget = responseData.getTwoByteValue(87);
|
||||
logger.trace("setResponse(): ntfTarget={}.", ntfTarget);
|
||||
int ntfFP1CurrentPosition = responseData.getTwoByteValue(89);
|
||||
logger.trace("setResponse(): ntfFP1CurrentPosition={}.", ntfFP1CurrentPosition);
|
||||
int ntfFP2CurrentPosition = responseData.getTwoByteValue(91);
|
||||
logger.trace("setResponse(): ntfFP2CurrentPosition={}.", ntfFP2CurrentPosition);
|
||||
int ntfFP3CurrentPosition = responseData.getTwoByteValue(93);
|
||||
logger.trace("setResponse(): ntfFP3CurrentPosition={}.", ntfFP3CurrentPosition);
|
||||
int ntfFP4CurrentPosition = responseData.getTwoByteValue(95);
|
||||
logger.trace("setResponse(): ntfFP4CurrentPosition={}.", ntfFP4CurrentPosition);
|
||||
FunctionalParameters ntfFunctionalParameters = FunctionalParameters.readArray(responseData, 89);
|
||||
int ntfRemainingTime = responseData.getTwoByteValue(97);
|
||||
logger.trace("setResponse(): ntfRemainingTime={}.", ntfRemainingTime);
|
||||
int ntfTimeStamp = responseData.getFourByteValue(99);
|
||||
logger.trace("setResponse(): ntfTimeStamp={}.", ntfTimeStamp);
|
||||
int ntfNbrOfAlias = responseData.getOneByteValue(103);
|
||||
logger.trace("setResponse(): ntfNbrOfAlias={}.", ntfNbrOfAlias);
|
||||
int ntfAliasOne = responseData.getFourByteValue(104);
|
||||
logger.trace("setResponse(): ntfAliasOne={}.", ntfAliasOne);
|
||||
int ntfAliasTwo = responseData.getFourByteValue(108);
|
||||
logger.trace("setResponse(): ntfAliasTwo={}.", ntfAliasTwo);
|
||||
int ntfAliasThree = responseData.getFourByteValue(112);
|
||||
logger.trace("setResponse(): ntfAliasThree={}.", ntfAliasThree);
|
||||
int ntfAliasFour = responseData.getFourByteValue(116);
|
||||
logger.trace("setResponse(): ntfAliasFour={}.", ntfAliasFour);
|
||||
int ntfAliasFive = responseData.getFourByteValue(120);
|
||||
logger.trace("setResponse(): ntfAliasFive={}.", ntfAliasFive);
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("setResponse(): ntfNodeID={}.", ntfNodeID);
|
||||
logger.trace("setResponse(): ntfOrder={}.", ntfOrder);
|
||||
logger.trace("setResponse(): ntfPlacement={}.", ntfPlacement);
|
||||
logger.trace("setResponse(): ntfName={}.", ntfName);
|
||||
logger.trace("setResponse(): ntfVelocity={}.", ntfVelocity);
|
||||
logger.trace("setResponse(): ntfNodeTypeSubType={} ({}).", ntfNodeTypeSubType,
|
||||
VeluxProductType.get(ntfNodeTypeSubType));
|
||||
logger.trace("setResponse(): derived product description={}.",
|
||||
VeluxProductType.toString(ntfNodeTypeSubType));
|
||||
logger.trace("setResponse(): ntfProductGroup={}.", ntfProductGroup);
|
||||
logger.trace("setResponse(): ntfProductType={}.", ntfProductType);
|
||||
logger.trace("setResponse(): ntfNodeVariation={}.", ntfNodeVariation);
|
||||
logger.trace("setResponse(): ntfPowerMode={}.", ntfPowerMode);
|
||||
logger.trace("setResponse(): ntfBuildNumber={}.", ntfBuildNumber);
|
||||
logger.trace("setResponse(): ntfSerialNumber={}.", VeluxProductSerialNo.toString(ntfSerialNumber));
|
||||
logger.trace("setResponse(): ntfState={}.", ntfState);
|
||||
logger.trace("setResponse(): ntfCurrentPosition={}.", String.format("0x%04X", ntfCurrentPosition));
|
||||
logger.trace("setResponse(): ntfTarget={}.", String.format("0x%04X", ntfTarget));
|
||||
logger.trace("setResponse(): ntfFunctionalParameters={}.", ntfFunctionalParameters);
|
||||
logger.trace("setResponse(): ntfRemainingTime={}.", ntfRemainingTime);
|
||||
logger.trace("setResponse(): ntfTimeStamp={}.", ntfTimeStamp);
|
||||
logger.trace("setResponse(): ntfNbrOfAlias={}.", ntfNbrOfAlias);
|
||||
logger.trace("setResponse(): ntfAliasOne={}.", ntfAliasOne);
|
||||
logger.trace("setResponse(): ntfAliasTwo={}.", ntfAliasTwo);
|
||||
logger.trace("setResponse(): ntfAliasThree={}.", ntfAliasThree);
|
||||
logger.trace("setResponse(): ntfAliasFour={}.", ntfAliasFour);
|
||||
logger.trace("setResponse(): ntfAliasFive={}.", ntfAliasFive);
|
||||
}
|
||||
|
||||
if ((ntfName.length() == 0) || ntfName.startsWith("_")) {
|
||||
ntfName = "#".concat(String.valueOf(ntfNodeID));
|
||||
@ -220,9 +218,10 @@ class SCgetProducts extends GetProducts implements SlipBridgeCommunicationProtoc
|
||||
}
|
||||
|
||||
VeluxProduct product = new VeluxProduct(new VeluxProductName(ntfName),
|
||||
VeluxProductType.get(ntfNodeTypeSubType), new ProductBridgeIndex(ntfNodeID), ntfOrder,
|
||||
ntfPlacement, ntfVelocity, ntfNodeVariation, ntfPowerMode, commonSerialNumber, ntfState,
|
||||
ntfCurrentPosition, ntfTarget, ntfRemainingTime, ntfTimeStamp);
|
||||
VeluxProductType.get(ntfNodeTypeSubType), ActuatorType.get(ntfNodeTypeSubType),
|
||||
new ProductBridgeIndex(ntfNodeID), ntfOrder, ntfPlacement, ntfVelocity, ntfNodeVariation,
|
||||
ntfPowerMode, commonSerialNumber, ntfState, ntfCurrentPosition, ntfTarget,
|
||||
ntfFunctionalParameters, ntfRemainingTime, ntfTimeStamp, COMMAND);
|
||||
if (nextProductArrayItem < totalNumberOfProducts) {
|
||||
productArray[nextProductArrayItem++] = product;
|
||||
} else {
|
||||
|
@ -15,11 +15,18 @@ package org.openhab.binding.velux.internal.bridge.slip;
|
||||
import java.util.Random;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.velux.internal.bridge.common.RunProductCommand;
|
||||
import org.openhab.binding.velux.internal.bridge.slip.utils.KLF200Response;
|
||||
import org.openhab.binding.velux.internal.bridge.slip.utils.Packet;
|
||||
import org.openhab.binding.velux.internal.things.VeluxKLFAPI.Command;
|
||||
import org.openhab.binding.velux.internal.things.VeluxKLFAPI.CommandNumber;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProduct;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProduct.DataSource;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProduct.ProductBridgeIndex;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProduct.ProductState;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProductName;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProductPosition;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -46,7 +53,7 @@ import org.slf4j.LoggerFactory;
|
||||
* @author Guenther Schreiner - Initial contribution.
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class SCrunProductCommand extends RunProductCommand implements SlipBridgeCommunicationProtocol {
|
||||
public class SCrunProductCommand extends RunProductCommand implements SlipBridgeCommunicationProtocol {
|
||||
private final Logger logger = LoggerFactory.getLogger(SCrunProductCommand.class);
|
||||
|
||||
private static final String DESCRIPTION = "Send Command to Actuator";
|
||||
@ -70,6 +77,7 @@ class SCrunProductCommand extends RunProductCommand implements SlipBridgeCommuni
|
||||
private int reqPL03 = 0; // unused
|
||||
private int reqPL47 = 0; // unused
|
||||
private int reqLockTime = 0; // 30 seconds
|
||||
private @Nullable FunctionalParameters reqFunctionalParameters = null;
|
||||
|
||||
/*
|
||||
* ===========================================================
|
||||
@ -86,6 +94,8 @@ class SCrunProductCommand extends RunProductCommand implements SlipBridgeCommuni
|
||||
private boolean success = false;
|
||||
private boolean finished = false;
|
||||
|
||||
private VeluxProduct product = VeluxProduct.UNKNOWN;
|
||||
|
||||
/*
|
||||
* ===========================================================
|
||||
* Constructor Method
|
||||
@ -120,10 +130,15 @@ class SCrunProductCommand extends RunProductCommand implements SlipBridgeCommuni
|
||||
public byte[] getRequestDataAsArrayOfBytes() {
|
||||
Packet request = new Packet(new byte[66]);
|
||||
reqSessionID = (reqSessionID + 1) & 0xffff;
|
||||
|
||||
request.setTwoByteValue(0, reqSessionID);
|
||||
request.setOneByteValue(2, reqCommandOriginator);
|
||||
request.setOneByteValue(3, reqPriorityLevel);
|
||||
request.setOneByteValue(4, reqParameterActive);
|
||||
|
||||
FunctionalParameters reqFunctionalParameters = this.reqFunctionalParameters;
|
||||
reqFPI1 = reqFunctionalParameters != null ? reqFunctionalParameters.writeArray(request, 9) : 0;
|
||||
|
||||
request.setOneByteValue(5, reqFPI1);
|
||||
request.setOneByteValue(6, reqFPI2);
|
||||
request.setTwoByteValue(7, reqMainParameter);
|
||||
@ -133,24 +148,39 @@ class SCrunProductCommand extends RunProductCommand implements SlipBridgeCommuni
|
||||
request.setOneByteValue(63, reqPL03);
|
||||
request.setOneByteValue(64, reqPL47);
|
||||
request.setOneByteValue(65, reqLockTime);
|
||||
logger.trace("getRequestDataAsArrayOfBytes(): ntfSessionID={}.", reqSessionID);
|
||||
logger.trace("getRequestDataAsArrayOfBytes(): reqCommandOriginator={}.", reqCommandOriginator);
|
||||
logger.trace("getRequestDataAsArrayOfBytes(): reqPriorityLevel={}.", reqPriorityLevel);
|
||||
logger.trace("getRequestDataAsArrayOfBytes(): reqParameterActive={}.", reqParameterActive);
|
||||
logger.trace("getRequestDataAsArrayOfBytes(): reqFPI1={}.", reqFPI1);
|
||||
logger.trace("getRequestDataAsArrayOfBytes(): reqFPI2={}.", reqFPI2);
|
||||
logger.trace("getRequestDataAsArrayOfBytes(): reqMainParameter={}.", reqMainParameter);
|
||||
logger.trace("getRequestDataAsArrayOfBytes(): reqIndexArrayCount={}.", reqIndexArrayCount);
|
||||
logger.trace("getRequestDataAsArrayOfBytes(): reqIndexArray01={}.", reqIndexArray01);
|
||||
logger.trace("getRequestDataAsArrayOfBytes(): reqPriorityLevelLock={}.", reqPriorityLevelLock);
|
||||
logger.trace("getRequestDataAsArrayOfBytes(): reqPL03={}.", reqPL03);
|
||||
logger.trace("getRequestDataAsArrayOfBytes(): reqPL47={}.", reqPL47);
|
||||
logger.trace("getRequestDataAsArrayOfBytes(): reqLockTime={}.", reqLockTime);
|
||||
|
||||
requestData = request.toByteArray();
|
||||
logger.trace("getRequestDataAsArrayOfBytes() data is {}.", new Packet(requestData).toString());
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("getRequestDataAsArrayOfBytes(): ntfSessionID={}.", hex(reqSessionID));
|
||||
logger.trace("getRequestDataAsArrayOfBytes(): reqCommandOriginator={}.", hex(reqCommandOriginator));
|
||||
logger.trace("getRequestDataAsArrayOfBytes(): reqPriorityLevel={}.", hex(reqPriorityLevel));
|
||||
logger.trace("getRequestDataAsArrayOfBytes(): reqParameterActive={}.", hex(reqParameterActive));
|
||||
logger.trace("getRequestDataAsArrayOfBytes(): reqFPI1={}.", bin(reqFPI1));
|
||||
logger.trace("getRequestDataAsArrayOfBytes(): reqFPI2={}.", bin(reqFPI2));
|
||||
logger.trace("getRequestDataAsArrayOfBytes(): reqMainParameter={}.", hex(reqMainParameter));
|
||||
logger.trace("getRequestDataAsArrayOfBytes(): reqFunctionalParameters={}.", reqFunctionalParameters);
|
||||
logger.trace("getRequestDataAsArrayOfBytes(): reqIndexArrayCount={}.", hex(reqIndexArrayCount));
|
||||
logger.trace("getRequestDataAsArrayOfBytes(): reqIndexArray01={} (reqNodeId={}).", reqIndexArray01,
|
||||
reqIndexArray01);
|
||||
logger.trace("getRequestDataAsArrayOfBytes(): reqPriorityLevelLock={}.", hex(reqPriorityLevelLock));
|
||||
logger.trace("getRequestDataAsArrayOfBytes(): reqPL03={}.", hex(reqPL03));
|
||||
logger.trace("getRequestDataAsArrayOfBytes(): reqPL47={}.", hex(reqPL47));
|
||||
logger.trace("getRequestDataAsArrayOfBytes(): reqLockTime={}.", hex(reqLockTime));
|
||||
|
||||
logger.trace("getRequestDataAsArrayOfBytes() data is {}.", new Packet(requestData).toString());
|
||||
}
|
||||
return requestData;
|
||||
}
|
||||
|
||||
private String hex(int i) {
|
||||
return Integer.toHexString(i);
|
||||
}
|
||||
|
||||
private String bin(int i) {
|
||||
return Integer.toBinaryString(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResponse(short responseCommand, byte[] thisResponseData, boolean isSequentialEnforced) {
|
||||
KLF200Response.introLogging(logger, responseCommand, thisResponseData);
|
||||
@ -201,15 +231,17 @@ class SCrunProductCommand extends RunProductCommand implements SlipBridgeCommuni
|
||||
int ntfRunStatus = responseData.getOneByteValue(7);
|
||||
int ntfStatusReply = responseData.getOneByteValue(8);
|
||||
int ntfInformationCode = responseData.getFourByteValue(9);
|
||||
// Extracting information items
|
||||
logger.debug("setResponse(): ntfSessionID={} (requested {}).", ntfSessionID, reqSessionID);
|
||||
logger.debug("setResponse(): ntfStatusiD={}.", ntfStatusiD);
|
||||
logger.debug("setResponse(): ntfIndex={}.", ntfIndex);
|
||||
logger.debug("setResponse(): ntfNodeParameter={}.", ntfNodeParameter);
|
||||
logger.debug("setResponse(): ntfParameterValue={}.", ntfParameterValue);
|
||||
logger.debug("setResponse(): ntfRunStatus={}.", ntfRunStatus);
|
||||
logger.debug("setResponse(): ntfStatusReply={}.", ntfStatusReply);
|
||||
logger.debug("setResponse(): ntfInformationCode={}.", ntfInformationCode);
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("setResponse(): ntfSessionID={} (requested {}).", ntfSessionID, reqSessionID);
|
||||
logger.trace("setResponse(): ntfStatusiD={}.", ntfStatusiD);
|
||||
logger.trace("setResponse(): ntfIndex={}.", ntfIndex);
|
||||
logger.trace("setResponse(): ntfNodeParameter={}.", ntfNodeParameter);
|
||||
logger.trace("setResponse(): ntfParameterValue={}.", String.format("0x%04X", ntfParameterValue));
|
||||
logger.trace("setResponse(): ntfRunStatus={}.", ntfRunStatus);
|
||||
logger.trace("setResponse(): ntfStatusReply={}.", ntfStatusReply);
|
||||
logger.trace("setResponse(): ntfInformationCode={}.", ntfInformationCode);
|
||||
}
|
||||
|
||||
if (!KLF200Response.check4matchingSessionID(logger, ntfSessionID, reqSessionID)) {
|
||||
finished = true;
|
||||
@ -253,11 +285,13 @@ class SCrunProductCommand extends RunProductCommand implements SlipBridgeCommuni
|
||||
finished = true;
|
||||
}
|
||||
|
||||
// Extracting information items
|
||||
logger.debug("setResponse(): timeNtfSessionID={}.", timeNtfSessionID);
|
||||
logger.debug("setResponse(): timeNtfIndex={}.", timeNtfIndex);
|
||||
logger.debug("setResponse(): timeNtfNodeParameter={}.", timeNtfNodeParameter);
|
||||
logger.debug("setResponse(): timeNtfSeconds={}.", timeNtfSeconds);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("setResponse(): timeNtfSessionID={}.", timeNtfSessionID);
|
||||
logger.debug("setResponse(): timeNtfIndex={}.", timeNtfIndex);
|
||||
logger.debug("setResponse(): timeNtfNodeParameter={}.", timeNtfNodeParameter);
|
||||
logger.debug("setResponse(): timeNtfSeconds={}.", timeNtfSeconds);
|
||||
}
|
||||
|
||||
if (!isSequentialEnforced) {
|
||||
logger.trace(
|
||||
"setResponse(): skipping wait for more packets as sequential processing is not enforced.");
|
||||
@ -303,10 +337,34 @@ class SCrunProductCommand extends RunProductCommand implements SlipBridgeCommuni
|
||||
*/
|
||||
|
||||
@Override
|
||||
public SCrunProductCommand setNodeAndMainParameter(int nodeId, int value) {
|
||||
logger.debug("setNodeAndMainParameter({}) called.", nodeId);
|
||||
this.reqIndexArray01 = nodeId;
|
||||
this.reqMainParameter = value;
|
||||
return this;
|
||||
public boolean setNodeIdAndParameters(int nodeId, @Nullable VeluxProductPosition mainParameter,
|
||||
@Nullable FunctionalParameters functionalParameters) {
|
||||
logger.debug("setNodeIdAndParameters({}) called.", nodeId);
|
||||
|
||||
if ((mainParameter != null) || (functionalParameters != null)) {
|
||||
reqIndexArray01 = nodeId;
|
||||
|
||||
reqMainParameter = (mainParameter == null) ? VeluxProductPosition.VPP_VELUX_STOP
|
||||
: mainParameter.getPositionAsVeluxType();
|
||||
|
||||
int setMainParameter = VeluxProductPosition.isValid(reqMainParameter) ? reqMainParameter
|
||||
: VeluxProductPosition.VPP_VELUX_IGNORE;
|
||||
|
||||
reqFunctionalParameters = functionalParameters;
|
||||
|
||||
// create notification product that clones the new command positions
|
||||
product = new VeluxProduct(VeluxProductName.UNKNOWN, new ProductBridgeIndex(reqIndexArray01),
|
||||
ProductState.EXECUTING.value, setMainParameter, setMainParameter, reqFunctionalParameters, COMMAND)
|
||||
.overrideDataSource(DataSource.BINDING);
|
||||
|
||||
return true;
|
||||
}
|
||||
product = VeluxProduct.UNKNOWN;
|
||||
return false;
|
||||
}
|
||||
|
||||
public VeluxProduct getProduct() {
|
||||
logger.trace("getProduct(): returning {}.", product);
|
||||
return product;
|
||||
}
|
||||
}
|
||||
|
@ -104,6 +104,7 @@ class SlipBridgeAPI implements BridgeAPI {
|
||||
private final SetProductLimitation slipSetProductLimitation = new SCsetLimitation();
|
||||
private final SetSceneVelocity slipSetSceneVelocity = new SCsetSceneVelocity();
|
||||
private final RunReboot slipRunReboot = new SCrunReboot();
|
||||
private final GetProduct slipGetProductStatus = new SCgetProductStatus();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@ -217,4 +218,9 @@ class SlipBridgeAPI implements BridgeAPI {
|
||||
public @Nullable RunReboot runReboot() {
|
||||
return slipRunReboot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable GetProduct getProductStatus() {
|
||||
return slipGetProductStatus;
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,6 @@ import org.openhab.binding.velux.internal.bridge.slip.utils.SlipRFC1055;
|
||||
import org.openhab.binding.velux.internal.development.Threads;
|
||||
import org.openhab.binding.velux.internal.handler.VeluxBridgeHandler;
|
||||
import org.openhab.binding.velux.internal.things.VeluxKLFAPI.Command;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProduct.ProductBridgeIndex;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -206,15 +205,15 @@ public class SlipVeluxBridge extends VeluxBridge implements Closeable {
|
||||
final boolean isProtocolTraceEnabled = this.bridgeInstance.veluxBridgeConfiguration().isProtocolTraceEnabled;
|
||||
final long expiryTime = System.currentTimeMillis() + COMMUNICATION_TIMEOUT_MSECS;
|
||||
|
||||
// logger format string
|
||||
final String loggerFmt = String.format("bridgeDirectCommunicate() [%s] %s => {} {} {}",
|
||||
this.bridgeInstance.veluxBridgeConfiguration().ipAddress, txName);
|
||||
// logger messages
|
||||
final String logMsg = "bridgeDirectCommunicate() [{}] {} => {} {} {}";
|
||||
final String ipAddr = bridgeInstance.veluxBridgeConfiguration().ipAddress;
|
||||
|
||||
if (isProtocolTraceEnabled) {
|
||||
Threads.findDeadlocked();
|
||||
}
|
||||
|
||||
logger.debug(loggerFmt, "started =>", Thread.currentThread(), "");
|
||||
logger.debug(logMsg, ipAddr, txName, "started =>", Thread.currentThread(), "");
|
||||
|
||||
boolean looping = false;
|
||||
boolean success = false;
|
||||
@ -225,41 +224,41 @@ public class SlipVeluxBridge extends VeluxBridge implements Closeable {
|
||||
// handling of the requests
|
||||
switch (txEnum) {
|
||||
case GW_OPENHAB_CLOSE:
|
||||
logger.trace(loggerFmt, "shut down command", "=> executing", "");
|
||||
logger.trace(logMsg, ipAddr, txName, "shut down command", "=> executing", "");
|
||||
connection.resetConnection();
|
||||
success = true;
|
||||
break;
|
||||
|
||||
case GW_OPENHAB_RECEIVEONLY:
|
||||
logger.trace(loggerFmt, "receive-only mode", "=> checking messages", "");
|
||||
logger.trace(logMsg, ipAddr, txName, "receive-only mode", "=> checking messages", "");
|
||||
if (!connection.isAlive()) {
|
||||
logger.trace(loggerFmt, "no connection", "=> opening", "");
|
||||
logger.trace(logMsg, ipAddr, txName, "no connection", "=> opening", "");
|
||||
looping = true;
|
||||
} else if (connection.isMessageAvailable()) {
|
||||
logger.trace(loggerFmt, "message(s) waiting", "=> start reading", "");
|
||||
logger.trace(logMsg, ipAddr, txName, "message(s) waiting", "=> start reading", "");
|
||||
looping = true;
|
||||
} else {
|
||||
logger.trace(loggerFmt, "no waiting messages", "=> done", "");
|
||||
logger.trace(logMsg, ipAddr, txName, "no waiting messages", "=> done", "");
|
||||
}
|
||||
rcvonly = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
logger.trace(loggerFmt, "send mode", "=> preparing command", "");
|
||||
logger.trace(logMsg, ipAddr, txName, "send mode", "=> preparing command", "");
|
||||
SlipEncoding slipEnc = new SlipEncoding(txCmd, txData);
|
||||
if (!slipEnc.isValid()) {
|
||||
logger.debug(loggerFmt, "slip encoding error", "=> aborting", "");
|
||||
logger.debug(logMsg, ipAddr, txName, "slip encoding error", "=> aborting", "");
|
||||
break;
|
||||
}
|
||||
txPacket = new SlipRFC1055().encode(slipEnc.toMessage());
|
||||
logger.trace(loggerFmt, "command ready", "=> start sending", "");
|
||||
logger.trace(logMsg, ipAddr, txName, "command ready", "=> start sending", "");
|
||||
looping = sending = true;
|
||||
}
|
||||
|
||||
while (looping) {
|
||||
// timeout
|
||||
if (System.currentTimeMillis() > expiryTime) {
|
||||
logger.warn(loggerFmt, "process loop time out", "=> aborting", "=> PLEASE REPORT !!");
|
||||
logger.warn(logMsg, ipAddr, txName, "process loop time out", "=> aborting", "=> PLEASE REPORT !!");
|
||||
// abort the processing loop
|
||||
break;
|
||||
}
|
||||
@ -272,9 +271,9 @@ public class SlipVeluxBridge extends VeluxBridge implements Closeable {
|
||||
logger.info("sending command {}", txName);
|
||||
}
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace(loggerFmt, txName, "=> sending data =>", new Packet(txData));
|
||||
logger.trace(logMsg, ipAddr, txName, txName, "=> sending data =>", new Packet(txData));
|
||||
} else {
|
||||
logger.debug(loggerFmt, txName, "=> sending data length =>", txData.length);
|
||||
logger.debug(logMsg, ipAddr, txName, txName, "=> sending data length =>", txData.length);
|
||||
}
|
||||
}
|
||||
rxPacket = connection.io(this.bridgeInstance, sending ? txPacket : emptyPacket);
|
||||
@ -283,13 +282,13 @@ public class SlipVeluxBridge extends VeluxBridge implements Closeable {
|
||||
if (rxPacket.length == 0) {
|
||||
// only log in send mode (in receive-only mode, no response is ok)
|
||||
if (!rcvonly) {
|
||||
logger.debug(loggerFmt, "no response", "=> aborting", "");
|
||||
logger.debug(logMsg, ipAddr, txName, "no response", "=> aborting", "");
|
||||
}
|
||||
// abort the processing loop
|
||||
break;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.debug(loggerFmt, "i/o error =>", e.getMessage(), "=> aborting");
|
||||
logger.debug(logMsg, ipAddr, txName, "i/o error =>", e.getMessage(), "=> aborting");
|
||||
// abort the processing loop
|
||||
break;
|
||||
}
|
||||
@ -299,7 +298,7 @@ public class SlipVeluxBridge extends VeluxBridge implements Closeable {
|
||||
try {
|
||||
rfc1055 = new SlipRFC1055().decode(rxPacket);
|
||||
} catch (ParseException e) {
|
||||
logger.debug(loggerFmt, "parsing error =>", e.getMessage(), "=> aborting");
|
||||
logger.debug(logMsg, ipAddr, txName, "parsing error =>", e.getMessage(), "=> aborting");
|
||||
// abort the processing loop
|
||||
break;
|
||||
}
|
||||
@ -307,7 +306,7 @@ public class SlipVeluxBridge extends VeluxBridge implements Closeable {
|
||||
// SLIP decode response
|
||||
SlipEncoding slipEnc = new SlipEncoding(rfc1055);
|
||||
if (!slipEnc.isValid()) {
|
||||
logger.debug(loggerFmt, "slip decode error", "=> aborting", "");
|
||||
logger.debug(logMsg, ipAddr, txName, "slip decode error", "=> aborting", "");
|
||||
// abort the processing loop
|
||||
break;
|
||||
}
|
||||
@ -320,9 +319,9 @@ public class SlipVeluxBridge extends VeluxBridge implements Closeable {
|
||||
|
||||
// logging
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace(loggerFmt, rxName, "=> received data =>", new Packet(rxData));
|
||||
logger.trace(logMsg, ipAddr, txName, rxName, "=> received data =>", new Packet(rxData));
|
||||
} else {
|
||||
logger.debug(loggerFmt, rxName, "=> received data length =>", rxData.length);
|
||||
logger.debug(logMsg, ipAddr, txName, rxName, "=> received data length =>", rxData.length);
|
||||
}
|
||||
if (isProtocolTraceEnabled) {
|
||||
logger.info("received message {} => {}", rxName, new Packet(rxData));
|
||||
@ -334,53 +333,52 @@ public class SlipVeluxBridge extends VeluxBridge implements Closeable {
|
||||
byte code = rxData[0];
|
||||
switch (code) {
|
||||
case 7: // busy
|
||||
logger.trace(loggerFmt, rxName, getErrorText(code), "=> retrying");
|
||||
logger.trace(logMsg, ipAddr, txName, rxName, getErrorText(code), "=> retrying");
|
||||
sending = true;
|
||||
break;
|
||||
case 12: // authentication failed
|
||||
logger.debug(loggerFmt, rxName, getErrorText(code), "=> aborting");
|
||||
logger.debug(logMsg, ipAddr, txName, rxName, getErrorText(code), "=> aborting");
|
||||
resetAuthentication();
|
||||
looping = false;
|
||||
break;
|
||||
default:
|
||||
logger.warn(loggerFmt, rxName, getErrorText(code), "=> aborting");
|
||||
logger.warn(logMsg, ipAddr, txName, rxName, getErrorText(code), "=> aborting");
|
||||
looping = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case GW_NODE_INFORMATION_CHANGED_NTF:
|
||||
case GW_ACTIVATION_LOG_UPDATED_NTF:
|
||||
logger.trace(loggerFmt, rxName, "=> ignorable command", "=> continuing");
|
||||
logger.trace(logMsg, ipAddr, txName, rxName, "=> ignorable command", "=> continuing");
|
||||
break;
|
||||
|
||||
case GW_NODE_STATE_POSITION_CHANGED_NTF:
|
||||
logger.trace(loggerFmt, rxName, "=> special command", "=> starting");
|
||||
SCgetHouseStatus receiver = new SCgetHouseStatus();
|
||||
logger.trace(logMsg, ipAddr, txName, rxName, "=> special command", "=> starting");
|
||||
SCgetHouseStatus receiver = new SCgetHouseStatus().setCreatorCommand(txEnum);
|
||||
receiver.setResponse(rxCmd, rxData, isSequentialEnforced);
|
||||
if (receiver.isCommunicationSuccessful()) {
|
||||
bridgeInstance.existingProducts().update(new ProductBridgeIndex(receiver.getNtfNodeID()),
|
||||
receiver.getNtfState(), receiver.getNtfCurrentPosition(), receiver.getNtfTarget());
|
||||
logger.trace(loggerFmt, rxName, "=> special command", "=> product updated");
|
||||
bridgeInstance.existingProducts().update(receiver.getProduct());
|
||||
logger.trace(logMsg, ipAddr, txName, rxName, "=> special command", "=> update submitted");
|
||||
if (rcvonly) {
|
||||
// receive-only: return success to confirm that product(s) were updated
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
logger.trace(loggerFmt, rxName, "=> special command", "=> continuing");
|
||||
logger.trace(logMsg, ipAddr, txName, rxName, "=> special command", "=> continuing");
|
||||
break;
|
||||
|
||||
case GW_COMMAND_RUN_STATUS_NTF:
|
||||
case GW_COMMAND_REMAINING_TIME_NTF:
|
||||
case GW_SESSION_FINISHED_NTF:
|
||||
if (!isSequentialEnforced) {
|
||||
logger.trace(loggerFmt, rxName, "=> parallelism allowed", "=> continuing");
|
||||
logger.trace(logMsg, ipAddr, txName, rxName, "=> parallelism allowed", "=> continuing");
|
||||
break;
|
||||
}
|
||||
logger.trace(loggerFmt, rxName, "=> serialism enforced", "=> default processing");
|
||||
logger.trace(logMsg, ipAddr, txName, rxName, "=> serialism enforced", "=> default processing");
|
||||
// fall through => execute default processing
|
||||
|
||||
default:
|
||||
logger.trace(loggerFmt, rxName, "=> applying data length =>", rxData.length);
|
||||
logger.trace(logMsg, ipAddr, txName, rxName, "=> applying data length =>", rxData.length);
|
||||
communication.setResponse(rxCmd, rxData, isSequentialEnforced);
|
||||
looping = !communication.isCommunicationFinished();
|
||||
success = communication.isCommunicationSuccessful();
|
||||
@ -388,7 +386,7 @@ public class SlipVeluxBridge extends VeluxBridge implements Closeable {
|
||||
|
||||
}
|
||||
// in receive-only mode 'failure` just means that no products were updated, so don't log it as a failure..
|
||||
logger.debug(loggerFmt, "finished", "=>", ((success || rcvonly) ? "success" : "failure"));
|
||||
logger.debug(logMsg, ipAddr, txName, "finished", "=>", ((success || rcvonly) ? "success" : "failure"));
|
||||
return success;
|
||||
}
|
||||
|
||||
|
@ -142,7 +142,7 @@ class DataInputStreamWithTimeout implements Closeable {
|
||||
logger.debug("startPolling() called");
|
||||
slipMessageQueue.clear();
|
||||
poller = new Poller(inputStream, slipMessageQueue);
|
||||
executor = Executors.newSingleThreadExecutor(bridge.getThreadFactory());
|
||||
ExecutorService executor = this.executor = Executors.newSingleThreadExecutor(bridge.getThreadFactory());
|
||||
future = executor.submit(poller);
|
||||
}
|
||||
}
|
||||
|
@ -143,7 +143,8 @@ public class KLF200Response {
|
||||
* @return <b>success</b> of type boolean which signals the success of the communication.
|
||||
*/
|
||||
public static boolean check4matchingNodeID(Logger logger, int reqNodeID, int cfmNodeID) {
|
||||
logger.trace("check4matchingNodeID() called for requestNodeID {} and responseNodeID {}.", reqNodeID, cfmNodeID);
|
||||
logger.trace("check4matchingNodeID() called for request NodeID {} and response NodeID {}.", reqNodeID,
|
||||
cfmNodeID);
|
||||
return check4matchingAnyID(logger, "NodeID", reqNodeID, cfmNodeID);
|
||||
}
|
||||
|
||||
@ -157,8 +158,8 @@ public class KLF200Response {
|
||||
* @return <b>success</b> of type boolean which signals the success of the communication.
|
||||
*/
|
||||
public static boolean check4matchingSessionID(Logger logger, int reqSessionID, int cfmSessionID) {
|
||||
logger.trace("check4matchingSessionID() called for requestNodeID {} and responseNodeID {}.", reqSessionID,
|
||||
cfmSessionID);
|
||||
logger.trace("check4matchingSessionID() called for request SessionID {} and response SessionID {}.",
|
||||
reqSessionID, cfmSessionID);
|
||||
return check4matchingAnyID(logger, "SessionID", reqSessionID, cfmSessionID);
|
||||
}
|
||||
}
|
||||
|
@ -68,9 +68,8 @@ public class VeluxDiscoveryService extends AbstractDiscoveryService implements R
|
||||
|
||||
// Private
|
||||
|
||||
@SuppressWarnings("PMD.CompareObjectsWithEquals")
|
||||
private void updateLocalization() {
|
||||
if (localization == Localization.UNKNOWN && localeProvider != null && i18nProvider != null) {
|
||||
if (Localization.UNKNOWN.equals(localization) && (localeProvider != null) && (i18nProvider != null)) {
|
||||
logger.trace("updateLocalization(): creating Localization based on locale={},translation={}).",
|
||||
localeProvider, i18nProvider);
|
||||
localization = new Localization(localeProvider, i18nProvider);
|
||||
|
@ -125,9 +125,8 @@ public class VeluxHandlerFactory extends BaseThingHandlerFactory {
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("PMD.CompareObjectsWithEquals")
|
||||
private void updateLocalization() {
|
||||
if (localization == Localization.UNKNOWN && localeProvider != null && i18nProvider != null) {
|
||||
if (Localization.UNKNOWN.equals(localization) && (localeProvider != null) && (i18nProvider != null)) {
|
||||
logger.trace("updateLocalization(): creating Localization based on locale={},translation={}).",
|
||||
localeProvider, i18nProvider);
|
||||
localization = new Localization(localeProvider, i18nProvider);
|
||||
|
@ -14,12 +14,20 @@ package org.openhab.binding.velux.internal.handler;
|
||||
|
||||
import static org.openhab.binding.velux.internal.VeluxBindingConstants.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.velux.internal.bridge.VeluxBridgeRunProductCommand;
|
||||
import org.openhab.binding.velux.internal.bridge.common.GetProduct;
|
||||
import org.openhab.binding.velux.internal.bridge.common.RunProductCommand;
|
||||
import org.openhab.binding.velux.internal.bridge.slip.FunctionalParameters;
|
||||
import org.openhab.binding.velux.internal.bridge.slip.SCrunProductCommand;
|
||||
import org.openhab.binding.velux.internal.handler.utils.Thing2VeluxActuator;
|
||||
import org.openhab.binding.velux.internal.things.VeluxExistingProducts;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProduct;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProduct.ProductBridgeIndex;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProduct.ProductState;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProductPosition;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
@ -48,6 +56,7 @@ import org.slf4j.LoggerFactory;
|
||||
* </UL>
|
||||
*
|
||||
* @author Guenther Schreiner - Initial contribution.
|
||||
* @author Andrew Fiddian-Green - Refactoring and use alternate API set for Vane Position.
|
||||
*/
|
||||
@NonNullByDefault
|
||||
final class ChannelActuatorPosition extends ChannelHandlerTemplate {
|
||||
@ -62,6 +71,12 @@ final class ChannelActuatorPosition extends ChannelHandlerTemplate {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
/*
|
||||
* List of product states that shall be processed
|
||||
*/
|
||||
private static final List<ProductState> STATES_TO_PROCESS = Arrays.asList(ProductState.DONE, ProductState.EXECUTING,
|
||||
ProductState.MANUAL, ProductState.UNKNOWN);
|
||||
|
||||
// Public methods
|
||||
|
||||
/**
|
||||
@ -81,41 +96,83 @@ final class ChannelActuatorPosition extends ChannelHandlerTemplate {
|
||||
if (thisBridgeHandler.bridgeParameters.actuators.autoRefresh(thisBridgeHandler.thisBridge)) {
|
||||
LOGGER.trace("handleRefresh(): there are some existing products.");
|
||||
}
|
||||
|
||||
Thing2VeluxActuator veluxActuator = thisBridgeHandler.channel2VeluxActuator.get(channelUID);
|
||||
if (veluxActuator == null || !veluxActuator.isKnown()) {
|
||||
LOGGER.warn("handleRefresh(): unknown actuator.");
|
||||
break;
|
||||
}
|
||||
GetProduct bcp = thisBridgeHandler.thisBridge.bridgeAPI().getProduct();
|
||||
|
||||
GetProduct bcp = null;
|
||||
switch (channelId) {
|
||||
case CHANNEL_VANE_POSITION:
|
||||
bcp = thisBridgeHandler.thisBridge.bridgeAPI().getProductStatus();
|
||||
break;
|
||||
case CHANNEL_ACTUATOR_POSITION:
|
||||
case CHANNEL_ACTUATOR_STATE:
|
||||
bcp = thisBridgeHandler.thisBridge.bridgeAPI().getProduct();
|
||||
default:
|
||||
// unknown channel, will exit
|
||||
}
|
||||
|
||||
if (bcp == null) {
|
||||
LOGGER.trace("handleRefresh(): aborting processing as handler is null.");
|
||||
break;
|
||||
}
|
||||
|
||||
bcp.setProductId(veluxActuator.getProductBridgeIndex().toInt());
|
||||
if (thisBridgeHandler.thisBridge.bridgeCommunicate(bcp) && bcp.isCommunicationSuccessful()) {
|
||||
try {
|
||||
VeluxProduct product = bcp.getProduct();
|
||||
VeluxProductPosition position = new VeluxProductPosition(product.getDisplayPosition());
|
||||
if (position.isValid()) {
|
||||
if (CHANNEL_ACTUATOR_POSITION.equals(channelId)) {
|
||||
newState = position.getPositionAsPercentType(veluxActuator.isInverted());
|
||||
LOGGER.trace("handleRefresh(): position of actuator is {}%.", newState);
|
||||
break;
|
||||
} else if (CHANNEL_ACTUATOR_STATE.equals(channelId)) {
|
||||
newState = OnOffType.from(
|
||||
position.getPositionAsPercentType(veluxActuator.isInverted()).intValue() > 50);
|
||||
LOGGER.trace("handleRefresh(): state of actuator is {}.", newState);
|
||||
break;
|
||||
if ((!thisBridgeHandler.thisBridge.bridgeCommunicate(bcp)) || (!bcp.isCommunicationSuccessful())) {
|
||||
LOGGER.trace("handleRefresh(): bridge communication request failed.");
|
||||
break;
|
||||
}
|
||||
|
||||
VeluxProduct newProduct = bcp.getProduct();
|
||||
if (STATES_TO_PROCESS.contains(newProduct.getProductState())) {
|
||||
ProductBridgeIndex productBridgeIndex = newProduct.getBridgeProductIndex();
|
||||
VeluxExistingProducts existingProducts = thisBridgeHandler.existingProducts();
|
||||
VeluxProduct existingProduct = existingProducts.get(productBridgeIndex);
|
||||
if (!VeluxProduct.UNKNOWN.equals(existingProduct)) {
|
||||
switch (channelId) {
|
||||
case CHANNEL_VANE_POSITION:
|
||||
case CHANNEL_ACTUATOR_POSITION:
|
||||
case CHANNEL_ACTUATOR_STATE: {
|
||||
if (existingProducts.update(newProduct)) {
|
||||
existingProduct = existingProducts.get(productBridgeIndex);
|
||||
int posValue = VeluxProductPosition.VPP_VELUX_UNKNOWN;
|
||||
switch (channelId) {
|
||||
case CHANNEL_VANE_POSITION:
|
||||
posValue = existingProduct.getVaneDisplayPosition();
|
||||
break;
|
||||
case CHANNEL_ACTUATOR_POSITION:
|
||||
case CHANNEL_ACTUATOR_STATE:
|
||||
posValue = existingProduct.getDisplayPosition();
|
||||
}
|
||||
VeluxProductPosition position = new VeluxProductPosition(posValue);
|
||||
if (position.isValid()) {
|
||||
switch (channelId) {
|
||||
case CHANNEL_VANE_POSITION:
|
||||
newState = position.getPositionAsPercentType(false);
|
||||
break;
|
||||
case CHANNEL_ACTUATOR_POSITION:
|
||||
newState = position.getPositionAsPercentType(veluxActuator.isInverted());
|
||||
break;
|
||||
case CHANNEL_ACTUATOR_STATE:
|
||||
newState = OnOffType
|
||||
.from(position.getPositionAsPercentType(veluxActuator.isInverted())
|
||||
.intValue() > 50);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LOGGER.trace("handleRefresh(): position of actuator is 'UNDEFINED'.");
|
||||
newState = UnDefType.UNDEF;
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("handleRefresh(): getProducts() exception: {}.", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (newState == null) {
|
||||
newState = UnDefType.UNDEF;
|
||||
}
|
||||
} while (false); // common exit
|
||||
LOGGER.trace("handleRefresh() returns {}.", newState);
|
||||
LOGGER.trace("handleRefresh(): new state for channel id '{}' is '{}'.", channelId, newState);
|
||||
return newState;
|
||||
}
|
||||
|
||||
@ -129,7 +186,6 @@ final class ChannelActuatorPosition extends ChannelHandlerTemplate {
|
||||
* information for this channel.
|
||||
* @return newValue ...
|
||||
*/
|
||||
@SuppressWarnings("PMD.CompareObjectsWithEquals")
|
||||
static @Nullable Command handleCommand(ChannelUID channelUID, String channelId, Command command,
|
||||
VeluxBridgeHandler thisBridgeHandler) {
|
||||
LOGGER.debug("handleCommand({},{},{},{}) called.", channelUID, channelId, command, thisBridgeHandler);
|
||||
@ -138,48 +194,80 @@ final class ChannelActuatorPosition extends ChannelHandlerTemplate {
|
||||
if (thisBridgeHandler.bridgeParameters.actuators.autoRefresh(thisBridgeHandler.thisBridge)) {
|
||||
LOGGER.trace("handleCommand(): there are some existing products.");
|
||||
}
|
||||
|
||||
Thing2VeluxActuator veluxActuator = thisBridgeHandler.channel2VeluxActuator.get(channelUID);
|
||||
if (veluxActuator == null || !veluxActuator.isKnown()) {
|
||||
LOGGER.warn("handleRefresh(): unknown actuator.");
|
||||
break;
|
||||
}
|
||||
VeluxProductPosition targetLevel = VeluxProductPosition.UNKNOWN;
|
||||
if (CHANNEL_ACTUATOR_POSITION.equals(channelId)) {
|
||||
if (command instanceof UpDownType) {
|
||||
LOGGER.trace("handleCommand(): found UpDownType.{} command.", command);
|
||||
targetLevel = UpDownType.UP.equals(command) ^ veluxActuator.isInverted()
|
||||
? new VeluxProductPosition(PercentType.ZERO)
|
||||
: new VeluxProductPosition(PercentType.HUNDRED);
|
||||
} else if (command instanceof StopMoveType) {
|
||||
LOGGER.trace("handleCommand(): found StopMoveType.{} command.", command);
|
||||
targetLevel = StopMoveType.STOP.equals(command) ? new VeluxProductPosition() : targetLevel;
|
||||
} else if (command instanceof PercentType) {
|
||||
LOGGER.trace("handleCommand(): found PercentType.{} command", command);
|
||||
PercentType ptCommand = (PercentType) command;
|
||||
if (veluxActuator.isInverted()) {
|
||||
ptCommand = new PercentType(PercentType.HUNDRED.intValue() - ptCommand.intValue());
|
||||
|
||||
VeluxProductPosition mainParameter = null;
|
||||
FunctionalParameters functionalParameters = null;
|
||||
VeluxExistingProducts existingProducts = thisBridgeHandler.existingProducts();
|
||||
ProductBridgeIndex productBridgeIndex = veluxActuator.getProductBridgeIndex();
|
||||
|
||||
switch (channelId) {
|
||||
case CHANNEL_VANE_POSITION:
|
||||
if (command instanceof PercentType) {
|
||||
VeluxProduct existingProductClone = existingProducts.get(productBridgeIndex).clone();
|
||||
existingProductClone.setVanePosition(
|
||||
new VeluxProductPosition((PercentType) command).getPositionAsVeluxType());
|
||||
functionalParameters = existingProductClone.getFunctionalParameters();
|
||||
}
|
||||
LOGGER.trace("handleCommand(): found command to set level to {}.", ptCommand);
|
||||
targetLevel = new VeluxProductPosition(ptCommand);
|
||||
}
|
||||
} else if (CHANNEL_ACTUATOR_STATE.equals(channelId)) {
|
||||
if (command instanceof OnOffType) {
|
||||
LOGGER.trace("handleCommand(): found OnOffType.{} command.", command);
|
||||
targetLevel = OnOffType.OFF.equals(command) ^ veluxActuator.isInverted()
|
||||
? new VeluxProductPosition(PercentType.ZERO)
|
||||
: new VeluxProductPosition(PercentType.HUNDRED);
|
||||
}
|
||||
break;
|
||||
|
||||
case CHANNEL_ACTUATOR_POSITION:
|
||||
if (command instanceof UpDownType) {
|
||||
mainParameter = UpDownType.UP.equals(command) ^ veluxActuator.isInverted()
|
||||
? new VeluxProductPosition(PercentType.ZERO)
|
||||
: new VeluxProductPosition(PercentType.HUNDRED);
|
||||
} else if (command instanceof StopMoveType) {
|
||||
mainParameter = StopMoveType.STOP.equals(command) ? new VeluxProductPosition() : mainParameter;
|
||||
} else if (command instanceof PercentType) {
|
||||
PercentType ptCommand = (PercentType) command;
|
||||
if (veluxActuator.isInverted()) {
|
||||
ptCommand = new PercentType(PercentType.HUNDRED.intValue() - ptCommand.intValue());
|
||||
}
|
||||
mainParameter = new VeluxProductPosition(ptCommand);
|
||||
}
|
||||
break;
|
||||
|
||||
case CHANNEL_ACTUATOR_STATE:
|
||||
if (command instanceof OnOffType) {
|
||||
mainParameter = OnOffType.OFF.equals(command) ^ veluxActuator.isInverted()
|
||||
? new VeluxProductPosition(PercentType.ZERO)
|
||||
: new VeluxProductPosition(PercentType.HUNDRED);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// unknown channel => do nothing..
|
||||
}
|
||||
if (targetLevel == VeluxProductPosition.UNKNOWN) {
|
||||
LOGGER.info("handleCommand({},{}): ignoring command.", channelUID.getAsString(), command);
|
||||
break;
|
||||
}
|
||||
LOGGER.debug("handleCommand(): sending command with target level {}.", targetLevel);
|
||||
new VeluxBridgeRunProductCommand().sendCommand(thisBridgeHandler.thisBridge,
|
||||
veluxActuator.getProductBridgeIndex().toInt(), targetLevel);
|
||||
LOGGER.trace("handleCommand(): The new shutter level will be send through the home monitoring events.");
|
||||
if (thisBridgeHandler.bridgeParameters.actuators.autoRefresh(thisBridgeHandler.thisBridge)) {
|
||||
LOGGER.trace("handleCommand(): position of actuators are updated.");
|
||||
|
||||
if ((mainParameter != null) || (functionalParameters != null)) {
|
||||
LOGGER.debug("handleCommand(): sending command '{}' for channel id '{}'.", command, channelId);
|
||||
RunProductCommand bcp = thisBridgeHandler.thisBridge.bridgeAPI().runProductCommand();
|
||||
boolean success = false;
|
||||
if (bcp instanceof SCrunProductCommand) {
|
||||
synchronized (bcp) {
|
||||
if (bcp.setNodeIdAndParameters(productBridgeIndex.toInt(), mainParameter, functionalParameters)
|
||||
&& thisBridgeHandler.thisBridge.bridgeCommunicate(bcp)
|
||||
&& bcp.isCommunicationSuccessful()) {
|
||||
success = true;
|
||||
if (thisBridgeHandler.bridgeParameters.actuators
|
||||
.autoRefresh(thisBridgeHandler.thisBridge)) {
|
||||
LOGGER.trace("handleCommand(): actuator position will be updated via polling.");
|
||||
}
|
||||
if (existingProducts.update(((SCrunProductCommand) bcp).getProduct())) {
|
||||
LOGGER.trace("handleCommand(): actuator position immediate update requested.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LOGGER.debug("handleCommand(): sendCommand() finished {}.",
|
||||
(success ? "successfully" : "with failure"));
|
||||
} else {
|
||||
LOGGER.info("handleCommand(): ignoring command '{}' for channel id '{}'.", command, channelId);
|
||||
}
|
||||
} while (false); // common exit
|
||||
return newValue;
|
||||
|
@ -107,7 +107,6 @@ final class ChannelVShutterPosition extends ChannelHandlerTemplate {
|
||||
* information for this channel.
|
||||
* @return newValue ...
|
||||
*/
|
||||
@SuppressWarnings("PMD.CompareObjectsWithEquals")
|
||||
static @Nullable Command handleCommand(ChannelUID channelUID, String channelId, Command command,
|
||||
VeluxBridgeHandler thisBridgeHandler) {
|
||||
LOGGER.debug("handleCommand({},{},{},{}) called.", channelUID, channelId, command, thisBridgeHandler);
|
||||
@ -148,7 +147,7 @@ final class ChannelVShutterPosition extends ChannelHandlerTemplate {
|
||||
LOGGER.trace("handleCommand(): scene name is {}.", sceneName);
|
||||
VeluxScene thisScene2 = thisBridgeHandler.bridgeParameters.scenes.getChannel().existingScenes
|
||||
.get(new SceneName(sceneName));
|
||||
if (thisScene2 == VeluxScene.UNKNOWN) {
|
||||
if (VeluxScene.UNKNOWN.equals(thisScene2)) {
|
||||
LOGGER.warn(
|
||||
"handleCommand(): aborting command as scene with name {} is not registered; please check your KLF scene definitions.",
|
||||
sceneName);
|
||||
|
@ -55,6 +55,7 @@ import org.openhab.binding.velux.internal.things.VeluxExistingScenes;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProduct;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProduct.ProductBridgeIndex;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProductPosition;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProductPosition.PositionType;
|
||||
import org.openhab.binding.velux.internal.utils.Localization;
|
||||
import org.openhab.core.common.AbstractUID;
|
||||
import org.openhab.core.common.NamedThreadFactory;
|
||||
@ -541,20 +542,27 @@ public class VeluxBridgeHandler extends ExtendedBaseBridgeHandler implements Vel
|
||||
if (!channelPbi.equals(productPbi)) {
|
||||
continue;
|
||||
}
|
||||
// Handle value inversion
|
||||
boolean isInverted = actuator.isInverted();
|
||||
logger.trace("syncChannelsWithProducts(): isInverted is {}.", isInverted);
|
||||
VeluxProductPosition position = new VeluxProductPosition(product.getDisplayPosition());
|
||||
boolean isInverted;
|
||||
VeluxProductPosition position;
|
||||
if (channelUID.getId().equals(VeluxBindingConstants.CHANNEL_VANE_POSITION)) {
|
||||
isInverted = false;
|
||||
position = new VeluxProductPosition(product.getVanePosition());
|
||||
} else {
|
||||
// Handle value inversion
|
||||
isInverted = actuator.isInverted();
|
||||
logger.trace("syncChannelsWithProducts(): isInverted is {}.", isInverted);
|
||||
position = new VeluxProductPosition(product.getDisplayPosition());
|
||||
}
|
||||
if (position.isValid()) {
|
||||
PercentType positionAsPercent = position.getPositionAsPercentType(isInverted);
|
||||
logger.debug("syncChannelsWithProducts(): updating channel {} to position {}%.", channelUID,
|
||||
positionAsPercent);
|
||||
updateState(channelUID, positionAsPercent);
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
logger.trace("syncChannelsWithProducts(): update channel {} to 'UNDEFINED'.", channelUID);
|
||||
logger.trace("syncChannelsWithProducts(): updating channel {} to 'UNDEFINED'.", channelUID);
|
||||
updateState(channelUID, UnDefType.UNDEF);
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
logger.trace("syncChannelsWithProducts(): resetting dirty flag.");
|
||||
@ -674,6 +682,7 @@ public class VeluxBridgeHandler extends ExtendedBaseBridgeHandler implements Vel
|
||||
case ACTUATOR_STATE:
|
||||
case ROLLERSHUTTER_POSITION:
|
||||
case WINDOW_POSITION:
|
||||
case ROLLERSHUTTER_VANE_POSITION:
|
||||
newState = ChannelActuatorPosition.handleRefresh(channelUID, channelId, this);
|
||||
break;
|
||||
case ACTUATOR_LIMIT_MINIMUM:
|
||||
@ -694,9 +703,8 @@ public class VeluxBridgeHandler extends ExtendedBaseBridgeHandler implements Vel
|
||||
break;
|
||||
|
||||
default:
|
||||
logger.trace(
|
||||
"handleCommandCommsJob(): cannot handle REFRESH on channel {} as it is of type {}.",
|
||||
itemName, channelId);
|
||||
logger.warn("{} Cannot handle REFRESH on channel {} as it is of type {}.",
|
||||
VeluxBindingConstants.LOGGING_CONTACT, itemName, channelId);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.warn("Cannot handle REFRESH on channel {} as it isn't (yet) known to the bridge.", itemName);
|
||||
@ -769,6 +777,7 @@ public class VeluxBridgeHandler extends ExtendedBaseBridgeHandler implements Vel
|
||||
case ACTUATOR_STATE:
|
||||
case ROLLERSHUTTER_POSITION:
|
||||
case WINDOW_POSITION:
|
||||
case ROLLERSHUTTER_VANE_POSITION:
|
||||
newValue = ChannelActuatorPosition.handleCommand(channelUID, channelId, command, this);
|
||||
break;
|
||||
case ACTUATOR_LIMIT_MINIMUM:
|
||||
@ -845,13 +854,17 @@ public class VeluxBridgeHandler extends ExtendedBaseBridgeHandler implements Vel
|
||||
logger.trace("moveRelative() called on {}", getThing().getUID());
|
||||
RunProductCommand bcp = thisBridge.bridgeAPI().runProductCommand();
|
||||
if (bcp != null) {
|
||||
bcp.setNodeAndMainParameter(nodeId, new VeluxProductPosition(new PercentType(Math.abs(relativePercent)))
|
||||
.getAsRelativePosition((relativePercent >= 0)));
|
||||
// background execution of moveRelative
|
||||
submitCommunicationsJob(() -> {
|
||||
if (thisBridge.bridgeCommunicate(bcp)) {
|
||||
logger.trace("moveRelative() command {}sucessfully sent to {}",
|
||||
bcp.isCommunicationSuccessful() ? "" : "un", getThing().getUID());
|
||||
synchronized (bcp) {
|
||||
bcp.setNodeIdAndParameters(nodeId,
|
||||
new VeluxProductPosition(new PercentType(Math.abs(relativePercent))).overridePositionType(
|
||||
relativePercent > 0 ? PositionType.OFFSET_POSITIVE : PositionType.OFFSET_NEGATIVE),
|
||||
null);
|
||||
if (thisBridge.bridgeCommunicate(bcp)) {
|
||||
logger.trace("moveRelative() command {}sucessfully sent to {}",
|
||||
bcp.isCommunicationSuccessful() ? "" : "un", getThing().getUID());
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
|
@ -135,12 +135,11 @@ public class Thing2VeluxActuator {
|
||||
*
|
||||
* @return <b>bridgeProductIndex</B> for accessing the Velux device (or ProductBridgeIndex.UNKNOWN if not found).
|
||||
*/
|
||||
@SuppressWarnings("PMD.CompareObjectsWithEquals")
|
||||
public ProductBridgeIndex getProductBridgeIndex() {
|
||||
if (thisProduct == VeluxProduct.UNKNOWN) {
|
||||
if (VeluxProduct.UNKNOWN.equals(thisProduct)) {
|
||||
mapThing2Velux();
|
||||
}
|
||||
if (thisProduct == VeluxProduct.UNKNOWN) {
|
||||
if (VeluxProduct.UNKNOWN.equals(thisProduct)) {
|
||||
return ProductBridgeIndex.UNKNOWN;
|
||||
}
|
||||
return thisProduct.getBridgeProductIndex();
|
||||
@ -152,9 +151,8 @@ public class Thing2VeluxActuator {
|
||||
*
|
||||
* @return <b>isKnown</B> as boolean.
|
||||
*/
|
||||
@SuppressWarnings("PMD.CompareObjectsWithEquals")
|
||||
public boolean isKnown() {
|
||||
return (!(this.getProductBridgeIndex() == ProductBridgeIndex.UNKNOWN));
|
||||
return (!(ProductBridgeIndex.UNKNOWN.equals(getProductBridgeIndex())));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -164,12 +162,11 @@ public class Thing2VeluxActuator {
|
||||
*
|
||||
* @return <b>isInverted</B> for handling of values of the Velux device (or false if not found)..
|
||||
*/
|
||||
@SuppressWarnings("PMD.CompareObjectsWithEquals")
|
||||
public boolean isInverted() {
|
||||
if (thisProduct == VeluxProduct.UNKNOWN) {
|
||||
if (VeluxProduct.UNKNOWN.equals(thisProduct)) {
|
||||
mapThing2Velux();
|
||||
}
|
||||
if (thisProduct == VeluxProduct.UNKNOWN) {
|
||||
if (VeluxProduct.UNKNOWN.equals(thisProduct)) {
|
||||
logger.warn("isInverted(): Thing not found in Velux Bridge.");
|
||||
}
|
||||
return isInverted;
|
||||
|
@ -12,12 +12,17 @@
|
||||
*/
|
||||
package org.openhab.binding.velux.internal.things;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.velux.internal.VeluxBindingConstants;
|
||||
import org.openhab.binding.velux.internal.bridge.slip.FunctionalParameters;
|
||||
import org.openhab.binding.velux.internal.things.VeluxKLFAPI.Command;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProduct.ProductBridgeIndex;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProduct.ProductState;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -56,6 +61,12 @@ public class VeluxExistingProducts {
|
||||
*/
|
||||
private boolean dirty;
|
||||
|
||||
/*
|
||||
* Permitted list of product states whose position values shall be accepted.
|
||||
*/
|
||||
private static final List<ProductState> PERMITTED_VALUE_STATES = Arrays.asList(ProductState.EXECUTING,
|
||||
ProductState.DONE);
|
||||
|
||||
// Constructor methods
|
||||
|
||||
public VeluxExistingProducts() {
|
||||
@ -110,32 +121,87 @@ public class VeluxExistingProducts {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean update(ProductBridgeIndex bridgeProductIndex, int productState, int productPosition,
|
||||
int productTarget) {
|
||||
logger.debug("update(bridgeProductIndex={},productState={},productPosition={},productTarget={}) called.",
|
||||
bridgeProductIndex.toInt(), productState, productPosition, productTarget);
|
||||
if (!isRegistered(bridgeProductIndex)) {
|
||||
logger.warn("update() failed as actuator (with index {}) is not registered.", bridgeProductIndex.toInt());
|
||||
/**
|
||||
* Update the product in the existing products database by applying the data from the new product argument. This
|
||||
* method may ignore the new product if it was created by certain originating commands, or if the new product has
|
||||
* certain actuator states.
|
||||
*
|
||||
* @param requestingCommand the command that requested the data from the hub and so triggered calling this method.
|
||||
* @param newProduct the product containing new data.
|
||||
*
|
||||
* @return true if the product exists in the database.
|
||||
*/
|
||||
public boolean update(VeluxProduct newProduct) {
|
||||
ProductBridgeIndex productBridgeIndex = newProduct.getBridgeProductIndex();
|
||||
if (!isRegistered(productBridgeIndex)) {
|
||||
logger.warn("update() failed as actuator (with index {}) is not registered.", productBridgeIndex.toInt());
|
||||
return false;
|
||||
}
|
||||
VeluxProduct thisProduct = this.get(bridgeProductIndex);
|
||||
dirty |= thisProduct.setState(productState);
|
||||
dirty |= thisProduct.setCurrentPosition(productPosition);
|
||||
dirty |= thisProduct.setTarget(productTarget);
|
||||
if (dirty) {
|
||||
String uniqueIndex = thisProduct.getProductUniqueIndex();
|
||||
logger.trace("update(): updating by UniqueIndex {}.", uniqueIndex);
|
||||
existingProductsByUniqueIndex.replace(uniqueIndex, thisProduct);
|
||||
modifiedProductsByUniqueIndex.put(uniqueIndex, thisProduct);
|
||||
}
|
||||
logger.trace("update() successfully finished (dirty={}).", dirty);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean update(VeluxProduct currentProduct) {
|
||||
logger.trace("update(currentProduct={}) called.", currentProduct);
|
||||
return update(currentProduct.getBridgeProductIndex(), currentProduct.getState(),
|
||||
currentProduct.getCurrentPosition(), currentProduct.getTarget());
|
||||
VeluxProduct theProduct = this.get(productBridgeIndex);
|
||||
|
||||
String oldProduct = "";
|
||||
if (logger.isDebugEnabled()) {
|
||||
oldProduct = theProduct.toString();
|
||||
}
|
||||
|
||||
boolean dirty = false;
|
||||
|
||||
// ignore commands with state 'not used'
|
||||
boolean ignoreNotUsed = (ProductState.NOT_USED == ProductState.of(newProduct.getState()));
|
||||
|
||||
// specially ignore commands from buggy devices (e.g. Somfy) which have bad data
|
||||
boolean ignoreSpecial = theProduct.isSomfyProduct()
|
||||
&& (Command.GW_OPENHAB_RECEIVEONLY == newProduct.getCreatorCommand())
|
||||
&& !VeluxProductPosition.isValid(newProduct.getCurrentPosition())
|
||||
&& !VeluxProductPosition.isValid(newProduct.getTarget());
|
||||
|
||||
if ((!ignoreNotUsed) && (!ignoreSpecial)) {
|
||||
int newState = newProduct.getState();
|
||||
int theState = theProduct.getState();
|
||||
|
||||
// always update the actuator state, but only set dirty flag if they are not operationally equivalent
|
||||
if (theProduct.setState(newState)) {
|
||||
dirty |= !ProductState.equivalent(theState, newState);
|
||||
}
|
||||
|
||||
// only update the actual position values if the state is permitted
|
||||
if (PERMITTED_VALUE_STATES.contains(ProductState.of(newState))) {
|
||||
int newValue = newProduct.getCurrentPosition();
|
||||
if (VeluxProductPosition.isUnknownOrValid(newValue)) {
|
||||
dirty |= theProduct.setCurrentPosition(newValue);
|
||||
}
|
||||
newValue = newProduct.getTarget();
|
||||
if (VeluxProductPosition.isUnknownOrValid(newValue)) {
|
||||
dirty |= theProduct.setTarget(newValue);
|
||||
}
|
||||
if (theProduct.supportsVanePosition()) {
|
||||
FunctionalParameters newFunctionalParameters = newProduct.getFunctionalParameters();
|
||||
if (newFunctionalParameters != null) {
|
||||
dirty |= theProduct.setFunctionalParameters(newFunctionalParameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update modified product database
|
||||
if (dirty) {
|
||||
this.dirty = true;
|
||||
String uniqueIndex = theProduct.getProductUniqueIndex();
|
||||
logger.trace("update(): updating by UniqueIndex {}.", uniqueIndex);
|
||||
existingProductsByUniqueIndex.replace(uniqueIndex, theProduct);
|
||||
modifiedProductsByUniqueIndex.put(uniqueIndex, theProduct);
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
if (dirty) {
|
||||
logger.debug("update() theProduct:{} (previous)", oldProduct);
|
||||
}
|
||||
logger.debug("update() newProduct:{} ({})", newProduct, dirty ? "modifier" : "identical");
|
||||
logger.debug("update() theProduct:{} ({})", theProduct, dirty ? "modified" : "unchanged");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public VeluxProduct get(String productUniqueIndex) {
|
||||
|
@ -156,7 +156,7 @@ public class VeluxKLFAPI {
|
||||
"Acknowledge to GW_CS_GET_SYSTEM_TABLE_DATA_REQList of nodes in the gateways systemtable."),
|
||||
GW_CS_DISCOVER_NODES_REQ((short) 0x0103, "Start CS DiscoverNodes macro in KLF200."),
|
||||
GW_CS_DISCOVER_NODES_CFM((short) 0x0104, "Acknowledge to GW_CS_DISCOVER_NODES_REQ command."),
|
||||
GW_CS_DISCOVER_NODES_NTF((short) 0x0105, "Acknowledge to GW_CS_DISCOVER_NODES_REQ command."),
|
||||
GW_CS_DISCOVER_NODES_NTF((short) 0x0105, "Notification to GW_CS_DISCOVER_NODES_REQ command."),
|
||||
GW_CS_REMOVE_NODES_REQ((short) 0x0106, "Remove one or more nodes in the systemtable."),
|
||||
GW_CS_REMOVE_NODES_CFM((short) 0x0107, "Acknowledge to GW_CS_REMOVE_NODES_REQ."),
|
||||
GW_CS_VIRGIN_STATE_REQ((short) 0x0108, "Clear systemtable and delete system key."),
|
||||
@ -164,11 +164,11 @@ public class VeluxKLFAPI {
|
||||
GW_CS_CONTROLLER_COPY_REQ((short) 0x010A,
|
||||
"Setup KLF200 to get or give a system to or from another io-homecontrol® remote control. By a system means all nodes in the systemtable and the system key."),
|
||||
GW_CS_CONTROLLER_COPY_CFM((short) 0x010B, "Acknowledge to GW_CS_CONTROLLER_COPY_REQ."),
|
||||
GW_CS_CONTROLLER_COPY_NTF((short) 0x010C, "Acknowledge to GW_CS_CONTROLLER_COPY_REQ."),
|
||||
GW_CS_CONTROLLER_COPY_NTF((short) 0x010C, "Notification to GW_CS_CONTROLLER_COPY_REQ."),
|
||||
GW_CS_CONTROLLER_COPY_CANCEL_NTF((short) 0x010D, "Cancellation of system copy to other controllers."),
|
||||
GW_CS_RECEIVE_KEY_REQ((short) 0x010E, "Receive system key from another controller."),
|
||||
GW_CS_RECEIVE_KEY_CFM((short) 0x010F, "Acknowledge to GW_CS_RECEIVE_KEY_REQ."),
|
||||
GW_CS_RECEIVE_KEY_NTF((short) 0x0110, "Acknowledge to GW_CS_RECEIVE_KEY_REQ with status."),
|
||||
GW_CS_RECEIVE_KEY_NTF((short) 0x0110, "Notification to GW_CS_RECEIVE_KEY_REQ with status."),
|
||||
GW_CS_PGC_JOB_NTF((short) 0x0111,
|
||||
"Information on Product Generic Configuration job initiated by press on PGC button."),
|
||||
GW_CS_SYSTEM_TABLE_UPDATE_NTF((short) 0x0112,
|
||||
@ -185,27 +185,27 @@ public class VeluxKLFAPI {
|
||||
|
||||
GW_GET_NODE_INFORMATION_REQ((short) 0x0200, "Request extended information of one specific actuator node."),
|
||||
GW_GET_NODE_INFORMATION_CFM((short) 0x0201, "Acknowledge to GW_GET_NODE_INFORMATION_REQ."),
|
||||
GW_GET_NODE_INFORMATION_NTF((short) 0x0210, "Acknowledge to GW_GET_NODE_INFORMATION_REQ."),
|
||||
GW_GET_NODE_INFORMATION_NTF((short) 0x0210, "Notification to GW_GET_NODE_INFORMATION_REQ."),
|
||||
GW_GET_ALL_NODES_INFORMATION_REQ((short) 0x0202, "Request extended information of all nodes."),
|
||||
GW_GET_ALL_NODES_INFORMATION_CFM((short) 0x0203, "Acknowledge to GW_GET_ALL_NODES_INFORMATION_REQ"),
|
||||
GW_GET_ALL_NODES_INFORMATION_NTF((short) 0x0204,
|
||||
"Acknowledge to GW_GET_ALL_NODES_INFORMATION_REQ. Holds node information"),
|
||||
"Notification to GW_GET_ALL_NODES_INFORMATION_REQ. Holds node information"),
|
||||
GW_GET_ALL_NODES_INFORMATION_FINISHED_NTF((short) 0x0205,
|
||||
"Acknowledge to GW_GET_ALL_NODES_INFORMATION_REQ. No more nodes."),
|
||||
"Notificatione to GW_GET_ALL_NODES_INFORMATION_REQ. No more nodes."),
|
||||
GW_SET_NODE_VARIATION_REQ((short) 0x0206, "Set node variation."),
|
||||
GW_SET_NODE_VARIATION_CFM((short) 0x0207, "Acknowledge to GW_SET_NODE_VARIATION_REQ."),
|
||||
GW_SET_NODE_NAME_REQ((short) 0x0208, "Set node name."),
|
||||
GW_SET_NODE_NAME_CFM((short) 0x0209, "Acknowledge to GW_SET_NODE_NAME_REQ."),
|
||||
GW_SET_NODE_VELOCITY_REQ((short) 0x020A, "Set node velocity."),
|
||||
GW_SET_NODE_VELOCITY_CFM((short) 0x020B, "Acknowledge to GW_SET_NODE_VELOCITY_REQ."),
|
||||
GW_NODE_INFORMATION_CHANGED_NTF((short) 0x020C, "Information has been updated."),
|
||||
GW_NODE_STATE_POSITION_CHANGED_NTF((short) 0x0211, "Information has been updated."),
|
||||
GW_NODE_INFORMATION_CHANGED_NTF((short) 0x020C, "Notification that information has been updated."),
|
||||
GW_NODE_STATE_POSITION_CHANGED_NTF((short) 0x0211, "Notification information has been updated."),
|
||||
GW_SET_NODE_ORDER_AND_PLACEMENT_REQ((short) 0x020D, "Set search order and room placement."),
|
||||
GW_SET_NODE_ORDER_AND_PLACEMENT_CFM((short) 0x020E, "Acknowledge to GW_SET_NODE_ORDER_AND_PLACEMENT_REQ."),
|
||||
|
||||
GW_GET_GROUP_INFORMATION_REQ((short) 0x0220, "Request information about all defined groups."),
|
||||
GW_GET_GROUP_INFORMATION_CFM((short) 0x0221, "Acknowledge to GW_GET_GROUP_INFORMATION_REQ."),
|
||||
GW_GET_GROUP_INFORMATION_NTF((short) 0x0230, "Acknowledge to GW_GET_NODE_INFORMATION_REQ."),
|
||||
GW_GET_GROUP_INFORMATION_NTF((short) 0x0230, "Notification to GW_GET_GROUP_INFORMATION_REQ."),
|
||||
GW_SET_GROUP_INFORMATION_REQ((short) 0x0222, "Change an existing group."),
|
||||
GW_SET_GROUP_INFORMATION_CFM((short) 0x0223, "Acknowledge to GW_SET_GROUP_INFORMATION_REQ."),
|
||||
GW_GROUP_INFORMATION_CHANGED_NTF((short) 0x0224,
|
||||
@ -216,8 +216,9 @@ public class VeluxKLFAPI {
|
||||
GW_NEW_GROUP_CFM((short) 0x0228, ""),
|
||||
GW_GET_ALL_GROUPS_INFORMATION_REQ((short) 0x0229, "Request information about all defined groups."),
|
||||
GW_GET_ALL_GROUPS_INFORMATION_CFM((short) 0x022A, "Acknowledge to GW_GET_ALL_GROUPS_INFORMATION_REQ."),
|
||||
GW_GET_ALL_GROUPS_INFORMATION_NTF((short) 0x022B, "Acknowledge to GW_GET_ALL_GROUPS_INFORMATION_REQ."),
|
||||
GW_GET_ALL_GROUPS_INFORMATION_FINISHED_NTF((short) 0x022C, "Acknowledge to GW_GET_ALL_GROUPS_INFORMATION_REQ."),
|
||||
GW_GET_ALL_GROUPS_INFORMATION_NTF((short) 0x022B, "Notification to GW_GET_ALL_GROUPS_INFORMATION_REQ."),
|
||||
GW_GET_ALL_GROUPS_INFORMATION_FINISHED_NTF((short) 0x022C,
|
||||
"Notification to GW_GET_ALL_GROUPS_INFORMATION_REQ."),
|
||||
GW_GROUP_DELETED_NTF((short) 0x022D,
|
||||
"GW_GROUP_DELETED_NTF is broadcasted to all, when a group has been removed."),
|
||||
GW_HOUSE_STATUS_MONITOR_ENABLE_REQ((short) 0x0240, "Enable house status monitor."),
|
||||
@ -227,55 +228,55 @@ public class VeluxKLFAPI {
|
||||
|
||||
GW_COMMAND_SEND_REQ((short) 0x0300, "Send activating command direct to one or more io-homecontrol® nodes."),
|
||||
GW_COMMAND_SEND_CFM((short) 0x0301, "Acknowledge to GW_COMMAND_SEND_REQ."),
|
||||
GW_COMMAND_RUN_STATUS_NTF((short) 0x0302, "Gives run status for io-homecontrol® node."),
|
||||
GW_COMMAND_RUN_STATUS_NTF((short) 0x0302, "Notification gives run status for io-homecontrol® node."),
|
||||
GW_COMMAND_REMAINING_TIME_NTF((short) 0x0303,
|
||||
"Gives remaining time before io-homecontrol® node enter target position."),
|
||||
"Notification gives remaining time before io-homecontrol® node enter target position."),
|
||||
GW_SESSION_FINISHED_NTF((short) 0x0304,
|
||||
"Command send, Status request, Wink, Mode or Stop session is finished."),
|
||||
"Notification command send, Status request, Wink, Mode or Stop session is finished."),
|
||||
GW_STATUS_REQUEST_REQ((short) 0x0305, "Get status request from one or more io-homecontrol® nodes."),
|
||||
GW_STATUS_REQUEST_CFM((short) 0x0306, "Acknowledge to GW_STATUS_REQUEST_REQ."),
|
||||
GW_STATUS_REQUEST_NTF((short) 0x0307,
|
||||
"Acknowledge to GW_STATUS_REQUEST_REQ. Status request from one or more io-homecontrol® nodes."),
|
||||
"Notification to GW_STATUS_REQUEST_REQ. Status request from one or more io-homecontrol® nodes."),
|
||||
GW_WINK_SEND_REQ((short) 0x0308, "Request from one or more io-homecontrol® nodes to Wink."),
|
||||
GW_WINK_SEND_CFM((short) 0x0309, "Acknowledge to GW_WINK_SEND_REQ"),
|
||||
GW_WINK_SEND_NTF((short) 0x030A, "Status info for performed wink request."),
|
||||
GW_WINK_SEND_NTF((short) 0x030A, "Notification status info for performed wink request."),
|
||||
|
||||
GW_SET_LIMITATION_REQ((short) 0x0310, "Set a parameter limitation in an actuator."),
|
||||
GW_SET_LIMITATION_CFM((short) 0x0311, "Acknowledge to GW_SET_LIMITATION_REQ."),
|
||||
GW_GET_LIMITATION_STATUS_REQ((short) 0x0312, "Get parameter limitation in an actuator."),
|
||||
GW_GET_LIMITATION_STATUS_CFM((short) 0x0313, "Acknowledge to GW_GET_LIMITATION_STATUS_REQ."),
|
||||
GW_LIMITATION_STATUS_NTF((short) 0x0314, "Hold information about limitation."),
|
||||
GW_LIMITATION_STATUS_NTF((short) 0x0314, "Notification hold information about limitation."),
|
||||
GW_MODE_SEND_REQ((short) 0x0320, "Send Activate Mode to one or more io-homecontrol® nodes."),
|
||||
GW_MODE_SEND_CFM((short) 0x0321, "Acknowledge to GW_MODE_SEND_REQ"),
|
||||
GW_MODE_SEND_NTF((short) 0x0322, "Notify with Mode activation info."),
|
||||
GW_MODE_SEND_NTF((short) 0x0322, "Notification with Mode activation info."),
|
||||
|
||||
GW_INITIALIZE_SCENE_REQ((short) 0x0400, "Prepare gateway to record a scene."),
|
||||
GW_INITIALIZE_SCENE_CFM((short) 0x0401, "Acknowledge to GW_INITIALIZE_SCENE_REQ."),
|
||||
GW_INITIALIZE_SCENE_NTF((short) 0x0402, "Acknowledge to GW_INITIALIZE_SCENE_REQ."),
|
||||
GW_INITIALIZE_SCENE_NTF((short) 0x0402, "Notification to GW_INITIALIZE_SCENE_REQ."),
|
||||
GW_INITIALIZE_SCENE_CANCEL_REQ((short) 0x0403, "Cancel record scene process."),
|
||||
GW_INITIALIZE_SCENE_CANCEL_CFM((short) 0x0404, "Acknowledge to GW_INITIALIZE_SCENE_CANCEL_REQ command."),
|
||||
GW_RECORD_SCENE_REQ((short) 0x0405, "Store actuator positions changes since GW_INITIALIZE_SCENE, as a scene."),
|
||||
GW_RECORD_SCENE_CFM((short) 0x0406, "Acknowledge to GW_RECORD_SCENE_REQ."),
|
||||
GW_RECORD_SCENE_NTF((short) 0x0407, "Acknowledge to GW_RECORD_SCENE_REQ."),
|
||||
GW_RECORD_SCENE_NTF((short) 0x0407, "Notification to GW_RECORD_SCENE_REQ."),
|
||||
GW_DELETE_SCENE_REQ((short) 0x0408, "Delete a recorded scene."),
|
||||
GW_DELETE_SCENE_CFM((short) 0x0409, "Acknowledge to GW_DELETE_SCENE_REQ."),
|
||||
GW_RENAME_SCENE_REQ((short) 0x040A, "Request a scene to be renamed."),
|
||||
GW_RENAME_SCENE_CFM((short) 0x040B, "Acknowledge to GW_RENAME_SCENE_REQ."),
|
||||
GW_GET_SCENE_LIST_REQ((short) 0x040C, "Request a list of scenes."),
|
||||
GW_GET_SCENE_LIST_CFM((short) 0x040D, "Acknowledge to GW_GET_SCENE_LIST."),
|
||||
GW_GET_SCENE_LIST_NTF((short) 0x040E, "Acknowledge to GW_GET_SCENE_LIST."),
|
||||
GW_GET_SCENE_LIST_NTF((short) 0x040E, "Notification to GW_GET_SCENE_LIST."),
|
||||
GW_GET_SCENE_INFOAMATION_REQ((short) 0x040F, "Request extended information for one given scene."),
|
||||
GW_GET_SCENE_INFOAMATION_CFM((short) 0x0410, "Acknowledge to GW_GET_SCENE_INFOAMATION_REQ."),
|
||||
GW_GET_SCENE_INFOAMATION_NTF((short) 0x0411, "Acknowledge to GW_GET_SCENE_INFOAMATION_REQ."),
|
||||
GW_GET_SCENE_INFOAMATION_NTF((short) 0x0411, "Notification to GW_GET_SCENE_INFOAMATION_REQ."),
|
||||
GW_ACTIVATE_SCENE_REQ((short) 0x0412, "Request gateway to enter a scene."),
|
||||
GW_ACTIVATE_SCENE_CFM((short) 0x0413, "Acknowledge to GW_ACTIVATE_SCENE_REQ."),
|
||||
GW_STOP_SCENE_REQ((short) 0x0415, "Request all nodes in a given scene to stop at their current position."),
|
||||
GW_STOP_SCENE_CFM((short) 0x0416, "Acknowledge to GW_STOP_SCENE_REQ."),
|
||||
GW_SCENE_INFORMATION_CHANGED_NTF((short) 0x0419, "A scene has either been changed or removed."),
|
||||
GW_SCENE_INFORMATION_CHANGED_NTF((short) 0x0419, "Notification a scene has either been changed or removed."),
|
||||
|
||||
GW_ACTIVATE_PRODUCTGROUP_REQ((short) 0x0447, "Activate a product group in a given direction."),
|
||||
GW_ACTIVATE_PRODUCTGROUP_CFM((short) 0x0448, "Acknowledge to GW_ACTIVATE_PRODUCTGROUP_REQ."),
|
||||
GW_ACTIVATE_PRODUCTGROUP_NTF((short) 0x0449, "Acknowledge to GW_ACTIVATE_PRODUCTGROUP_REQ."),
|
||||
GW_ACTIVATE_PRODUCTGROUP_NTF((short) 0x0449, "Notification to GW_ACTIVATE_PRODUCTGROUP_REQ."),
|
||||
|
||||
GW_GET_CONTACT_INPUT_LINK_LIST_REQ((short) 0x0460,
|
||||
"Get list of assignments to all Contact Input to scene or product group."),
|
||||
@ -290,11 +291,11 @@ public class VeluxKLFAPI {
|
||||
GW_CLEAR_ACTIVATION_LOG_REQ((short) 0x0502, "Request clear all data in activation log."),
|
||||
GW_CLEAR_ACTIVATION_LOG_CFM((short) 0x0503, "Confirm clear all data in activation log."),
|
||||
GW_GET_ACTIVATION_LOG_LINE_REQ((short) 0x0504, "Request line from activation log."),
|
||||
GW_GET_ACTIVATION_LOG_LINE_CFM((short) 0x0505, "Confirm line from activation log."),
|
||||
GW_ACTIVATION_LOG_UPDATED_NTF((short) 0x0506, "Confirm line from activation log."),
|
||||
GW_GET_ACTIVATION_LOG_LINE_CFM((short) 0x0505, "Acknowledge to confirm line from activation log."),
|
||||
GW_ACTIVATION_LOG_UPDATED_NTF((short) 0x0506, "Notification to confirm line from activation log."),
|
||||
GW_GET_MULTIPLE_ACTIVATION_LOG_LINES_REQ((short) 0x0507, "Request lines from activation log."),
|
||||
GW_GET_MULTIPLE_ACTIVATION_LOG_LINES_NTF((short) 0x0508, "Error log data from activation log."),
|
||||
GW_GET_MULTIPLE_ACTIVATION_LOG_LINES_CFM((short) 0x0509, "Confirm lines from activation log."),
|
||||
GW_GET_MULTIPLE_ACTIVATION_LOG_LINES_NTF((short) 0x0508, "Notification error log data from activation log."),
|
||||
GW_GET_MULTIPLE_ACTIVATION_LOG_LINES_CFM((short) 0x0509, "Acknowledge to confirm lines from activation log."),
|
||||
|
||||
GW_SET_UTC_REQ((short) 0x2000, "Request to set UTC time."),
|
||||
GW_SET_UTC_CFM((short) 0x2001, "Acknowledge to GW_SET_UTC_REQ."),
|
||||
@ -308,7 +309,7 @@ public class VeluxKLFAPI {
|
||||
GW_PASSWORD_CHANGE_REQ((short) 0x3002, "Request password change."),
|
||||
GW_PASSWORD_CHANGE_CFM((short) 0x3003, "Acknowledge to GW_PASSWORD_CHANGE_REQ."),
|
||||
GW_PASSWORD_CHANGE_NTF((short) 0x3004,
|
||||
"Acknowledge to GW_PASSWORD_CHANGE_REQ. Broadcasted to all connected clients."),
|
||||
"Notification to GW_PASSWORD_CHANGE_REQ. Broadcasted to all connected clients."),
|
||||
|
||||
;
|
||||
|
||||
|
@ -12,7 +12,13 @@
|
||||
*/
|
||||
package org.openhab.binding.velux.internal.things;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.velux.internal.bridge.slip.FunctionalParameters;
|
||||
import org.openhab.binding.velux.internal.things.VeluxKLFAPI.Command;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProductType.ActuatorType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -57,28 +63,90 @@ public class VeluxProduct {
|
||||
}
|
||||
}
|
||||
|
||||
// State (of movement) of an actuator
|
||||
public static enum State {
|
||||
/**
|
||||
* State (of movement) of an actuator product.
|
||||
*
|
||||
* @author AndrewFG - Initial contribution.
|
||||
*/
|
||||
public static enum ProductState {
|
||||
NON_EXECUTING(0),
|
||||
ERROR(1),
|
||||
NOT_USED(2),
|
||||
WAITING_FOR_POWER(3),
|
||||
EXECUTING(4),
|
||||
DONE(5),
|
||||
MANUAL_OVERRIDE(0x80),
|
||||
UNKNOWN(0xFF);
|
||||
UNKNOWN(0xFF),
|
||||
MANUAL(0b10000000);
|
||||
|
||||
private static final int ACTION_MASK = 0b111;
|
||||
private static final int EQUIVALENT_MASK = ACTION_MASK | MANUAL.value;
|
||||
|
||||
public final int value;
|
||||
|
||||
private State(int value) {
|
||||
private ProductState(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an ProductState from an integer seed value.
|
||||
*
|
||||
* @param value the seed value.
|
||||
* @return the ProductState.
|
||||
*/
|
||||
public static ProductState of(int value) {
|
||||
if ((value < NON_EXECUTING.value) || (value > UNKNOWN.value)) {
|
||||
return ERROR;
|
||||
}
|
||||
if (value == UNKNOWN.value) {
|
||||
return UNKNOWN;
|
||||
}
|
||||
if ((value & MANUAL.value) != 0) {
|
||||
return MANUAL;
|
||||
}
|
||||
int masked = value & ACTION_MASK;
|
||||
for (ProductState state : values()) {
|
||||
if (state.value > DONE.value) {
|
||||
break;
|
||||
}
|
||||
if (masked == state.value) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
return ERROR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the masked values of two state values are operationally equivalent, including if both values are
|
||||
* 'unknown' (0xFF).
|
||||
*
|
||||
* @param a first value to compare
|
||||
* @param b second value to compare
|
||||
* @return true if the masked values are equivalent.
|
||||
*/
|
||||
public static boolean equivalent(int a, int b) {
|
||||
return (a & EQUIVALENT_MASK) == (b & EQUIVALENT_MASK);
|
||||
}
|
||||
}
|
||||
|
||||
// pattern to match a Velux serial number '00:00:00:00:00:00:00:00'
|
||||
private static final Pattern VELUX_SERIAL_NUMBER = Pattern.compile(
|
||||
"^[A-Fa-f0-9]{2}:[A-Fa-f0-9]{2}:[A-Fa-f0-9]{2}:[A-Fa-f0-9]{2}:[A-Fa-f0-9]{2}:[A-Fa-f0-9]{2}:[A-Fa-f0-9]{2}:[A-Fa-f0-9]{2}$");
|
||||
|
||||
/**
|
||||
* Indicates the data source where the product's contents came from.
|
||||
*
|
||||
* @author AndrewFG - Initial contribution.
|
||||
*/
|
||||
public static enum DataSource {
|
||||
GATEWAY,
|
||||
BINDING;
|
||||
}
|
||||
|
||||
// Class internal
|
||||
|
||||
private VeluxProductName name;
|
||||
private VeluxProductType typeId;
|
||||
private ActuatorType actuatorType;
|
||||
private ProductBridgeIndex bridgeProductIndex;
|
||||
|
||||
private boolean v2 = false;
|
||||
@ -88,11 +156,15 @@ public class VeluxProduct {
|
||||
private int variation = 0;
|
||||
private int powerMode = 0;
|
||||
private String serialNumber = VeluxProductSerialNo.UNKNOWN;
|
||||
private int state = State.UNKNOWN.value;
|
||||
private int state = ProductState.UNKNOWN.value;
|
||||
private int currentPosition = 0;
|
||||
private int targetPosition = 0;
|
||||
private @Nullable FunctionalParameters functionalParameters = null;
|
||||
private int remainingTime = 0;
|
||||
private int timeStamp = 0;
|
||||
private Command creatorCommand = Command.UNDEFTYPE;
|
||||
private DataSource dataSource = DataSource.GATEWAY;
|
||||
private boolean isSomfyProduct;
|
||||
|
||||
// Constructor
|
||||
|
||||
@ -106,6 +178,8 @@ public class VeluxProduct {
|
||||
this.name = VeluxProductName.UNKNOWN;
|
||||
this.typeId = VeluxProductType.UNDEFTYPE;
|
||||
this.bridgeProductIndex = ProductBridgeIndex.UNKNOWN;
|
||||
this.actuatorType = ActuatorType.UNDEFTYPE;
|
||||
this.isSomfyProduct = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -118,10 +192,12 @@ public class VeluxProduct {
|
||||
* value from 0 to 199.
|
||||
*/
|
||||
public VeluxProduct(VeluxProductName name, VeluxProductType typeId, ProductBridgeIndex bridgeProductIndex) {
|
||||
logger.trace("VeluxProduct(v1,name={}) created.", name.toString());
|
||||
logger.trace("VeluxProduct(v1,name={}) created.", name);
|
||||
this.name = name;
|
||||
this.typeId = typeId;
|
||||
this.bridgeProductIndex = bridgeProductIndex;
|
||||
this.actuatorType = ActuatorType.WINDOW_4_0;
|
||||
this.isSomfyProduct = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -142,15 +218,20 @@ public class VeluxProduct {
|
||||
* @param state This field indicates the operating state of the node.
|
||||
* @param currentPosition This field indicates the current position of the node.
|
||||
* @param target This field indicates the target position of the current operation.
|
||||
* @param functionalParameters the target Functional Parameters (may be null).
|
||||
* @param remainingTime This field indicates the remaining time for a node activation in seconds.
|
||||
* @param timeStamp UTC time stamp for last known position.
|
||||
* @param creatorCommand the API command that caused this instance to be created.
|
||||
*/
|
||||
public VeluxProduct(VeluxProductName name, VeluxProductType typeId, ProductBridgeIndex bridgeProductIndex,
|
||||
int order, int placement, int velocity, int variation, int powerMode, String serialNumber, int state,
|
||||
int currentPosition, int target, int remainingTime, int timeStamp) {
|
||||
logger.trace("VeluxProduct(v2,name={}) created.", name.toString());
|
||||
public VeluxProduct(VeluxProductName name, VeluxProductType typeId, ActuatorType actuatorType,
|
||||
ProductBridgeIndex bridgeProductIndex, int order, int placement, int velocity, int variation, int powerMode,
|
||||
String serialNumber, int state, int currentPosition, int target,
|
||||
@Nullable FunctionalParameters functionalParameters, int remainingTime, int timeStamp,
|
||||
Command creatorCommand) {
|
||||
logger.trace("VeluxProduct(v2, name={}) created.", name);
|
||||
this.name = name;
|
||||
this.typeId = typeId;
|
||||
this.actuatorType = actuatorType;
|
||||
this.bridgeProductIndex = bridgeProductIndex;
|
||||
this.v2 = true;
|
||||
this.order = order;
|
||||
@ -162,8 +243,44 @@ public class VeluxProduct {
|
||||
this.state = state;
|
||||
this.currentPosition = currentPosition;
|
||||
this.targetPosition = target;
|
||||
this.functionalParameters = functionalParameters;
|
||||
this.remainingTime = remainingTime;
|
||||
this.timeStamp = timeStamp;
|
||||
this.creatorCommand = creatorCommand;
|
||||
|
||||
// isSomfyProduct is true if serial number not matching the '00:00:00:00:00:00:00:00' pattern
|
||||
this.isSomfyProduct = !VELUX_SERIAL_NUMBER.matcher(serialNumber).find();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for a 'notification' product. Such products are used as data transfer objects to carry the limited
|
||||
* sub
|
||||
* set of data fields which are returned by 'GW_STATUS_REQUEST_NTF' or 'GW_NODE_STATE_POSITION_CHANGED_NTF'
|
||||
* notifications, and to transfer those respective field values to another product that had already been created via
|
||||
* a 'GW_GET_NODE_INFORMATION_NTF' notification, with all the other fields already filled.
|
||||
*
|
||||
* @param name the name of the notification command that created the product.
|
||||
* @param bridgeProductIndex the product bridge index from the notification.
|
||||
* @param state the actuator state from the notification.
|
||||
* @param currentPosition the current actuator position from the notification.
|
||||
* @param target the target position from the notification (may be VeluxProductPosition.VPP_VELUX_IGNORE).
|
||||
* @param functionalParameters the actuator functional parameters (may be null).
|
||||
* @param creatorCommand the API command that caused this instance to be created.
|
||||
*/
|
||||
public VeluxProduct(VeluxProductName name, ProductBridgeIndex bridgeProductIndex, int state, int currentPosition,
|
||||
int target, @Nullable FunctionalParameters functionalParameters, Command creatorCommand) {
|
||||
logger.trace("VeluxProduct(v2, name={}) [notification product] created.", name);
|
||||
this.v2 = true;
|
||||
this.typeId = VeluxProductType.UNDEFTYPE;
|
||||
this.actuatorType = ActuatorType.UNDEFTYPE;
|
||||
this.name = name;
|
||||
this.bridgeProductIndex = bridgeProductIndex;
|
||||
this.state = state;
|
||||
this.currentPosition = currentPosition;
|
||||
this.targetPosition = target;
|
||||
this.functionalParameters = functionalParameters;
|
||||
this.isSomfyProduct = false;
|
||||
this.creatorCommand = creatorCommand;
|
||||
}
|
||||
|
||||
// Utility methods
|
||||
@ -171,11 +288,13 @@ public class VeluxProduct {
|
||||
@Override
|
||||
public VeluxProduct clone() {
|
||||
if (this.v2) {
|
||||
return new VeluxProduct(this.name, this.typeId, this.bridgeProductIndex, this.order, this.placement,
|
||||
this.velocity, this.variation, this.powerMode, this.serialNumber, this.state, this.currentPosition,
|
||||
this.targetPosition, this.remainingTime, this.timeStamp);
|
||||
FunctionalParameters functionalParameters = this.functionalParameters;
|
||||
return new VeluxProduct(name, typeId, actuatorType, bridgeProductIndex, order, placement, velocity,
|
||||
variation, powerMode, serialNumber, state, currentPosition, targetPosition,
|
||||
functionalParameters == null ? null : functionalParameters.clone(), remainingTime, timeStamp,
|
||||
creatorCommand);
|
||||
} else {
|
||||
return new VeluxProduct(this.name, this.typeId, this.bridgeProductIndex);
|
||||
return new VeluxProduct(name, typeId, bridgeProductIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@ -206,16 +325,26 @@ public class VeluxProduct {
|
||||
@Override
|
||||
public String toString() {
|
||||
if (this.v2) {
|
||||
return String.format("Product \"%s\" / %s (bridgeIndex=%d,serial=%s,position=%04X)", this.name, this.typeId,
|
||||
this.bridgeProductIndex.toInt(), this.serialNumber, this.currentPosition);
|
||||
FunctionalParameters functionalParameters = this.functionalParameters;
|
||||
return String.format(
|
||||
"VeluxProduct(v2, creator:%s, dataSource:%s, name:%s, typeId:%s, bridgeIndex:%d, state:%d, serial:%s, position:%04X, target:%04X, functionalParameters:%s)",
|
||||
creatorCommand.name(), dataSource.name(), name, typeId, bridgeProductIndex.toInt(), state,
|
||||
serialNumber, currentPosition, targetPosition,
|
||||
functionalParameters == null ? "null" : functionalParameters.toString());
|
||||
} else {
|
||||
return String.format("Product \"%s\" / %s (bridgeIndex %d)", this.name, this.typeId,
|
||||
this.bridgeProductIndex.toInt());
|
||||
return String.format("VeluxProduct(v1, name:%s, typeId:%s, bridgeIndex:%d)", name, typeId,
|
||||
bridgeProductIndex.toInt());
|
||||
}
|
||||
}
|
||||
|
||||
// Class helper methods
|
||||
|
||||
/**
|
||||
* Return the product unique index.
|
||||
* Either the serial number (for normal Velux devices), or its name (for e.g. Somfy devices).
|
||||
*
|
||||
* @return the serial number or its name
|
||||
*/
|
||||
public String getProductUniqueIndex() {
|
||||
if (!v2 || serialNumber.startsWith(VeluxProductSerialNo.UNKNOWN)) {
|
||||
return name.toString();
|
||||
@ -282,6 +411,15 @@ public class VeluxProduct {
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the actuator state.
|
||||
*
|
||||
* @return state cast to an ActuatorState enum.
|
||||
*/
|
||||
public ProductState getProductState() {
|
||||
return ProductState.of(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param newState Update the operating state of the node.
|
||||
* @return <B>modified</B> as type boolean to signal a real modification.
|
||||
@ -290,8 +428,8 @@ public class VeluxProduct {
|
||||
if (this.state == newState) {
|
||||
return false;
|
||||
} else {
|
||||
logger.trace("setState(name={},index={}) state {} replaced by {}.", name.toString(),
|
||||
bridgeProductIndex.toInt(), this.state, newState);
|
||||
logger.trace("setState(name={},index={}) state {} replaced by {}.", name, bridgeProductIndex, state,
|
||||
newState);
|
||||
this.state = newState;
|
||||
return true;
|
||||
}
|
||||
@ -312,8 +450,8 @@ public class VeluxProduct {
|
||||
if (this.currentPosition == newCurrentPosition) {
|
||||
return false;
|
||||
} else {
|
||||
logger.trace("setCurrentPosition(name={},index={}) currentPosition {} replaced by {}.", name.toString(),
|
||||
bridgeProductIndex.toInt(), this.currentPosition, newCurrentPosition);
|
||||
logger.trace("setCurrentPosition(name={},index={}) currentPosition {} replaced by {}.", name,
|
||||
bridgeProductIndex, currentPosition, newCurrentPosition);
|
||||
this.currentPosition = newCurrentPosition;
|
||||
return true;
|
||||
}
|
||||
@ -334,8 +472,8 @@ public class VeluxProduct {
|
||||
if (this.targetPosition == newTarget) {
|
||||
return false;
|
||||
} else {
|
||||
logger.trace("setCurrentPosition(name={},index={}) target {} replaced by {}.", name.toString(),
|
||||
bridgeProductIndex.toInt(), this.targetPosition, newTarget);
|
||||
logger.trace("setTarget(name={},index={}) target {} replaced by {}.", name, bridgeProductIndex,
|
||||
targetPosition, newTarget);
|
||||
this.targetPosition = newTarget;
|
||||
return true;
|
||||
}
|
||||
@ -365,24 +503,176 @@ public class VeluxProduct {
|
||||
* @return The display position of the actuator
|
||||
*/
|
||||
public int getDisplayPosition() {
|
||||
// manual override flag set: position is 'unknown'
|
||||
if ((state & State.MANUAL_OVERRIDE.value) != 0) {
|
||||
return VeluxProductPosition.VPP_VELUX_UNKNOWN;
|
||||
switch (getProductState()) {
|
||||
case EXECUTING:
|
||||
if (VeluxProductPosition.isValid(targetPosition)) {
|
||||
return targetPosition;
|
||||
}
|
||||
break;
|
||||
case DONE:
|
||||
if (!VeluxProductPosition.isValid(currentPosition) && VeluxProductPosition.isValid(targetPosition)) {
|
||||
return targetPosition;
|
||||
}
|
||||
break;
|
||||
case ERROR:
|
||||
case UNKNOWN:
|
||||
case MANUAL:
|
||||
return VeluxProductPosition.VPP_VELUX_UNKNOWN;
|
||||
default:
|
||||
}
|
||||
// only check other conditions if targetPosition is valid and differs from currentPosition
|
||||
if ((targetPosition != currentPosition) && (targetPosition <= VeluxProductPosition.VPP_VELUX_MAX)
|
||||
&& (targetPosition >= VeluxProductPosition.VPP_VELUX_MIN)) {
|
||||
int state = this.state & 0xf;
|
||||
// actuator is in motion: for quicker UI update, return targetPosition
|
||||
if ((state > State.ERROR.value) && (state < State.DONE.value)) {
|
||||
return targetPosition;
|
||||
}
|
||||
// motion complete but currentPosition is not valid: return targetPosition
|
||||
if ((state == State.DONE.value) && ((currentPosition > VeluxProductPosition.VPP_VELUX_MAX)
|
||||
|| (currentPosition < VeluxProductPosition.VPP_VELUX_MIN))) {
|
||||
return targetPosition;
|
||||
}
|
||||
return VeluxProductPosition.isValid(currentPosition) ? currentPosition : VeluxProductPosition.VPP_VELUX_UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Functional Parameters.
|
||||
*
|
||||
* @return the Functional Parameters.
|
||||
*/
|
||||
public @Nullable FunctionalParameters getFunctionalParameters() {
|
||||
return functionalParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Functional Parameters. Calls getMergeSubstitute() to merge the existing parameters (if any) and the new
|
||||
* parameters (if any).
|
||||
*
|
||||
* @param newFunctionalParameters the new values of the Functional Parameters, or null if nothing is to be set.
|
||||
* @return <b>modified</b> if any of the Functional Parameters have been changed.
|
||||
*/
|
||||
public boolean setFunctionalParameters(@Nullable FunctionalParameters newFunctionalParameters) {
|
||||
if ((newFunctionalParameters == null) || newFunctionalParameters.equals(functionalParameters)) {
|
||||
return false;
|
||||
}
|
||||
return currentPosition;
|
||||
functionalParameters = FunctionalParameters.createMergeSubstitute(functionalParameters,
|
||||
newFunctionalParameters);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines which of the Functional Parameters contains the vane position.
|
||||
* As defined in the Velux KLF 200 API Technical Specification Appendix 2 Table 276.
|
||||
*
|
||||
* @return the index of the vane position Functional Parameter, or -1 if not supported.
|
||||
*/
|
||||
private int getVanePositionIndex() {
|
||||
switch (actuatorType) {
|
||||
case BLIND_1_0:
|
||||
return 0;
|
||||
case ROLLERSHUTTER_2_1:
|
||||
case BLIND_17:
|
||||
case BLIND_18:
|
||||
return 2;
|
||||
default:
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the actuator supports a vane position.
|
||||
*
|
||||
* @return true if vane position is supported.
|
||||
*/
|
||||
public boolean supportsVanePosition() {
|
||||
return getVanePositionIndex() >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the vane position. Reads the vane position from the Functional Parameters, or returns 'UNKNOWN' if vane
|
||||
* position is not supported.
|
||||
*
|
||||
* @return the vane position.
|
||||
*/
|
||||
public int getVanePosition() {
|
||||
FunctionalParameters functionalParameters = this.functionalParameters;
|
||||
int index = getVanePositionIndex();
|
||||
if ((index >= 0) && (functionalParameters != null)) {
|
||||
return functionalParameters.getValue(index);
|
||||
}
|
||||
return VeluxProductPosition.VPP_VELUX_UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the vane position into the appropriate Functional Parameter. If the actuator does not support vane positions
|
||||
* then a message is logged.
|
||||
*
|
||||
* @param vanePosition the new vane position.
|
||||
*/
|
||||
public void setVanePosition(int vanePosition) {
|
||||
int index = getVanePositionIndex();
|
||||
if ((index >= 0) && FunctionalParameters.isNormalPosition(vanePosition)) {
|
||||
functionalParameters = new FunctionalParameters(index, vanePosition);
|
||||
} else {
|
||||
functionalParameters = null;
|
||||
logger.info("setVanePosition(): actuator type {} ({}) does not support vane position {}.",
|
||||
ActuatorType.get(actuatorType.getNodeType()), actuatorType.getDescription(), vanePosition);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the display position of the vanes depending on the product state.
|
||||
* See 'getDisplayPosition()'.
|
||||
*
|
||||
* @return the display position.
|
||||
*/
|
||||
public int getVaneDisplayPosition() {
|
||||
switch (getProductState()) {
|
||||
case ERROR:
|
||||
case UNKNOWN:
|
||||
case MANUAL:
|
||||
return VeluxProductPosition.VPP_VELUX_UNKNOWN;
|
||||
default:
|
||||
}
|
||||
return getVanePosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the actuator type.
|
||||
*
|
||||
* @return the actuator type.
|
||||
*/
|
||||
public ActuatorType getActuatorType() {
|
||||
return this.actuatorType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the actuator type.
|
||||
* Only allowed if the current value is undefined.
|
||||
*
|
||||
* @param actuatorType the new value for the actuator type.
|
||||
*/
|
||||
public void setActuatorType(ActuatorType actuatorType) {
|
||||
if (this.actuatorType == ActuatorType.UNDEFTYPE) {
|
||||
this.actuatorType = actuatorType;
|
||||
this.typeId = actuatorType.getTypeClass();
|
||||
} else {
|
||||
logger.debug("setActuatorType() failed: not allowed to change actuatorType from {} to {}.",
|
||||
this.actuatorType, actuatorType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if it is a Somfy product.
|
||||
*/
|
||||
public boolean isSomfyProduct() {
|
||||
return isSomfyProduct;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the API command that caused this instance to be created.
|
||||
*
|
||||
* @return the API command.
|
||||
*/
|
||||
public Command getCreatorCommand() {
|
||||
return creatorCommand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the indicator of the source of the data for this product.
|
||||
*
|
||||
* @return the data source.
|
||||
*/
|
||||
public VeluxProduct overrideDataSource(DataSource dataSource) {
|
||||
this.dataSource = dataSource;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -64,13 +64,32 @@ public class VeluxProductPosition {
|
||||
public static final int VPP_VELUX_UNKNOWN = 0xF7FF;
|
||||
|
||||
// relative mode commands
|
||||
private static final int VPP_VELUX_RELATIVE_ORIGIN = 0xCCE8;
|
||||
private static final int VPP_VELUX_RELATIVE_RANGE = 1000; // same for positive and negative offsets
|
||||
public static final int VPP_VELUX_RELATIVE_ORIGIN = 0xCCE8;
|
||||
public static final int VPP_VELUX_RELATIVE_RANGE = 1000; // same for positive and negative offsets
|
||||
|
||||
/**
|
||||
* Enum that determines whether the position is an absolute value, or a positive / negative offset relative to the
|
||||
* current position.
|
||||
*
|
||||
* @author AndrewFG - Initial contribution.
|
||||
*/
|
||||
public static enum PositionType {
|
||||
ABSOLUTE_VALUE(0f),
|
||||
OFFSET_POSITIVE(1f),
|
||||
OFFSET_NEGATIVE(-1f);
|
||||
|
||||
private float value;
|
||||
|
||||
private PositionType(float i) {
|
||||
value = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Class internal
|
||||
|
||||
private PercentType position;
|
||||
private boolean isValid = false;
|
||||
private PositionType positionType = PositionType.ABSOLUTE_VALUE;
|
||||
|
||||
// Constructor
|
||||
|
||||
@ -102,21 +121,28 @@ public class VeluxProductPosition {
|
||||
* 0xc800, or 0xD200 for stop).
|
||||
*/
|
||||
public VeluxProductPosition(int veluxPosition) {
|
||||
logger.trace("VeluxProductPosition(constructur with {} as veluxPosition) called.", veluxPosition);
|
||||
if ((veluxPosition == VPP_VELUX_UNKNOWN) || (veluxPosition == VPP_VELUX_STOP) || (veluxPosition < VPP_VELUX_MIN)
|
||||
|| (veluxPosition > VPP_VELUX_MAX)) {
|
||||
logger.trace("VeluxProductPosition() gives up.");
|
||||
this.position = new PercentType(VPP_UNKNOWN);
|
||||
this.isValid = false;
|
||||
} else {
|
||||
logger.trace("VeluxProductPosition(constructor with {} as veluxPosition) called.", veluxPosition);
|
||||
if (isValid(veluxPosition)) {
|
||||
float result = (ONE * veluxPosition - VPP_VELUX_MIN) / (VPP_VELUX_MAX - VPP_VELUX_MIN);
|
||||
result = Math.round(VPP_OPENHAB_MIN + result * (VPP_OPENHAB_MAX - VPP_OPENHAB_MIN));
|
||||
logger.trace("VeluxProductPosition() created with percent-type {}.", (int) result);
|
||||
this.position = new PercentType((int) result);
|
||||
this.isValid = true;
|
||||
logger.trace("VeluxProductPosition() created with percent-type {}.", (int) result);
|
||||
} else {
|
||||
this.position = new PercentType(VPP_UNKNOWN);
|
||||
this.isValid = false;
|
||||
logger.trace("VeluxProductPosition() gives up.");
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isValid(int position) {
|
||||
return (position <= VeluxProductPosition.VPP_VELUX_MAX) && (position >= VeluxProductPosition.VPP_VELUX_MIN);
|
||||
}
|
||||
|
||||
public static boolean isUnknownOrValid(int position) {
|
||||
return (position == VeluxProductPosition.VPP_UNKNOWN) || isValid(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creation of a Position object to specify a STOP.
|
||||
*/
|
||||
@ -142,8 +168,15 @@ public class VeluxProductPosition {
|
||||
|
||||
public int getPositionAsVeluxType() {
|
||||
if (this.isValid) {
|
||||
float result = (ONE * position.intValue() - VPP_OPENHAB_MIN) / (VPP_OPENHAB_MAX - VPP_OPENHAB_MIN);
|
||||
result = VPP_VELUX_MIN + result * (VPP_VELUX_MAX - VPP_VELUX_MIN);
|
||||
float result;
|
||||
if (positionType == PositionType.ABSOLUTE_VALUE) {
|
||||
result = (ONE * position.intValue() - VPP_OPENHAB_MIN) / (VPP_OPENHAB_MAX - VPP_OPENHAB_MIN);
|
||||
result = VPP_VELUX_MIN + result * (VPP_VELUX_MAX - VPP_VELUX_MIN);
|
||||
} else {
|
||||
result = VPP_VELUX_RELATIVE_ORIGIN
|
||||
+ ((positionType.value * position.intValue() * VPP_VELUX_RELATIVE_RANGE)
|
||||
/ (VPP_OPENHAB_MAX - VPP_OPENHAB_MIN));
|
||||
}
|
||||
return (int) result;
|
||||
} else {
|
||||
return VPP_VELUX_STOP;
|
||||
@ -161,8 +194,8 @@ public class VeluxProductPosition {
|
||||
|
||||
// Helper methods
|
||||
|
||||
public int getAsRelativePosition(boolean positive) {
|
||||
int offset = position.intValue() * VPP_VELUX_RELATIVE_RANGE / (VPP_OPENHAB_MAX - VPP_OPENHAB_MIN);
|
||||
return positive ? VPP_VELUX_RELATIVE_ORIGIN + offset : VPP_VELUX_RELATIVE_ORIGIN - offset;
|
||||
public VeluxProductPosition overridePositionType(PositionType positionType) {
|
||||
this.positionType = positionType;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ public enum VeluxProductType {
|
||||
SWITCH,
|
||||
UNDEFTYPE;
|
||||
|
||||
private static enum ActuatorType {
|
||||
public static enum ActuatorType {
|
||||
UNDEFTYPE((short) 0xffff, VeluxBindingConstants.UNKNOWN, VeluxProductType.SWITCH),
|
||||
BLIND_1_0((short) 0x0040, "Interior Venetian Blind", VeluxProductType.SLIDER_SHUTTER),
|
||||
ROLLERSHUTTER_2_0((short) 0x0080, "Roller Shutter", VeluxProductType.SLIDER_SHUTTER),
|
||||
@ -97,15 +97,19 @@ public enum VeluxProductType {
|
||||
|
||||
// Class access methods
|
||||
|
||||
int getNodeType() {
|
||||
public int getNodeType() {
|
||||
return nodeType;
|
||||
}
|
||||
|
||||
String getDescription() {
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
static ActuatorType get(int nodeType) {
|
||||
public VeluxProductType getTypeClass() {
|
||||
return typeClass;
|
||||
}
|
||||
|
||||
public static ActuatorType get(int nodeType) {
|
||||
return LOOKUPTYPEID2ENUM.getOrDefault(nodeType, ActuatorType.UNDEFTYPE);
|
||||
}
|
||||
}
|
||||
|
@ -135,6 +135,8 @@ channel-type.velux.check.description = Result of the check of current item confi
|
||||
#
|
||||
channel-type.velux.position.label = Position
|
||||
channel-type.velux.position.description = Device control (UP, DOWN, STOP, closure 0-100%).
|
||||
channel-type.velux.vanePosition.label = Vane Position
|
||||
channel-type.velux.vanePosition.description = Venetian blind vane (tilt) position.
|
||||
channel-type.velux.state.label = State
|
||||
channel-type.velux.state.description = Device control (ON, OFF).
|
||||
channel-type.velux.action.label = Start of a Scene
|
||||
|
@ -139,6 +139,14 @@
|
||||
<category>Blinds</category>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="vanePosition">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>@text/channel-type.velux.vanePosition.label</label>
|
||||
<description>@text/channel-type.velux.vanePosition.description</description>
|
||||
<category>Blinds</category>
|
||||
<state min="0" max="100"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="state">
|
||||
<item-type>Switch</item-type>
|
||||
<label>@text/channel-type.velux.state.label</label>
|
||||
|
@ -19,6 +19,7 @@
|
||||
<channel id="position" typeId="position"/>
|
||||
<channel id="limitMinimum" typeId="limitMinimum"/>
|
||||
<channel id="limitMaximum" typeId="limitMaximum"/>
|
||||
<channel id="vanePosition" typeId="vanePosition"/>
|
||||
</channels>
|
||||
<representation-property>serial</representation-property>
|
||||
<config-description-ref uri="thing-type:velux:rollershutter"/>
|
||||
|
@ -0,0 +1,907 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.velux.test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
import org.openhab.binding.velux.internal.bridge.slip.FunctionalParameters;
|
||||
import org.openhab.binding.velux.internal.bridge.slip.SCgetHouseStatus;
|
||||
import org.openhab.binding.velux.internal.bridge.slip.SCgetProduct;
|
||||
import org.openhab.binding.velux.internal.bridge.slip.SCgetProductStatus;
|
||||
import org.openhab.binding.velux.internal.bridge.slip.SCrunProductCommand;
|
||||
import org.openhab.binding.velux.internal.things.VeluxExistingProducts;
|
||||
import org.openhab.binding.velux.internal.things.VeluxKLFAPI;
|
||||
import org.openhab.binding.velux.internal.things.VeluxKLFAPI.Command;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProduct;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProduct.ProductBridgeIndex;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProduct.ProductState;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProductName;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProductPosition;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProductPosition.PositionType;
|
||||
import org.openhab.binding.velux.internal.things.VeluxProductType.ActuatorType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
|
||||
/**
|
||||
* JUnit test suite to check the proper parsing of actuator notification packets, and to confirm that the existing
|
||||
* products database is working correctly.
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Initial contribution.
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class TestNotificationsAndDatabase {
|
||||
// validation parameters
|
||||
private static final byte PRODUCT_INDEX_A = 6;
|
||||
private static final byte PRODUCT_INDEX_B = 0;
|
||||
private static final int MAIN_POSITION_A = 0xC800;
|
||||
private static final int MAIN_POSITION_B = 0x4600;
|
||||
private static final int VANE_POSITION_A = 0x634f;
|
||||
private static final int TARGET_POSITION = 0xB800;
|
||||
private static final int STATE_SOMFY = 0x2D;
|
||||
|
||||
private static final int UNKNOWN_POSITION = VeluxProductPosition.VPP_VELUX_UNKNOWN;
|
||||
private static final int IGNORE_POSITION = VeluxProductPosition.VPP_VELUX_IGNORE;
|
||||
private static final int STATE_DONE = VeluxProduct.ProductState.DONE.value;
|
||||
|
||||
private static final ActuatorType ACTUATOR_TYPE_SOMFY = ActuatorType.BLIND_17;
|
||||
private static final ActuatorType ACTUATOR_TYPE_VELUX = ActuatorType.WINDOW_4_1;
|
||||
private static final ActuatorType ACTUATOR_TYPE_UNDEF = ActuatorType.UNDEFTYPE;
|
||||
|
||||
// existing products database
|
||||
private static final VeluxExistingProducts EXISTING_PRODUCTS = new VeluxExistingProducts();
|
||||
|
||||
private static byte[] toByteArray(String input) {
|
||||
String[] data = input.split(" ");
|
||||
byte[] result = new byte[data.length];
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
result[i] = Integer.decode("0x" + data[i]).byteValue();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private VeluxExistingProducts getExistingProducts() {
|
||||
return EXISTING_PRODUCTS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm the existing products database is initialised.
|
||||
*/
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testInitialized() {
|
||||
assertEquals(0, getExistingProducts().getNoMembers());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the 'supportsVanePosition()' method for two types of products.
|
||||
*/
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testSupportsVanePosition() {
|
||||
VeluxProduct product = new VeluxProduct();
|
||||
assertFalse(product.supportsVanePosition());
|
||||
product.setActuatorType(ACTUATOR_TYPE_SOMFY);
|
||||
assertTrue(product.supportsVanePosition());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the SCgetProduct command by checking for the correct parsing of a 'GW_GET_NODE_INFORMATION_NTF' notification
|
||||
* packet. Note: this packet is from a Somfy roller shutter with main and vane position.
|
||||
*/
|
||||
@Test
|
||||
@Order(3)
|
||||
public void testSCgetProduct() {
|
||||
// initialise the test parameters
|
||||
final String packet = "06 00 06 00 48 6F 62 62 79 6B 61 6D 65 72 00 00 00 00 00 00 00 00 00 00 00 00 00 00"
|
||||
+ " 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00"
|
||||
+ " 00 00 00 00 00 01 04 40 00 00 00 00 00 00 00 00 00 00 00 00 00 2D C8 00 C8 00 F7 FF F7 FF 00 00 F7 FF 00"
|
||||
+ " 00 4F 00 4A EA 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00";
|
||||
final Command command = VeluxKLFAPI.Command.GW_GET_NODE_INFORMATION_NTF;
|
||||
|
||||
// initialise the BCP
|
||||
SCgetProduct bcp = new SCgetProduct();
|
||||
bcp.setProductId(PRODUCT_INDEX_A);
|
||||
|
||||
// set the packet response
|
||||
bcp.setResponse(command.getShort(), toByteArray(packet), false);
|
||||
|
||||
// check BCP status
|
||||
assertTrue(bcp.isCommunicationSuccessful());
|
||||
assertTrue(bcp.isCommunicationFinished());
|
||||
|
||||
// initialise the product
|
||||
VeluxProduct product = bcp.getProduct();
|
||||
|
||||
// check positive assertions
|
||||
assertEquals(STATE_SOMFY, product.getState());
|
||||
assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
|
||||
assertEquals(MAIN_POSITION_A, product.getTarget());
|
||||
assertEquals(PRODUCT_INDEX_A, product.getBridgeProductIndex().toInt());
|
||||
assertEquals(ACTUATOR_TYPE_SOMFY, product.getActuatorType());
|
||||
assertEquals(UNKNOWN_POSITION, product.getVanePosition());
|
||||
assertNull(product.getFunctionalParameters());
|
||||
assertTrue(product.supportsVanePosition());
|
||||
assertTrue(product.isSomfyProduct());
|
||||
assertEquals(ProductState.DONE, product.getProductState());
|
||||
|
||||
// check negative assertions
|
||||
assertNotEquals(VANE_POSITION_A, product.getVanePosition());
|
||||
|
||||
// register in existing products database
|
||||
VeluxExistingProducts existingProducts = getExistingProducts();
|
||||
assertTrue(existingProducts.register(product));
|
||||
assertTrue(existingProducts.isRegistered(product));
|
||||
assertTrue(existingProducts.isRegistered(product.getBridgeProductIndex()));
|
||||
assertEquals(1, existingProducts.getNoMembers());
|
||||
|
||||
// confirm that a dummy product is NOT in the database
|
||||
assertFalse(existingProducts.isRegistered(new ProductBridgeIndex(99)));
|
||||
|
||||
// check dirty flag
|
||||
assertTrue(existingProducts.isDirty());
|
||||
existingProducts.resetDirtyFlag();
|
||||
assertFalse(existingProducts.isDirty());
|
||||
|
||||
// re-registering the same product should return false
|
||||
assertFalse(existingProducts.register(product));
|
||||
|
||||
// updating again with the same data should NOT set the dirty flag
|
||||
assertTrue(existingProducts.update(product));
|
||||
assertFalse(existingProducts.isDirty());
|
||||
|
||||
// check that the product in the database is indeed the one just created
|
||||
VeluxProduct existing = existingProducts.get(new ProductBridgeIndex(PRODUCT_INDEX_A));
|
||||
assertEquals(product, existing);
|
||||
assertEquals(1, existingProducts.getNoMembers());
|
||||
assertTrue(existingProducts.isRegistered(product.getBridgeProductIndex()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm that the product in the existing database has the same values as the product created and added in test 3.
|
||||
*/
|
||||
@Test
|
||||
@Order(4)
|
||||
public void testExistingUnknownVanePosition() {
|
||||
VeluxExistingProducts existingProducts = getExistingProducts();
|
||||
VeluxProduct product = existingProducts.get(new ProductBridgeIndex(PRODUCT_INDEX_A));
|
||||
|
||||
// confirm the product details
|
||||
assertEquals(STATE_SOMFY, product.getState());
|
||||
assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
|
||||
assertEquals(MAIN_POSITION_A, product.getTarget());
|
||||
assertEquals(PRODUCT_INDEX_A, product.getBridgeProductIndex().toInt());
|
||||
assertEquals(ACTUATOR_TYPE_SOMFY, product.getActuatorType());
|
||||
assertEquals(UNKNOWN_POSITION, product.getVanePosition());
|
||||
assertNull(product.getFunctionalParameters());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the SCgetProductStatus command by checking for the correct parsing of a 'GW_STATUS_REQUEST_NTF' notification
|
||||
* packet. Note: this packet is from a Somfy roller shutter with main and vane position.
|
||||
*/
|
||||
@Test
|
||||
@Order(5)
|
||||
public void testSCgetProductStatus() {
|
||||
// initialise the test parameters
|
||||
final String packet = "00 D8 01 06 00 01 01 02 00 C8 00 03 63 4F 00 00 00 00 00 00 00 00 00 00 00 00 00"
|
||||
+ " 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00";
|
||||
final Command command = VeluxKLFAPI.Command.GW_STATUS_REQUEST_NTF;
|
||||
|
||||
// initialise the BCP
|
||||
SCgetProductStatus bcp = new SCgetProductStatus();
|
||||
bcp.setProductId(PRODUCT_INDEX_A);
|
||||
|
||||
// set the packet response
|
||||
bcp.setResponse(command.getShort(), toByteArray(packet), false);
|
||||
|
||||
// check BCP status
|
||||
assertTrue(bcp.isCommunicationSuccessful());
|
||||
assertTrue(bcp.isCommunicationFinished());
|
||||
|
||||
// initialise the product
|
||||
VeluxProduct product = bcp.getProduct();
|
||||
|
||||
// change actuator type
|
||||
assertEquals(ACTUATOR_TYPE_UNDEF, product.getActuatorType());
|
||||
product.setActuatorType(ACTUATOR_TYPE_SOMFY);
|
||||
|
||||
// check positive assertions
|
||||
assertEquals(STATE_DONE, product.getState());
|
||||
assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
|
||||
assertEquals(IGNORE_POSITION, product.getTarget());
|
||||
assertEquals(PRODUCT_INDEX_A, product.getBridgeProductIndex().toInt());
|
||||
assertEquals(ACTUATOR_TYPE_SOMFY, product.getActuatorType());
|
||||
assertEquals(VANE_POSITION_A, product.getVanePosition());
|
||||
assertNotNull(product.getFunctionalParameters());
|
||||
assertEquals(ProductState.DONE, product.getProductState());
|
||||
|
||||
// test updating the existing product in the database
|
||||
VeluxExistingProducts existingProducts = getExistingProducts();
|
||||
assertTrue(existingProducts.update(product));
|
||||
assertTrue(existingProducts.isDirty());
|
||||
|
||||
// updating again with the same data should NOT set the dirty flag
|
||||
existingProducts.resetDirtyFlag();
|
||||
assertTrue(existingProducts.update(product));
|
||||
assertFalse(existingProducts.isDirty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm that the product in the existing database has the same values as the product created and updated to the
|
||||
* database in test 5.
|
||||
*/
|
||||
@Test
|
||||
@Order(6)
|
||||
public void testExistingValidVanePosition() {
|
||||
VeluxExistingProducts existingProducts = getExistingProducts();
|
||||
VeluxProduct product = existingProducts.get(new ProductBridgeIndex(PRODUCT_INDEX_A));
|
||||
|
||||
// confirm the product details
|
||||
assertEquals(STATE_DONE, product.getState());
|
||||
assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
|
||||
assertEquals(MAIN_POSITION_A, product.getTarget());
|
||||
assertEquals(PRODUCT_INDEX_A, product.getBridgeProductIndex().toInt());
|
||||
assertEquals(ACTUATOR_TYPE_SOMFY, product.getActuatorType());
|
||||
assertEquals(VANE_POSITION_A, product.getVanePosition());
|
||||
assertNotNull(product.getFunctionalParameters());
|
||||
assertEquals(ProductState.DONE, product.getProductState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the SCgetHouseStatus command by checking for the correct parsing of a 'GW_NODE_STATE_POSITION_CHANGED_NTF'
|
||||
* notification packet. Note: this packet is from a Somfy roller shutter with main and vane position.
|
||||
*/
|
||||
@Test
|
||||
@Order(7)
|
||||
public void testSCgetHouseStatus() {
|
||||
// initialise the test parameters
|
||||
final String packet = "06 2D C8 00 B8 00 F7 FF F7 FF 00 00 F7 FF 00 00 4A E5 00 00";
|
||||
final short command = VeluxKLFAPI.Command.GW_NODE_STATE_POSITION_CHANGED_NTF.getShort();
|
||||
|
||||
// initialise the BCP
|
||||
SCgetHouseStatus bcp = new SCgetHouseStatus();
|
||||
|
||||
// set the packet response
|
||||
bcp.setResponse(command, toByteArray(packet), false);
|
||||
|
||||
// check BCP status
|
||||
assertTrue(bcp.isCommunicationSuccessful());
|
||||
assertTrue(bcp.isCommunicationFinished());
|
||||
|
||||
// initialise the product
|
||||
VeluxProduct product = bcp.getProduct();
|
||||
|
||||
// change actuator type
|
||||
assertEquals(ACTUATOR_TYPE_UNDEF, product.getActuatorType());
|
||||
product.setActuatorType(ACTUATOR_TYPE_SOMFY);
|
||||
|
||||
// check positive assertions
|
||||
assertEquals(STATE_SOMFY, product.getState());
|
||||
assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
|
||||
assertEquals(TARGET_POSITION, product.getTarget());
|
||||
assertEquals(PRODUCT_INDEX_A, product.getBridgeProductIndex().toInt());
|
||||
assertEquals(ACTUATOR_TYPE_SOMFY, product.getActuatorType());
|
||||
assertEquals(UNKNOWN_POSITION, product.getVanePosition());
|
||||
assertNull(product.getFunctionalParameters());
|
||||
|
||||
// check negative assertions
|
||||
assertNotEquals(VANE_POSITION_A, product.getVanePosition());
|
||||
|
||||
VeluxExistingProducts existingProducts = getExistingProducts();
|
||||
existingProducts.update(product);
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm that the product in the existing database has the same values as the product created and updated to the
|
||||
* database in test 7.
|
||||
*/
|
||||
@Test
|
||||
@Order(8)
|
||||
public void testExistingValidVanePositionWithNewTargetValue() {
|
||||
VeluxExistingProducts existingProducts = getExistingProducts();
|
||||
VeluxProduct product = existingProducts.get(new ProductBridgeIndex(PRODUCT_INDEX_A));
|
||||
|
||||
// confirm the product details
|
||||
assertEquals(STATE_SOMFY, product.getState());
|
||||
assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
|
||||
assertEquals(TARGET_POSITION, product.getTarget());
|
||||
assertEquals(PRODUCT_INDEX_A, product.getBridgeProductIndex().toInt());
|
||||
assertEquals(ACTUATOR_TYPE_SOMFY, product.getActuatorType());
|
||||
assertEquals(VANE_POSITION_A, product.getVanePosition());
|
||||
assertNotNull(product.getFunctionalParameters());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the SCgetProduct by checking for the correct parsing of a 'GW_GET_NODE_INFORMATION_NTF' notification packet.
|
||||
* Note: this packet is from a Velux roof window without vane position.
|
||||
*/
|
||||
@Test
|
||||
@Order(9)
|
||||
public void testSCgetProductOnVelux() {
|
||||
// initialise the test parameters
|
||||
final String packet = "00 00 00 00 53 68 65 64 20 57 69 6E 64 6F 77 00 00 00 00 00 00 00 00 00 00 00 00 00"
|
||||
+ " 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00"
|
||||
+ " 00 00 00 00 00 00 00 00 01 01 01 03 07 00 01 16 56 24 5C 26 14 19 00 FC 05 46 00 46 00 F7 FF F7"
|
||||
+ " FF F7 FF F7 FF 00 00 4F 05 B3 5F 01 D8 03 B2 1C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00";
|
||||
final short command = VeluxKLFAPI.Command.GW_GET_NODE_INFORMATION_NTF.getShort();
|
||||
|
||||
// initialise the BCP
|
||||
SCgetProduct bcp = new SCgetProduct();
|
||||
bcp.setProductId(PRODUCT_INDEX_B);
|
||||
|
||||
// set the packet response
|
||||
bcp.setResponse(command, toByteArray(packet), false);
|
||||
|
||||
// check BCP status
|
||||
assertTrue(bcp.isCommunicationSuccessful());
|
||||
assertTrue(bcp.isCommunicationFinished());
|
||||
|
||||
// initialise the product
|
||||
VeluxProduct product = bcp.getProduct();
|
||||
|
||||
// check positive assertions
|
||||
assertEquals(STATE_DONE, product.getState());
|
||||
assertEquals(MAIN_POSITION_B, product.getCurrentPosition());
|
||||
assertEquals(MAIN_POSITION_B, product.getTarget());
|
||||
assertEquals(PRODUCT_INDEX_B, product.getBridgeProductIndex().toInt());
|
||||
assertEquals(ACTUATOR_TYPE_VELUX, product.getActuatorType());
|
||||
assertEquals(UNKNOWN_POSITION, product.getVanePosition());
|
||||
assertNull(product.getFunctionalParameters());
|
||||
assertFalse(product.isSomfyProduct());
|
||||
|
||||
// check negative assertions
|
||||
assertFalse(product.supportsVanePosition());
|
||||
assertNotEquals(VANE_POSITION_A, product.getVanePosition());
|
||||
|
||||
// register in existing products database
|
||||
VeluxExistingProducts existingProducts = getExistingProducts();
|
||||
assertTrue(existingProducts.register(product));
|
||||
assertTrue(existingProducts.isRegistered(product));
|
||||
assertTrue(existingProducts.isRegistered(product.getBridgeProductIndex()));
|
||||
assertEquals(2, existingProducts.getNoMembers());
|
||||
|
||||
// check that the product in the database is indeed the one just created
|
||||
VeluxProduct existing = existingProducts.get(new ProductBridgeIndex(PRODUCT_INDEX_B));
|
||||
assertEquals(product, existing);
|
||||
assertTrue(existingProducts.isRegistered(product.getBridgeProductIndex()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm that the modified products list is functioning.
|
||||
*/
|
||||
@Test
|
||||
@Order(10)
|
||||
public void testModifiedList() {
|
||||
VeluxExistingProducts existingProducts = getExistingProducts();
|
||||
VeluxProduct[] modified = existingProducts.valuesOfModified();
|
||||
|
||||
// confirm that the list contains two entries
|
||||
assertEquals(2, modified.length);
|
||||
|
||||
// confirm the product details for the Somfy product
|
||||
VeluxProduct product = modified[0];
|
||||
assertEquals(STATE_SOMFY, product.getState());
|
||||
assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
|
||||
assertEquals(TARGET_POSITION, product.getTarget());
|
||||
assertEquals(PRODUCT_INDEX_A, product.getBridgeProductIndex().toInt());
|
||||
assertEquals(ACTUATOR_TYPE_SOMFY, product.getActuatorType());
|
||||
assertEquals(VANE_POSITION_A, product.getVanePosition());
|
||||
assertTrue(product.isSomfyProduct());
|
||||
assertNotNull(product.getFunctionalParameters());
|
||||
|
||||
// confirm the product details for the Velux product
|
||||
product = modified[1];
|
||||
assertEquals(STATE_DONE, product.getState());
|
||||
assertEquals(MAIN_POSITION_B, product.getCurrentPosition());
|
||||
assertEquals(MAIN_POSITION_B, product.getTarget());
|
||||
assertEquals(PRODUCT_INDEX_B, product.getBridgeProductIndex().toInt());
|
||||
assertEquals(ACTUATOR_TYPE_VELUX, product.getActuatorType());
|
||||
assertEquals(UNKNOWN_POSITION, product.getVanePosition());
|
||||
assertNull(product.getFunctionalParameters());
|
||||
assertFalse(product.isSomfyProduct());
|
||||
|
||||
// reset the dirty flag
|
||||
existingProducts.resetDirtyFlag();
|
||||
assertFalse(existingProducts.isDirty());
|
||||
|
||||
// confirm modified list is now empty again
|
||||
modified = existingProducts.valuesOfModified();
|
||||
assertEquals(0, modified.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test actuator type setting.
|
||||
*/
|
||||
@Test
|
||||
@Order(11)
|
||||
public void testActuatorTypeSetting() {
|
||||
VeluxProduct product = new VeluxProduct();
|
||||
assertEquals(ACTUATOR_TYPE_UNDEF, product.getActuatorType());
|
||||
|
||||
// set actuator type
|
||||
product.setActuatorType(ACTUATOR_TYPE_SOMFY);
|
||||
assertEquals(ACTUATOR_TYPE_SOMFY, product.getActuatorType());
|
||||
|
||||
// try to set it again
|
||||
product.setActuatorType(ACTUATOR_TYPE_VELUX);
|
||||
assertNotEquals(ACTUATOR_TYPE_VELUX, product.getActuatorType());
|
||||
|
||||
// try with a clean product
|
||||
product = new VeluxProduct();
|
||||
product.setActuatorType(ACTUATOR_TYPE_VELUX);
|
||||
assertEquals(ACTUATOR_TYPE_VELUX, product.getActuatorType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the SCrunProduct command by creating packet for a given main position and vane position, and checking the
|
||||
* created packet is as expected.
|
||||
*/
|
||||
@Test
|
||||
@Order(12)
|
||||
public void testSCrunProductA() {
|
||||
final String expectedString = "02 1C 08 05 00 20 00 90 00 00 00 00 00 A0 00 00 00 00 00 00 00 00 00 00 00"
|
||||
+ " 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 06 00 00 00 00 00 00 00 00 00 00 00 00 00 00"
|
||||
+ " 00 00 00 00 00 00 00 00 00";
|
||||
final byte[] expectedPacket = toByteArray(expectedString);
|
||||
final int targetMainPosition = 0x9000;
|
||||
final int targetVanePosition = 0xA000;
|
||||
|
||||
// initialise the product to be commanded
|
||||
VeluxProduct product = new VeluxProduct(VeluxProductName.UNKNOWN, new ProductBridgeIndex(PRODUCT_INDEX_A), 0, 0,
|
||||
0, null, Command.UNDEFTYPE);
|
||||
product.setActuatorType(ACTUATOR_TYPE_SOMFY);
|
||||
product.setCurrentPosition(targetMainPosition);
|
||||
product.setVanePosition(targetVanePosition);
|
||||
|
||||
// create the run product command, and initialise it from the test product's state values
|
||||
SCrunProductCommand bcp = new SCrunProductCommand();
|
||||
bcp.setNodeIdAndParameters(product.getBridgeProductIndex().toInt(),
|
||||
new VeluxProductPosition(product.getCurrentPosition()), product.getFunctionalParameters());
|
||||
|
||||
// get the resulting data packet
|
||||
byte[] actualPacket = bcp.getRequestDataAsArrayOfBytes();
|
||||
|
||||
// check the packet lengths are the same
|
||||
assertEquals(expectedPacket.length, actualPacket.length);
|
||||
|
||||
// check the packet contents are identical (note start at i = 2 because session id won't match)
|
||||
boolean identical = true;
|
||||
for (int i = 2; i < expectedPacket.length; i++) {
|
||||
if (actualPacket[i] != expectedPacket[i]) {
|
||||
identical = false;
|
||||
}
|
||||
}
|
||||
assertTrue(identical);
|
||||
|
||||
// check the resulting updater product state is 'executing' with the new values
|
||||
product = bcp.getProduct();
|
||||
product.setActuatorType(ACTUATOR_TYPE_SOMFY);
|
||||
assertEquals(ProductState.EXECUTING, product.getProductState());
|
||||
assertEquals(targetMainPosition, product.getCurrentPosition());
|
||||
assertEquals(targetMainPosition, product.getTarget());
|
||||
assertEquals(targetMainPosition, product.getDisplayPosition());
|
||||
assertEquals(targetVanePosition, product.getVanePosition());
|
||||
assertEquals(targetVanePosition, product.getVaneDisplayPosition());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the SCrunProduct command by creating a packet with some bad values, and checking the product is as expected.
|
||||
*/
|
||||
@Test
|
||||
@Order(12)
|
||||
public void testSCrunProductB() {
|
||||
SCrunProductCommand bcp = new SCrunProductCommand();
|
||||
|
||||
final int errorMainPosition = 0xffff;
|
||||
VeluxProduct product = new VeluxProduct(VeluxProductName.UNKNOWN, new ProductBridgeIndex(PRODUCT_INDEX_A), 0, 0,
|
||||
0, null, Command.UNDEFTYPE);
|
||||
product.setActuatorType(ACTUATOR_TYPE_SOMFY);
|
||||
product.setCurrentPosition(errorMainPosition);
|
||||
|
||||
bcp.setNodeIdAndParameters(product.getBridgeProductIndex().toInt(),
|
||||
new VeluxProductPosition(product.getCurrentPosition()), product.getFunctionalParameters());
|
||||
product = bcp.getProduct();
|
||||
product.setActuatorType(ACTUATOR_TYPE_SOMFY);
|
||||
|
||||
assertEquals(ProductState.EXECUTING, product.getProductState());
|
||||
assertEquals(IGNORE_POSITION, product.getCurrentPosition());
|
||||
assertEquals(IGNORE_POSITION, product.getTarget());
|
||||
assertEquals(UNKNOWN_POSITION, product.getDisplayPosition());
|
||||
assertEquals(UNKNOWN_POSITION, product.getVanePosition());
|
||||
assertEquals(UNKNOWN_POSITION, product.getVaneDisplayPosition());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the actuator state.
|
||||
*/
|
||||
@Test
|
||||
@Order(14)
|
||||
public void testActuatorState() {
|
||||
VeluxProduct product = new VeluxProduct(VeluxProductName.UNKNOWN, new ProductBridgeIndex(PRODUCT_INDEX_A), 0, 0,
|
||||
0, null, Command.UNDEFTYPE);
|
||||
int[] inputStates = { 0, 1, 2, 3, 4, 5, 0x2c, 0x2d, 0xff, 0x80 };
|
||||
ProductState[] expected = { ProductState.NON_EXECUTING, ProductState.ERROR, ProductState.NOT_USED,
|
||||
ProductState.WAITING_FOR_POWER, ProductState.EXECUTING, ProductState.DONE, ProductState.EXECUTING,
|
||||
ProductState.DONE, ProductState.UNKNOWN, ProductState.MANUAL };
|
||||
for (int i = 0; i < inputStates.length; i++) {
|
||||
product.setState(inputStates[i]);
|
||||
assertEquals(expected[i], product.getProductState());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test actuator positions and display positions.
|
||||
*/
|
||||
@Test
|
||||
@Order(15)
|
||||
public void testActuatorPositions() {
|
||||
VeluxProduct product = new VeluxProduct(VeluxProductName.UNKNOWN, new ProductBridgeIndex(PRODUCT_INDEX_A), 0, 0,
|
||||
0, null, Command.UNDEFTYPE);
|
||||
|
||||
product.setCurrentPosition(MAIN_POSITION_A);
|
||||
product.setTarget(TARGET_POSITION);
|
||||
product.setActuatorType(ACTUATOR_TYPE_SOMFY);
|
||||
product.setVanePosition(VANE_POSITION_A);
|
||||
|
||||
// state uninitialised
|
||||
assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
|
||||
assertEquals(MAIN_POSITION_A, product.getDisplayPosition());
|
||||
assertEquals(TARGET_POSITION, product.getTarget());
|
||||
assertEquals(VANE_POSITION_A, product.getVaneDisplayPosition());
|
||||
assertEquals(VANE_POSITION_A, product.getVanePosition());
|
||||
|
||||
// state = done
|
||||
product.setState(ProductState.DONE.value);
|
||||
assertEquals(MAIN_POSITION_A, product.getDisplayPosition());
|
||||
assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
|
||||
assertEquals(TARGET_POSITION, product.getTarget());
|
||||
assertEquals(VANE_POSITION_A, product.getVaneDisplayPosition());
|
||||
assertEquals(VANE_POSITION_A, product.getVanePosition());
|
||||
|
||||
// state = not used
|
||||
product.setState(ProductState.NOT_USED.value);
|
||||
assertEquals(MAIN_POSITION_A, product.getDisplayPosition());
|
||||
assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
|
||||
assertEquals(TARGET_POSITION, product.getTarget());
|
||||
assertEquals(VANE_POSITION_A, product.getVaneDisplayPosition());
|
||||
assertEquals(VANE_POSITION_A, product.getVanePosition());
|
||||
|
||||
// state = executing
|
||||
product.setState(ProductState.EXECUTING.value);
|
||||
assertEquals(TARGET_POSITION, product.getDisplayPosition());
|
||||
assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
|
||||
assertEquals(TARGET_POSITION, product.getTarget());
|
||||
assertEquals(VANE_POSITION_A, product.getVaneDisplayPosition());
|
||||
assertEquals(VANE_POSITION_A, product.getVanePosition());
|
||||
|
||||
// state = manual + excuting
|
||||
product.setState(ProductState.MANUAL.value + ProductState.EXECUTING.value);
|
||||
assertEquals(UNKNOWN_POSITION, product.getDisplayPosition());
|
||||
assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
|
||||
assertEquals(TARGET_POSITION, product.getTarget());
|
||||
assertEquals(UNKNOWN_POSITION, product.getVaneDisplayPosition());
|
||||
assertEquals(VANE_POSITION_A, product.getVanePosition());
|
||||
|
||||
// state = error
|
||||
product.setState(ProductState.ERROR.value);
|
||||
assertEquals(UNKNOWN_POSITION, product.getDisplayPosition());
|
||||
assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
|
||||
assertEquals(TARGET_POSITION, product.getTarget());
|
||||
assertEquals(UNKNOWN_POSITION, product.getVaneDisplayPosition());
|
||||
assertEquals(VANE_POSITION_A, product.getVanePosition());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the SCgetHouseStatus command by checking for the correct parsing of a 'GW_NODE_STATE_POSITION_CHANGED_NTF'
|
||||
* notification packet. Note: this packet is from a Velux roller shutter with main and vane position.
|
||||
*/
|
||||
@Test
|
||||
@Order(16)
|
||||
public void testSCgetHouseStatusOnVelux() {
|
||||
// initialise the test parameters
|
||||
final String packet = "00 2D C8 00 B8 00 F7 FF F7 FF 00 00 F7 FF 00 00 4A E5 00 00";
|
||||
final short command = VeluxKLFAPI.Command.GW_NODE_STATE_POSITION_CHANGED_NTF.getShort();
|
||||
|
||||
// initialise the BCP
|
||||
SCgetHouseStatus bcp = new SCgetHouseStatus();
|
||||
|
||||
// set the packet response
|
||||
bcp.setResponse(command, toByteArray(packet), false);
|
||||
|
||||
// check BCP status
|
||||
assertTrue(bcp.isCommunicationSuccessful());
|
||||
assertTrue(bcp.isCommunicationFinished());
|
||||
|
||||
// initialise the product
|
||||
VeluxProduct product = bcp.getProduct();
|
||||
|
||||
// change actuator type
|
||||
assertEquals(ACTUATOR_TYPE_UNDEF, product.getActuatorType());
|
||||
product.setActuatorType(ACTUATOR_TYPE_SOMFY);
|
||||
|
||||
// check negative assertions
|
||||
assertNotEquals(VANE_POSITION_A, product.getVanePosition());
|
||||
|
||||
// test updating the existing product in the database
|
||||
VeluxExistingProducts existingProducts = getExistingProducts();
|
||||
|
||||
// process this as a receive only command for a Velux product => update IS applied
|
||||
existingProducts.resetDirtyFlag();
|
||||
assertTrue(existingProducts.update(product));
|
||||
assertTrue(existingProducts.isDirty());
|
||||
|
||||
// process this as an information request command for a Velux product => update IS applied
|
||||
existingProducts.resetDirtyFlag();
|
||||
product.setCurrentPosition(MAIN_POSITION_B);
|
||||
assertTrue(existingProducts.update(product));
|
||||
assertTrue(existingProducts.isDirty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test updating logic with various states applied.
|
||||
*/
|
||||
@Test
|
||||
@Order(17)
|
||||
public void testUpdatingLogic() {
|
||||
VeluxExistingProducts existingProducts = getExistingProducts();
|
||||
ProductBridgeIndex index = new ProductBridgeIndex(PRODUCT_INDEX_A);
|
||||
VeluxProduct product = existingProducts.get(index).clone();
|
||||
|
||||
assertEquals(ProductState.DONE, product.getProductState());
|
||||
|
||||
// state = done
|
||||
assertEquals(MAIN_POSITION_A, product.getDisplayPosition());
|
||||
assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
|
||||
assertEquals(TARGET_POSITION, product.getTarget());
|
||||
assertEquals(VANE_POSITION_A, product.getVaneDisplayPosition());
|
||||
assertEquals(VANE_POSITION_A, product.getVanePosition());
|
||||
|
||||
// state = not used
|
||||
product.setState(ProductState.NOT_USED.value);
|
||||
product.setCurrentPosition(MAIN_POSITION_A - 1);
|
||||
product.setTarget(TARGET_POSITION - 1);
|
||||
product.setVanePosition(VANE_POSITION_A - 1);
|
||||
existingProducts.update(product);
|
||||
product = existingProducts.get(index).clone();
|
||||
assertEquals(MAIN_POSITION_A, product.getDisplayPosition());
|
||||
assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
|
||||
assertEquals(TARGET_POSITION, product.getTarget());
|
||||
assertEquals(VANE_POSITION_A, product.getVaneDisplayPosition());
|
||||
assertEquals(VANE_POSITION_A, product.getVanePosition());
|
||||
|
||||
// state = manual + excuting
|
||||
product.setState(ProductState.MANUAL.value + ProductState.EXECUTING.value);
|
||||
product.setCurrentPosition(MAIN_POSITION_A - 1);
|
||||
product.setTarget(TARGET_POSITION - 1);
|
||||
product.setVanePosition(VANE_POSITION_A - 1);
|
||||
existingProducts.update(product);
|
||||
product = existingProducts.get(index).clone();
|
||||
assertEquals(UNKNOWN_POSITION, product.getDisplayPosition());
|
||||
assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
|
||||
assertEquals(TARGET_POSITION, product.getTarget());
|
||||
assertEquals(UNKNOWN_POSITION, product.getVaneDisplayPosition());
|
||||
assertEquals(VANE_POSITION_A, product.getVanePosition());
|
||||
|
||||
// state = error
|
||||
product.setState(ProductState.ERROR.value);
|
||||
product.setCurrentPosition(MAIN_POSITION_A - 1);
|
||||
product.setTarget(TARGET_POSITION - 1);
|
||||
product.setVanePosition(VANE_POSITION_A - 1);
|
||||
existingProducts.update(product);
|
||||
product = existingProducts.get(index).clone();
|
||||
assertEquals(UNKNOWN_POSITION, product.getDisplayPosition());
|
||||
assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
|
||||
assertEquals(TARGET_POSITION, product.getTarget());
|
||||
assertEquals(UNKNOWN_POSITION, product.getVaneDisplayPosition());
|
||||
assertEquals(VANE_POSITION_A, product.getVanePosition());
|
||||
|
||||
// state = executing
|
||||
product.setState(ProductState.EXECUTING.value);
|
||||
product.setCurrentPosition(MAIN_POSITION_A - 1);
|
||||
product.setTarget(TARGET_POSITION - 1);
|
||||
product.setVanePosition(VANE_POSITION_A - 1);
|
||||
existingProducts.update(product);
|
||||
product = existingProducts.get(index).clone();
|
||||
assertNotEquals(TARGET_POSITION, product.getDisplayPosition());
|
||||
assertNotEquals(MAIN_POSITION_A, product.getCurrentPosition());
|
||||
assertNotEquals(TARGET_POSITION, product.getTarget());
|
||||
assertNotEquals(VANE_POSITION_A, product.getVaneDisplayPosition());
|
||||
assertNotEquals(VANE_POSITION_A, product.getVanePosition());
|
||||
|
||||
// state = done
|
||||
product.setState(ProductState.EXECUTING.value);
|
||||
product.setCurrentPosition(MAIN_POSITION_A);
|
||||
product.setTarget(TARGET_POSITION);
|
||||
product.setVanePosition(VANE_POSITION_A);
|
||||
existingProducts.update(product);
|
||||
product = existingProducts.get(index).clone();
|
||||
assertEquals(TARGET_POSITION, product.getDisplayPosition());
|
||||
assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
|
||||
assertEquals(TARGET_POSITION, product.getTarget());
|
||||
assertEquals(VANE_POSITION_A, product.getVaneDisplayPosition());
|
||||
assertEquals(VANE_POSITION_A, product.getVanePosition());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test updating the existing product in the database with special exceptions.
|
||||
*/
|
||||
@Test
|
||||
@Order(18)
|
||||
public void testSpecialExceptions() {
|
||||
VeluxExistingProducts existingProducts = getExistingProducts();
|
||||
VeluxProduct existing = existingProducts.get(new ProductBridgeIndex(PRODUCT_INDEX_A));
|
||||
|
||||
VeluxProduct product;
|
||||
|
||||
// process this as a receive only command for a Somfy product => update IS applied
|
||||
product = new VeluxProduct(existing.getProductName(), existing.getBridgeProductIndex(), existing.getState(),
|
||||
existing.getCurrentPosition(), existing.getTarget(), existing.getFunctionalParameters(),
|
||||
Command.GW_OPENHAB_RECEIVEONLY);
|
||||
existingProducts.resetDirtyFlag();
|
||||
product.setState(ProductState.DONE.value);
|
||||
product.setCurrentPosition(MAIN_POSITION_B);
|
||||
assertTrue(existingProducts.update(product));
|
||||
assertTrue(existingProducts.isDirty());
|
||||
|
||||
// process this as an information request command for a Somfy product => update IS applied
|
||||
product = new VeluxProduct(existing.getProductName(), existing.getBridgeProductIndex(), existing.getState(),
|
||||
existing.getCurrentPosition(), existing.getTarget(), existing.getFunctionalParameters(),
|
||||
Command.GW_GET_NODE_INFORMATION_REQ);
|
||||
existingProducts.resetDirtyFlag();
|
||||
product.setCurrentPosition(MAIN_POSITION_A);
|
||||
assertTrue(existingProducts.update(product));
|
||||
assertTrue(existingProducts.isDirty());
|
||||
|
||||
// process this as a receive only command for a Somfy product with bad data => update NOT applied
|
||||
product = new VeluxProduct(existing.getProductName(), existing.getBridgeProductIndex(), existing.getState(),
|
||||
existing.getCurrentPosition(), existing.getTarget(), existing.getFunctionalParameters(),
|
||||
Command.GW_OPENHAB_RECEIVEONLY);
|
||||
existingProducts.resetDirtyFlag();
|
||||
product.setCurrentPosition(UNKNOWN_POSITION);
|
||||
product.setTarget(UNKNOWN_POSITION);
|
||||
assertTrue(existingProducts.update(product));
|
||||
assertFalse(existingProducts.isDirty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test VeluxProductPosition
|
||||
*/
|
||||
@Test
|
||||
@Order(19)
|
||||
public void testVeluxProductPosition() {
|
||||
VeluxProductPosition position;
|
||||
int target;
|
||||
|
||||
// on and inside range limits
|
||||
assertTrue(new VeluxProductPosition(VeluxProductPosition.VPP_VELUX_MIN).isValid());
|
||||
assertTrue(new VeluxProductPosition(VeluxProductPosition.VPP_VELUX_MAX).isValid());
|
||||
assertTrue(new VeluxProductPosition(VeluxProductPosition.VPP_VELUX_MAX - 1).isValid());
|
||||
assertTrue(new VeluxProductPosition(VeluxProductPosition.VPP_VELUX_MIN + 1).isValid());
|
||||
|
||||
// outside range limits
|
||||
assertFalse(new VeluxProductPosition(VeluxProductPosition.VPP_VELUX_MIN - 1).isValid());
|
||||
assertFalse(new VeluxProductPosition(VeluxProductPosition.VPP_VELUX_MAX + 1).isValid());
|
||||
assertFalse(new VeluxProductPosition(VeluxProductPosition.VPP_VELUX_IGNORE).isValid());
|
||||
assertFalse(new VeluxProductPosition(VeluxProductPosition.VPP_VELUX_DEFAULT).isValid());
|
||||
assertFalse(new VeluxProductPosition(VeluxProductPosition.VPP_VELUX_STOP).isValid());
|
||||
assertFalse(new VeluxProductPosition(VeluxProductPosition.VPP_VELUX_UNKNOWN).isValid());
|
||||
|
||||
// 80% absolute position
|
||||
position = new VeluxProductPosition(new PercentType(80));
|
||||
assertEquals(0xA000, position.getPositionAsVeluxType());
|
||||
assertTrue(position.isValid());
|
||||
|
||||
// 80% absolute position
|
||||
position = new VeluxProductPosition(new PercentType(80), false);
|
||||
assertEquals(0xA000, position.getPositionAsVeluxType());
|
||||
assertTrue(position.isValid());
|
||||
|
||||
// 80% inverted absolute position (i.e. 20%)
|
||||
position = new VeluxProductPosition(new PercentType(80), true);
|
||||
assertEquals(0x2800, position.getPositionAsVeluxType());
|
||||
assertTrue(position.isValid());
|
||||
|
||||
// 80% positive relative position
|
||||
target = VeluxProductPosition.VPP_VELUX_RELATIVE_ORIGIN
|
||||
+ (VeluxProductPosition.VPP_VELUX_RELATIVE_RANGE * 8 / 10);
|
||||
position = new VeluxProductPosition(new PercentType(80)).overridePositionType(PositionType.OFFSET_POSITIVE);
|
||||
assertTrue(position.isValid());
|
||||
assertEquals(target, position.getPositionAsVeluxType());
|
||||
|
||||
// 80% negative relative position
|
||||
target = VeluxProductPosition.VPP_VELUX_RELATIVE_ORIGIN
|
||||
- (VeluxProductPosition.VPP_VELUX_RELATIVE_RANGE * 8 / 10);
|
||||
position = new VeluxProductPosition(new PercentType(80)).overridePositionType(PositionType.OFFSET_NEGATIVE);
|
||||
assertTrue(position.isValid());
|
||||
assertEquals(target, position.getPositionAsVeluxType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test SCrunProductResult results
|
||||
*/
|
||||
@Test
|
||||
@Order(20)
|
||||
public void testSCrunProductResults() {
|
||||
SCrunProductCommand bcp = new SCrunProductCommand();
|
||||
|
||||
// create a dummy product to get some functional parameters from
|
||||
VeluxProduct product = new VeluxProduct(VeluxProductName.UNKNOWN, new ProductBridgeIndex(PRODUCT_INDEX_A), 0, 0,
|
||||
0, null, Command.UNDEFTYPE);
|
||||
product.setActuatorType(ACTUATOR_TYPE_SOMFY);
|
||||
product.setVanePosition(VANE_POSITION_A);
|
||||
final FunctionalParameters functionalParameters = product.getFunctionalParameters();
|
||||
|
||||
boolean ok;
|
||||
|
||||
// test setting both main and vane position
|
||||
ok = bcp.setNodeIdAndParameters(PRODUCT_INDEX_A, new VeluxProductPosition(MAIN_POSITION_A),
|
||||
functionalParameters);
|
||||
assertTrue(ok);
|
||||
product = bcp.getProduct();
|
||||
product.setActuatorType(ACTUATOR_TYPE_SOMFY);
|
||||
assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
|
||||
assertEquals(VANE_POSITION_A, product.getVanePosition());
|
||||
|
||||
// test setting vane position only
|
||||
ok = bcp.setNodeIdAndParameters(PRODUCT_INDEX_A, null, functionalParameters);
|
||||
assertTrue(ok);
|
||||
product = bcp.getProduct();
|
||||
product.setActuatorType(ACTUATOR_TYPE_SOMFY);
|
||||
assertEquals(IGNORE_POSITION, product.getCurrentPosition());
|
||||
assertEquals(VANE_POSITION_A, product.getVanePosition());
|
||||
|
||||
// test setting main position only
|
||||
ok = bcp.setNodeIdAndParameters(PRODUCT_INDEX_A, new VeluxProductPosition(MAIN_POSITION_A), null);
|
||||
assertTrue(ok);
|
||||
product = bcp.getProduct();
|
||||
product.setActuatorType(ACTUATOR_TYPE_SOMFY);
|
||||
assertEquals(MAIN_POSITION_A, product.getCurrentPosition());
|
||||
assertEquals(UNKNOWN_POSITION, product.getVanePosition());
|
||||
|
||||
// test setting neither
|
||||
ok = bcp.setNodeIdAndParameters(PRODUCT_INDEX_A, null, null);
|
||||
assertFalse(ok);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test SCgetProductStatus exceptional error state processing.
|
||||
*/
|
||||
@Test
|
||||
@Order(21)
|
||||
public void testErrorStateMapping() {
|
||||
// initialise the test parameters
|
||||
final String packet = "0F A3 01 06 01 00 01 02 00 9A 36 03 00 00 00 00 00 00 00 00 00 00 00 00 00"
|
||||
+ " 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00"
|
||||
+ " 00 00 00 00 00";
|
||||
final Command command = VeluxKLFAPI.Command.GW_STATUS_REQUEST_NTF;
|
||||
|
||||
// initialise the BCP
|
||||
SCgetProductStatus bcp = new SCgetProductStatus();
|
||||
bcp.setProductId(PRODUCT_INDEX_A);
|
||||
|
||||
// set the packet response
|
||||
bcp.setResponse(command.getShort(), toByteArray(packet), false);
|
||||
|
||||
// check BCP status
|
||||
assertTrue(bcp.isCommunicationSuccessful());
|
||||
assertTrue(bcp.isCommunicationFinished());
|
||||
|
||||
// check the product state
|
||||
assertEquals(ProductState.UNKNOWN.value, bcp.getProduct().getState());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user