From df36e79925fcc943d92e2bb484b7c2126082af1b Mon Sep 17 00:00:00 2001 From: Cody Cutrer Date: Fri, 25 Oct 2024 10:12:04 -0500 Subject: [PATCH] [mqtt.homeassistant] bring AlarmControlPanel in line with current documentation (#17607) * [mqtt.homeassistant] bring AlarmControlPanel in line with current documentation Signed-off-by: Cody Cutrer --- .../internal/component/AlarmControlPanel.java | 108 +++++++++++++----- .../AlarmControlPanelDeprecatedTests.java | 100 ++++++++++++++++ .../component/AlarmControlPanelTests.java | 26 ++--- 3 files changed, 193 insertions(+), 41 deletions(-) create mode 100644 bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanelDeprecatedTests.java diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanel.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanel.java index a915c11f43b7c..2976d254867ed 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanel.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanel.java @@ -12,6 +12,9 @@ */ package org.openhab.binding.mqtt.homeassistant.internal.component; +import java.util.ArrayList; +import java.util.List; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.mqtt.generic.values.TextValue; @@ -31,10 +34,29 @@ */ @NonNullByDefault public class AlarmControlPanel extends AbstractComponent { - public static final String STATE_CHANNEL_ID = "alarm"; // Randomly chosen channel "ID" - public static final String SWITCH_DISARM_CHANNEL_ID = "disarm"; // Randomly chosen channel "ID" - public static final String SWITCH_ARM_HOME_CHANNEL_ID = "armhome"; // Randomly chosen channel "ID" - public static final String SWITCH_ARM_AWAY_CHANNEL_ID = "armaway"; // Randomly chosen channel "ID" + public static final String STATE_CHANNEL_ID = "state"; + public static final String STATE_CHANNEL_ID_DEPRECATED = "alarm"; + public static final String SWITCH_DISARM_CHANNEL_ID = "disarm"; + public static final String SWITCH_ARM_HOME_CHANNEL_ID = "armhome"; + public static final String SWITCH_ARM_AWAY_CHANNEL_ID = "armaway"; + + public static final String FEATURE_ARM_HOME = "arm_home"; + public static final String FEATURE_ARM_AWAY = "arm_away"; + public static final String FEATURE_ARM_NIGHT = "arm_night"; + public static final String FEATURE_ARM_VACATION = "arm_vacation"; + public static final String FEATURE_ARM_CUSTOM_BYPASS = "arm_custom_bypass"; + public static final String FEATURE_TRIGGER = "trigger"; + + public static final String STATE_ARMED_AWAY = "armed_away"; + public static final String STATE_ARMED_CUSTOM_BYPASS = "armed_custom_bypass"; + public static final String STATE_ARMED_HOME = "armed_home"; + public static final String STATE_ARMED_NIGHT = "armed_night"; + public static final String STATE_ARMED_VACATION = "armed_vacation"; + public static final String STATE_ARMING = "arming"; + public static final String STATE_DISARMED = "disarmed"; + public static final String STATE_DISARMING = "disarming"; + public static final String STATE_PENDING = "pending"; + public static final String STATE_TRIGGERED = "triggered"; /** * Configuration class for MQTT component @@ -48,40 +70,73 @@ static class ChannelConfiguration extends AbstractChannelConfiguration { @SerializedName("state_topic") protected String stateTopic = ""; - @SerializedName("state_disarmed") - protected String stateDisarmed = "disarmed"; - @SerializedName("state_armed_home") - protected String stateArmedHome = "armed_home"; - @SerializedName("state_armed_away") - protected String stateArmedAway = "armed_away"; - @SerializedName("state_pending") - protected String statePending = "pending"; - @SerializedName("state_triggered") - protected String stateTriggered = "triggered"; @SerializedName("command_topic") protected @Nullable String commandTopic; - @SerializedName("payload_disarm") - protected String payloadDisarm = "DISARM"; - @SerializedName("payload_arm_home") - protected String payloadArmHome = "ARM_HOME"; @SerializedName("payload_arm_away") protected String payloadArmAway = "ARM_AWAY"; + @SerializedName("payload_arm_home") + protected String payloadArmHome = "ARM_HOME"; + @SerializedName("payload_arm_night") + protected String payloadArmNight = "ARM_NIGHT"; + @SerializedName("payload_arm_vacation") + protected String payloadArmVacation = "ARM_VACATION"; + @SerializedName("payload_arm_custom_bypass") + protected String payloadArmCustomBypass = "ARM_CUSTOM_BYPASS"; + @SerializedName("payload_disarm") + protected String payloadDisarm = "DISARM"; + @SerializedName("payload_trigger") + protected String payloadTrigger = "TRIGGER"; + + @SerializedName("supported_features") + protected List supportedFeatures = List.of(FEATURE_ARM_HOME, FEATURE_ARM_AWAY, FEATURE_ARM_NIGHT, + FEATURE_ARM_VACATION, FEATURE_ARM_CUSTOM_BYPASS, FEATURE_TRIGGER); } public AlarmControlPanel(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) { super(componentConfiguration, ChannelConfiguration.class, newStyleChannels); - final String[] stateEnum = { channelConfiguration.stateDisarmed, channelConfiguration.stateArmedHome, - channelConfiguration.stateArmedAway, channelConfiguration.statePending, - channelConfiguration.stateTriggered }; - buildChannel(STATE_CHANNEL_ID, ComponentChannelType.STRING, new TextValue(stateEnum), getName(), - componentConfiguration.getUpdateListener()) - .stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())// - .build(); + List stateEnum = new ArrayList(List.of(STATE_DISARMED, STATE_TRIGGERED, STATE_ARMING, STATE_DISARMING, + STATE_PENDING, STATE_TRIGGERED)); + List commandEnum = new ArrayList(List.of(channelConfiguration.payloadDisarm)); + if (channelConfiguration.supportedFeatures.contains(FEATURE_ARM_HOME)) { + stateEnum.add(STATE_ARMED_HOME); + commandEnum.add(channelConfiguration.payloadArmHome); + } + if (channelConfiguration.supportedFeatures.contains(FEATURE_ARM_AWAY)) { + stateEnum.add(STATE_ARMED_AWAY); + commandEnum.add(channelConfiguration.payloadArmAway); + } + if (channelConfiguration.supportedFeatures.contains(FEATURE_ARM_NIGHT)) { + stateEnum.add(STATE_ARMED_NIGHT); + commandEnum.add(channelConfiguration.payloadArmNight); + } + if (channelConfiguration.supportedFeatures.contains(FEATURE_ARM_VACATION)) { + stateEnum.add(STATE_ARMED_VACATION); + commandEnum.add(channelConfiguration.payloadArmVacation); + } + if (channelConfiguration.supportedFeatures.contains(FEATURE_ARM_CUSTOM_BYPASS)) { + stateEnum.add(STATE_ARMED_CUSTOM_BYPASS); + commandEnum.add(channelConfiguration.payloadArmCustomBypass); + } + if (channelConfiguration.supportedFeatures.contains(FEATURE_TRIGGER)) { + commandEnum.add(channelConfiguration.payloadTrigger); + } String commandTopic = channelConfiguration.commandTopic; - if (commandTopic != null) { + TextValue value = (newStyleChannels && commandTopic != null) + ? new TextValue(stateEnum.toArray(new String[0]), commandEnum.toArray(new String[0])) + : new TextValue(stateEnum.toArray(new String[0])); + var builder = buildChannel(newStyleChannels ? STATE_CHANNEL_ID : STATE_CHANNEL_ID_DEPRECATED, + ComponentChannelType.STRING, value, getName(), componentConfiguration.getUpdateListener()) + .stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate()); + + if (newStyleChannels && commandTopic != null) { + builder.commandTopic(commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos()); + } + builder.build(); + + if (!newStyleChannels && commandTopic != null) { buildChannel(SWITCH_DISARM_CHANNEL_ID, ComponentChannelType.STRING, new TextValue(new String[] { channelConfiguration.payloadDisarm }), getName(), componentConfiguration.getUpdateListener()) @@ -97,6 +152,7 @@ public AlarmControlPanel(ComponentFactory.ComponentConfiguration componentConfig componentConfiguration.getUpdateListener()) .commandTopic(commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos()).build(); } + finalizeChannels(); } } diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanelDeprecatedTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanelDeprecatedTests.java new file mode 100644 index 0000000000000..6881d758b69ce --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanelDeprecatedTests.java @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mqtt.homeassistant.internal.component; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.mqtt.generic.values.TextValue; +import org.openhab.core.library.types.StringType; + +/** + * Tests for {@link AlarmControlPanel} + * + * @author Anton Kharuzhy - Initial contribution + */ +@NonNullByDefault +public class AlarmControlPanelDeprecatedTests extends AbstractComponentTests { + public static final String CONFIG_TOPIC = "alarm_control_panel/0x0000000000000000_alarm_control_panel_zigbee2mqtt"; + + @SuppressWarnings("null") + @Test + public void testAlarmControlPanel() { + // @formatter:off + var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), + """ + { \ + "availability": [ \ + { \ + "topic": "zigbee2mqtt/bridge/state" \ + } \ + ], \ + "code": "12345", \ + "command_topic": "zigbee2mqtt/alarm/set/state", \ + "device": { \ + "identifiers": [ \ + "zigbee2mqtt_0x0000000000000000" \ + ], \ + "manufacturer": "BestAlarmEver", \ + "model": "Heavy duty super duper alarm", \ + "name": "Alarm", \ + "sw_version": "Zigbee2MQTT 1.18.2" \ + }, \ + "name": "alarm", \ + "payload_arm_away": "ARM_AWAY_", \ + "payload_arm_home": "ARM_HOME_", \ + "payload_arm_night": "ARM_NIGHT_", \ + "payload_arm_custom_bypass": "ARM_CUSTOM_BYPASS_", \ + "payload_disarm": "DISARM_", \ + "state_topic": "zigbee2mqtt/alarm/state" \ + } \ + """); + // @formatter:on + + assertThat(component.channels.size(), is(4)); + assertThat(component.getName(), is("alarm")); + + assertChannel(component, AlarmControlPanel.STATE_CHANNEL_ID_DEPRECATED, "zigbee2mqtt/alarm/state", "", "alarm", + TextValue.class); + assertChannel(component, AlarmControlPanel.SWITCH_DISARM_CHANNEL_ID, "", "zigbee2mqtt/alarm/set/state", "alarm", + TextValue.class); + assertChannel(component, AlarmControlPanel.SWITCH_ARM_AWAY_CHANNEL_ID, "", "zigbee2mqtt/alarm/set/state", + "alarm", TextValue.class); + assertChannel(component, AlarmControlPanel.SWITCH_ARM_HOME_CHANNEL_ID, "", "zigbee2mqtt/alarm/set/state", + "alarm", TextValue.class); + + publishMessage("zigbee2mqtt/alarm/state", "armed_home"); + assertState(component, AlarmControlPanel.STATE_CHANNEL_ID_DEPRECATED, new StringType("armed_home")); + publishMessage("zigbee2mqtt/alarm/state", "armed_away"); + assertState(component, AlarmControlPanel.STATE_CHANNEL_ID_DEPRECATED, new StringType("armed_away")); + + component.getChannel(AlarmControlPanel.SWITCH_DISARM_CHANNEL_ID).getState() + .publishValue(new StringType("DISARM_")); + assertPublished("zigbee2mqtt/alarm/set/state", "DISARM_"); + component.getChannel(AlarmControlPanel.SWITCH_ARM_AWAY_CHANNEL_ID).getState() + .publishValue(new StringType("ARM_AWAY_")); + assertPublished("zigbee2mqtt/alarm/set/state", "ARM_AWAY_"); + component.getChannel(AlarmControlPanel.SWITCH_ARM_HOME_CHANNEL_ID).getState() + .publishValue(new StringType("ARM_HOME_")); + assertPublished("zigbee2mqtt/alarm/set/state", "ARM_HOME_"); + } + + @Override + protected Set getConfigTopics() { + return Set.of(CONFIG_TOPIC); + } +} diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanelTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanelTests.java index 37f557ba781b3..4ab20cac1bd7d 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanelTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AlarmControlPanelTests.java @@ -65,31 +65,22 @@ public void testAlarmControlPanel() { """); // @formatter:on - assertThat(component.channels.size(), is(4)); + assertThat(component.channels.size(), is(1)); assertThat(component.getName(), is("alarm")); - assertChannel(component, AlarmControlPanel.STATE_CHANNEL_ID, "zigbee2mqtt/alarm/state", "", "alarm", - TextValue.class); - assertChannel(component, AlarmControlPanel.SWITCH_DISARM_CHANNEL_ID, "", "zigbee2mqtt/alarm/set/state", "alarm", - TextValue.class); - assertChannel(component, AlarmControlPanel.SWITCH_ARM_AWAY_CHANNEL_ID, "", "zigbee2mqtt/alarm/set/state", - "alarm", TextValue.class); - assertChannel(component, AlarmControlPanel.SWITCH_ARM_HOME_CHANNEL_ID, "", "zigbee2mqtt/alarm/set/state", - "alarm", TextValue.class); + assertChannel(component, AlarmControlPanel.STATE_CHANNEL_ID, "zigbee2mqtt/alarm/state", + "zigbee2mqtt/alarm/set/state", "alarm", TextValue.class); publishMessage("zigbee2mqtt/alarm/state", "armed_home"); assertState(component, AlarmControlPanel.STATE_CHANNEL_ID, new StringType("armed_home")); publishMessage("zigbee2mqtt/alarm/state", "armed_away"); assertState(component, AlarmControlPanel.STATE_CHANNEL_ID, new StringType("armed_away")); - component.getChannel(AlarmControlPanel.SWITCH_DISARM_CHANNEL_ID).getState() - .publishValue(new StringType("DISARM_")); + component.getChannel(AlarmControlPanel.STATE_CHANNEL_ID).getState().publishValue(new StringType("DISARM_")); assertPublished("zigbee2mqtt/alarm/set/state", "DISARM_"); - component.getChannel(AlarmControlPanel.SWITCH_ARM_AWAY_CHANNEL_ID).getState() - .publishValue(new StringType("ARM_AWAY_")); + component.getChannel(AlarmControlPanel.STATE_CHANNEL_ID).getState().publishValue(new StringType("ARM_AWAY_")); assertPublished("zigbee2mqtt/alarm/set/state", "ARM_AWAY_"); - component.getChannel(AlarmControlPanel.SWITCH_ARM_HOME_CHANNEL_ID).getState() - .publishValue(new StringType("ARM_HOME_")); + component.getChannel(AlarmControlPanel.STATE_CHANNEL_ID).getState().publishValue(new StringType("ARM_HOME_")); assertPublished("zigbee2mqtt/alarm/set/state", "ARM_HOME_"); } @@ -97,4 +88,9 @@ public void testAlarmControlPanel() { protected Set getConfigTopics() { return Set.of(CONFIG_TOPIC); } + + @Override + protected boolean useNewStyleChannels() { + return true; + } }