Available values:
-- `SHORT_PRESS`: triggered on a short key press
-- `LONG_PRESS`: triggered on a key press longer than `LONG_PRESS_TIME` (variable configuration per key, default is 0.4 s)
-- `DOUBLE_PRESS`: triggered on a short key press but only if the latest `SHORT_PRESS` or `DOUBLE_PRESS` event is not older than 2.0 s (not related to `DBL_PRESS_TIME` configuration, which is more like a key lock because if it is other than `0.0` single presses are not notified anymore)
+- `SHORT_PRESSED`: triggered on a short key press
+- `LONG_PRESSED`: triggered on a key press longer than `LONG_PRESS_TIME` (variable configuration per key, default is 0.4 s)
+- `LONG_REPEATED`: triggered on long key press repetition, that is, in `LONG_PRESS_TIME` intervals as long as key is held
+- `LONG_RELEASED`: triggered when a key is released after being long pressed
**Example:** to capture a short key press on the 19 button remote control in a rule
```javascript
rule "example trigger rule"
when
- Channel 'homematic:HM-RC-19-B:ccu:KEQ0012345:1#BUTTON' triggered SHORT_PRESS
+ Channel 'homematic:HM-RC-19-B:ccu:KEQ0012345:1#BUTTON' triggered SHORT_PRESSED
then
...
end
public class ButtonVirtualDatapointHandler extends AbstractVirtualDatapointHandler {
private final Logger logger = LoggerFactory.getLogger(ButtonVirtualDatapointHandler.class);
+ private static final String LONG_REPEATED_EVENT = "LONG_REPEATED";
+ private static final String LONG_RELEASED_EVENT = "LONG_RELEASED";
+
@Override
public String getName() {
return VIRTUAL_DATAPOINT_NAME_BUTTON;
HmDatapoint dp = addDatapoint(device, channel.getNumber(), getName(), HmValueType.STRING, null, false);
dp.setTrigger(true);
dp.setOptions(new String[] { CommonTriggerEvents.SHORT_PRESSED, CommonTriggerEvents.LONG_PRESSED,
- CommonTriggerEvents.DOUBLE_PRESSED });
+ LONG_REPEATED_EVENT, LONG_RELEASED_EVENT });
}
}
}
@Override
public void handleEvent(VirtualGateway gateway, HmDatapoint dp) {
- HmDatapoint vdp = getVirtualDatapoint(dp.getChannel());
+ HmChannel channel = dp.getChannel();
+ HmDatapoint vdp = getVirtualDatapoint(channel);
+ int usPos = dp.getName().indexOf("_");
+ String pressType = usPos == -1 ? dp.getName() : dp.getName().substring(usPos + 1);
+ boolean isLongPressActive = CommonTriggerEvents.LONG_PRESSED.equals(vdp.getValue())
+ || LONG_REPEATED_EVENT.equals(vdp.getValue());
if (MiscUtils.isTrueValue(dp.getValue())) {
- int usPos = dp.getName().indexOf("_");
- String pressType = usPos == -1 ? dp.getName() : dp.getName().substring(usPos + 1);
switch (pressType) {
- case "SHORT":
- if (dp.getValue() == null || !dp.getValue().equals(dp.getPreviousValue())) {
- vdp.setValue(CommonTriggerEvents.SHORT_PRESSED);
- } else {
- // two (or more) PRESS_SHORT events were received
- // within AbstractHomematicGateway#DEFAULT_DISABLE_DELAY seconds
- vdp.setValue(CommonTriggerEvents.DOUBLE_PRESSED);
- }
+ case "SHORT": {
+ vdp.setValue(null); // Force sending new event
+ vdp.setValue(CommonTriggerEvents.SHORT_PRESSED);
break;
+ }
case "LONG":
- vdp.setValue(CommonTriggerEvents.LONG_PRESSED);
+ if (LONG_REPEATED_EVENT.equals(vdp.getValue())) {
+ // Suppress long press events during an ongoing long press
+ vdp.setValue(LONG_REPEATED_EVENT);
+ } else {
+ vdp.setValue(CommonTriggerEvents.LONG_PRESSED);
+ }
break;
case "LONG_RELEASE":
+ // Only send release events if we sent a pressed event before
+ vdp.setValue(isLongPressActive ? LONG_RELEASED_EVENT : null);
+ break;
case "CONT":
+ // Clear previous value to force re-triggering of repetition
vdp.setValue(null);
+ // Only send repetitions if there was a pressed event before
+ // (a CONT might arrive simultaneously with the initial LONG event)
+ if (isLongPressActive) {
+ vdp.setValue(LONG_REPEATED_EVENT);
+ }
break;
default:
vdp.setValue(null);
logger.warn("Unexpected vaule '{}' for PRESS virtual datapoint", pressType);
}
} else {
- vdp.setValue(null);
+ if ("LONG".equals(pressType) && LONG_REPEATED_EVENT.equals(vdp.getValue())) {
+ // If we're currently processing a repeated long-press event, don't let the initial LONG
+ // event time out the repetitions, the CONT delay handler will take care of it
+ vdp.setValue(LONG_REPEATED_EVENT);
+ } else if (isLongPressActive) {
+ // We seemingly missed an event (either a CONT or the final LONG_RELEASE),
+ // so end the long press cycle now
+ vdp.setValue(LONG_RELEASED_EVENT);
+ } else {
+ vdp.setValue(null);
+ }
}
+ logger.debug("Handled virtual button event on {}:{}: press type {}, value {}, button state {} -> {}",
+ channel.getDevice().getAddress(), channel.getNumber(), pressType, dp.getValue(), vdp.getPreviousValue(),
+ vdp.getValue());
}
}
private void updateChannelState(final HmDatapoint dp, Channel channel)
throws IOException, GatewayNotAvailableException, ConverterException {
if (dp.isTrigger()) {
- if (dp.getValue() != null) {
- triggerChannel(channel.getUID(), dp.getValue() == null ? "" : dp.getValue().toString());
+ final Object value = dp.getValue();
+ if (value != null && !value.equals(dp.getPreviousValue())) {
+ triggerChannel(channel.getUID(), value.toString());
}
} else if (isLinked(channel)) {
loadHomematicChannelValues(dp.getChannel());
import java.io.IOException;
import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.openhab.binding.homematic.internal.misc.HomematicClientException;
import org.openhab.binding.homematic.internal.misc.HomematicConstants;
HmDatapoint buttonVirtualDatapoint = getButtonVirtualDatapoint(longPressDp);
mockEventReceiver.eventReceived(longPressDp);
-
assertThat(buttonVirtualDatapoint.getValue(), is(CommonTriggerEvents.LONG_PRESSED));
+
+ HmDatapoint contPressDp = createPressDatapointFrom(longPressDp, "PRESS_CONT", Boolean.TRUE);
+ mockEventReceiver.eventReceived(contPressDp);
+ assertThat(buttonVirtualDatapoint.getValue(), is("LONG_REPEATED"));
+
+ HmDatapoint releaseDp = createPressDatapointFrom(longPressDp, "PRESS_LONG_RELEASE", Boolean.TRUE);
+ mockEventReceiver.eventReceived(releaseDp);
+ assertThat(buttonVirtualDatapoint.getValue(), is("LONG_RELEASED"));
}
@Test
mockEventReceiver.eventReceived(releaseDp);
HmDatapoint crapDp = createPressDatapoint("CRAP", Boolean.TRUE);
- HmDatapoint crapButtonVirtualDatapoint = getButtonVirtualDatapoint(releaseDp);
+ HmDatapoint crapButtonVirtualDatapoint = getButtonVirtualDatapoint(crapDp);
mockEventReceiver.eventReceived(crapDp);
+ // CONT and LONG_RELEASE events without previous LONG event are supposed to yield no trigger
assertThat(contButtonVirtualDatapoint.getValue(), nullValue());
assertThat(releaseButtonVirtualDatapoint.getValue(), nullValue());
- assertThat(crapButtonVirtualDatapoint.getValue(), nullValue());
- }
-
- @Test
- @Disabled(value = "Test is unstable see #10753")
- public void testDoublePress() throws IOException, HomematicClientException, InterruptedException {
- HmDatapoint shortPressDp = createPressDatapoint("PRESS_SHORT", Boolean.TRUE);
- HmDatapoint buttonVirtualDatapoint = getButtonVirtualDatapoint(shortPressDp);
-
- mockEventReceiver.eventReceived(shortPressDp);
- assertThat(buttonVirtualDatapoint.getValue(), is(CommonTriggerEvents.SHORT_PRESSED));
-
- Thread.sleep(DISABLE_DATAPOINT_DELAY / 2);
-
- shortPressDp.setValue(Boolean.TRUE);
- mockEventReceiver.eventReceived(shortPressDp);
- assertThat(buttonVirtualDatapoint.getValue(), is(CommonTriggerEvents.DOUBLE_PRESSED));
-
- Thread.sleep(DISABLE_DATAPOINT_DELAY * 2);
-
- shortPressDp.setValue(Boolean.TRUE);
- mockEventReceiver.eventReceived(shortPressDp);
- assertThat(buttonVirtualDatapoint.getValue(), is(CommonTriggerEvents.SHORT_PRESSED));
+ assertThat(crapButtonVirtualDatapoint, nullValue());
}
private HmDatapoint createPressDatapoint(String channelName, Object value) {
return pressDp;
}
+ private HmDatapoint createPressDatapointFrom(HmDatapoint originalDatapoint, String channelName, Object value) {
+ HmDatapoint pressDp = new HmDatapoint(channelName, "", HmValueType.ACTION, value, true, HmParamsetType.VALUES);
+ HmChannel hmChannel = originalDatapoint.getChannel();
+ hmChannel.addDatapoint(pressDp);
+ pressDp.setChannel(hmChannel);
+
+ return pressDp;
+ }
+
private HmDatapoint getButtonVirtualDatapoint(HmDatapoint originalDatapoint) {
return originalDatapoint.getChannel().getDatapoints().stream()
.filter(dp -> HomematicConstants.VIRTUAL_DATAPOINT_NAME_BUTTON.equals(dp.getName())).findFirst()