From: Boris Krivonog Date: Mon, 7 Dec 2020 23:05:29 +0000 (+0100) Subject: [nikobus] - added new trigger channels for Nikobus push button (#9166) X-Git-Url: https://git.basschouten.com/?a=commitdiff_plain;h=25ea32e2bfe5025bf1b7010cb594ce67993a4320;p=openhab-addons.git [nikobus] - added new trigger channels for Nikobus push button (#9166) * Added new trigger channels for Nikobus push button. Signed-off-by: Boris Krivonog --- diff --git a/bundles/org.openhab.binding.nikobus/README.md b/bundles/org.openhab.binding.nikobus/README.md index c23fe401f3..c5982f629d 100644 --- a/bundles/org.openhab.binding.nikobus/README.md +++ b/bundles/org.openhab.binding.nikobus/README.md @@ -170,6 +170,29 @@ Thing push-button pb1 [ address = "28092A", impactedModules = "switch-module:s1: In addition to the status requests triggered by button presses, there is also a scheduled status update interval defined by the `refreshInterval` parameter and explained above. +#### Push Button Trigger Channels + +Beside receiving a status update (ON) when a physical Nikobus push button is pressed (and kept pressed), additional triggers can be added and configured to determine how press&hold of a physical push button should generate trigger events. Two types of trigger channels are supported: + +* filter trigger and +* button trigger. + +##### Filter Trigger + +* `command` - command to be send, +* `delay` - a required delay in milliseconds defining how much time must a button be pressed before an initial trigger event is fired, +* `period` - optional time in milliseconds between successive triggers. + +Examples: + +* `command = PRESSED, delay = 0, period = ` - triggers `PRESSED` event immediatelly when Nikobus button is pressed and is not triggered anymore while holding down the button, +* `command = INCREMENT, delay = 1000, period = 500` - triggers initial `INCREMENT` event after 1 second and then every half a second while holding down the button. + + +##### Button Trigger + +`threshold` - a required long-press threshold in miliseconds. Defines how long must a button be pressed before a long press event is triggered - pressing&holding a Nikobus push-button for `threshold` or more miliseconds will trigger long press event, otherwise a short press event will be triggered. + ## Discovery Pressing a physical Nikobus push-button will generate a new inbox entry with an exception of buttons already discovered or setup. @@ -245,6 +268,7 @@ so ``` Above example `14E7F4:3` would give: + * for 4 buttons switch - push button A, * for 8 buttons switch - push button 2A. diff --git a/bundles/org.openhab.binding.nikobus/src/main/java/org/openhab/binding/nikobus/internal/NikobusBindingConstants.java b/bundles/org.openhab.binding.nikobus/src/main/java/org/openhab/binding/nikobus/internal/NikobusBindingConstants.java index 15a38baf74..1cc7bdade8 100644 --- a/bundles/org.openhab.binding.nikobus/src/main/java/org/openhab/binding/nikobus/internal/NikobusBindingConstants.java +++ b/bundles/org.openhab.binding.nikobus/src/main/java/org/openhab/binding/nikobus/internal/NikobusBindingConstants.java @@ -37,6 +37,8 @@ public class NikobusBindingConstants { // List of all Channel ids public static final String CHANNEL_BUTTON = "button"; + public static final String CHANNEL_TRIGGER_FILTER = "trigger-filter"; + public static final String CHANNEL_TRIGGER_BUTTON = "trigger-button"; public static final String CHANNEL_OUTPUT_PREFIX = "output-"; // Configuration parameters 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 bbf7244eae..ef8e6653b3 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 @@ -15,9 +15,8 @@ package org.openhab.binding.nikobus.internal.handler; import static org.openhab.binding.nikobus.internal.NikobusBindingConstants.*; import static org.openhab.binding.nikobus.internal.protocol.SwitchModuleGroup.*; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @@ -29,13 +28,16 @@ import org.openhab.binding.nikobus.internal.utils.Utils; import org.openhab.core.common.AbstractUID; import org.openhab.core.library.types.OnOffType; import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Channel; import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.CommonTriggerEvents; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.ThingUID; import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.type.ChannelTypeUID; import org.openhab.core.types.Command; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,61 +49,10 @@ import org.slf4j.LoggerFactory; */ @NonNullByDefault public class NikobusPushButtonHandler extends NikobusBaseThingHandler { - private static class ImpactedModule { - private final ThingUID thingUID; - private final SwitchModuleGroup group; - - ImpactedModule(ThingUID thingUID, SwitchModuleGroup group) { - this.thingUID = thingUID; - this.group = group; - } - - public ThingUID getThingUID() { - return thingUID; - } - - public SwitchModuleGroup getGroup() { - return group; - } - - @Override - public String toString() { - return "'" + thingUID + "'-" + group; - } - } - - private static class ImpactedModuleUID extends AbstractUID { - ImpactedModuleUID(String uid) { - super(uid); - } - - String getThingTypeId() { - return getSegment(0); - } - - String getThingId() { - return getSegment(1); - } - - SwitchModuleGroup getGroup() { - if (getSegment(2).equals("1")) { - return FIRST; - } - if (getSegment(2).equals("2")) { - return SECOND; - } - throw new IllegalArgumentException("Unexpected group found " + getSegment(2)); - } - - @Override - protected int getMinimalNumberOfSegments() { - return 3; - } - } - private static final String END_OF_TRANSMISSION = "\r#E1"; private final Logger logger = LoggerFactory.getLogger(NikobusPushButtonHandler.class); - private final List impactedModules = Collections.synchronizedList(new ArrayList<>()); + private final List impactedModules = new CopyOnWriteArrayList<>(); + private final List triggerProcessors = new CopyOnWriteArrayList<>(); private @Nullable Future requestUpdateFuture; public NikobusPushButtonHandler(Thing thing) { @@ -117,6 +68,7 @@ public class NikobusPushButtonHandler extends NikobusBaseThingHandler { } impactedModules.clear(); + triggerProcessors.clear(); Object impactedModulesObject = getConfig().get(CONFIG_IMPACTED_MODULES); if (impactedModulesObject != null) { @@ -153,6 +105,20 @@ 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); + } + } + } catch (RuntimeException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage()); + return; + } + + logger.debug("Trigger channels for {} = {}", thing.getUID(), triggerProcessors); + NikobusPcLinkHandler pcLink = getPcLink(); if (pcLink != null) { pcLink.addListener(getAddress(), this::commandReceived); @@ -197,6 +163,11 @@ public class NikobusPushButtonHandler extends NikobusBaseThingHandler { updateState(CHANNEL_BUTTON, OnOffType.ON); + if (!triggerProcessors.isEmpty()) { + long currentTimeMillis = System.currentTimeMillis(); + triggerProcessors.forEach(processor -> processor.process(currentTimeMillis)); + } + if (!impactedModules.isEmpty()) { Utils.cancel(requestUpdateFuture); requestUpdateFuture = scheduler.schedule(this::update, 400, TimeUnit.MILLISECONDS); @@ -234,4 +205,177 @@ public class NikobusPushButtonHandler extends NikobusBaseThingHandler { protected String getAddress() { return "#N" + super.getAddress(); } + + private @Nullable TriggerProcessor createTriggerProcessor(Channel channel) { + ChannelTypeUID channelTypeUID = channel.getChannelTypeUID(); + if (channelTypeUID != null) { + switch (channelTypeUID.getId()) { + case CHANNEL_TRIGGER_FILTER: + return new TriggerFilter(channel); + case CHANNEL_TRIGGER_BUTTON: + return new TriggerButton(channel); + } + } + return null; + } + + private static class ImpactedModule { + private final ThingUID thingUID; + private final SwitchModuleGroup group; + + ImpactedModule(ThingUID thingUID, SwitchModuleGroup group) { + this.thingUID = thingUID; + this.group = group; + } + + public ThingUID getThingUID() { + return thingUID; + } + + public SwitchModuleGroup getGroup() { + return group; + } + + @Override + public String toString() { + return "'" + thingUID + "'-" + group; + } + } + + private static class ImpactedModuleUID extends AbstractUID { + ImpactedModuleUID(String uid) { + super(uid); + } + + String getThingTypeId() { + return getSegment(0); + } + + String getThingId() { + return getSegment(1); + } + + SwitchModuleGroup getGroup() { + if (getSegment(2).equals("1")) { + return FIRST; + } + if (getSegment(2).equals("2")) { + return SECOND; + } + throw new IllegalArgumentException("Unexpected group found " + getSegment(2)); + } + + @Override + protected int getMinimalNumberOfSegments() { + return 3; + } + } + + private interface TriggerProcessor { + void process(long currentTimeMillis); + } + + private abstract class AbstractTriggerProcessor implements TriggerProcessor { + private long lastCommandReceivedTimestamp = 0; + protected final ChannelUID channelUID; + protected final Config config; + + // Nikobus push button will send a new message on bus every ~50ms so + // lets assume if we haven't received a new message in over 150ms that + // button was released and pressed again. + protected static final long BUTTON_RELEASED_MILIS = 150; + + protected AbstractTriggerProcessor(Class configType, Channel channel) { + this.channelUID = channel.getUID(); + this.config = channel.getConfiguration().as(configType); + } + + @Override + public void process(long currentTimeMillis) { + if (Math.abs(currentTimeMillis - lastCommandReceivedTimestamp) > BUTTON_RELEASED_MILIS) { + reset(currentTimeMillis); + } + lastCommandReceivedTimestamp = currentTimeMillis; + processNext(currentTimeMillis); + } + + abstract protected void reset(long currentTimeMillis); + + abstract protected void processNext(long currentTimeMillis); + } + + public static class TriggerButtonConfig { + public int threshold = 1000; + } + + private class TriggerButton extends AbstractTriggerProcessor { + private long nextLongPressTimestamp = 0; + private @Nullable Future triggerShortPressFuture; + + TriggerButton(Channel channel) { + super(TriggerButtonConfig.class, channel); + } + + @Override + protected void reset(long currentTimeMillis) { + nextLongPressTimestamp = currentTimeMillis + config.threshold; + } + + @Override + protected void processNext(long currentTimeMillis) { + if (currentTimeMillis < nextLongPressTimestamp) { + Utils.cancel(triggerShortPressFuture); + triggerShortPressFuture = scheduler.schedule( + () -> triggerChannel(channelUID, CommonTriggerEvents.SHORT_PRESSED), BUTTON_RELEASED_MILIS, + TimeUnit.MILLISECONDS); + } else if (nextLongPressTimestamp != 0) { + Utils.cancel(triggerShortPressFuture); + nextLongPressTimestamp = 0; + triggerChannel(channelUID, CommonTriggerEvents.LONG_PRESSED); + } + } + + @Override + public String toString() { + return "TriggerButton '" + channelUID + "', config: threshold = " + config.threshold; + } + } + + public static class TriggerFilterConfig { + public @Nullable String command; + public int delay = 0; + public int period = -1; + } + + private class TriggerFilter extends AbstractTriggerProcessor { + private long nextTriggerTimestamp = 0; + + TriggerFilter(Channel channel) { + super(TriggerFilterConfig.class, channel); + } + + @Override + protected void reset(long currentTimeMillis) { + nextTriggerTimestamp = currentTimeMillis + config.delay; + } + + @Override + protected void processNext(long currentTimeMillis) { + if (currentTimeMillis >= nextTriggerTimestamp) { + nextTriggerTimestamp = (config.period < 0) ? Long.MAX_VALUE : currentTimeMillis + config.period; + String command = config.command; + if (command != null) { + triggerChannel(channelUID, command); + } else { + triggerChannel(channelUID); + } + } + } + + @Override + public String toString() { + return "TriggerFilter '" + channelUID + "', config: command = '" + config.command + "', delay = " + + config.delay + ", period = " + config.period; + } + } } 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 new file mode 100644 index 0000000000..7418e61c50 --- /dev/null +++ b/bundles/org.openhab.binding.nikobus/src/main/resources/OH-INF/config/config.xml @@ -0,0 +1,38 @@ + + + + + + + Command to send + + + + Delay in milliseconds before triggered + 0 + + + + Time in milliseconds between successive triggers + + + + + + + Long-press threshold in milliseconds + 1000 + + + + + + + The Nikobus address of the module + + + + diff --git a/bundles/org.openhab.binding.nikobus/src/main/resources/OH-INF/thing/dimmer-module.xml b/bundles/org.openhab.binding.nikobus/src/main/resources/OH-INF/thing/dimmer-module.xml new file mode 100644 index 0000000000..86f7e00447 --- /dev/null +++ b/bundles/org.openhab.binding.nikobus/src/main/resources/OH-INF/thing/dimmer-module.xml @@ -0,0 +1,63 @@ + + + + + + + + + + Nikobus Dimmer module + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Dimmer + + Dimmer Module's Output + + + diff --git a/bundles/org.openhab.binding.nikobus/src/main/resources/OH-INF/thing/pc-link.xml b/bundles/org.openhab.binding.nikobus/src/main/resources/OH-INF/thing/pc-link.xml new file mode 100644 index 0000000000..3825d1590c --- /dev/null +++ b/bundles/org.openhab.binding.nikobus/src/main/resources/OH-INF/thing/pc-link.xml @@ -0,0 +1,26 @@ + + + + + + PC-Link via serial connection + + + + + serial-port + false + The serial port used to connect to the Nikobus PC Link. + + + 60 + + Refresh interval in seconds. + + + + + diff --git a/bundles/org.openhab.binding.nikobus/src/main/resources/OH-INF/thing/push-button.xml b/bundles/org.openhab.binding.nikobus/src/main/resources/OH-INF/thing/push-button.xml new file mode 100644 index 0000000000..f272ea7fe0 --- /dev/null +++ b/bundles/org.openhab.binding.nikobus/src/main/resources/OH-INF/thing/push-button.xml @@ -0,0 +1,56 @@ + + + + + + + + + + A single push button + + + + + + + + + The Nikobus address of the push-button. + + + + Comma separated list of impacted modules, i.e. switch-module:s1:1 + + + + + + Switch + + Fires when the button is pressed + + + + trigger + + + + + + + trigger + + + + + + + + + + + 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 new file mode 100644 index 0000000000..258f5cd55d --- /dev/null +++ b/bundles/org.openhab.binding.nikobus/src/main/resources/OH-INF/thing/rollershutter-module.xml @@ -0,0 +1,45 @@ + + + + + + + + + + Nikobus Rollershutter module + + + + + + + + + + + + + + + + + + + + + + + + + + + Rollershutter + + Rollershutter Module's Output + + + diff --git a/bundles/org.openhab.binding.nikobus/src/main/resources/OH-INF/thing/switch-module.xml b/bundles/org.openhab.binding.nikobus/src/main/resources/OH-INF/thing/switch-module.xml new file mode 100644 index 0000000000..419ce16b9c --- /dev/null +++ b/bundles/org.openhab.binding.nikobus/src/main/resources/OH-INF/thing/switch-module.xml @@ -0,0 +1,63 @@ + + + + + + + + + + Nikobus Switch module + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Switch + + Switch Module's Output + + + diff --git a/bundles/org.openhab.binding.nikobus/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.nikobus/src/main/resources/OH-INF/thing/thing-types.xml deleted file mode 100644 index 8c3d4ed5cb..0000000000 --- a/bundles/org.openhab.binding.nikobus/src/main/resources/OH-INF/thing/thing-types.xml +++ /dev/null @@ -1,228 +0,0 @@ - - - - - - PC-Link via serial connection - - - - - serial-port - false - The serial port used to connect to the Nikobus PC Link. - - - 60 - - Refresh interval in seconds. - - - - - - - - - - - A single push button - - - - - - - - - The Nikobus address of the module - - - - Comma separated list of impacted modules, i.e. switch-module:s1:1 - - - - - - Switch - - Fires when the button is pressed - - - - - The Nikobus address of the module - - - - - - - - - - - Nikobus Switch module - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The Nikobus address of the module - - - - - - - - - - - Nikobus Dimmer module - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The Nikobus address of the module - - - - - - - - - - - Nikobus Rollershutter module - - - - - - - - - - - - - - - - - - - - - - - - - - The Nikobus address of the module - - - - - - Switch - - Switch Module's Output - - - - Dimmer - - Dimmer Module's Output - - - - Rollershutter - - Rollershutter Module's Output - - -