diff --git a/homeassistant/components/totalconnect/alarm_control_panel.py b/homeassistant/components/totalconnect/alarm_control_panel.py index 36f7e5297f2..c7cd6cb939a 100644 --- a/homeassistant/components/totalconnect/alarm_control_panel.py +++ b/homeassistant/components/totalconnect/alarm_control_panel.py @@ -21,12 +21,16 @@ from homeassistant.const import ( STATE_ALARM_TRIGGERED, ) from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers import entity_platform from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN _LOGGER = logging.getLogger(__name__) +SERVICE_ALARM_ARM_AWAY_INSTANT = "arm_away_instant" +SERVICE_ALARM_ARM_HOME_INSTANT = "arm_home_instant" + async def async_setup_entry(hass, entry, async_add_entities) -> None: """Set up TotalConnect alarm panels based on a config entry.""" @@ -48,6 +52,21 @@ async def async_setup_entry(hass, entry, async_add_entities) -> None: async_add_entities(alarms, True) + # Set up services + platform = entity_platform.async_get_current_platform() + + platform.async_register_entity_service( + SERVICE_ALARM_ARM_AWAY_INSTANT, + None, + "async_alarm_arm_away_instant", + ) + + platform.async_register_entity_service( + SERVICE_ALARM_ARM_HOME_INSTANT, + None, + "async_alarm_arm_home_instant", + ) + class TotalConnectAlarm(CoordinatorEntity, alarm.AlarmControlPanelEntity): """Represent an TotalConnect status.""" @@ -201,3 +220,31 @@ class TotalConnectAlarm(CoordinatorEntity, alarm.AlarmControlPanelEntity): raise HomeAssistantError( f"TotalConnect failed to arm night {self._name}." ) from error + + async def async_alarm_arm_home_instant(self, code=None): + """Send arm home instant command.""" + await self.hass.async_add_executor_job(self._arm_home_instant) + await self.coordinator.async_request_refresh() + + def _arm_home_instant(self): + """Arm home instant synchronous.""" + try: + ArmingHelper(self._partition).arm_stay_instant() + except BadResultCodeError as error: + raise HomeAssistantError( + f"TotalConnect failed to arm home instant {self._name}." + ) from error + + async def async_alarm_arm_away_instant(self, code=None): + """Send arm away instant command.""" + await self.hass.async_add_executor_job(self._arm_away_instant) + await self.coordinator.async_request_refresh() + + def _arm_away_instant(self, code=None): + """Arm away instant synchronous.""" + try: + ArmingHelper(self._partition).arm_away_instant() + except BadResultCodeError as error: + raise HomeAssistantError( + f"TotalConnect failed to arm away instant {self._name}." + ) from error diff --git a/homeassistant/components/totalconnect/services.yaml b/homeassistant/components/totalconnect/services.yaml new file mode 100644 index 00000000000..0e8f8f8e217 --- /dev/null +++ b/homeassistant/components/totalconnect/services.yaml @@ -0,0 +1,15 @@ +arm_away_instant: + name: Arm Away Instant + description: Arm Away with zero entry delay. + target: + entity: + integration: totalconnect + domain: alarm_control_panel + +arm_home_instant: + name: Arm Home Instant + description: Arm Home with zero entry delay. + target: + entity: + integration: totalconnect + domain: alarm_control_panel diff --git a/tests/components/totalconnect/test_alarm_control_panel.py b/tests/components/totalconnect/test_alarm_control_panel.py index 368ebf93a04..c623b826459 100644 --- a/tests/components/totalconnect/test_alarm_control_panel.py +++ b/tests/components/totalconnect/test_alarm_control_panel.py @@ -5,6 +5,11 @@ from unittest.mock import patch import pytest from homeassistant.components.alarm_control_panel import DOMAIN as ALARM_DOMAIN +from homeassistant.components.totalconnect import DOMAIN +from homeassistant.components.totalconnect.alarm_control_panel import ( + SERVICE_ALARM_ARM_AWAY_INSTANT, + SERVICE_ALARM_ARM_HOME_INSTANT, +) from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, @@ -121,6 +126,82 @@ async def test_arm_home_failure(hass: HomeAssistant) -> None: assert mock_request.call_count == 2 +async def test_arm_home_instant_success(hass: HomeAssistant) -> None: + """Test arm home instant method success.""" + responses = [RESPONSE_DISARMED, RESPONSE_ARM_SUCCESS, RESPONSE_ARMED_STAY] + with patch(TOTALCONNECT_REQUEST, side_effect=responses) as mock_request: + await setup_platform(hass, ALARM_DOMAIN) + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED + assert hass.states.get(ENTITY_ID_2).state == STATE_ALARM_DISARMED + assert mock_request.call_count == 1 + + await hass.services.async_call( + DOMAIN, SERVICE_ALARM_ARM_HOME_INSTANT, DATA, blocking=True + ) + assert mock_request.call_count == 2 + + async_fire_time_changed(hass, dt.utcnow() + DELAY) + await hass.async_block_till_done() + assert mock_request.call_count == 3 + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_ARMED_HOME + + +async def test_arm_home_instant_failure(hass: HomeAssistant) -> None: + """Test arm home instant method failure.""" + responses = [RESPONSE_DISARMED, RESPONSE_ARM_FAILURE] + with patch(TOTALCONNECT_REQUEST, side_effect=responses) as mock_request: + await setup_platform(hass, ALARM_DOMAIN) + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED + assert mock_request.call_count == 1 + + with pytest.raises(HomeAssistantError) as err: + await hass.services.async_call( + DOMAIN, SERVICE_ALARM_ARM_HOME_INSTANT, DATA, blocking=True + ) + await hass.async_block_till_done() + assert f"{err.value}" == "TotalConnect failed to arm home instant test." + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED + assert mock_request.call_count == 2 + + +async def test_arm_away_instant_success(hass: HomeAssistant) -> None: + """Test arm home instant method success.""" + responses = [RESPONSE_DISARMED, RESPONSE_ARM_SUCCESS, RESPONSE_ARMED_AWAY] + with patch(TOTALCONNECT_REQUEST, side_effect=responses) as mock_request: + await setup_platform(hass, ALARM_DOMAIN) + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED + assert hass.states.get(ENTITY_ID_2).state == STATE_ALARM_DISARMED + assert mock_request.call_count == 1 + + await hass.services.async_call( + DOMAIN, SERVICE_ALARM_ARM_AWAY_INSTANT, DATA, blocking=True + ) + assert mock_request.call_count == 2 + + async_fire_time_changed(hass, dt.utcnow() + DELAY) + await hass.async_block_till_done() + assert mock_request.call_count == 3 + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_ARMED_AWAY + + +async def test_arm_away_instant_failure(hass: HomeAssistant) -> None: + """Test arm home instant method failure.""" + responses = [RESPONSE_DISARMED, RESPONSE_ARM_FAILURE] + with patch(TOTALCONNECT_REQUEST, side_effect=responses) as mock_request: + await setup_platform(hass, ALARM_DOMAIN) + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED + assert mock_request.call_count == 1 + + with pytest.raises(HomeAssistantError) as err: + await hass.services.async_call( + DOMAIN, SERVICE_ALARM_ARM_AWAY_INSTANT, DATA, blocking=True + ) + await hass.async_block_till_done() + assert f"{err.value}" == "TotalConnect failed to arm away instant test." + assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED + assert mock_request.call_count == 2 + + async def test_arm_home_invalid_usercode(hass: HomeAssistant) -> None: """Test arm home method with invalid usercode.""" responses = [RESPONSE_DISARMED, RESPONSE_USER_CODE_INVALID]