From: Boris Krivonog Date: Fri, 11 Dec 2020 18:29:10 +0000 (+0100) Subject: [nikobus] rollershutter position estimator added (#9294) X-Git-Url: https://git.basschouten.com/?a=commitdiff_plain;h=1362f9e5c113f3acc6e3004706ce918a99398056;p=openhab-addons.git [nikobus] rollershutter position estimator added (#9294) * Implemented rollershutter position estimator. * Removed unneeded try-catch. Signed-off-by: Boris Krivonog --- diff --git a/bundles/org.openhab.binding.nikobus/README.md b/bundles/org.openhab.binding.nikobus/README.md index c5982f629d..b75868e5ff 100644 --- a/bundles/org.openhab.binding.nikobus/README.md +++ b/bundles/org.openhab.binding.nikobus/README.md @@ -131,6 +131,24 @@ Defines a `rollershutter-module` with address `4C6C`. | output-5 | Rollershutter | Output 5 | | output-6 | Rollershutter | Output 6 | +##### Estimating Position + +Nikobus rollershuter module does not provide information about rollershutter's position. In order to bridge this gap, an optional parameter `duration` can be set per channel, describing the amount of time needed by a rollershutter to get from open to closed state (or vice-versa). + +Binding uses this information to interpolate rollershutter’s position. On startup binding will assume completely open rollershutters but opening/closing a rollershutter once should bring it back in sync. + +After `duration` seconds elapsed, binding will set module's output back to neutral (OFF) state after additional number of seconds, as specified by the `delay` parameter. If not specified, it defaults to 5 seconds. + +Example: + +`duration = 30s` + +binding will automatically switch Nikobus rollershutter module’s output to OFF after + +`30s + 5s = 35s` + +**Note:** Please ensure all Nikobus Push Buttons manipulating rollershutters have `impactedModules` set so binding is notified about changes. + ### Buttons Once an openHAB item has been configured as a Nikobus button, it will receive a status update to ON when the physical button is pressed. diff --git a/bundles/org.openhab.binding.nikobus/src/main/java/org/openhab/binding/nikobus/internal/handler/NikobusPushButtonHandler.java b/bundles/org.openhab.binding.nikobus/src/main/java/org/openhab/binding/nikobus/internal/handler/NikobusPushButtonHandler.java index ef8e6653b3..27322eac03 100644 --- a/bundles/org.openhab.binding.nikobus/src/main/java/org/openhab/binding/nikobus/internal/handler/NikobusPushButtonHandler.java +++ b/bundles/org.openhab.binding.nikobus/src/main/java/org/openhab/binding/nikobus/internal/handler/NikobusPushButtonHandler.java @@ -105,16 +105,11 @@ public class NikobusPushButtonHandler extends NikobusBaseThingHandler { logger.debug("Impacted modules for {} = {}", thing.getUID(), impactedModules); } - try { - for (Channel channel : thing.getChannels()) { - TriggerProcessor processor = createTriggerProcessor(channel); - if (processor != null) { - triggerProcessors.add(processor); - } + for (Channel channel : thing.getChannels()) { + TriggerProcessor processor = createTriggerProcessor(channel); + if (processor != null) { + triggerProcessors.add(processor); } - } catch (RuntimeException e) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage()); - return; } logger.debug("Trigger channels for {} = {}", thing.getUID(), triggerProcessors); 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 9d749f68e5..60f70403a0 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 @@ -12,13 +12,26 @@ */ package org.openhab.binding.nikobus.internal.handler; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + 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.OnOffType; +import org.openhab.core.library.types.PercentType; import org.openhab.core.library.types.StopMoveType; import org.openhab.core.library.types.UpDownType; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; import org.openhab.core.types.Command; import org.openhab.core.types.State; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * The {@link NikobusRollershutterModuleHandler} is responsible for communication between Nikobus @@ -28,10 +41,33 @@ import org.openhab.core.types.State; */ @NonNullByDefault public class NikobusRollershutterModuleHandler extends NikobusModuleHandler { + private final Logger logger = LoggerFactory.getLogger(NikobusRollershutterModuleHandler.class); + private final List positionEstimators = new CopyOnWriteArrayList<>(); + public NikobusRollershutterModuleHandler(Thing thing) { super(thing); } + @Override + public void initialize() { + super.initialize(); + + if (thing.getStatus() == ThingStatus.OFFLINE) { + return; + } + + positionEstimators.clear(); + + for (Channel channel : thing.getChannels()) { + PositionEstimatorConfig config = channel.getConfiguration().as(PositionEstimatorConfig.class); + if (config.delay >= 0 && config.duration > 0) { + positionEstimators.add(new PositionEstimator(channel.getUID(), config)); + } + } + + logger.debug("Position estimators for {} = {}", thing.getUID(), positionEstimators); + } + @Override protected int valueFromCommand(Command command) { if (command == UpDownType.DOWN || command == StopMoveType.MOVE) { @@ -60,4 +96,111 @@ public class NikobusRollershutterModuleHandler extends NikobusModuleHandler { } throw new IllegalArgumentException("Unexpected value " + value + " received"); } + + @Override + 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)); + } + + private void updateState(ChannelUID channelUID, int percent) { + super.updateState(channelUID, new PercentType(percent)); + } + + public static class PositionEstimatorConfig { + public int duration = -1; + public int delay = 5; + } + + private class PositionEstimator { + private static final int updateIntervalInSec = 1; + private final ChannelUID channelUID; + private final int durationInMillis; + private final int delayInMillis; + private int position = 0; + private int turnOffMillis = 0; + private long startTimeMillis = 0; + private int direction = 0; + private @Nullable Future updateEstimateFuture; + + PositionEstimator(ChannelUID channelUID, PositionEstimatorConfig config) { + this.channelUID = channelUID; + + // Configuration is in seconds, but we operate with ms. + durationInMillis = config.duration * 1000; + delayInMillis = config.delay * 1000; + } + + public ChannelUID getChannelUID() { + return channelUID; + } + + public void start(int direction) { + stop(); + synchronized (this) { + this.direction = direction; + turnOffMillis = delayInMillis + durationInMillis; + startTimeMillis = System.currentTimeMillis(); + } + updateEstimateFuture = scheduler.scheduleWithFixedDelay(() -> { + updateEstimate(); + if (turnOffMillis <= 0) { + handleCommand(channelUID, StopMoveType.STOP); + } + }, updateIntervalInSec, updateIntervalInSec, TimeUnit.SECONDS); + } + + public void stop() { + Utils.cancel(updateEstimateFuture); + updateEstimate(); + synchronized (this) { + this.direction = 0; + startTimeMillis = 0; + } + } + + private void updateEstimate() { + int direction; + int ellapsedMillis; + + synchronized (this) { + direction = this.direction; + if (startTimeMillis == 0) { + ellapsedMillis = 0; + } else { + long currentTimeMillis = System.currentTimeMillis(); + ellapsedMillis = (int) (currentTimeMillis - startTimeMillis); + startTimeMillis = currentTimeMillis; + } + } + + turnOffMillis -= ellapsedMillis; + position = Math.min(durationInMillis, Math.max(0, ellapsedMillis * direction + position)); + int percent = (int) ((double) position / (double) durationInMillis * 100.0 + 0.5); + + logger.debug( + "Update estimate for '{}': position = {}, percent = {}, elapsed = {}ms, duration = {}ms, delay = {}ms, turnOff = {}ms", + channelUID, position, percent, ellapsedMillis, durationInMillis, delayInMillis, turnOffMillis); + + updateState(channelUID, percent); + } + + @Override + public String toString() { + return "PositionEstimator('" + channelUID + "', duration = " + durationInMillis + "ms, delay = " + + delayInMillis + "ms)"; + } + } } diff --git a/bundles/org.openhab.binding.nikobus/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.nikobus/src/main/resources/OH-INF/config/config.xml index 7418e61c50..c7353d3e59 100644 --- a/bundles/org.openhab.binding.nikobus/src/main/resources/OH-INF/config/config.xml +++ b/bundles/org.openhab.binding.nikobus/src/main/resources/OH-INF/config/config.xml @@ -35,4 +35,15 @@ + + + + Duration in seconds required for a rollershutter to get from open to closed + + + + Delay specifying how many seconds after duration module's output is set to OFF. Defaults to 5 seconds + + + diff --git a/bundles/org.openhab.binding.nikobus/src/main/resources/OH-INF/thing/rollershutter-module.xml b/bundles/org.openhab.binding.nikobus/src/main/resources/OH-INF/thing/rollershutter-module.xml index 258f5cd55d..ee6ec5b652 100644 --- a/bundles/org.openhab.binding.nikobus/src/main/resources/OH-INF/thing/rollershutter-module.xml +++ b/bundles/org.openhab.binding.nikobus/src/main/resources/OH-INF/thing/rollershutter-module.xml @@ -40,6 +40,7 @@ Rollershutter Rollershutter Module's Output +