From 7dcfc66a62b95b3628fa4d1bbe22d46e8c7212d5 Mon Sep 17 00:00:00 2001 From: Boris Krivonog Date: Sun, 14 Nov 2021 11:33:39 +0100 Subject: [PATCH] [nikobus] added option to set rollershutter position (#11548) Signed-off-by: Boris Krivonog boris.krivonog@inova.si Signed-off-by: Boris Krivonog --- .../NikobusRollershutterModuleHandler.java | 114 ++++++++++++++---- 1 file changed, 90 insertions(+), 24 deletions(-) diff --git a/bundles/org.openhab.binding.nikobus/src/main/java/org/openhab/binding/nikobus/internal/handler/NikobusRollershutterModuleHandler.java b/bundles/org.openhab.binding.nikobus/src/main/java/org/openhab/binding/nikobus/internal/handler/NikobusRollershutterModuleHandler.java index 9637557638564..f0451bef40bc9 100644 --- a/bundles/org.openhab.binding.nikobus/src/main/java/org/openhab/binding/nikobus/internal/handler/NikobusRollershutterModuleHandler.java +++ b/bundles/org.openhab.binding.nikobus/src/main/java/org/openhab/binding/nikobus/internal/handler/NikobusRollershutterModuleHandler.java @@ -14,6 +14,7 @@ import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Future; @@ -22,6 +23,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.nikobus.internal.utils.Utils; +import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.PercentType; import org.openhab.core.library.types.StopMoveType; @@ -45,7 +47,6 @@ public class NikobusRollershutterModuleHandler extends NikobusModuleHandler { private final Logger logger = LoggerFactory.getLogger(NikobusRollershutterModuleHandler.class); private final List positionEstimators = new CopyOnWriteArrayList<>(); - private final Map directionConfigurations = new ConcurrentHashMap<>(); public NikobusRollershutterModuleHandler(Thing thing) { @@ -77,18 +78,26 @@ public void initialize() { logger.debug("Position estimators for {} = {}", thing.getUID(), positionEstimators); } + @Override + public void dispose() { + positionEstimators.forEach(PositionEstimator::destroy); + super.dispose(); + } + @Override protected int valueFromCommand(String channelId, Command command) { - if (command == StopMoveType.STOP) { - return 0x00; - } - if (command == UpDownType.DOWN || command == StopMoveType.MOVE) { - return getDirectionConfiguration(channelId).down; - } - if (command == UpDownType.UP) { - return getDirectionConfiguration(channelId).up; + Optional positionEstimator = getPositionEstimator(channelId); + if (command instanceof DecimalType) { + return positionEstimator.map(estimator -> { + return estimator.processSetPosition(((DecimalType) command).intValue()); + }).orElseThrow(() -> { + throw new IllegalArgumentException( + "Received position request but no estimation configured for channel " + channelId); + }); } - throw new IllegalArgumentException("Command '" + command + "' not supported"); + int result = convertCommandToValue(channelId, command); + positionEstimator.ifPresent(PositionEstimator::cancelStopMovement); + return result; } @Override @@ -96,7 +105,6 @@ protected State stateFromValue(String channelId, int value) { if (value == 0x00) { return OnOffType.OFF; } - DirectionConfiguration configuration = getDirectionConfiguration(channelId); if (value == configuration.up) { return UpDownType.UP; @@ -104,7 +112,6 @@ protected State stateFromValue(String channelId, int value) { if (value == configuration.down) { return UpDownType.DOWN; } - throw new IllegalArgumentException("Unexpected value " + value + " received"); } @@ -112,24 +119,41 @@ protected State stateFromValue(String channelId, int value) { protected void updateState(ChannelUID channelUID, State state) { logger.debug("updateState {} {}", channelUID, state); - positionEstimators.stream().filter(estimator -> channelUID.equals(estimator.getChannelUID())).findFirst() - .ifPresentOrElse(estimator -> { - if (state == UpDownType.UP) { - estimator.start(-1); - } else if (state == UpDownType.DOWN) { - estimator.start(1); - } else if (state == OnOffType.OFF) { - estimator.stop(); - } else { - logger.debug("Unexpected state update '{}' for '{}'", state, channelUID); - } - }, () -> super.updateState(channelUID, state)); + getPositionEstimator(channelUID.getId()).ifPresentOrElse(estimator -> { + if (state == UpDownType.UP) { + estimator.start(-1); + } else if (state == UpDownType.DOWN) { + estimator.start(1); + } else if (state == OnOffType.OFF) { + estimator.stop(); + } else { + logger.debug("Unexpected state update '{}' for '{}'", state, channelUID); + } + }, () -> super.updateState(channelUID, state)); } private void updateState(ChannelUID channelUID, int percent) { super.updateState(channelUID, new PercentType(percent)); } + protected int convertCommandToValue(String channelId, Command command) { + if (command == StopMoveType.STOP) { + return 0x00; + } + if (command == UpDownType.DOWN || command == StopMoveType.MOVE) { + return getDirectionConfiguration(channelId).down; + } + if (command == UpDownType.UP) { + return getDirectionConfiguration(channelId).up; + } + throw new IllegalArgumentException("Command '" + command + "' not supported"); + } + + private Optional getPositionEstimator(String channelId) { + return positionEstimators.stream().filter(estimator -> channelId.equals(estimator.getChannelUID().getId())) + .findFirst(); + } + private DirectionConfiguration getDirectionConfiguration(String channelId) { DirectionConfiguration configuration = directionConfigurations.get(channelId); if (configuration == null) { @@ -154,6 +178,7 @@ private class PositionEstimator { private long startTimeMillis = 0; private int direction = 0; private @Nullable Future updateEstimateFuture; + private @Nullable Future stopMovementFuture; PositionEstimator(ChannelUID channelUID, PositionEstimatorConfig config) { this.channelUID = channelUID; @@ -167,6 +192,12 @@ public ChannelUID getChannelUID() { return channelUID; } + public void destroy() { + Utils.cancel(updateEstimateFuture); + updateEstimateFuture = null; + cancelStopMovement(); + } + public void start(int direction) { stop(); synchronized (this) { @@ -191,6 +222,41 @@ public void stop() { } } + public int processSetPosition(int percent) { + if (percent < 0 || percent > 100) { + throw new IllegalArgumentException("Position % out of range - expecting [0, 100] but got " + percent + + " for " + channelUID.getId()); + } + + cancelStopMovement(); + + int newPosition = (int) ((double) percent * (double) durationInMillis / 100.0 + 0.5); + int delta = position - newPosition; + + logger.debug("Position set command {} for {}: delta = {}, current pos: {}, new position: {}", percent, + channelUID, delta, position, newPosition); + + if (delta == 0) { + return convertCommandToValue(channelUID.getId(), StopMoveType.STOP); + } + + int time = Math.abs(delta); + if (percent == 0 || percent == 100) { + time += 5000; // Make sure we get to completely open/closed position. + } + + stopMovementFuture = scheduler.schedule(() -> { + handleCommand(channelUID, StopMoveType.STOP); + }, time, TimeUnit.MILLISECONDS); + + return convertCommandToValue(channelUID.getId(), delta > 0 ? UpDownType.UP : UpDownType.DOWN); + } + + public void cancelStopMovement() { + Utils.cancel(stopMovementFuture); + stopMovementFuture = null; + } + private void updateEstimate() { int direction; int ellapsedMillis;