diff --git a/homeassistant/components/mqtt/alarm_control_panel.py b/homeassistant/components/mqtt/alarm_control_panel.py index 06f91403057..a0939fdc615 100644 --- a/homeassistant/components/mqtt/alarm_control_panel.py +++ b/homeassistant/components/mqtt/alarm_control_panel.py @@ -39,6 +39,7 @@ from .const import ( CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, + CONF_SUPPORTED_FEATURES, ) from .debug_info import log_messages from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper @@ -47,6 +48,15 @@ from .util import get_mqtt_data, valid_publish_topic, valid_subscribe_topic _LOGGER = logging.getLogger(__name__) +_SUPPORTED_FEATURES = { + "arm_home": AlarmControlPanelEntityFeature.ARM_HOME, + "arm_away": AlarmControlPanelEntityFeature.ARM_AWAY, + "arm_night": AlarmControlPanelEntityFeature.ARM_NIGHT, + "arm_vacation": AlarmControlPanelEntityFeature.ARM_VACATION, + "arm_custom_bypass": AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS, + "trigger": AlarmControlPanelEntityFeature.TRIGGER, +} + CONF_CODE_ARM_REQUIRED = "code_arm_required" CONF_CODE_DISARM_REQUIRED = "code_disarm_required" CONF_CODE_TRIGGER_REQUIRED = "code_trigger_required" @@ -81,6 +91,9 @@ REMOTE_CODE_TEXT = "REMOTE_CODE_TEXT" PLATFORM_SCHEMA_MODERN = MQTT_BASE_SCHEMA.extend( { + vol.Optional(CONF_SUPPORTED_FEATURES, default=list(_SUPPORTED_FEATURES)): [ + vol.In(_SUPPORTED_FEATURES) + ], vol.Optional(CONF_CODE): cv.string, vol.Optional(CONF_CODE_ARM_REQUIRED, default=True): cv.boolean, vol.Optional(CONF_CODE_DISARM_REQUIRED, default=True): cv.boolean, @@ -167,6 +180,9 @@ class MqttAlarm(MqttEntity, alarm.AlarmControlPanelEntity): config[CONF_COMMAND_TEMPLATE], entity=self ).async_render + for feature in self._config[CONF_SUPPORTED_FEATURES]: + self._attr_supported_features |= _SUPPORTED_FEATURES[feature] + def _prepare_subscribe_topics(self) -> None: """(Re)Subscribe to topics.""" @@ -214,18 +230,6 @@ class MqttAlarm(MqttEntity, alarm.AlarmControlPanelEntity): """Return the state of the device.""" return self._state - @property - def supported_features(self) -> AlarmControlPanelEntityFeature: - """Return the list of supported features.""" - return ( - AlarmControlPanelEntityFeature.ARM_HOME - | AlarmControlPanelEntityFeature.ARM_AWAY - | AlarmControlPanelEntityFeature.ARM_NIGHT - | AlarmControlPanelEntityFeature.ARM_VACATION - | AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS - | AlarmControlPanelEntityFeature.TRIGGER - ) - @property def code_format(self) -> alarm.CodeFormat | None: """Return one or more digits/characters.""" diff --git a/homeassistant/components/mqtt/const.py b/homeassistant/components/mqtt/const.py index fcdfeb4bd7d..97d2e1473f5 100644 --- a/homeassistant/components/mqtt/const.py +++ b/homeassistant/components/mqtt/const.py @@ -28,6 +28,7 @@ CONF_WS_PATH = "ws_path" CONF_WS_HEADERS = "ws_headers" CONF_WILL_MESSAGE = "will_message" CONF_PAYLOAD_RESET = "payload_reset" +CONF_SUPPORTED_FEATURES = "supported_features" CONF_ACTION_TEMPLATE = "action_template" CONF_ACTION_TOPIC = "action_topic" diff --git a/tests/components/mqtt/test_alarm_control_panel.py b/tests/components/mqtt/test_alarm_control_panel.py index e69839e6b16..35fba9e2a0c 100644 --- a/tests/components/mqtt/test_alarm_control_panel.py +++ b/tests/components/mqtt/test_alarm_control_panel.py @@ -7,6 +7,7 @@ from unittest.mock import patch import pytest from homeassistant.components import alarm_control_panel, mqtt +from homeassistant.components.alarm_control_panel import AlarmControlPanelEntityFeature from homeassistant.components.mqtt.alarm_control_panel import ( MQTT_ALARM_ATTRIBUTES_BLOCKED, ) @@ -74,6 +75,15 @@ from tests.typing import MqttMockHAClientGenerator, MqttMockPahoClient CODE_NUMBER = "1234" CODE_TEXT = "HELLO_CODE" +DEFAULT_FEATURES = ( + AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_AWAY + | AlarmControlPanelEntityFeature.ARM_NIGHT + | AlarmControlPanelEntityFeature.ARM_VACATION + | AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS + | AlarmControlPanelEntityFeature.TRIGGER +) + DEFAULT_CONFIG = { mqtt.DOMAIN: { alarm_control_panel.DOMAIN: { @@ -223,6 +233,89 @@ async def test_ignore_update_state_if_unknown_via_state_topic( assert hass.states.get(entity_id).state == STATE_UNKNOWN +@pytest.mark.parametrize( + ("hass_config", "expected_features", "valid"), + [ + ( + DEFAULT_CONFIG, + DEFAULT_FEATURES, + True, + ), + ( + help_custom_config( + alarm_control_panel.DOMAIN, + DEFAULT_CONFIG, + ({"supported_features": []},), + ), + AlarmControlPanelEntityFeature(0), + True, + ), + ( + help_custom_config( + alarm_control_panel.DOMAIN, + DEFAULT_CONFIG, + ({"supported_features": ["arm_home"]},), + ), + AlarmControlPanelEntityFeature.ARM_HOME, + True, + ), + ( + help_custom_config( + alarm_control_panel.DOMAIN, + DEFAULT_CONFIG, + ({"supported_features": ["arm_home", "arm_away"]},), + ), + AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_AWAY, + True, + ), + ( + help_custom_config( + alarm_control_panel.DOMAIN, + DEFAULT_CONFIG, + ({"supported_features": "invalid"},), + ), + None, + False, + ), + ( + help_custom_config( + alarm_control_panel.DOMAIN, + DEFAULT_CONFIG, + ({"supported_features": ["invalid"]},), + ), + None, + False, + ), + ( + help_custom_config( + alarm_control_panel.DOMAIN, + DEFAULT_CONFIG, + ({"supported_features": ["arm_home", "invalid"]},), + ), + None, + False, + ), + ], +) +async def test_supported_features( + hass: HomeAssistant, + mqtt_mock_entry: MqttMockHAClientGenerator, + expected_features: AlarmControlPanelEntityFeature | None, + valid: bool, +) -> None: + """Test conditional enablement of supported features.""" + if valid: + await mqtt_mock_entry() + assert ( + hass.states.get("alarm_control_panel.test").attributes["supported_features"] + == expected_features + ) + else: + with pytest.raises(AssertionError): + await mqtt_mock_entry() + + @pytest.mark.parametrize( ("hass_config", "service", "payload"), [