From b66fd820ff04eccd61671532bb11e02e47f4d682 Mon Sep 17 00:00:00 2001 From: Eduard van Valkenburg Date: Mon, 17 Jan 2022 10:50:53 +0100 Subject: [PATCH] Implement entity_descriptions in SIA (#63130) * moved sia to entity_descriptions * cleanup * moved logger to const * redid entity description to static * small fix * moved entity description classes to proper file * improved entity def's and undid logger * further cleanup * redid naming logic * Clean up Co-authored-by: Martin Hjelmare --- .../components/sia/alarm_control_panel.py | 151 +++++++----- homeassistant/components/sia/binary_sensor.py | 223 ++++++++++-------- homeassistant/components/sia/config_flow.py | 1 - homeassistant/components/sia/const.py | 52 ++-- homeassistant/components/sia/hub.py | 1 - .../components/sia/sia_entity_base.py | 90 +++---- 6 files changed, 296 insertions(+), 222 deletions(-) diff --git a/homeassistant/components/sia/alarm_control_panel.py b/homeassistant/components/sia/alarm_control_panel.py index 5a6a4f6f55c..4743c5f0401 100644 --- a/homeassistant/components/sia/alarm_control_panel.py +++ b/homeassistant/components/sia/alarm_control_panel.py @@ -1,14 +1,18 @@ """Module for SIA Alarm Control Panels.""" from __future__ import annotations +from dataclasses import dataclass import logging -from typing import Any from pysiaalarm import SIAEvent -from homeassistant.components.alarm_control_panel import AlarmControlPanelEntity +from homeassistant.components.alarm_control_panel import ( + AlarmControlPanelEntity, + AlarmControlPanelEntityDescription, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( + CONF_PORT, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_CUSTOM_BYPASS, STATE_ALARM_ARMED_NIGHT, @@ -20,40 +24,58 @@ from homeassistant.core import HomeAssistant, State from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType -from .const import CONF_ACCOUNT, CONF_ACCOUNTS, CONF_ZONES, SIA_UNIQUE_ID_FORMAT_ALARM -from .sia_entity_base import SIABaseEntity +from .const import ( + CONF_ACCOUNT, + CONF_ACCOUNTS, + CONF_PING_INTERVAL, + CONF_ZONES, + KEY_ALARM, + PREVIOUS_STATE, + SIA_NAME_FORMAT, + SIA_UNIQUE_ID_FORMAT_ALARM, +) +from .sia_entity_base import SIABaseEntity, SIAEntityDescription _LOGGER = logging.getLogger(__name__) -DEVICE_CLASS_ALARM = "alarm" -PREVIOUS_STATE = "previous_state" -CODE_CONSEQUENCES: dict[str, StateType] = { - "PA": STATE_ALARM_TRIGGERED, - "JA": STATE_ALARM_TRIGGERED, - "TA": STATE_ALARM_TRIGGERED, - "BA": STATE_ALARM_TRIGGERED, - "CA": STATE_ALARM_ARMED_AWAY, - "CB": STATE_ALARM_ARMED_AWAY, - "CG": STATE_ALARM_ARMED_AWAY, - "CL": STATE_ALARM_ARMED_AWAY, - "CP": STATE_ALARM_ARMED_AWAY, - "CQ": STATE_ALARM_ARMED_AWAY, - "CS": STATE_ALARM_ARMED_AWAY, - "CF": STATE_ALARM_ARMED_CUSTOM_BYPASS, - "OA": STATE_ALARM_DISARMED, - "OB": STATE_ALARM_DISARMED, - "OG": STATE_ALARM_DISARMED, - "OP": STATE_ALARM_DISARMED, - "OQ": STATE_ALARM_DISARMED, - "OR": STATE_ALARM_DISARMED, - "OS": STATE_ALARM_DISARMED, - "NC": STATE_ALARM_ARMED_NIGHT, - "NL": STATE_ALARM_ARMED_NIGHT, - "BR": PREVIOUS_STATE, - "NP": PREVIOUS_STATE, - "NO": PREVIOUS_STATE, -} +@dataclass +class SIAAlarmControlPanelEntityDescription( + AlarmControlPanelEntityDescription, + SIAEntityDescription, +): + """Describes SIA alarm control panel entity.""" + + +ENTITY_DESCRIPTION_ALARM = SIAAlarmControlPanelEntityDescription( + key=KEY_ALARM, + code_consequences={ + "PA": STATE_ALARM_TRIGGERED, + "JA": STATE_ALARM_TRIGGERED, + "TA": STATE_ALARM_TRIGGERED, + "BA": STATE_ALARM_TRIGGERED, + "CA": STATE_ALARM_ARMED_AWAY, + "CB": STATE_ALARM_ARMED_AWAY, + "CG": STATE_ALARM_ARMED_AWAY, + "CL": STATE_ALARM_ARMED_AWAY, + "CP": STATE_ALARM_ARMED_AWAY, + "CQ": STATE_ALARM_ARMED_AWAY, + "CS": STATE_ALARM_ARMED_AWAY, + "CF": STATE_ALARM_ARMED_CUSTOM_BYPASS, + "OA": STATE_ALARM_DISARMED, + "OB": STATE_ALARM_DISARMED, + "OG": STATE_ALARM_DISARMED, + "OP": STATE_ALARM_DISARMED, + "OQ": STATE_ALARM_DISARMED, + "OR": STATE_ALARM_DISARMED, + "OS": STATE_ALARM_DISARMED, + "NC": STATE_ALARM_ARMED_NIGHT, + "NL": STATE_ALARM_ARMED_NIGHT, + "BR": PREVIOUS_STATE, + "NP": PREVIOUS_STATE, + "NO": PREVIOUS_STATE, + }, +) async def async_setup_entry( @@ -63,7 +85,19 @@ async def async_setup_entry( ) -> None: """Set up SIA alarm_control_panel(s) from a config entry.""" async_add_entities( - SIAAlarmControlPanel(entry, account_data, zone) + SIAAlarmControlPanel( + port=entry.data[CONF_PORT], + account=account_data[CONF_ACCOUNT], + zone=zone, + ping_interval=account_data[CONF_PING_INTERVAL], + entity_description=ENTITY_DESCRIPTION_ALARM, + unique_id=SIA_UNIQUE_ID_FORMAT_ALARM.format( + entry.entry_id, account_data[CONF_ACCOUNT], zone + ), + name=SIA_NAME_FORMAT.format( + entry.data[CONF_PORT], account_data[CONF_ACCOUNT], zone, "alarm" + ), + ) for account_data in entry.data[CONF_ACCOUNTS] for zone in range( 1, @@ -75,29 +109,32 @@ async def async_setup_entry( class SIAAlarmControlPanel(SIABaseEntity, AlarmControlPanelEntity): """Class for SIA Alarm Control Panels.""" + entity_description: SIAAlarmControlPanelEntityDescription + _attr_supported_features = 0 + def __init__( self, - entry: ConfigEntry, - account_data: dict[str, Any], - zone: int, + port: int, + account: str, + zone: int | None, + ping_interval: int, + entity_description: SIAAlarmControlPanelEntityDescription, + unique_id: str, + name: str, ) -> None: """Create SIAAlarmControlPanel object.""" - super().__init__(entry, account_data, zone, DEVICE_CLASS_ALARM) - self._attr_state: StateType = None - self._old_state: StateType = None - - self._attr_unique_id = SIA_UNIQUE_ID_FORMAT_ALARM.format( - self._entry.entry_id, self._account, self._zone + super().__init__( + port, + account, + zone, + ping_interval, + entity_description, + unique_id, + name, ) - def update_state(self, sia_event: SIAEvent) -> None: - """Update the state of the alarm control panel.""" - new_state = CODE_CONSEQUENCES.get(sia_event.code, None) - if new_state is not None: - _LOGGER.debug("New state will be %s", new_state) - if new_state == PREVIOUS_STATE: - new_state = self._old_state - self._attr_state, self._old_state = new_state, self._attr_state + self._attr_state: StateType = None + self._old_state: StateType = None def handle_last_state(self, last_state: State | None) -> None: """Handle the last state.""" @@ -106,7 +143,13 @@ class SIAAlarmControlPanel(SIABaseEntity, AlarmControlPanelEntity): if self.state == STATE_UNAVAILABLE: self._attr_available = False - @property - def supported_features(self) -> int: - """Return the list of supported features.""" - return 0 + def update_state(self, sia_event: SIAEvent) -> bool: + """Update the state of the alarm control panel.""" + new_state = self.entity_description.code_consequences.get(sia_event.code) + if new_state is None: + return False + _LOGGER.debug("New state will be %s", new_state) + if new_state == PREVIOUS_STATE: + new_state = self._old_state + self._attr_state, self._old_state = new_state, self._attr_state + return True diff --git a/homeassistant/components/sia/binary_sensor.py b/homeassistant/components/sia/binary_sensor.py index 980596367b2..e26e26cc0b7 100644 --- a/homeassistant/components/sia/binary_sensor.py +++ b/homeassistant/components/sia/binary_sensor.py @@ -2,64 +2,148 @@ from __future__ import annotations from collections.abc import Iterable +from dataclasses import dataclass import logging -from typing import Any from pysiaalarm import SIAEvent from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, BinarySensorEntity, + BinarySensorEntityDescription, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE +from homeassistant.const import CONF_PORT, STATE_OFF, STATE_ON, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant, State +from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ( CONF_ACCOUNT, CONF_ACCOUNTS, + CONF_PING_INTERVAL, CONF_ZONES, + KEY_MOISTURE, + KEY_POWER, + KEY_SMOKE, SIA_HUB_ZONE, + SIA_NAME_FORMAT, + SIA_NAME_FORMAT_HUB, SIA_UNIQUE_ID_FORMAT_BINARY, ) -from .sia_entity_base import SIABaseEntity +from .sia_entity_base import SIABaseEntity, SIAEntityDescription _LOGGER = logging.getLogger(__name__) -POWER_CODE_CONSEQUENCES: dict[str, bool] = { - "AT": False, - "AR": True, -} - -SMOKE_CODE_CONSEQUENCES: dict[str, bool] = { - "GA": True, - "GH": False, - "FA": True, - "FH": False, - "KA": True, - "KH": False, -} - -MOISTURE_CODE_CONSEQUENCES: dict[str, bool] = { - "WA": True, - "WH": False, -} +@dataclass +class SIABinarySensorEntityDescription( + BinarySensorEntityDescription, + SIAEntityDescription, +): + """Describes SIA sensor entity.""" -def generate_binary_sensors(entry) -> Iterable[SIABinarySensorBase]: +ENTITY_DESCRIPTION_POWER = SIABinarySensorEntityDescription( + key=KEY_POWER, + device_class=BinarySensorDeviceClass.POWER, + entity_category=EntityCategory.DIAGNOSTIC, + code_consequences={ + "AT": False, + "AR": True, + }, +) + +ENTITY_DESCRIPTION_SMOKE = SIABinarySensorEntityDescription( + key=KEY_SMOKE, + device_class=BinarySensorDeviceClass.SMOKE, + code_consequences={ + "GA": True, + "GH": False, + "FA": True, + "FH": False, + "KA": True, + "KH": False, + }, + entity_registry_enabled_default=False, +) + +ENTITY_DESCRIPTION_MOISTURE = SIABinarySensorEntityDescription( + key=KEY_MOISTURE, + device_class=BinarySensorDeviceClass.MOISTURE, + code_consequences={ + "WA": True, + "WH": False, + }, + entity_registry_enabled_default=False, +) + + +def generate_binary_sensors(entry) -> Iterable[SIABinarySensor]: """Generate binary sensors. For each Account there is one power sensor with zone == 0. For each Zone in each Account there is one smoke and one moisture sensor. """ - for account in entry.data[CONF_ACCOUNTS]: - yield SIABinarySensorPower(entry, account) - zones = entry.options[CONF_ACCOUNTS][account[CONF_ACCOUNT]][CONF_ZONES] + for account_data in entry.data[CONF_ACCOUNTS]: + yield SIABinarySensor( + port=entry.data[CONF_PORT], + account=account_data[CONF_ACCOUNT], + zone=SIA_HUB_ZONE, + ping_interval=account_data[CONF_PING_INTERVAL], + entity_description=ENTITY_DESCRIPTION_POWER, + unique_id=SIA_UNIQUE_ID_FORMAT_BINARY.format( + entry.entry_id, + account_data[CONF_ACCOUNT], + SIA_HUB_ZONE, + ENTITY_DESCRIPTION_POWER.device_class, + ), + name=SIA_NAME_FORMAT_HUB.format( + entry.data[CONF_PORT], + account_data[CONF_ACCOUNT], + ENTITY_DESCRIPTION_POWER.device_class, + ), + ) + zones = entry.options[CONF_ACCOUNTS][account_data[CONF_ACCOUNT]][CONF_ZONES] for zone in range(1, zones + 1): - yield SIABinarySensorSmoke(entry, account, zone) - yield SIABinarySensorMoisture(entry, account, zone) + yield SIABinarySensor( + port=entry.data[CONF_PORT], + account=account_data[CONF_ACCOUNT], + zone=zone, + ping_interval=account_data[CONF_PING_INTERVAL], + entity_description=ENTITY_DESCRIPTION_SMOKE, + unique_id=SIA_UNIQUE_ID_FORMAT_BINARY.format( + entry.entry_id, + account_data[CONF_ACCOUNT], + zone, + ENTITY_DESCRIPTION_SMOKE.device_class, + ), + name=SIA_NAME_FORMAT.format( + entry.data[CONF_PORT], + account_data[CONF_ACCOUNT], + zone, + ENTITY_DESCRIPTION_SMOKE.device_class, + ), + ) + yield SIABinarySensor( + port=entry.data[CONF_PORT], + account=account_data[CONF_ACCOUNT], + zone=zone, + ping_interval=account_data[CONF_PING_INTERVAL], + entity_description=ENTITY_DESCRIPTION_MOISTURE, + unique_id=SIA_UNIQUE_ID_FORMAT_BINARY.format( + entry.entry_id, + account_data[CONF_ACCOUNT], + zone, + ENTITY_DESCRIPTION_MOISTURE.device_class, + ), + name=SIA_NAME_FORMAT.format( + entry.data[CONF_PORT], + account_data[CONF_ACCOUNT], + zone, + ENTITY_DESCRIPTION_MOISTURE.device_class, + ), + ) async def async_setup_entry( @@ -71,22 +155,10 @@ async def async_setup_entry( async_add_entities(generate_binary_sensors(entry)) -class SIABinarySensorBase(SIABaseEntity, BinarySensorEntity): +class SIABinarySensor(SIABaseEntity, BinarySensorEntity): """Class for SIA Binary Sensors.""" - def __init__( - self, - entry: ConfigEntry, - account_data: dict[str, Any], - zone: int, - device_class: BinarySensorDeviceClass, - ) -> None: - """Initialize a base binary sensor.""" - super().__init__(entry, account_data, zone) - self._attr_device_class = device_class - self._attr_unique_id = SIA_UNIQUE_ID_FORMAT_BINARY.format( - self._entry.entry_id, self._account, self._zone, self._attr_device_class - ) + entity_description: SIABinarySensorEntityDescription def handle_last_state(self, last_state: State | None) -> None: """Handle the last state.""" @@ -98,66 +170,11 @@ class SIABinarySensorBase(SIABaseEntity, BinarySensorEntity): elif last_state.state == STATE_UNAVAILABLE: self._attr_available = False - -class SIABinarySensorMoisture(SIABinarySensorBase): - """Class for Moisture Binary Sensors.""" - - def __init__( - self, - entry: ConfigEntry, - account_data: dict[str, Any], - zone: int, - ) -> None: - """Initialize a Moisture binary sensor.""" - super().__init__(entry, account_data, zone, BinarySensorDeviceClass.MOISTURE) - self._attr_entity_registry_enabled_default = False - - def update_state(self, sia_event: SIAEvent) -> None: + def update_state(self, sia_event: SIAEvent) -> bool: """Update the state of the binary sensor.""" - new_state = MOISTURE_CODE_CONSEQUENCES.get(sia_event.code, None) - if new_state is not None: - _LOGGER.debug("New state will be %s", new_state) - self._attr_is_on = new_state - - -class SIABinarySensorSmoke(SIABinarySensorBase): - """Class for Smoke Binary Sensors.""" - - def __init__( - self, - entry: ConfigEntry, - account_data: dict[str, Any], - zone: int, - ) -> None: - """Initialize a Smoke binary sensor.""" - super().__init__(entry, account_data, zone, BinarySensorDeviceClass.SMOKE) - self._attr_entity_registry_enabled_default = False - - def update_state(self, sia_event: SIAEvent) -> None: - """Update the state of the binary sensor.""" - new_state = SMOKE_CODE_CONSEQUENCES.get(sia_event.code, None) - if new_state is not None: - _LOGGER.debug("New state will be %s", new_state) - self._attr_is_on = new_state - - -class SIABinarySensorPower(SIABinarySensorBase): - """Class for Power Binary Sensors.""" - - def __init__( - self, - entry: ConfigEntry, - account_data: dict[str, Any], - ) -> None: - """Initialize a Power binary sensor.""" - super().__init__( - entry, account_data, SIA_HUB_ZONE, BinarySensorDeviceClass.POWER - ) - self._attr_entity_registry_enabled_default = True - - def update_state(self, sia_event: SIAEvent) -> None: - """Update the state of the binary sensor.""" - new_state = POWER_CODE_CONSEQUENCES.get(sia_event.code, None) - if new_state is not None: - _LOGGER.debug("New state will be %s", new_state) - self._attr_is_on = new_state + new_state = self.entity_description.code_consequences.get(sia_event.code) + if new_state is None: + return False + _LOGGER.debug("New state will be %s", new_state) + self._attr_is_on = bool(new_state) + return True diff --git a/homeassistant/components/sia/config_flow.py b/homeassistant/components/sia/config_flow.py index c43faf5475c..a2f7bc744e3 100644 --- a/homeassistant/components/sia/config_flow.py +++ b/homeassistant/components/sia/config_flow.py @@ -35,7 +35,6 @@ from .hub import SIAHub _LOGGER = logging.getLogger(__name__) - HUB_SCHEMA = vol.Schema( { vol.Required(CONF_PORT): int, diff --git a/homeassistant/components/sia/const.py b/homeassistant/components/sia/const.py index 183b3422f78..82ef6ad9429 100644 --- a/homeassistant/components/sia/const.py +++ b/homeassistant/components/sia/const.py @@ -1,28 +1,40 @@ """Constants for the sia integration.""" +from __future__ import annotations + +from typing import Final + from homeassistant.const import Platform -PLATFORMS = [Platform.ALARM_CONTROL_PANEL, Platform.BINARY_SENSOR] +PLATFORMS: Final = [Platform.ALARM_CONTROL_PANEL, Platform.BINARY_SENSOR] -DOMAIN = "sia" +DOMAIN: Final = "sia" -ATTR_CODE = "last_code" -ATTR_ZONE = "last_zone" -ATTR_MESSAGE = "last_message" -ATTR_ID = "last_id" -ATTR_TIMESTAMP = "last_timestamp" +ATTR_CODE: Final = "last_code" +ATTR_ZONE: Final = "last_zone" +ATTR_MESSAGE: Final = "last_message" +ATTR_ID: Final = "last_id" +ATTR_TIMESTAMP: Final = "last_timestamp" -TITLE = "SIA Alarm on port {}" -CONF_ACCOUNT = "account" -CONF_ACCOUNTS = "accounts" -CONF_ADDITIONAL_ACCOUNTS = "additional_account" -CONF_ENCRYPTION_KEY = "encryption_key" -CONF_IGNORE_TIMESTAMPS = "ignore_timestamps" -CONF_PING_INTERVAL = "ping_interval" -CONF_ZONES = "zones" +TITLE: Final = "SIA Alarm on port {}" +CONF_ACCOUNT: Final = "account" +CONF_ACCOUNTS: Final = "accounts" +CONF_ADDITIONAL_ACCOUNTS: Final = "additional_account" +CONF_ENCRYPTION_KEY: Final = "encryption_key" +CONF_IGNORE_TIMESTAMPS: Final = "ignore_timestamps" +CONF_PING_INTERVAL: Final = "ping_interval" +CONF_ZONES: Final = "zones" -SIA_NAME_FORMAT = "{} - {} - zone {} - {}" -SIA_UNIQUE_ID_FORMAT_ALARM = "{}_{}_{}" -SIA_UNIQUE_ID_FORMAT_BINARY = "{}_{}_{}_{}" -SIA_HUB_ZONE = 0 +SIA_NAME_FORMAT: Final = "{} - {} - zone {} - {}" +SIA_NAME_FORMAT_HUB: Final = "{} - {} - {}" +SIA_UNIQUE_ID_FORMAT_ALARM: Final = "{}_{}_{}" +SIA_UNIQUE_ID_FORMAT_BINARY: Final = "{}_{}_{}_{}" +SIA_UNIQUE_ID_FORMAT_HUB: Final = "{}_{}_{}" +SIA_HUB_ZONE: Final = 0 +SIA_EVENT: Final = "sia_event_{}_{}" -SIA_EVENT = "sia_event_{}_{}" +KEY_ALARM: Final = "alarm_control_panel" +KEY_SMOKE: Final = "smoke" +KEY_MOISTURE: Final = "moisture" +KEY_POWER: Final = "power" + +PREVIOUS_STATE: Final = "previous_state" diff --git a/homeassistant/components/sia/hub.py b/homeassistant/components/sia/hub.py index 7db432256f9..4f46e88162c 100644 --- a/homeassistant/components/sia/hub.py +++ b/homeassistant/components/sia/hub.py @@ -27,7 +27,6 @@ from .utils import get_event_data_from_sia_event _LOGGER = logging.getLogger(__name__) - DEFAULT_TIMEBAND = (80, 40) IGNORED_TIMEBAND = (3600, 1800) diff --git a/homeassistant/components/sia/sia_entity_base.py b/homeassistant/components/sia/sia_entity_base.py index f4937e326eb..fee3c0b2262 100644 --- a/homeassistant/components/sia/sia_entity_base.py +++ b/homeassistant/components/sia/sia_entity_base.py @@ -2,52 +2,68 @@ from __future__ import annotations from abc import abstractmethod +from dataclasses import dataclass import logging -from typing import Any from pysiaalarm import SIAEvent -from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_PORT from homeassistant.core import CALLBACK_TYPE, State, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.entity import DeviceInfo, EntityDescription from homeassistant.helpers.event import async_call_later from homeassistant.helpers.restore_state import RestoreEntity +from homeassistant.helpers.typing import StateType -from .const import CONF_ACCOUNT, CONF_PING_INTERVAL, DOMAIN, SIA_EVENT, SIA_NAME_FORMAT +from .const import DOMAIN, SIA_EVENT, SIA_HUB_ZONE from .utils import get_attr_from_sia_event, get_unavailability_interval _LOGGER = logging.getLogger(__name__) +@dataclass +class SIARequiredKeysMixin: + """Required keys for SIA entities.""" + + code_consequences: dict[str, StateType | bool] + + +@dataclass +class SIAEntityDescription(EntityDescription, SIARequiredKeysMixin): + """Entity Description for SIA entities.""" + + class SIABaseEntity(RestoreEntity): """Base class for SIA entities.""" + entity_description: SIAEntityDescription + def __init__( self, - entry: ConfigEntry, - account_data: dict[str, Any], - zone: int, - device_class: str | None = None, + port: int, + account: str, + zone: int | None, + ping_interval: int, + entity_description: SIAEntityDescription, + unique_id: str, + name: str, ) -> None: """Create SIABaseEntity object.""" - self._entry: ConfigEntry = entry - self._account_data: dict[str, Any] = account_data - self._zone: int = zone - self._attr_device_class = device_class - - self._port: int = self._entry.data[CONF_PORT] - self._account: str = self._account_data[CONF_ACCOUNT] - self._ping_interval: int = self._account_data[CONF_PING_INTERVAL] + self.port = port + self.account = account + self.zone = zone + self.ping_interval = ping_interval + self.entity_description = entity_description + self._attr_unique_id = unique_id + self._attr_name = name + self._attr_device_info = DeviceInfo( + name=name, + identifiers={(DOMAIN, unique_id)}, + via_device=(DOMAIN, f"{port}_{account}"), + ) self._cancel_availability_cb: CALLBACK_TYPE | None = None - self._attr_extra_state_attributes = {} self._attr_should_poll = False - self._attr_name = SIA_NAME_FORMAT.format( - self._port, self._account, self._zone, self._attr_device_class - ) async def async_added_to_hass(self) -> None: """Run when entity about to be added to hass. @@ -61,7 +77,7 @@ class SIABaseEntity(RestoreEntity): self.async_on_remove( async_dispatcher_connect( self.hass, - SIA_EVENT.format(self._port, self._account), + SIA_EVENT.format(self.port, self.account), self.async_handle_event, ) ) @@ -83,19 +99,18 @@ class SIABaseEntity(RestoreEntity): @callback def async_handle_event(self, sia_event: SIAEvent) -> None: - """Listen to dispatcher events for this port and account and update state and attributes. - - If the port and account combo receives any message it means it is online and can therefore be set to available. - """ + """Listen to dispatcher events for this port and account and update state and attributes.""" _LOGGER.debug("Received event: %s", sia_event) - if int(sia_event.ri) == self._zone: - self._attr_extra_state_attributes.update(get_attr_from_sia_event(sia_event)) - self.update_state(sia_event) - self.async_reset_availability_cb() + if int(sia_event.ri) not in (self.zone, SIA_HUB_ZONE): + return + self._attr_extra_state_attributes.update(get_attr_from_sia_event(sia_event)) + state_changed = self.update_state(sia_event) + if state_changed: + self.async_reset_availability_cb() self.async_write_ha_state() @abstractmethod - def update_state(self, sia_event: SIAEvent) -> None: + def update_state(self, sia_event: SIAEvent) -> bool: """Do the entity specific state updates.""" @callback @@ -110,7 +125,7 @@ class SIABaseEntity(RestoreEntity): """Create a availability cb and return the callback.""" self._cancel_availability_cb = async_call_later( self.hass, - get_unavailability_interval(self._ping_interval), + get_unavailability_interval(self.ping_interval), self.async_set_unavailable, ) @@ -119,14 +134,3 @@ class SIABaseEntity(RestoreEntity): """Set unavailable.""" self._attr_available = False self.async_write_ha_state() - - @property - def device_info(self) -> DeviceInfo: - """Return the device_info.""" - assert self._attr_name is not None - assert self.unique_id is not None - return DeviceInfo( - name=self._attr_name, - identifiers={(DOMAIN, self.unique_id)}, - via_device=(DOMAIN, f"{self._port}_{self._account}"), - )