From a76400ee775af339b91d84786f796129d1f5cf17 Mon Sep 17 00:00:00 2001 From: Cody Cutrer Date: Mon, 13 Nov 2023 17:07:36 -0700 Subject: [PATCH] [mqtt.homeassistant] Improve support for Lock component * expose full state possibilities * expose OPEN command --- .../internal/component/Lock.java | 53 ++++++++++++++++--- .../internal/component/LockTests.java | 23 +++++--- 2 files changed, 62 insertions(+), 14 deletions(-) diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Lock.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Lock.java index ccfab925c9b48..65a08aa7f2b7c 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Lock.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Lock.java @@ -12,9 +12,13 @@ */ package org.openhab.binding.mqtt.homeassistant.internal.component; +import java.util.HashSet; +import java.util.Set; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.mqtt.generic.values.OnOffValue; +import org.openhab.binding.mqtt.generic.values.TextValue; import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration; import org.openhab.binding.mqtt.homeassistant.internal.exception.ConfigurationException; @@ -27,7 +31,9 @@ */ @NonNullByDefault public class Lock extends AbstractComponent { - public static final String SWITCH_CHANNEL_ID = "lock"; // Randomly chosen channel "ID" + public static final String LOCK_CHANNEL_ID = "lock"; + public static final String STATE_CHANNEL_ID = "state"; + public static final String OPEN_CHANNEL_ID = "open"; /** * Configuration class for MQTT component @@ -39,14 +45,26 @@ static class ChannelConfiguration extends AbstractChannelConfiguration { protected boolean optimistic = false; + @SerializedName("command_topic") + protected @Nullable String commandTopic; @SerializedName("state_topic") protected String stateTopic = ""; @SerializedName("payload_lock") protected String payloadLock = "LOCK"; @SerializedName("payload_unlock") protected String payloadUnlock = "UNLOCK"; - @SerializedName("command_topic") - protected @Nullable String commandTopic; + @SerializedName("payload_open") + protected String payloadOpen = "OPEN"; + @SerializedName("state_jammed") + protected String stateJammed = "JAMMED"; + @SerializedName("state_locked") + protected String stateLocked = "LOCKED"; + @SerializedName("state_locking") + protected String stateLocking = "LOCKING"; + @SerializedName("state_unlocked") + protected String stateUnlocked = "UNLOCKED"; + @SerializedName("state_unlocking") + protected String stateUnlocking = "UNLOCKING"; } public Lock(ComponentFactory.ComponentConfiguration componentConfiguration) { @@ -57,12 +75,35 @@ public Lock(ComponentFactory.ComponentConfiguration componentConfiguration) { throw new ConfigurationException("Component:Lock does not support forced optimistic mode"); } - buildChannel(SWITCH_CHANNEL_ID, - new OnOffValue(channelConfiguration.payloadLock, channelConfiguration.payloadUnlock), getName(), - componentConfiguration.getUpdateListener()) + String stateTopic = channelConfiguration.stateTopic; + + // State can indicate additional information than just + // locked/unlocked, so expose it as a separate channel + if (stateTopic != null) { + Set states = new HashSet<>(); + states.add(channelConfiguration.stateJammed); + states.add(channelConfiguration.stateLocked); + states.add(channelConfiguration.stateLocking); + states.add(channelConfiguration.stateUnlocked); + states.add(channelConfiguration.stateUnlocking); + + TextValue value = new TextValue(states); + buildChannel(STATE_CHANNEL_ID, value, "State", componentConfiguration.getUpdateListener()) + .stateTopic(stateTopic).isAdvanced(true).build(); + } + + buildChannel(LOCK_CHANNEL_ID, + new OnOffValue(channelConfiguration.stateLocked, channelConfiguration.stateUnlocked, + channelConfiguration.payloadLock, channelConfiguration.payloadUnlock), + "Lock", componentConfiguration.getUpdateListener()) .stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate()) .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos()) .build(); + buildChannel(OPEN_CHANNEL_ID, new OnOffValue(channelConfiguration.payloadOpen), "Open", + componentConfiguration.getUpdateListener()) + .commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(), + channelConfiguration.getQos()) + .build(); } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/LockTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/LockTests.java index c988806ef3811..0993ce045f13c 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/LockTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/LockTests.java @@ -20,6 +20,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; import org.openhab.binding.mqtt.generic.values.OnOffValue; +import org.openhab.binding.mqtt.generic.values.TextValue; import org.openhab.core.library.types.OnOffType; /** @@ -55,31 +56,37 @@ public void test() throws InterruptedException { "name": "lock", \ "payload_unlock": "UNLOCK_", \ "payload_lock": "LOCK_", \ + "state_unlocked": "UNLOCK_", \ + "state_locked": "LOCK_", \ "state_topic": "zigbee2mqtt/lock/state", \ "command_topic": "zigbee2mqtt/lock/set/state" \ }\ """); // @formatter:on - assertThat(component.channels.size(), is(1)); + assertThat(component.channels.size(), is(3)); assertThat(component.getName(), is("lock")); - assertChannel(component, Lock.SWITCH_CHANNEL_ID, "zigbee2mqtt/lock/state", "zigbee2mqtt/lock/set/state", "lock", + assertChannel(component, Lock.LOCK_CHANNEL_ID, "zigbee2mqtt/lock/state", "zigbee2mqtt/lock/set/state", "Lock", OnOffValue.class); + assertChannel(component, Lock.STATE_CHANNEL_ID, "zigbee2mqtt/lock/state", "", "State", TextValue.class); + assertChannel(component, Lock.OPEN_CHANNEL_ID, "", "zigbee2mqtt/lock/set/state", "Open", OnOffValue.class); publishMessage("zigbee2mqtt/lock/state", "LOCK_"); - assertState(component, Lock.SWITCH_CHANNEL_ID, OnOffType.ON); + assertState(component, Lock.LOCK_CHANNEL_ID, OnOffType.ON); publishMessage("zigbee2mqtt/lock/state", "LOCK_"); - assertState(component, Lock.SWITCH_CHANNEL_ID, OnOffType.ON); + assertState(component, Lock.LOCK_CHANNEL_ID, OnOffType.ON); publishMessage("zigbee2mqtt/lock/state", "UNLOCK_"); - assertState(component, Lock.SWITCH_CHANNEL_ID, OnOffType.OFF); + assertState(component, Lock.LOCK_CHANNEL_ID, OnOffType.OFF); publishMessage("zigbee2mqtt/lock/state", "LOCK_"); - assertState(component, Lock.SWITCH_CHANNEL_ID, OnOffType.ON); + assertState(component, Lock.LOCK_CHANNEL_ID, OnOffType.ON); - component.getChannel(Lock.SWITCH_CHANNEL_ID).getState().publishValue(OnOffType.OFF); + component.getChannel(Lock.LOCK_CHANNEL_ID).getState().publishValue(OnOffType.OFF); assertPublished("zigbee2mqtt/lock/set/state", "UNLOCK_"); - component.getChannel(Lock.SWITCH_CHANNEL_ID).getState().publishValue(OnOffType.ON); + component.getChannel(Lock.LOCK_CHANNEL_ID).getState().publishValue(OnOffType.ON); assertPublished("zigbee2mqtt/lock/set/state", "LOCK_"); + component.getChannel(Lock.OPEN_CHANNEL_ID).getState().publishValue(OnOffType.ON); + assertPublished("zigbee2mqtt/lock/set/state", "OPEN"); } @Test