[nikobus] rollershutter position estimator added (#9294)
* Implemented rollershutter position estimator. * Removed unneeded try-catch. Signed-off-by: Boris Krivonog <boris.krivonog@inova.si>
This commit is contained in:
@@ -131,6 +131,24 @@ Defines a `rollershutter-module` with address `4C6C`.
|
|||||||
| output-5 | Rollershutter | Output 5 |
|
| output-5 | Rollershutter | Output 5 |
|
||||||
| output-6 | Rollershutter | Output 6 |
|
| 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
|
### 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.
|
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.
|
||||||
|
|||||||
@@ -105,17 +105,12 @@ public class NikobusPushButtonHandler extends NikobusBaseThingHandler {
|
|||||||
logger.debug("Impacted modules for {} = {}", thing.getUID(), impactedModules);
|
logger.debug("Impacted modules for {} = {}", thing.getUID(), impactedModules);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
for (Channel channel : thing.getChannels()) {
|
for (Channel channel : thing.getChannels()) {
|
||||||
TriggerProcessor processor = createTriggerProcessor(channel);
|
TriggerProcessor processor = createTriggerProcessor(channel);
|
||||||
if (processor != null) {
|
if (processor != null) {
|
||||||
triggerProcessors.add(processor);
|
triggerProcessors.add(processor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (RuntimeException e) {
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug("Trigger channels for {} = {}", thing.getUID(), triggerProcessors);
|
logger.debug("Trigger channels for {} = {}", thing.getUID(), triggerProcessors);
|
||||||
|
|
||||||
|
|||||||
@@ -12,13 +12,26 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.nikobus.internal.handler;
|
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.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.OnOffType;
|
||||||
|
import org.openhab.core.library.types.PercentType;
|
||||||
import org.openhab.core.library.types.StopMoveType;
|
import org.openhab.core.library.types.StopMoveType;
|
||||||
import org.openhab.core.library.types.UpDownType;
|
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.Thing;
|
||||||
|
import org.openhab.core.thing.ThingStatus;
|
||||||
import org.openhab.core.types.Command;
|
import org.openhab.core.types.Command;
|
||||||
import org.openhab.core.types.State;
|
import org.openhab.core.types.State;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link NikobusRollershutterModuleHandler} is responsible for communication between Nikobus
|
* The {@link NikobusRollershutterModuleHandler} is responsible for communication between Nikobus
|
||||||
@@ -28,10 +41,33 @@ import org.openhab.core.types.State;
|
|||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class NikobusRollershutterModuleHandler extends NikobusModuleHandler {
|
public class NikobusRollershutterModuleHandler extends NikobusModuleHandler {
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(NikobusRollershutterModuleHandler.class);
|
||||||
|
private final List<PositionEstimator> positionEstimators = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
public NikobusRollershutterModuleHandler(Thing thing) {
|
public NikobusRollershutterModuleHandler(Thing thing) {
|
||||||
super(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
|
@Override
|
||||||
protected int valueFromCommand(Command command) {
|
protected int valueFromCommand(Command command) {
|
||||||
if (command == UpDownType.DOWN || command == StopMoveType.MOVE) {
|
if (command == UpDownType.DOWN || command == StopMoveType.MOVE) {
|
||||||
@@ -60,4 +96,111 @@ public class NikobusRollershutterModuleHandler extends NikobusModuleHandler {
|
|||||||
}
|
}
|
||||||
throw new IllegalArgumentException("Unexpected value " + value + " received");
|
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)";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,4 +35,15 @@
|
|||||||
</parameter>
|
</parameter>
|
||||||
</config-description>
|
</config-description>
|
||||||
|
|
||||||
|
<config-description uri="rollershutter-module:rollershutter-output:config">
|
||||||
|
<parameter name="duration" type="integer" min="1" unit="s">
|
||||||
|
<label>Duration</label>
|
||||||
|
<description>Duration in seconds required for a rollershutter to get from open to closed</description>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="delay" type="integer" min="0" unit="s">
|
||||||
|
<label>Delay</label>
|
||||||
|
<description>Delay specifying how many seconds after duration module's output is set to OFF. Defaults to 5 seconds</description>
|
||||||
|
</parameter>
|
||||||
|
</config-description>
|
||||||
|
|
||||||
</config-description:config-descriptions>
|
</config-description:config-descriptions>
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
<item-type>Rollershutter</item-type>
|
<item-type>Rollershutter</item-type>
|
||||||
<label>Output</label>
|
<label>Output</label>
|
||||||
<description>Rollershutter Module's Output</description>
|
<description>Rollershutter Module's Output</description>
|
||||||
|
<config-description-ref uri="rollershutter-module:rollershutter-output:config"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
</thing:thing-descriptions>
|
</thing:thing-descriptions>
|
||||||
|
|||||||
Reference in New Issue
Block a user