diff --git a/org.openhab.binding.zigbee/ESH-INF/thing/xiaomi/lumiremoteb286acn01.xml b/org.openhab.binding.zigbee/ESH-INF/thing/xiaomi/lumiremoteb286acn01.xml new file mode 100644 index 000000000..3ad0af807 --- /dev/null +++ b/org.openhab.binding.zigbee/ESH-INF/thing/xiaomi/lumiremoteb286acn01.xml @@ -0,0 +1,70 @@ + + + + + + Xiaomi Wall Switch + WallSwitch + + + + + + 1 + 0x0012 + 85 + 1 + 0x0012 + 85 + 0 + 0x0012 + 85 + 2 + + + + + + + 2 + 0x0012 + 85 + 1 + 0x0012 + 85 + 0 + 0x0012 + 85 + 2 + + + + + + + 1 + 0x00 + 5 + lumi.remote.b286acn01 + + + + + + Xiaomi + lumi.remote.b286acn01 + END_DEVICE + + + zigbee_macaddress + + + + + + + + diff --git a/org.openhab.binding.zigbee/src/main/java/org/openhab/binding/zigbee/internal/converter/ZigBeeConverterGenericButton.java b/org.openhab.binding.zigbee/src/main/java/org/openhab/binding/zigbee/internal/converter/ZigBeeConverterGenericButton.java index c9bcc9e0f..3ee817005 100644 --- a/org.openhab.binding.zigbee/src/main/java/org/openhab/binding/zigbee/internal/converter/ZigBeeConverterGenericButton.java +++ b/org.openhab.binding.zigbee/src/main/java/org/openhab/binding/zigbee/internal/converter/ZigBeeConverterGenericButton.java @@ -9,18 +9,17 @@ package org.openhab.binding.zigbee.internal.converter; import static java.lang.Integer.*; -import static java.lang.String.format; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Objects; -import java.util.Set; import java.util.concurrent.ExecutionException; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import com.zsmartsystems.zigbee.zcl.*; import org.eclipse.smarthome.core.thing.Channel; import org.eclipse.smarthome.core.thing.CommonTriggerEvents; import org.eclipse.smarthome.core.thing.ThingUID; @@ -30,9 +29,6 @@ import com.zsmartsystems.zigbee.CommandResult; import com.zsmartsystems.zigbee.ZigBeeEndpoint; -import com.zsmartsystems.zigbee.zcl.ZclCluster; -import com.zsmartsystems.zigbee.zcl.ZclCommand; -import com.zsmartsystems.zigbee.zcl.ZclCommandListener; /** * Generic converter for buttons (e.g., from remote controls). @@ -43,8 +39,9 @@ * As the configuration is done via channel properties, this converter is usable via static thing types only. * * @author Henning Sudbrock - initial contribution + * @author Thomas Weißschuh - support for attribute-based buttons */ -public class ZigBeeConverterGenericButton extends ZigBeeBaseChannelConverter implements ZclCommandListener { +public class ZigBeeConverterGenericButton extends ZigBeeBaseChannelConverter implements ZclCommandListener, ZclAttributeListener { private Logger logger = LoggerFactory.getLogger(this.getClass()); @@ -52,63 +49,49 @@ public class ZigBeeConverterGenericButton extends ZigBeeBaseChannelConverter imp private static final String COMMAND = "command_id"; private static final String PARAM_NAME = "parameter_name"; private static final String PARAM_VALUE = "parameter_value"; + private static final String ATTRIBUTE_ID = "attribute_id"; + private static final String ATTRIBUTE_VALUE = "attribute_value"; - private Map handledCommands = new HashMap<>(); + private Map handledEvents = new EnumMap<>(ButtonPressType.class); private Set clientClusters = new HashSet<>(); + private Set serverClusters = new HashSet<>(); @Override public synchronized boolean initializeConverter() { for (ButtonPressType buttonPressType : ButtonPressType.values()) { - CommandSpec commandSpec = parseCommandSpec(buttonPressType); - if (commandSpec != null) { - handledCommands.put(buttonPressType, commandSpec); + EventSpec eventSpec = parseEventSpec(channel.getProperties(), buttonPressType); + if (eventSpec != null) { + handledEvents.put(buttonPressType, eventSpec); } } - if (handledCommands.isEmpty()) { + if (handledEvents.isEmpty()) { logger.error("{}: No command is specified for any of the possible button press types in channel {}", endpoint.getIeeeAddress(), channel.getUID()); return false; } - for (CommandSpec commandSpec : handledCommands.values()) { - int clusterId = commandSpec.getClusterId(); + boolean allBindsSucceeded = true; - if (clientClusters.stream().anyMatch(cluster -> cluster.getClusterId().intValue() == clusterId)) { - // bind to each output cluster only once - continue; - } - - ZclCluster clientCluster = endpoint.getOutputCluster(clusterId); - if (clientCluster == null) { - logger.error("{}: Error opening client cluster {} on endpoint {}", endpoint.getIeeeAddress(), clusterId, - endpoint.getEndpointId()); - return false; - } - - try { - CommandResult bindResponse = bind(clientCluster).get(); - if (!bindResponse.isSuccess()) { - logger.error("{}: Error 0x{} setting client binding for cluster {}", endpoint.getIeeeAddress(), - toHexString(bindResponse.getStatusCode()), clusterId); - } - } catch (InterruptedException | ExecutionException e) { - logger.error(endpoint.getIeeeAddress() + ": Exception setting binding to cluster " + clusterId, e); - } - - clientCluster.addCommandListener(this); - clientClusters.add(clientCluster); + for (EventSpec eventSpec: handledEvents.values()) { + allBindsSucceeded &= eventSpec.bindCluster(); } - return true; + return allBindsSucceeded; } + @Override public void disposeConverter() { for (ZclCluster clientCluster : clientClusters) { - logger.debug("{}: Closing cluster {}", endpoint.getIeeeAddress(), clientCluster.getClusterId()); + logger.debug("{}: Closing client cluster {}", endpoint.getIeeeAddress(), clientCluster.getClusterId()); clientCluster.removeCommandListener(this); } + + for (ZclCluster serverCluster : serverClusters) { + logger.debug("{}: Closing server cluster {}", endpoint.getIeeeAddress(), serverCluster.getClusterId()); + serverCluster.removeAttributeListener(this); + } } @Override @@ -133,6 +116,16 @@ public void commandReceived(ZclCommand command) { } } + @Override + public void attributeUpdated(ZclAttribute attribute) { + ButtonPressType buttonPressType = getButtonPressType(attribute); + if (buttonPressType != null) { + logger.debug("{}: Matching ZigBee attribute for press type {} received: {}", endpoint.getIeeeAddress(), + buttonPressType, attribute); + thing.triggerChannel(channel.getUID(), getEvent(buttonPressType)); + } + } + private String getEvent(ButtonPressType pressType) { switch (pressType) { case DOUBLE_PRESS: @@ -147,27 +140,31 @@ private String getEvent(ButtonPressType pressType) { } } + private ButtonPressType getButtonPressType(ZclAttribute attribute) { + return getButtonPressType(cs -> cs.matches(attribute)); + } + private ButtonPressType getButtonPressType(ZclCommand command) { - for (Entry entry : handledCommands.entrySet()) { - if (entry.getValue().matchesCommand(command)) { + return getButtonPressType(cs -> cs.matches(command)); + } + + private ButtonPressType getButtonPressType(Predicate predicate) { + for (Entry entry : handledEvents.entrySet()) { + if (predicate.test(entry.getValue())) { return entry.getKey(); } } return null; } - private CommandSpec parseCommandSpec(ButtonPressType pressType) { - String clusterProperty = channel.getProperties().get(getParameterName(CLUSTER, pressType)); - String commandProperty = channel.getProperties().get(getParameterName(COMMAND, pressType)); - String commandParameterName = channel.getProperties().get(getParameterName(PARAM_NAME, pressType)); - String commandParameterValue = channel.getProperties().get(getParameterName(PARAM_VALUE, pressType)); + private EventSpec parseEventSpec(Map properties, ButtonPressType pressType) { + String clusterProperty = properties.get(getParameterName(CLUSTER, pressType)); - if (clusterProperty == null || commandProperty == null) { + if (clusterProperty == null) { return null; } int clusterId; - int commandId; try { clusterId = parseId(clusterProperty); @@ -176,6 +173,59 @@ private CommandSpec parseCommandSpec(ButtonPressType pressType) { return null; } + boolean hasCommand = properties.containsKey(getParameterName(COMMAND, pressType)); + boolean hasAttribute = properties.containsKey(getParameterName(ATTRIBUTE_ID, pressType)); + + if (hasCommand && hasAttribute) { + logger.warn("{}: Only one of command or attribute can be used", endpoint.getIeeeAddress()); + return null; + } + + if (hasCommand) { + return parseCommandSpec(clusterId, properties, pressType); + } else { + return parseAttributeReportSpec(clusterId, properties, pressType); + } + } + + private AttributeReportSpec parseAttributeReportSpec(int clusterId, Map properties, ButtonPressType pressType) { + String attributeIdProperty = properties.get(getParameterName(ATTRIBUTE_ID, pressType)); + String attributeValue = properties.get(getParameterName(ATTRIBUTE_VALUE, pressType)); + + if (attributeIdProperty == null) { + logger.warn("{}: Missing attribute id", endpoint.getIeeeAddress()); + return null; + } + + Integer attributeId; + + try { + attributeId = parseId(attributeIdProperty); + } catch (NumberFormatException e) { + logger.warn("{}: Could not parse attribute property {}", endpoint.getIeeeAddress(), attributeIdProperty); + return null; + } + + if (attributeValue == null) { + logger.warn("{}: No attribute value for attribute {} specified", endpoint.getIeeeAddress(), attributeId); + return null; + } + + return new AttributeReportSpec(clusterId, attributeId, attributeValue); + } + + private CommandSpec parseCommandSpec(int clusterId, Map properties, ButtonPressType pressType) { + String commandProperty = properties.get(getParameterName(COMMAND, pressType)); + String commandParameterName = properties.get(getParameterName(PARAM_NAME, pressType)); + String commandParameterValue = properties.get(getParameterName(PARAM_VALUE, pressType)); + + if (commandProperty == null) { + logger.warn("{}: Missing command", endpoint.getIeeeAddress()); + return null; + } + + Integer commandId; + try { commandId = parseId(commandProperty); } catch (NumberFormatException e) { @@ -190,16 +240,17 @@ private CommandSpec parseCommandSpec(ButtonPressType pressType) { return null; } + return new CommandSpec(clusterId, commandId, commandParameterName, commandParameterValue); } - private String getParameterName(String parameterType, ButtonPressType buttonPressType) { + private static String getParameterName(String parameterType, ButtonPressType buttonPressType) { return String.format("zigbee_%s_%s", buttonPressType, parameterType); } - private int parseId(String id) throws NumberFormatException { + private static int parseId(String id) throws NumberFormatException { if (id.startsWith("0x")) { - return parseInt(id.substring(2, id.length()), 16); + return parseInt(id.substring(2), 16); } else { return parseInt(id); } @@ -222,25 +273,101 @@ public String toString() { } } - private class CommandSpec { + private abstract class EventSpec { private final int clusterId; - private final int commandId; + + EventSpec(int clusterId) { + this.clusterId = clusterId; + } + + int getClusterId() { + return clusterId; + } + + abstract boolean matches(ZclCommand command); + abstract boolean matches(ZclAttribute attribute); + abstract boolean bindCluster(); + + boolean bindCluster(String clusterType, Collection existingClusters, int clusterId, + Function getClusterById, + Consumer registrationFunction + ) { + if (existingClusters.stream().anyMatch(c -> c.getClusterId().intValue() == clusterId)) { + // bind to each output cluster only once + return true; + } + + ZclCluster cluster = getClusterById.apply(clusterId); + if (cluster == null) { + logger.error("{}: Error opening {} cluster {} on endpoint {}", endpoint.getIeeeAddress(), clusterType, clusterId, + endpoint.getEndpointId()); + return false; + } + + try { + CommandResult bindResponse = bind(cluster).get(); + if (!bindResponse.isSuccess()) { + logger.error("{}: Error 0x{} setting {} binding for cluster {}", endpoint.getIeeeAddress(), + toHexString(bindResponse.getStatusCode()), clusterType, clusterId); + } + } catch (InterruptedException | ExecutionException e) { + logger.error("{}: Exception setting {} binding to cluster {}", endpoint.getIeeeAddress(), clusterType, clusterId, e); + } + + registrationFunction.accept(cluster); + existingClusters.add(cluster); + return true; + } + } + + protected final class AttributeReportSpec extends EventSpec { + private final Integer attributeId; + private final String attributeValue; + + AttributeReportSpec(int clusterId, Integer attributeId, String attributeValue) { + super(clusterId); + this.attributeId = attributeId; + this.attributeValue = attributeValue; + } + + @Override + boolean matches(ZclCommand command) { + return false; + } + + @Override + boolean matches(ZclAttribute attribute) { + if (attributeId == null) { + return false; + } + boolean attributeIdMatches = attribute.getId() == attributeId; + boolean attributeValueMatches = Objects.equals( + Objects.toString(attribute.getLastValue()), + attributeValue + ); + return attributeIdMatches && attributeValueMatches; + } + + @Override + boolean bindCluster() { + return bindCluster( + "server", serverClusters, getClusterId(), endpoint::getInputCluster, + cluster -> cluster.addAttributeListener(ZigBeeConverterGenericButton.this)); + } + } + + private final class CommandSpec extends EventSpec { + private final Integer commandId; private final String commandParameterName; private final String commandParameterValue; - public CommandSpec(int clusterId, int commandId, String commandParameterName, String commandParameterValue) { - this.clusterId = clusterId; + private CommandSpec(int clusterId, Integer commandId, String commandParameterName, String commandParameterValue) { + super(clusterId); this.commandId = commandId; this.commandParameterName = commandParameterName; this.commandParameterValue = commandParameterValue; } - public boolean matchesCommand(ZclCommand command) { - boolean commandIdMatches = command.getCommandId().intValue() == commandId; - return commandIdMatches - && (commandParameterName == null || commandParameterValue == null || matchesParameter(command)); - } - private boolean matchesParameter(ZclCommand command) { String capitalizedParameterName = commandParameterName.substring(0, 1).toUpperCase() + commandParameterName.substring(1); @@ -250,14 +377,31 @@ private boolean matchesParameter(ZclCommand command) { return Objects.equals(result.toString(), commandParameterValue); } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - logger.warn(format("%s: Could not read parameter %s for command %s", endpoint.getIeeeAddress(), - commandParameterName, command), e); + logger.warn("{}: Could not read parameter {} for command {}", endpoint.getIeeeAddress(), + commandParameterName, command, e); return false; } } - public int getClusterId() { - return clusterId; + @Override + boolean matches(ZclCommand command) { + if (commandId == null) { + return false; + } + boolean commandIdMatches = command.getCommandId().intValue() == commandId; + return commandIdMatches + && (commandParameterName == null || commandParameterValue == null || matchesParameter(command)); + } + + @Override + boolean matches(ZclAttribute attribute) { + return false; + } + + @Override + boolean bindCluster() { + return bindCluster("client", clientClusters, getClusterId(), endpoint::getOutputCluster, + cluster -> cluster.addCommandListener(ZigBeeConverterGenericButton.this)); } } } diff --git a/org.openhab.binding.zigbee/src/main/resources/discovery.txt b/org.openhab.binding.zigbee/src/main/resources/discovery.txt index f8fda708c..7e3b3f75d 100644 --- a/org.openhab.binding.zigbee/src/main/resources/discovery.txt +++ b/org.openhab.binding.zigbee/src/main/resources/discovery.txt @@ -5,5 +5,6 @@ bitron-video-902010-23,vendor=Bitron Home,modelId=902010/23 bitron-video-av2010-34,vendor=Bitron Video,modelId=AV2010/34 xiaomi_lumisensorht,modelId=lumi.sensor_ht xiaomi_lumisensor-motion,modelId=lumi.sensor_motion +xiaomi_lumiremoteb286acn01,modelId=lumi.remote.b286acn01 innr-rc-110,vendor=innr,modelId=RC 110 osram-switch-4x-eu,vendor=OSRAM,modelId=Switch 4x EU-LIGHTIFY diff --git a/org.openhab.binding.zigbee/src/test/java/org/openhab/binding/zigbee/internal/converter/ZigBeeConverterGenericButtonTest.java b/org.openhab.binding.zigbee/src/test/java/org/openhab/binding/zigbee/internal/converter/ZigBeeConverterGenericButtonTest.java index 4055a252c..2a2e1b677 100644 --- a/org.openhab.binding.zigbee/src/test/java/org/openhab/binding/zigbee/internal/converter/ZigBeeConverterGenericButtonTest.java +++ b/org.openhab.binding.zigbee/src/test/java/org/openhab/binding/zigbee/internal/converter/ZigBeeConverterGenericButtonTest.java @@ -3,10 +3,16 @@ import static org.junit.Assert.*; import static org.mockito.Mockito.*; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; +import com.zsmartsystems.zigbee.zcl.ZclAttribute; +import com.zsmartsystems.zigbee.zcl.ZclAttributeListener; +import com.zsmartsystems.zigbee.zcl.ZclCommandListener; +import com.zsmartsystems.zigbee.zcl.protocol.ZclClusterType; +import com.zsmartsystems.zigbee.zcl.protocol.ZclDataType; import org.eclipse.smarthome.core.thing.Channel; import org.eclipse.smarthome.core.thing.CommonTriggerEvents; import org.eclipse.smarthome.core.thing.ThingUID; @@ -62,7 +68,7 @@ public void setup() { } @Test - public void converterInitializationBindsToCorrectCluster() { + public void converterInitializationForCommandBindsToCorrectCluster() { channelProperties.put("zigbee_shortpress_cluster_id", "0x0008"); channelProperties.put("zigbee_shortpress_command_id", "0x0017"); @@ -72,6 +78,22 @@ public void converterInitializationBindsToCorrectCluster() { assertTrue(initResult); verify(cluster, times(1)).addCommandListener(converter); + verify(cluster, times(0)).addAttributeListener(any(ZclAttributeListener.class)); + } + + @Test + public void converterInitializationForAttributeBindsToCorrectCluster() { + channelProperties.put("zigbee_shortpress_cluster_id", "0x0008"); + channelProperties.put("zigbee_shortpress_attribute_id", "0x0017"); + channelProperties.put("zigbee_shortpress_attribute_value", "2"); + + ZclCluster cluster = mockCluster(8); + + boolean initResult = converter.initializeConverter(); + + assertTrue(initResult); + verify(cluster, times(1)).addAttributeListener(converter); + verify(cluster, times(0)).addCommandListener(any(ZclCommandListener.class)); } @Test @@ -103,7 +125,7 @@ public void converterInitializationClusterIdIsMandatory() { } @Test - public void converterInitializationCommandIdIsMandatory() { + public void converterInitializationCommandIdOrAttributeIsMandatory() { channelProperties.put("zigbee_shortpress_cluster_id", "0x008"); mockCluster(8); boolean initResult = converter.initializeConverter(); @@ -127,6 +149,16 @@ public void converterCannotInitializeWithUnparseableCommandId() { assertFalse(initResult); } + @Test + public void converterCannotInitializeWithUnparseableAttributeId() { + channelProperties.put("zigbee_shortpress_cluster_id", "0x8"); + channelProperties.put("zigbee_shortpress_attribute_id", "abc"); + channelProperties.put("zigbee_shortpress_attribute_value", "abc"); + mockCluster(8); + boolean initResult = converter.initializeConverter(); + assertFalse(initResult); + } + @Test public void converterCannotInitializeWithIncompleteParamSpecWithoutValue() { channelProperties.put("zigbee_shortpress_cluster_id", "0x8"); @@ -147,6 +179,26 @@ public void converterCannotInitializeWithIncompleteParamSpecWithoutName() { assertFalse(initResult); } + @Test + public void converterCannotInitializeWithIncompleteAttributeSpec() { + channelProperties.put("zigbee_shortpress_cluster_id", "0x8"); + channelProperties.put("zigbee_shortpress_attribute_id", "1"); + mockCluster(8); + boolean initResult = converter.initializeConverter(); + assertFalse(initResult); + } + + @Test + public void converterCannotInitializeWithCommandAndAttribute() { + channelProperties.put("zigbee_shortpress_cluster_id", "0x0008"); + channelProperties.put("zigbee_shortpress_command_id", "0xabc"); + channelProperties.put("zigbee_shortpress_attribute_id", "1"); + channelProperties.put("zigbee_shortpress_attribute_value", "2"); + mockCluster(8); + boolean initResult = converter.initializeConverter(); + assertFalse(initResult); + } + @Test public void cannotInitializeConverterWithoutChannel() { assertNull(converter.getChannel(mock(ThingUID.class), endpoint)); @@ -161,7 +213,22 @@ public void commandListenersAreRemovedOnDispose() { converter.initializeConverter(); converter.disposeConverter(); - verify(cluster).removeCommandListener(converter); + verify(cluster, times(1)).removeCommandListener(converter); + verify(cluster, times(0)).removeAttributeListener(any(ZclAttributeListener.class)); + } + + @Test + public void attributeListenersAreRemovedOnDispose() { + channelProperties.put("zigbee_shortpress_cluster_id", "0x0008"); + channelProperties.put("zigbee_shortpress_attribute_id", "0x0017"); + channelProperties.put("zigbee_shortpress_attribute_value", "2"); + ZclCluster cluster = mockCluster(8); + + converter.initializeConverter(); + converter.disposeConverter(); + + verify(cluster, times(1)).removeAttributeListener(converter); + verify(cluster, times(0)).removeCommandListener(any(ZclCommandListener.class)); } @Test @@ -196,6 +263,27 @@ public void commandWithMatchingSpecifiedParamIsHandled() { verify(thingHandler, times(0)).triggerChannel(channel.getUID(), CommonTriggerEvents.DOUBLE_PRESSED); } + @Test + public void attributeWithMatchingValueIsHandled() { + channelProperties.put("zigbee_shortpress_cluster_id", "768"); + channelProperties.put("zigbee_shortpress_attribute_id", "85"); + channelProperties.put("zigbee_shortpress_attribute_value", "1"); + mockCluster(768); + converter.initializeConverter(); + + ZclAttribute attribute = new ZclAttribute( + ZclClusterType.MULTISTATE_INPUT__BASIC, 85, "foo", + ZclDataType.UNSIGNED_16_BIT_INTEGER, + false, false, true, true); + attribute.updateValue(1); + converter.attributeUpdated(attribute); + + verify(thingHandler, times(1)).triggerChannel(channel.getUID(), CommonTriggerEvents.SHORT_PRESSED); + verify(thingHandler, times(0)).triggerChannel(channel.getUID(), CommonTriggerEvents.LONG_PRESSED); + verify(thingHandler, times(0)).triggerChannel(channel.getUID(), CommonTriggerEvents.DOUBLE_PRESSED); + } + + @Test public void commandWithNonMatchingSpecifiedParamNameIsNotHandled() { channelProperties.put("zigbee_shortpress_cluster_id", "768"); @@ -232,6 +320,27 @@ public void commandWithNonMatchingSpecifiedParamValueIsNotHandled() { verify(thingHandler, times(0)).triggerChannel(channel.getUID(), CommonTriggerEvents.DOUBLE_PRESSED); } + @Test + public void attributeWithNonMatchingValueIsNotHandled() { + channelProperties.put("zigbee_shortpress_cluster_id", "768"); + channelProperties.put("zigbee_shortpress_attribute_id", "85"); + channelProperties.put("zigbee_shortpress_attribute_value", "1"); + mockCluster(768); + converter.initializeConverter(); + + ZclAttribute attribute = new ZclAttribute( + ZclClusterType.MULTISTATE_INPUT__BASIC, 85, "foo", + ZclDataType.UNSIGNED_16_BIT_INTEGER, + false, false, true, true); + attribute.updateValue(2); + converter.attributeUpdated(attribute); + + verify(thingHandler, times(0)).triggerChannel(channel.getUID(), CommonTriggerEvents.SHORT_PRESSED); + verify(thingHandler, times(0)).triggerChannel(channel.getUID(), CommonTriggerEvents.LONG_PRESSED); + verify(thingHandler, times(0)).triggerChannel(channel.getUID(), CommonTriggerEvents.DOUBLE_PRESSED); + } + + @Test public void commandTypeIsCorrectlyDetected() { channelProperties.put("zigbee_shortpress_cluster_id", "0x0008"); @@ -253,16 +362,42 @@ public void commandTypeIsCorrectlyDetected() { verify(thingHandler, times(0)).triggerChannel(channel.getUID(), CommonTriggerEvents.DOUBLE_PRESSED); } - private ZclCluster mockCluster(int clusterId) { - IeeeAddress ieeeAddress = new IeeeAddress(); + @Test + public void attributeTypeIsCorrectlyDetected() { + channelProperties.put("zigbee_shortpress_cluster_id", "0x0008"); + channelProperties.put("zigbee_shortpress_attribute_id", "0x0017"); + channelProperties.put("zigbee_shortpress_attribute_value", "0x01"); + channelProperties.put("zigbee_longpress_cluster_id", "768"); + channelProperties.put("zigbee_longpress_attribute_id", "0x0017"); + channelProperties.put("zigbee_longpress_attribute_value", "2"); + + channelProperties.put("zigbee_doublepress_cluster_id", "0x0009"); + channelProperties.put("zigbee_doublepress_attribute_id", "0x0017"); + channelProperties.put("zigbee_doublepress_attribute_value", "0x03"); + + mockCluster(768); + converter.initializeConverter(); + + ZclAttribute attribute = new ZclAttribute( + ZclClusterType.MULTISTATE_INPUT__BASIC, 0x17, "foo", + ZclDataType.UNSIGNED_16_BIT_INTEGER, + false, false, true, true); + attribute.updateValue(2); + converter.attributeUpdated(attribute); + + verify(thingHandler, times(0)).triggerChannel(channel.getUID(), CommonTriggerEvents.SHORT_PRESSED); + verify(thingHandler, times(1)).triggerChannel(channel.getUID(), CommonTriggerEvents.LONG_PRESSED); + verify(thingHandler, times(0)).triggerChannel(channel.getUID(), CommonTriggerEvents.DOUBLE_PRESSED); + } + + private ZclCluster mockCluster(int clusterId) { ZclCluster cluster = mock(ZclCluster.class); when(cluster.getClusterId()).thenReturn(clusterId); - // when(cluster.bind(ieeeAddress, ZigBeeProfileType.ZIGBEE_HOME_AUTOMATION.getKey())) when(cluster.bind(ArgumentMatchers.any(IeeeAddress.class), ArgumentMatchers.anyInt())) .thenReturn(CompletableFuture.completedFuture(new CommandResult(new ZigBeeCommand()))); when(endpoint.getOutputCluster(clusterId)).thenReturn(cluster); + when(endpoint.getInputCluster(clusterId)).thenReturn(cluster); return cluster; } - }