2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.nikobus.internal.handler;
15 import java.util.List;
16 import java.util.concurrent.CopyOnWriteArrayList;
17 import java.util.concurrent.Future;
18 import java.util.concurrent.TimeUnit;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.binding.nikobus.internal.utils.Utils;
23 import org.openhab.core.library.types.OnOffType;
24 import org.openhab.core.library.types.PercentType;
25 import org.openhab.core.library.types.StopMoveType;
26 import org.openhab.core.library.types.UpDownType;
27 import org.openhab.core.thing.Channel;
28 import org.openhab.core.thing.ChannelUID;
29 import org.openhab.core.thing.Thing;
30 import org.openhab.core.thing.ThingStatus;
31 import org.openhab.core.types.Command;
32 import org.openhab.core.types.State;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
37 * The {@link NikobusRollershutterModuleHandler} is responsible for communication between Nikobus
38 * rollershutter-controller and binding.
40 * @author Boris Krivonog - Initial contribution
43 public class NikobusRollershutterModuleHandler extends NikobusModuleHandler {
44 private final Logger logger = LoggerFactory.getLogger(NikobusRollershutterModuleHandler.class);
45 private final List<PositionEstimator> positionEstimators = new CopyOnWriteArrayList<>();
47 public NikobusRollershutterModuleHandler(Thing thing) {
52 public void initialize() {
55 if (thing.getStatus() == ThingStatus.OFFLINE) {
59 positionEstimators.clear();
61 for (Channel channel : thing.getChannels()) {
62 PositionEstimatorConfig config = channel.getConfiguration().as(PositionEstimatorConfig.class);
63 if (config.delay >= 0 && config.duration > 0) {
64 positionEstimators.add(new PositionEstimator(channel.getUID(), config));
68 logger.debug("Position estimators for {} = {}", thing.getUID(), positionEstimators);
72 protected int valueFromCommand(Command command) {
73 if (command == UpDownType.DOWN || command == StopMoveType.MOVE) {
76 if (command == UpDownType.UP) {
79 if (command == StopMoveType.STOP) {
83 throw new IllegalArgumentException("Command '" + command + "' not supported");
87 protected State stateFromValue(int value) {
95 return UpDownType.DOWN;
97 throw new IllegalArgumentException("Unexpected value " + value + " received");
101 protected void updateState(ChannelUID channelUID, State state) {
102 logger.debug("updateState {} {}", channelUID, state);
104 positionEstimators.stream().filter(estimator -> channelUID.equals(estimator.getChannelUID())).findFirst()
105 .ifPresentOrElse(estimator -> {
106 if (state == UpDownType.UP) {
108 } else if (state == UpDownType.DOWN) {
110 } else if (state == OnOffType.OFF) {
113 logger.debug("Unexpected state update '{}' for '{}'", state, channelUID);
115 }, () -> super.updateState(channelUID, state));
118 private void updateState(ChannelUID channelUID, int percent) {
119 super.updateState(channelUID, new PercentType(percent));
122 public static class PositionEstimatorConfig {
123 public int duration = -1;
124 public int delay = 5;
127 private class PositionEstimator {
128 private static final int updateIntervalInSec = 1;
129 private final ChannelUID channelUID;
130 private final int durationInMillis;
131 private final int delayInMillis;
132 private int position = 0;
133 private int turnOffMillis = 0;
134 private long startTimeMillis = 0;
135 private int direction = 0;
136 private @Nullable Future<?> updateEstimateFuture;
138 PositionEstimator(ChannelUID channelUID, PositionEstimatorConfig config) {
139 this.channelUID = channelUID;
141 // Configuration is in seconds, but we operate with ms.
142 durationInMillis = config.duration * 1000;
143 delayInMillis = config.delay * 1000;
146 public ChannelUID getChannelUID() {
150 public void start(int direction) {
152 synchronized (this) {
153 this.direction = direction;
154 turnOffMillis = delayInMillis + durationInMillis;
155 startTimeMillis = System.currentTimeMillis();
157 updateEstimateFuture = scheduler.scheduleWithFixedDelay(() -> {
159 if (turnOffMillis <= 0) {
160 handleCommand(channelUID, StopMoveType.STOP);
162 }, updateIntervalInSec, updateIntervalInSec, TimeUnit.SECONDS);
166 Utils.cancel(updateEstimateFuture);
168 synchronized (this) {
174 private void updateEstimate() {
178 synchronized (this) {
179 direction = this.direction;
180 if (startTimeMillis == 0) {
183 long currentTimeMillis = System.currentTimeMillis();
184 ellapsedMillis = (int) (currentTimeMillis - startTimeMillis);
185 startTimeMillis = currentTimeMillis;
189 turnOffMillis -= ellapsedMillis;
190 position = Math.min(durationInMillis, Math.max(0, ellapsedMillis * direction + position));
191 int percent = (int) ((double) position / (double) durationInMillis * 100.0 + 0.5);
194 "Update estimate for '{}': position = {}, percent = {}, elapsed = {}ms, duration = {}ms, delay = {}ms, turnOff = {}ms",
195 channelUID, position, percent, ellapsedMillis, durationInMillis, delayInMillis, turnOffMillis);
197 updateState(channelUID, percent);
201 public String toString() {
202 return "PositionEstimator('" + channelUID + "', duration = " + durationInMillis + "ms, delay = "
203 + delayInMillis + "ms)";