[velux] fix bugs with Somfy devices, and switch devices (#9245)

* [velux] add data type to config params in readme
* [velux] eliminate mvn warning
* [velux] fix Somfy bugs: uniqueindex = serial number or name
* [velux] fix inconsistent switch state logic
* [velux] cosmetics on BRIDGE_THING_TYPE_UID

Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
This commit is contained in:
Andrew Fiddian-Green
2020-12-08 17:20:14 +00:00
committed by GitHub
parent d2e5c3e7dd
commit ce5d5ca61d
8 changed files with 129 additions and 97 deletions

View File

@@ -153,4 +153,6 @@ public class VeluxBindingConstants {
public static final String UNKNOWN_THING_TYPE_ID = "FAILED";
public static final String UNKNOWN_IP_ADDRESS = "xxx.xxx.xxx.xxx";
public static final String BRIDGE_THING_TYPE_UID = THING_TYPE_BRIDGE.toString();
}

View File

@@ -12,7 +12,7 @@
*/
package org.openhab.binding.velux.internal.handler;
import static org.openhab.binding.velux.internal.VeluxBindingConstants.CHANNEL_ACTUATOR_POSITION;
import static org.openhab.binding.velux.internal.VeluxBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
@@ -82,6 +82,10 @@ final class ChannelActuatorPosition extends ChannelHandlerTemplate {
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();
if (bcp == null) {
LOGGER.trace("handleRefresh(): aborting processing as handler is null.");
@@ -93,10 +97,16 @@ final class ChannelActuatorPosition extends ChannelHandlerTemplate {
VeluxProduct product = bcp.getProduct();
VeluxProductPosition position = new VeluxProductPosition(product.getDisplayPosition());
if (position.isValid()) {
PercentType posPercent = position.getPositionAsPercentType(veluxActuator.isInverted());
LOGGER.trace("handleRefresh(): position of actuator is {}%.", posPercent);
newState = posPercent;
break;
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;
}
}
LOGGER.trace("handleRefresh(): position of actuator is 'UNDEFINED'.");
newState = UnDefType.UNDEF;
@@ -124,55 +134,49 @@ final class ChannelActuatorPosition extends ChannelHandlerTemplate {
LOGGER.debug("handleCommand({},{},{},{}) called.", channelUID, channelId, command, thisBridgeHandler);
Command newValue = null;
do { // just for common exit
assert thisBridgeHandler.bridgeParameters.actuators != null : "VeluxBridgeHandler.bridgeParameters.actuators not initialized.";
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 (channelId.equals(CHANNEL_ACTUATOR_POSITION)) {
if ((command instanceof UpDownType) && (command == UpDownType.UP)) {
LOGGER.trace("handleCommand(): found UP command.");
targetLevel = veluxActuator.isInverted() ? new VeluxProductPosition(PercentType.HUNDRED)
: new VeluxProductPosition(PercentType.ZERO);
} else if ((command instanceof UpDownType) && (command == UpDownType.DOWN)) {
LOGGER.trace("handleCommand(): found DOWN command.");
targetLevel = veluxActuator.isInverted() ? new VeluxProductPosition(PercentType.ZERO)
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) && (command == StopMoveType.STOP)) {
LOGGER.trace("handleCommand(): found STOP command.");
targetLevel = new VeluxProductPosition();
} 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 command of type PercentType.");
LOGGER.trace("handleCommand(): found PercentType.{} command", command);
PercentType ptCommand = (PercentType) command;
if (veluxActuator.isInverted()) {
ptCommand = new PercentType(PercentType.HUNDRED.intValue() - ptCommand.intValue());
}
LOGGER.trace("handleCommand(): found command to set level to {}.", ptCommand);
targetLevel = new VeluxProductPosition(ptCommand);
} else {
LOGGER.info("handleCommand({},{}): ignoring command.", channelUID.getAsString(), command);
break;
}
} else {
if ((command instanceof OnOffType) && (command == OnOffType.ON)) {
LOGGER.trace("handleCommand(): found ON command.");
targetLevel = veluxActuator.isInverted() ? new VeluxProductPosition(PercentType.HUNDRED)
: new VeluxProductPosition(PercentType.ZERO);
} else if ((command instanceof OnOffType) && (command == OnOffType.OFF)) {
LOGGER.trace("handleCommand(): found OFF command.");
targetLevel = veluxActuator.isInverted() ? new VeluxProductPosition(PercentType.ZERO)
} 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);
} else {
LOGGER.info("handleCommand({},{}): ignoring command.", channelUID.getAsString(), command);
break;
}
}
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.");
}

