Skip to content

Commit

Permalink
[velux] Add support for vane/slat position (openhab#12618)
Browse files Browse the repository at this point in the history
Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
Signed-off-by: Andras Uhrin <andras.uhrin@gmail.com>
  • Loading branch information
andrewfg authored and andrasU committed Nov 12, 2022
1 parent ed88f68 commit 9950fdc
Show file tree
Hide file tree
Showing 32 changed files with 2,399 additions and 467 deletions.
23 changes: 22 additions & 1 deletion bundles/org.openhab.binding.velux/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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.

Expand All @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,7 @@ public interface BridgeAPI {

@Nullable
RunReboot runReboot();

@Nullable
GetProduct getProductStatus();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -22,7 +25,7 @@
* 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
Expand All @@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -211,4 +211,9 @@ public SetSceneVelocity setSceneVelocity() {
public @Nullable RunReboot runReboot() {
return null;
}

@Override
public @Nullable GetProduct getProductStatus() {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -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);
};
}
Loading

0 comments on commit 9950fdc

Please sign in to comment.