From 453039e8601dcc3adecc61aca488e43bf0a3d03c Mon Sep 17 00:00:00 2001 From: Nathan Spencer Date: Sun, 3 Nov 2024 23:49:48 -0700 Subject: [PATCH] Change alexa arm handler to allow switching arm states unless in armed_away mode (#129701) * Change alexa arm handler to allow switching arm states unless in armed_away mode * Address PR comments --- homeassistant/components/alexa/handlers.py | 8 +- tests/components/alexa/test_smart_home.py | 102 +++++++++++++++++++++ 2 files changed, 109 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/alexa/handlers.py b/homeassistant/components/alexa/handlers.py index d2f6c292e6fded..8ea61ddbceb109 100644 --- a/homeassistant/components/alexa/handlers.py +++ b/homeassistant/components/alexa/handlers.py @@ -1083,7 +1083,13 @@ async def async_api_arm( arm_state = directive.payload["armState"] data: dict[str, Any] = {ATTR_ENTITY_ID: entity.entity_id} - if entity.state != alarm_control_panel.AlarmControlPanelState.DISARMED: + # Per Alexa Documentation: users are not allowed to switch from armed_away + # directly to another armed state without first disarming the system. + # https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-securitypanelcontroller.html#arming + if ( + entity.state == alarm_control_panel.AlarmControlPanelState.ARMED_AWAY + and arm_state != "ARMED_AWAY" + ): msg = "You must disarm the system before you can set the requested arm state." raise AlexaSecurityPanelAuthorizationRequired(msg) diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index 4ae78421596dab..68010a6a7111c5 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -3999,6 +3999,108 @@ async def test_alarm_control_panel_code_arm_required(hass: HomeAssistant) -> Non await discovery_test(device, hass, expected_endpoints=0) +async def test_alarm_control_panel_disarm_required(hass: HomeAssistant) -> None: + """Test alarm_control_panel disarm required.""" + device = ( + "alarm_control_panel.test_4", + "armed_away", + { + "friendly_name": "Test Alarm Control Panel 4", + "code_arm_required": False, + "code_format": "FORMAT_NUMBER", + "code": "1234", + "supported_features": 3, + }, + ) + appliance = await discovery_test(device, hass) + + assert appliance["endpointId"] == "alarm_control_panel#test_4" + assert appliance["displayCategories"][0] == "SECURITY_PANEL" + assert appliance["friendlyName"] == "Test Alarm Control Panel 4" + assert_endpoint_capabilities( + appliance, "Alexa.SecurityPanelController", "Alexa.EndpointHealth", "Alexa" + ) + + properties = await reported_properties(hass, "alarm_control_panel#test_4") + properties.assert_equal("Alexa.SecurityPanelController", "armState", "ARMED_AWAY") + + msg = await assert_request_fails( + "Alexa.SecurityPanelController", + "Arm", + "alarm_control_panel#test_4", + "alarm_control_panel.alarm_arm_home", + hass, + payload={"armState": "ARMED_STAY"}, + ) + assert msg["event"]["payload"]["type"] == "AUTHORIZATION_REQUIRED" + assert ( + msg["event"]["payload"]["message"] + == "You must disarm the system before you can set the requested arm state." + ) + + _, msg = await assert_request_calls_service( + "Alexa.SecurityPanelController", + "Arm", + "alarm_control_panel#test_4", + "alarm_control_panel.alarm_arm_away", + hass, + response_type="Arm.Response", + payload={"armState": "ARMED_AWAY"}, + ) + properties = ReportedProperties(msg["context"]["properties"]) + properties.assert_equal("Alexa.SecurityPanelController", "armState", "ARMED_AWAY") + + +async def test_alarm_control_panel_change_arm_type(hass: HomeAssistant) -> None: + """Test alarm_control_panel change arm type.""" + device = ( + "alarm_control_panel.test_5", + "armed_home", + { + "friendly_name": "Test Alarm Control Panel 5", + "code_arm_required": False, + "code_format": "FORMAT_NUMBER", + "code": "1234", + "supported_features": 3, + }, + ) + appliance = await discovery_test(device, hass) + + assert appliance["endpointId"] == "alarm_control_panel#test_5" + assert appliance["displayCategories"][0] == "SECURITY_PANEL" + assert appliance["friendlyName"] == "Test Alarm Control Panel 5" + assert_endpoint_capabilities( + appliance, "Alexa.SecurityPanelController", "Alexa.EndpointHealth", "Alexa" + ) + + properties = await reported_properties(hass, "alarm_control_panel#test_5") + properties.assert_equal("Alexa.SecurityPanelController", "armState", "ARMED_STAY") + + _, msg = await assert_request_calls_service( + "Alexa.SecurityPanelController", + "Arm", + "alarm_control_panel#test_5", + "alarm_control_panel.alarm_arm_home", + hass, + response_type="Arm.Response", + payload={"armState": "ARMED_STAY"}, + ) + properties = ReportedProperties(msg["context"]["properties"]) + properties.assert_equal("Alexa.SecurityPanelController", "armState", "ARMED_STAY") + + _, msg = await assert_request_calls_service( + "Alexa.SecurityPanelController", + "Arm", + "alarm_control_panel#test_5", + "alarm_control_panel.alarm_arm_away", + hass, + response_type="Arm.Response", + payload={"armState": "ARMED_AWAY"}, + ) + properties = ReportedProperties(msg["context"]["properties"]) + properties.assert_equal("Alexa.SecurityPanelController", "armState", "ARMED_AWAY") + + async def test_range_unsupported_domain(hass: HomeAssistant) -> None: """Test rangeController with unsupported domain.""" device = ("switch.test", "on", {"friendly_name": "Test switch"})