From 4b6e196606082a85b6b97769b0c97ec3ff507991 Mon Sep 17 00:00:00 2001
From: G Johansson <goran.johansson@shiftit.se>
Date: Tue, 11 Jan 2022 16:49:46 +0100
Subject: [PATCH] Improve code quality alarm_control_panel in yale_smart_alarm
 (#63672)

* Code quality yale_smart_alarm

* Raise error on arming

* Raise error if not possible to arm/disarm

* Type checking and settings properties

* Fix direct write state
---
 .../yale_smart_alarm/alarm_control_panel.py   | 138 +++++++++++++-----
 1 file changed, 102 insertions(+), 36 deletions(-)

diff --git a/homeassistant/components/yale_smart_alarm/alarm_control_panel.py b/homeassistant/components/yale_smart_alarm/alarm_control_panel.py
index d9106031345..d1f38059413 100644
--- a/homeassistant/components/yale_smart_alarm/alarm_control_panel.py
+++ b/homeassistant/components/yale_smart_alarm/alarm_control_panel.py
@@ -1,7 +1,15 @@
 """Support for Yale Alarm."""
 from __future__ import annotations
 
+from typing import TYPE_CHECKING
+
 import voluptuous as vol
+from yalesmartalarmclient.const import (
+    YALE_STATE_ARM_FULL,
+    YALE_STATE_ARM_PARTIAL,
+    YALE_STATE_DISARM,
+)
+from yalesmartalarmclient.exceptions import AuthenticationError, UnknownError
 
 from homeassistant.components.alarm_control_panel import (
     PLATFORM_SCHEMA as PARENT_PLATFORM_SCHEMA,
@@ -14,10 +22,11 @@ from homeassistant.components.alarm_control_panel.const import (
 from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
 from homeassistant.const import CONF_NAME, CONF_PASSWORD, CONF_USERNAME
 from homeassistant.core import HomeAssistant
+from homeassistant.exceptions import HomeAssistantError
 import homeassistant.helpers.config_validation as cv
 from homeassistant.helpers.entity import DeviceInfo
 from homeassistant.helpers.entity_platform import AddEntitiesCallback
-from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
+from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType
 from homeassistant.helpers.update_coordinator import CoordinatorEntity
 
 from .const import (
@@ -75,51 +84,108 @@ async def async_setup_entry(
 class YaleAlarmDevice(CoordinatorEntity, AlarmControlPanelEntity):
     """Represent a Yale Smart Alarm."""
 
+    _attr_code_arm_required = False
+    _attr_supported_features = SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY
+
     def __init__(self, coordinator: YaleDataUpdateCoordinator) -> None:
         """Initialize the Yale Alarm Device."""
         super().__init__(coordinator)
-        self._attr_name: str = coordinator.entry.data[CONF_NAME]
+        self._coordinator = coordinator
+        self._attr_name = coordinator.entry.data[CONF_NAME]
         self._attr_unique_id = coordinator.entry.entry_id
-        self._identifier: str = coordinator.entry.data[CONF_USERNAME]
-
-    @property
-    def device_info(self) -> DeviceInfo:
-        """Return device information about this entity."""
-        return DeviceInfo(
-            identifiers={(DOMAIN, self._identifier)},
+        self._attr_device_info = DeviceInfo(
+            identifiers={(DOMAIN, coordinator.entry.data[CONF_USERNAME])},
             manufacturer=MANUFACTURER,
             model=MODEL,
-            name=str(self.name),
+            name=self._attr_name,
         )
 
-    @property
-    def state(self):
-        """Return the state of the device."""
-        return STATE_MAP.get(self.coordinator.data["alarm"])
-
-    @property
-    def available(self):
-        """Return if entity is available."""
-        return STATE_MAP.get(self.coordinator.data["alarm"]) is not None
-
-    @property
-    def code_arm_required(self):
-        """Whether the code is required for arm actions."""
-        return False
-
-    @property
-    def supported_features(self) -> int:
-        """Return the list of supported features."""
-        return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY
-
-    def alarm_disarm(self, code=None):
+    async def async_alarm_disarm(self, code=None) -> None:
         """Send disarm command."""
-        self.coordinator.yale.disarm()
+        if TYPE_CHECKING:
+            assert self._coordinator.yale, "Connection to API is missing"
 
-    def alarm_arm_home(self, code=None):
+        try:
+            alarm_state = await self.hass.async_add_executor_job(
+                self._coordinator.yale.disarm
+            )
+        except (
+            AuthenticationError,
+            ConnectionError,
+            TimeoutError,
+            UnknownError,
+        ) as error:
+            raise HomeAssistantError(
+                f"Could not verify disarmed for {self._attr_name}: {error}"
+            ) from error
+
+        LOGGER.debug("Alarm disarmed: %s", alarm_state)
+        if alarm_state:
+            self.coordinator.data["alarm"] = YALE_STATE_DISARM
+            self.async_write_ha_state()
+            return
+        raise HomeAssistantError("Could not disarm, check system ready for disarming.")
+
+    async def async_alarm_arm_home(self, code=None) -> None:
         """Send arm home command."""
-        self.coordinator.yale.arm_partial()
+        if TYPE_CHECKING:
+            assert self._coordinator.yale, "Connection to API is missing"
 
-    def alarm_arm_away(self, code=None):
+        try:
+            alarm_state = await self.hass.async_add_executor_job(
+                self._coordinator.yale.arm_partial
+            )
+        except (
+            AuthenticationError,
+            ConnectionError,
+            TimeoutError,
+            UnknownError,
+        ) as error:
+            raise HomeAssistantError(
+                f"Could not verify armed home for {self._attr_name}: {error}"
+            ) from error
+
+        LOGGER.debug("Alarm armed home: %s", alarm_state)
+        if alarm_state:
+            self.coordinator.data["alarm"] = YALE_STATE_ARM_PARTIAL
+            self.async_write_ha_state()
+            return
+        raise HomeAssistantError("Could not arm home, check system ready for arming.")
+
+    async def async_alarm_arm_away(self, code=None) -> None:
         """Send arm away command."""
-        self.coordinator.yale.arm_full()
+        if TYPE_CHECKING:
+            assert self._coordinator.yale, "Connection to API is missing"
+
+        try:
+            alarm_state = await self.hass.async_add_executor_job(
+                self._coordinator.yale.arm_full
+            )
+        except (
+            AuthenticationError,
+            ConnectionError,
+            TimeoutError,
+            UnknownError,
+        ) as error:
+            raise HomeAssistantError(
+                f"Could not verify armed away for {self._attr_name}: {error}"
+            ) from error
+
+        LOGGER.debug("Alarm armed away: %s", alarm_state)
+        if alarm_state:
+            self.coordinator.data["alarm"] = YALE_STATE_ARM_FULL
+            self.async_write_ha_state()
+            return
+        raise HomeAssistantError("Could not arm away, check system ready for arming.")
+
+    @property
+    def available(self) -> bool:
+        """Return True if alarm is available."""
+        if STATE_MAP.get(self.coordinator.data["alarm"]) is None:
+            return False
+        return super().available
+
+    @property
+    def state(self) -> StateType:
+        """Return the state of the alarm."""
+        return STATE_MAP.get(self.coordinator.data["alarm"])