diff --git a/bundles/org.openhab.binding.velux/src/main/java/org/openhab/binding/velux/internal/bridge/common/GetProduct.java b/bundles/org.openhab.binding.velux/src/main/java/org/openhab/binding/velux/internal/bridge/common/GetProduct.java index 6709302d2..a4c647d28 100644 --- a/bundles/org.openhab.binding.velux/src/main/java/org/openhab/binding/velux/internal/bridge/common/GetProduct.java +++ b/bundles/org.openhab.binding.velux/src/main/java/org/openhab/binding/velux/internal/bridge/common/GetProduct.java @@ -13,6 +13,7 @@ package org.openhab.binding.velux.internal.bridge.common; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.velux.internal.things.StatusReply; import org.openhab.binding.velux.internal.things.VeluxProduct; /** @@ -47,4 +48,8 @@ public abstract class GetProduct implements BridgeCommunicationProtocol { * @return veluxProduct as VeluxProduct. */ public abstract VeluxProduct getProduct(); + + public StatusReply getStatusReply() { + return StatusReply.COMMAND_COMPLETED_OK; + } } diff --git a/bundles/org.openhab.binding.velux/src/main/java/org/openhab/binding/velux/internal/bridge/slip/SCgetProductStatus.java b/bundles/org.openhab.binding.velux/src/main/java/org/openhab/binding/velux/internal/bridge/slip/SCgetProductStatus.java index 2fd8d4f01..44bd6f59a 100644 --- a/bundles/org.openhab.binding.velux/src/main/java/org/openhab/binding/velux/internal/bridge/slip/SCgetProductStatus.java +++ b/bundles/org.openhab.binding.velux/src/main/java/org/openhab/binding/velux/internal/bridge/slip/SCgetProductStatus.java @@ -18,6 +18,7 @@ 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.StatusReply; 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; @@ -81,8 +82,8 @@ public class SCgetProductStatus extends GetProduct implements SlipBridgeCommunic private boolean success = false; private boolean finished = false; - private VeluxProduct product = VeluxProduct.UNKNOWN; + private StatusReply statusReply = StatusReply.COMMAND_COMPLETED_OK; public SCgetProductStatus() { logger.debug("SCgetProductStatus(Constructor) called."); @@ -218,6 +219,7 @@ public class SCgetProductStatus extends GetProduct implements SlipBridgeCommunic break; default: ntfState = VeluxProduct.ProductState.ERROR.value; + statusReply = StatusReply.fromCode(ntfStatusReply); } break; } @@ -281,4 +283,9 @@ public class SCgetProductStatus extends GetProduct implements SlipBridgeCommunic logger.trace("getProduct(): returning {}.", product); return product; } + + @Override + public StatusReply getStatusReply() { + return statusReply; + } } diff --git a/bundles/org.openhab.binding.velux/src/main/java/org/openhab/binding/velux/internal/handler/ChannelActuatorPosition.java b/bundles/org.openhab.binding.velux/src/main/java/org/openhab/binding/velux/internal/handler/ChannelActuatorPosition.java index d5cba669a..ecc4f059d 100644 --- a/bundles/org.openhab.binding.velux/src/main/java/org/openhab/binding/velux/internal/handler/ChannelActuatorPosition.java +++ b/bundles/org.openhab.binding.velux/src/main/java/org/openhab/binding/velux/internal/handler/ChannelActuatorPosition.java @@ -14,9 +14,6 @@ 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.common.GetProduct; @@ -24,6 +21,7 @@ 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.StatusReply; 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; @@ -71,12 +69,6 @@ final class ChannelActuatorPosition extends ChannelHandlerTemplate { throw new AssertionError(); } - /* - * List of product states that shall be processed - */ - private static final List STATES_TO_PROCESS = Arrays.asList(ProductState.DONE, ProductState.EXECUTING, - ProductState.MANUAL, ProductState.UNKNOWN); - // Public methods /** @@ -105,12 +97,10 @@ final class ChannelActuatorPosition extends ChannelHandlerTemplate { 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(); + case CHANNEL_VANE_POSITION: + bcp = thisBridgeHandler.thisBridge.bridgeAPI().getProductStatus(); default: // unknown channel, will exit } @@ -127,45 +117,68 @@ final class ChannelActuatorPosition extends ChannelHandlerTemplate { } 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()) { + ProductBridgeIndex productBridgeIndex = newProduct.getBridgeProductIndex(); + VeluxExistingProducts existingProducts = thisBridgeHandler.existingProducts(); + VeluxProduct existingProduct = existingProducts.get(productBridgeIndex); + ProductState productState = newProduct.getProductState(); + switch (productState) { + case DONE: + case EXECUTING: + case MANUAL: + case UNKNOWN: + 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: - newState = position.getPositionAsPercentType(false); + posValue = existingProduct.getVaneDisplayPosition(); break; case CHANNEL_ACTUATOR_POSITION: - newState = position.getPositionAsPercentType(veluxActuator.isInverted()); - break; case CHANNEL_ACTUATOR_STATE: - newState = OnOffType - .from(position.getPositionAsPercentType(veluxActuator.isInverted()) - .intValue() > 50); + 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); + } } } } } } - } + break; + case WAITING_FOR_POWER: + case ERROR: + StatusReply statusReply = productState == ProductState.WAITING_FOR_POWER + ? StatusReply.NODE_WAITING_FOR_POWER + : bcp.getStatusReply(); + if (statusReply.isError()) { + String id = VeluxProduct.UNKNOWN.equals(existingProduct) + ? newProduct.getBridgeProductIndex().toString() + : existingProduct.getProductUniqueIndex(); + if (statusReply.isCriticalError()) { + LOGGER.warn("Product Id:{} encountered an error with StatusReply:{}", id, statusReply); + } else { + LOGGER.info("Product Id:{} encountered an error with StatusReply:{}", id, statusReply); + } + } + default: } if (newState == null) { diff --git a/bundles/org.openhab.binding.velux/src/main/java/org/openhab/binding/velux/internal/things/StatusReply.java b/bundles/org.openhab.binding.velux/src/main/java/org/openhab/binding/velux/internal/things/StatusReply.java new file mode 100644 index 000000000..8053306f6 --- /dev/null +++ b/bundles/org.openhab.binding.velux/src/main/java/org/openhab/binding/velux/internal/things/StatusReply.java @@ -0,0 +1,123 @@ +/** + * 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.things; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * An enum that describes the various predefined Status Reply code values and their meanings. + * + * @author Andrew Fiddian-Green - Initial contribution + * + */ +@NonNullByDefault +public enum StatusReply { + UNKNOWN_STATUS_REPLY(0x00), + COMMAND_COMPLETED_OK(0x01), + NO_CONTACT(0x02), + MANUALLY_OPERATED(0x03), + BLOCKED(0x04), + WRONG_SYSTEMKEY(0x05), + PRIORITY_LEVEL_LOCKED(0x06), + REACHED_WRONG_POSITION(0x07), + ERROR_DURING_EXECUTION(0x08), + NO_EXECUTION(0x09), + CALIBRATING(0x0A), + POWER_CONSUMPTION_TOO_HIGH(0x0B), + POWER_CONSUMPTION_TOO_LOW(0x0C), + LOCK_POSITION_OPEN(0x0D), + MOTION_TIME_TOO_LONG(0x0E), + THERMAL_PROTECTION(0x0F), + PRODUCT_NOT_OPERATIONAL(0x10), + FILTER_MAINTENANCE_NEEDED(0x11), + BATTERY_LEVEL(0x12), + TARGET_MODIFIED(0x13), + MODE_NOT_IMPLEMENTED(0x14), + COMMAND_INCOMPATIBLE_TO_MOVEMENT(0x15), + USER_ACTION(0x16), + DEAD_BOLT_ERROR(0x17), + AUTOMATIC_CYCLE_ENGAGED(0x18), + WRONG_LOAD_CONNECTED(0x19), + COLOUR_NOT_REACHABLE(0x1A), + TARGET_NOT_REACHABLE(0x1B), + BAD_INDEX_RECEIVED(0x1C), + COMMAND_OVERRULED(0x1D), + NODE_WAITING_FOR_POWER(0x1E), + INFORMATION_CODE(0xDF), + PARAMETER_LIMITED(0xE0), + LIMITATION_BY_LOCAL_USER(0xE1), + LIMITATION_BY_USER(0xE2), + LIMITATION_BY_RAIN(0xE3), + LIMITATION_BY_TIMER(0xE4), + LIMITATION_BY_UPS(0xE6), + LIMITATION_BY_UNKNOWN_DEVICE(0xE7), + LIMITATION_BY_SAAC(0xEA), + LIMITATION_BY_WIND(0xEB), + LIMITATION_BY_MYSELF(0xEC), + LIMITATION_BY_AUTOMATIC_CYCLE(0xED), + LIMITATION_BY_EMERGENCY(0xEE); + + private final int code; + + private StatusReply(int code) { + this.code = code; + } + + /* + * List of critical errors + */ + private static final List CRITICAL_ERRORS = List.of(BLOCKED, POWER_CONSUMPTION_TOO_HIGH, + THERMAL_PROTECTION, LOCK_POSITION_OPEN, PRODUCT_NOT_OPERATIONAL, DEAD_BOLT_ERROR, FILTER_MAINTENANCE_NEEDED, + BATTERY_LEVEL, NODE_WAITING_FOR_POWER); + + public int getCode() { + return code; + } + + private static final Map LOOKUP = Stream.of(StatusReply.values()) + .collect(Collectors.toMap(StatusReply::getCode, Function.identity())); + + /** + * Get the StatusReply value that corresponds to the given status code. + * + * @param statusReplyCode the status code value + * @return the StatusReply value that corresponds to the status code + */ + public static StatusReply fromCode(int statusReplyCode) { + return LOOKUP.getOrDefault(statusReplyCode, UNKNOWN_STATUS_REPLY); + } + + /** + * Check if this Status Reply indicates an error. + * + * @return true if the status code is an error code. + */ + public boolean isError() { + return this != COMMAND_COMPLETED_OK; + } + + /** + * Check if this Status Reply indicates a critical error. + * + * @return true if the status code is a critical error code. + */ + public boolean isCriticalError() { + return isError() && CRITICAL_ERRORS.contains(this); + } +}