]> git.basschouten.com Git - openhab-addons.git/blob
95a986c6239af7794651670c6818d8c3f1ba5aa3
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.nikobus.internal.handler;
14
15 import java.util.List;
16 import java.util.concurrent.CopyOnWriteArrayList;
17 import java.util.concurrent.Future;
18 import java.util.concurrent.TimeUnit;
19
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;
35
36 /**
37  * The {@link NikobusRollershutterModuleHandler} is responsible for communication between Nikobus
38  * rollershutter-controller and binding.
39  *
40  * @author Boris Krivonog - Initial contribution
41  */
42 @NonNullByDefault
43 public class NikobusRollershutterModuleHandler extends NikobusModuleHandler {
44     private final Logger logger = LoggerFactory.getLogger(NikobusRollershutterModuleHandler.class);
45     private final List<PositionEstimator> positionEstimators = new CopyOnWriteArrayList<>();
46
47     public NikobusRollershutterModuleHandler(Thing thing) {
48         super(thing);
49     }
50
51     @Override
52     public void initialize() {
53         super.initialize();
54
55         if (thing.getStatus() == ThingStatus.OFFLINE) {
56             return;
57         }
58
59         positionEstimators.clear();
60
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));
65             }
66         }
67
68         logger.debug("Position estimators for {} = {}", thing.getUID(), positionEstimators);
69     }
70
71     @Override
72     protected int valueFromCommand(Command command) {
73         if (command == UpDownType.DOWN || command == StopMoveType.MOVE) {
74             return 0x02;
75         }
76         if (command == UpDownType.UP) {
77             return 0x01;
78         }
79         if (command == StopMoveType.STOP) {
80             return 0x00;
81         }
82
83         throw new IllegalArgumentException("Command '" + command + "' not supported");
84     }
85
86     @Override
87     protected State stateFromValue(int value) {
88         if (value == 0x00) {
89             return OnOffType.OFF;
90         }
91         if (value == 0x01) {
92             return UpDownType.UP;
93         }
94         if (value == 0x02) {
95             return UpDownType.DOWN;
96         }
97         throw new IllegalArgumentException("Unexpected value " + value + " received");
98     }
99
100     @Override
101     protected void updateState(ChannelUID channelUID, State state) {
102         logger.debug("updateState {} {}", channelUID, state);
103
104         positionEstimators.stream().filter(estimator -> channelUID.equals(estimator.getChannelUID())).findFirst()
105                 .ifPresentOrElse(estimator -> {
106                     if (state == UpDownType.UP) {
107                         estimator.start(-1);
108                     } else if (state == UpDownType.DOWN) {
109                         estimator.start(1);
110                     } else if (state == OnOffType.OFF) {
111                         estimator.stop();
112                     } else {
113                         logger.debug("Unexpected state update '{}' for '{}'", state, channelUID);
114                     }
115                 }, () -> super.updateState(channelUID, state));
116     }
117
118     private void updateState(ChannelUID channelUID, int percent) {
119         super.updateState(channelUID, new PercentType(percent));
120     }
121
122     public static class PositionEstimatorConfig {
123         public int duration = -1;
124         public int delay = 5;
125     }
126
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;
137
138         PositionEstimator(ChannelUID channelUID, PositionEstimatorConfig config) {
139             this.channelUID = channelUID;
140
141             // Configuration is in seconds, but we operate with ms.
142             durationInMillis = config.duration * 1000;
143             delayInMillis = config.delay * 1000;
144         }
145
146         public ChannelUID getChannelUID() {
147             return channelUID;
148         }
149
150         public void start(int direction) {
151             stop();
152             synchronized (this) {
153                 this.direction = direction;
154                 turnOffMillis = delayInMillis + durationInMillis;
155                 startTimeMillis = System.currentTimeMillis();
156             }
157             updateEstimateFuture = scheduler.scheduleWithFixedDelay(() -> {
158                 updateEstimate();
159                 if (turnOffMillis <= 0) {
160                     handleCommand(channelUID, StopMoveType.STOP);
161                 }
162             }, updateIntervalInSec, updateIntervalInSec, TimeUnit.SECONDS);
163         }
164
165         public void stop() {
166             Utils.cancel(updateEstimateFuture);
167             updateEstimate();
168             synchronized (this) {
169                 this.direction = 0;
170                 startTimeMillis = 0;
171             }
172         }
173
174         private void updateEstimate() {
175             int direction;
176             int ellapsedMillis;
177
178             synchronized (this) {
179                 direction = this.direction;
180                 if (startTimeMillis == 0) {
181                     ellapsedMillis = 0;
182                 } else {
183                     long currentTimeMillis = System.currentTimeMillis();
184                     ellapsedMillis = (int) (currentTimeMillis - startTimeMillis);
185                     startTimeMillis = currentTimeMillis;
186                 }
187             }
188
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);
192
193             logger.debug(
194                     "Update estimate for '{}': position = {}, percent = {}, elapsed = {}ms, duration = {}ms, delay = {}ms, turnOff = {}ms",
195                     channelUID, position, percent, ellapsedMillis, durationInMillis, delayInMillis, turnOffMillis);
196
197             updateState(channelUID, percent);
198         }
199
200         @Override
201         public String toString() {
202             return "PositionEstimator('" + channelUID + "', duration = " + durationInMillis + "ms, delay = "
203                     + delayInMillis + "ms)";
204         }
205     }
206 }