View File

@@ -466,7 +466,7 @@ public class VeluxBridgeHandler extends ExtendedBaseBridgeHandler implements Vel
continue;
}
Thing2VeluxActuator actuator = channel2VeluxActuator.get(channelUID);
if (!actuator.isKnown()) {
if (actuator == null || !actuator.isKnown()) {
logger.trace("syncChannelsWithProducts(): channel {} not registered on bridge.", channelUID);
continue;
}

View File

@@ -13,8 +13,10 @@
package org.openhab.binding.velux.internal.handler.utils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.velux.internal.VeluxBindingConstants;
import org.openhab.binding.velux.internal.VeluxBindingProperties;
import org.openhab.binding.velux.internal.handler.VeluxBridgeHandler;
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.VeluxProductSerialNo;
@@ -50,32 +52,60 @@ public class Thing2VeluxActuator {
// Private
private void mapThing2Velux() {
if (!ThingConfiguration.exists(bridgeHandler, channelUID,
VeluxBindingProperties.CONFIG_ACTUATOR_SERIALNUMBER)) {
logger.trace("mapThing2Velux(): aborting processing as {} is not set within {}.",
VeluxBindingProperties.CONFIG_ACTUATOR_SERIALNUMBER, channelUID);
if (channelUID.toString().startsWith(VeluxBindingConstants.BRIDGE_THING_TYPE_UID)) {
logger.trace("mapThing2Velux(): channel {} is on a Bridge Thing, exiting.", channelUID);
return;
}
String actuatorSerial = (String) ThingConfiguration.getValue(bridgeHandler, channelUID,
VeluxBindingProperties.CONFIG_ACTUATOR_SERIALNUMBER);
logger.trace("mapThing2Velux(): found actuatorSerial={}.", actuatorSerial);
// Handle value inversion
boolean propertyInverted = false;
if (ThingConfiguration.exists(bridgeHandler, channelUID, VeluxBindingProperties.PROPERTY_ACTUATOR_INVERTED)) {
propertyInverted = (boolean) ThingConfiguration.getValue(bridgeHandler, channelUID,
VeluxBindingProperties.PROPERTY_ACTUATOR_INVERTED);
// the uniqueIndex is the serial number (if valid)
String uniqueIndex = null;
boolean invert = false;
if (ThingConfiguration.exists(bridgeHandler, channelUID, VeluxBindingProperties.CONFIG_ACTUATOR_SERIALNUMBER)) {
String serial = (String) ThingConfiguration.getValue(bridgeHandler, channelUID,
VeluxBindingProperties.CONFIG_ACTUATOR_SERIALNUMBER);
invert = VeluxProductSerialNo.indicatesRevertedValues(serial);
serial = VeluxProductSerialNo.cleaned(serial);
uniqueIndex = ("".equals(serial) || serial.equals(VeluxProductSerialNo.UNKNOWN)) ? null : serial;
}
isInverted = propertyInverted || VeluxProductSerialNo.indicatesRevertedValues(actuatorSerial);
logger.trace("mapThing2Velux(): found isInverted={}.", isInverted);
actuatorSerial = VeluxProductSerialNo.cleaned(actuatorSerial);
logger.trace("mapThing2Velux(): in {} serialNumber={}.", channelUID, uniqueIndex);
if (!bridgeHandler.bridgeParameters.actuators.getChannel().existingProducts.isRegistered(actuatorSerial)) {
logger.warn("mapThing2Velux(): cannot work on unknown actuator with serial {}.", actuatorSerial);
// if serial number not valid, the uniqueIndex is name (if valid)
if (uniqueIndex == null) {
if (ThingConfiguration.exists(bridgeHandler, channelUID, VeluxBindingProperties.PROPERTY_ACTUATOR_NAME)) {
String name = (String) ThingConfiguration.getValue(bridgeHandler, channelUID,
VeluxBindingProperties.PROPERTY_ACTUATOR_NAME);
uniqueIndex = ("".equals(name) || name.equals(VeluxBindingConstants.UNKNOWN)) ? null : name;
}
logger.trace("mapThing2Velux(): in {} name={}.", channelUID, uniqueIndex);
}
if (uniqueIndex == null) {
logger.warn("mapThing2Velux(): in {} cannot find a uniqueIndex, aborting.", channelUID);
return;
} else {
logger.trace("mapThing2Velux(): in {} uniqueIndex={}, proceeding.", channelUID, uniqueIndex);
}
// handle value inversion
if (!invert) {
if (ThingConfiguration.exists(bridgeHandler, channelUID,
VeluxBindingProperties.PROPERTY_ACTUATOR_INVERTED)) {
invert = (boolean) ThingConfiguration.getValue(bridgeHandler, channelUID,
VeluxBindingProperties.PROPERTY_ACTUATOR_INVERTED);
}
}
isInverted = invert;
logger.trace("mapThing2Velux(): in {} isInverted={}.", channelUID, isInverted);
VeluxExistingProducts existing = bridgeHandler.bridgeParameters.actuators.getChannel().existingProducts;
if (!existing.isRegistered(uniqueIndex)) {
logger.warn("mapThing2Velux(): actuator with uniqueIndex={} is not registered", uniqueIndex);
return;
}
logger.trace("mapThing2Velux(): fetching actuator for {}.", actuatorSerial);
thisProduct = bridgeHandler.bridgeParameters.actuators.getChannel().existingProducts.get(actuatorSerial);
logger.trace("mapThing2Velux(): fetching actuator for {}.", uniqueIndex);
thisProduct = existing.get(uniqueIndex);
logger.debug("mapThing2Velux(): found actuator {}.", thisProduct);
return;
}

View File

@@ -47,7 +47,7 @@ public class VeluxExistingProducts {
// Type definitions, class-internal variables
private Map<String, VeluxProduct> existingProductsByUniqueIndex;
private Map<Integer, String> bridgeIndexToSerialNumber;
private Map<Integer, String> bridgeIndexToUniqueIndex;
private Map<String, VeluxProduct> modifiedProductsByUniqueIndex;
private int memberCount;
@@ -61,7 +61,7 @@ public class VeluxExistingProducts {
public VeluxExistingProducts() {
logger.trace("VeluxExistingProducts(constructor) called.");
existingProductsByUniqueIndex = new ConcurrentHashMap<>();
bridgeIndexToSerialNumber = new ConcurrentHashMap<>();
bridgeIndexToUniqueIndex = new ConcurrentHashMap<>();
modifiedProductsByUniqueIndex = new ConcurrentHashMap<>();
memberCount = 0;
dirty = true;
@@ -70,24 +70,22 @@ public class VeluxExistingProducts {
// Class access methods
public boolean isRegistered(String productUniqueIndexOrSerialNumber) {
logger.trace("isRegistered(String {}) returns {}.", productUniqueIndexOrSerialNumber,
existingProductsByUniqueIndex.containsKey(productUniqueIndexOrSerialNumber) ? "true" : "false");
return existingProductsByUniqueIndex.containsKey(productUniqueIndexOrSerialNumber);
public boolean isRegistered(String productUniqueIndex) {
boolean result = existingProductsByUniqueIndex.containsKey(productUniqueIndex);
logger.trace("isRegistered(String {}) returns {}.", productUniqueIndex, result);
return result;
}
public boolean isRegistered(VeluxProduct product) {
logger.trace("isRegistered(VeluxProduct {}) called.", product.toString());
if (product.isV2()) {
return isRegistered(product.getSerialNumber());
}
return isRegistered(product.getProductUniqueIndex());
boolean result = existingProductsByUniqueIndex.containsKey(product.getProductUniqueIndex());
logger.trace("isRegistered(VeluxProduct {}) returns {}.", product, result);
return result;
}
public boolean isRegistered(ProductBridgeIndex bridgeProductIndex) {
logger.trace("isRegisteredProductBridgeIndex {}) called.", bridgeProductIndex.toString());
String serialNumber = bridgeIndexToSerialNumber.get(bridgeProductIndex.toInt());
return serialNumber != null && isRegistered(serialNumber);
boolean result = bridgeIndexToUniqueIndex.containsKey(bridgeProductIndex.toInt());
logger.trace("isRegistered(ProductBridgeIndex {}) returns {}.", bridgeProductIndex, result);
return result;
}
public boolean register(VeluxProduct newProduct) {
@@ -97,12 +95,12 @@ public class VeluxExistingProducts {
}
logger.trace("register() registering new product {}.", newProduct);
String uniqueIndex = newProduct.isV2() ? newProduct.getSerialNumber() : newProduct.getProductUniqueIndex();
String uniqueIndex = newProduct.getProductUniqueIndex();
logger.trace("register() registering by UniqueIndex {}", uniqueIndex);
existingProductsByUniqueIndex.put(uniqueIndex, newProduct);
logger.trace("register() registering by ProductBridgeIndex {}", newProduct.getBridgeProductIndex().toInt());
bridgeIndexToSerialNumber.put(newProduct.getBridgeProductIndex().toInt(), newProduct.getSerialNumber());
bridgeIndexToUniqueIndex.put(newProduct.getBridgeProductIndex().toInt(), uniqueIndex);
logger.trace("register() registering set of modifications by UniqueIndex {}", uniqueIndex);
modifiedProductsByUniqueIndex.put(uniqueIndex, newProduct);
@@ -125,8 +123,7 @@ public class VeluxExistingProducts {
dirty |= thisProduct.setCurrentPosition(productPosition);
dirty |= thisProduct.setTarget(productTarget);
if (dirty) {
String uniqueIndex = thisProduct.isV2() ? thisProduct.getSerialNumber()
: thisProduct.getProductUniqueIndex();
String uniqueIndex = thisProduct.getProductUniqueIndex();
logger.trace("update(): updating by UniqueIndex {}.", uniqueIndex);
existingProductsByUniqueIndex.replace(uniqueIndex, thisProduct);
modifiedProductsByUniqueIndex.put(uniqueIndex, thisProduct);
@@ -141,21 +138,16 @@ public class VeluxExistingProducts {
currentProduct.getCurrentPosition(), currentProduct.getTarget());
}
public VeluxProduct get(String productUniqueIndexOrSerialNumber) {
logger.trace("get({}) called.", productUniqueIndexOrSerialNumber);
if (!isRegistered(productUniqueIndexOrSerialNumber)) {
return VeluxProduct.UNKNOWN;
}
return existingProductsByUniqueIndex.getOrDefault(productUniqueIndexOrSerialNumber, VeluxProduct.UNKNOWN);
public VeluxProduct get(String productUniqueIndex) {
logger.trace("get({}) called.", productUniqueIndex);
return existingProductsByUniqueIndex.getOrDefault(productUniqueIndex, VeluxProduct.UNKNOWN);
}
public VeluxProduct get(ProductBridgeIndex bridgeProductIndex) {
logger.trace("get({}) called.", bridgeProductIndex);
String serialNumber = bridgeIndexToSerialNumber.get(bridgeProductIndex.toInt());
if (!isRegistered(bridgeProductIndex) || serialNumber == null) {
return VeluxProduct.UNKNOWN;
}
return existingProductsByUniqueIndex.getOrDefault(serialNumber, VeluxProduct.UNKNOWN);
String unique = bridgeIndexToUniqueIndex.get(bridgeProductIndex.toInt());
return unique != null ? existingProductsByUniqueIndex.getOrDefault(unique, VeluxProduct.UNKNOWN)
: VeluxProduct.UNKNOWN;
}
public VeluxProduct[] values() {

View File

@@ -217,7 +217,10 @@ public class VeluxProduct {
// Class helper methods
public String getProductUniqueIndex() {
return this.name.toString().concat("#").concat(this.typeId.toString());
if (!v2 || serialNumber.startsWith(VeluxProductSerialNo.UNKNOWN)) {
return name.toString();
}
return VeluxProductSerialNo.cleaned(serialNumber);
}
// Getter and Setter methods

View File

@@ -138,6 +138,7 @@
<label>@text/config.velux.thing.rollershutter.serial.label</label>
<description>@text/config.velux.thing.rollershutter.serial.description</description>
<required>true</required>
<default>00:00:00:00:00:00:00:00</default>
<advanced>false</advanced>
</parameter>
<parameter name="name" type="text">