From e3e93df187346c009404b7748480a12b99b541a9 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Mon, 16 Sep 2024 15:19:09 +0200 Subject: [PATCH] Move elkm1 base entity to separate module (#126052) --- homeassistant/components/elkm1/__init__.py | 128 ---------------- .../components/elkm1/alarm_control_panel.py | 3 +- .../components/elkm1/binary_sensor.py | 3 +- homeassistant/components/elkm1/climate.py | 4 +- homeassistant/components/elkm1/entity.py | 144 ++++++++++++++++++ homeassistant/components/elkm1/light.py | 3 +- homeassistant/components/elkm1/scene.py | 3 +- homeassistant/components/elkm1/sensor.py | 3 +- homeassistant/components/elkm1/switch.py | 3 +- 9 files changed, 159 insertions(+), 135 deletions(-) create mode 100644 homeassistant/components/elkm1/entity.py diff --git a/homeassistant/components/elkm1/__init__.py b/homeassistant/components/elkm1/__init__.py index b66a4ce2ed8..34a35fbeb09 100644 --- a/homeassistant/components/elkm1/__init__.py +++ b/homeassistant/components/elkm1/__init__.py @@ -3,8 +3,6 @@ from __future__ import annotations import asyncio -from collections.abc import Iterable -from enum import Enum import logging import re from types import MappingProxyType @@ -17,7 +15,6 @@ import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( - ATTR_CONNECTIONS, CONF_ENABLED, CONF_EXCLUDE, CONF_HOST, @@ -33,8 +30,6 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant, ServiceCall, callback from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import ConfigType import homeassistant.util.dt as dt_util @@ -430,126 +425,3 @@ def _create_elk_services(hass: HomeAssistant) -> None: hass.services.async_register( DOMAIN, "set_time", _set_time_service, SET_TIME_SERVICE_SCHEMA ) - - -def create_elk_entities( - elk_data: ELKM1Data, - elk_elements: Iterable[Element], - element_type: str, - class_: Any, - entities: list[ElkEntity], -) -> list[ElkEntity] | None: - """Create the ElkM1 devices of a particular class.""" - auto_configure = elk_data.auto_configure - - if not auto_configure and not elk_data.config[element_type]["enabled"]: - return None - - elk = elk_data.elk - _LOGGER.debug("Creating elk entities for %s", elk) - - for element in elk_elements: - if auto_configure: - if not element.configured: - continue - # Only check the included list if auto configure is not - elif not elk_data.config[element_type]["included"][element.index]: - continue - - entities.append(class_(element, elk, elk_data)) - return entities - - -class ElkEntity(Entity): - """Base class for all Elk entities.""" - - _attr_has_entity_name = True - _attr_should_poll = False - - def __init__(self, element: Element, elk: Elk, elk_data: ELKM1Data) -> None: - """Initialize the base of all Elk devices.""" - self._elk = elk - self._element = element - self._mac = elk_data.mac - self._prefix = elk_data.prefix - self._temperature_unit: str = elk_data.config["temperature_unit"] - # unique_id starts with elkm1_ iff there is no prefix - # it starts with elkm1m_{prefix} iff there is a prefix - # this is to avoid a conflict between - # prefix=foo, name=bar (which would be elkm1_foo_bar) - # - and - - # prefix="", name="foo bar" (which would be elkm1_foo_bar also) - # we could have used elkm1__foo_bar for the latter, but that - # would have been a breaking change - if self._prefix != "": - uid_start = f"elkm1m_{self._prefix}" - else: - uid_start = "elkm1" - self._unique_id = f"{uid_start}_{self._element.default_name('_')}".lower() - self._attr_name = element.name - - @property - def unique_id(self) -> str: - """Return unique id of the element.""" - return self._unique_id - - @property - def extra_state_attributes(self) -> dict[str, Any]: - """Return the default attributes of the element.""" - dict_as_str = {} - for key, val in self._element.as_dict().items(): - dict_as_str[key] = val.value if isinstance(val, Enum) else val - return {**dict_as_str, **self.initial_attrs()} - - @property - def available(self) -> bool: - """Is the entity available to be updated.""" - return self._elk.is_connected() - - def initial_attrs(self) -> dict[str, Any]: - """Return the underlying element's attributes as a dict.""" - return {"index": self._element.index + 1} - - def _element_changed(self, element: Element, changeset: dict[str, Any]) -> None: - pass - - @callback - def _element_callback(self, element: Element, changeset: dict[str, Any]) -> None: - """Handle callback from an Elk element that has changed.""" - self._element_changed(element, changeset) - self.async_write_ha_state() - - async def async_added_to_hass(self) -> None: - """Register callback for ElkM1 changes and update entity state.""" - self._element.add_callback(self._element_callback) - self._element_callback(self._element, {}) - - @property - def device_info(self) -> DeviceInfo: - """Device info connecting via the ElkM1 system.""" - return DeviceInfo( - name=self._element.name, - identifiers={(DOMAIN, self._unique_id)}, - via_device=(DOMAIN, f"{self._prefix}_system"), - ) - - -class ElkAttachedEntity(ElkEntity): - """An elk entity that is attached to the elk system.""" - - @property - def device_info(self) -> DeviceInfo: - """Device info for the underlying ElkM1 system.""" - device_name = "ElkM1" - if self._prefix: - device_name += f" {self._prefix}" - device_info = DeviceInfo( - identifiers={(DOMAIN, f"{self._prefix}_system")}, - manufacturer="ELK Products, Inc.", - model="M1", - name=device_name, - sw_version=self._elk.panel.elkm1_version, - ) - if self._mac: - device_info[ATTR_CONNECTIONS] = {(CONNECTION_NETWORK_MAC, self._mac)} - return device_info diff --git a/homeassistant/components/elkm1/alarm_control_panel.py b/homeassistant/components/elkm1/alarm_control_panel.py index b24d0f869c6..f5437b6ed94 100644 --- a/homeassistant/components/elkm1/alarm_control_panel.py +++ b/homeassistant/components/elkm1/alarm_control_panel.py @@ -33,13 +33,14 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.typing import VolDictType -from . import ElkAttachedEntity, ElkEntity, ElkM1ConfigEntry, create_elk_entities +from . import ElkM1ConfigEntry from .const import ( ATTR_CHANGED_BY_ID, ATTR_CHANGED_BY_KEYPAD, ATTR_CHANGED_BY_TIME, ELK_USER_CODE_SERVICE_SCHEMA, ) +from .entity import ElkAttachedEntity, ElkEntity, create_elk_entities from .models import ELKM1Data DISPLAY_MESSAGE_SERVICE_SCHEMA: VolDictType = { diff --git a/homeassistant/components/elkm1/binary_sensor.py b/homeassistant/components/elkm1/binary_sensor.py index 171e9968ce6..854f8c56fb8 100644 --- a/homeassistant/components/elkm1/binary_sensor.py +++ b/homeassistant/components/elkm1/binary_sensor.py @@ -12,7 +12,8 @@ from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import ElkAttachedEntity, ElkEntity, ElkM1ConfigEntry +from . import ElkM1ConfigEntry +from .entity import ElkAttachedEntity, ElkEntity async def async_setup_entry( diff --git a/homeassistant/components/elkm1/climate.py b/homeassistant/components/elkm1/climate.py index 177f17d6e7e..bf5650f237b 100644 --- a/homeassistant/components/elkm1/climate.py +++ b/homeassistant/components/elkm1/climate.py @@ -22,7 +22,9 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue -from . import DOMAIN, ElkEntity, ElkM1ConfigEntry, create_elk_entities +from . import ElkM1ConfigEntry +from .const import DOMAIN +from .entity import ElkEntity, create_elk_entities SUPPORT_HVAC = [ HVACMode.OFF, diff --git a/homeassistant/components/elkm1/entity.py b/homeassistant/components/elkm1/entity.py new file mode 100644 index 00000000000..d9967d93967 --- /dev/null +++ b/homeassistant/components/elkm1/entity.py @@ -0,0 +1,144 @@ +"""Support the ElkM1 Gold and ElkM1 EZ8 alarm/integration panels.""" + +from __future__ import annotations + +from collections.abc import Iterable +from enum import Enum +import logging +from typing import Any + +from elkm1_lib.elements import Element +from elkm1_lib.elk import Elk + +from homeassistant.const import ATTR_CONNECTIONS +from homeassistant.core import callback +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo +from homeassistant.helpers.entity import Entity + +from .const import DOMAIN +from .models import ELKM1Data + +_LOGGER = logging.getLogger(__name__) + + +def create_elk_entities( + elk_data: ELKM1Data, + elk_elements: Iterable[Element], + element_type: str, + class_: Any, + entities: list[ElkEntity], +) -> list[ElkEntity] | None: + """Create the ElkM1 devices of a particular class.""" + auto_configure = elk_data.auto_configure + + if not auto_configure and not elk_data.config[element_type]["enabled"]: + return None + + elk = elk_data.elk + _LOGGER.debug("Creating elk entities for %s", elk) + + for element in elk_elements: + if auto_configure: + if not element.configured: + continue + # Only check the included list if auto configure is not + elif not elk_data.config[element_type]["included"][element.index]: + continue + + entities.append(class_(element, elk, elk_data)) + return entities + + +class ElkEntity(Entity): + """Base class for all Elk entities.""" + + _attr_has_entity_name = True + _attr_should_poll = False + + def __init__(self, element: Element, elk: Elk, elk_data: ELKM1Data) -> None: + """Initialize the base of all Elk devices.""" + self._elk = elk + self._element = element + self._mac = elk_data.mac + self._prefix = elk_data.prefix + self._temperature_unit: str = elk_data.config["temperature_unit"] + # unique_id starts with elkm1_ iff there is no prefix + # it starts with elkm1m_{prefix} iff there is a prefix + # this is to avoid a conflict between + # prefix=foo, name=bar (which would be elkm1_foo_bar) + # - and - + # prefix="", name="foo bar" (which would be elkm1_foo_bar also) + # we could have used elkm1__foo_bar for the latter, but that + # would have been a breaking change + if self._prefix != "": + uid_start = f"elkm1m_{self._prefix}" + else: + uid_start = "elkm1" + self._unique_id = f"{uid_start}_{self._element.default_name('_')}".lower() + self._attr_name = element.name + + @property + def unique_id(self) -> str: + """Return unique id of the element.""" + return self._unique_id + + @property + def extra_state_attributes(self) -> dict[str, Any]: + """Return the default attributes of the element.""" + dict_as_str = {} + for key, val in self._element.as_dict().items(): + dict_as_str[key] = val.value if isinstance(val, Enum) else val + return {**dict_as_str, **self.initial_attrs()} + + @property + def available(self) -> bool: + """Is the entity available to be updated.""" + return self._elk.is_connected() + + def initial_attrs(self) -> dict[str, Any]: + """Return the underlying element's attributes as a dict.""" + return {"index": self._element.index + 1} + + def _element_changed(self, element: Element, changeset: dict[str, Any]) -> None: + pass + + @callback + def _element_callback(self, element: Element, changeset: dict[str, Any]) -> None: + """Handle callback from an Elk element that has changed.""" + self._element_changed(element, changeset) + self.async_write_ha_state() + + async def async_added_to_hass(self) -> None: + """Register callback for ElkM1 changes and update entity state.""" + self._element.add_callback(self._element_callback) + self._element_callback(self._element, {}) + + @property + def device_info(self) -> DeviceInfo: + """Device info connecting via the ElkM1 system.""" + return DeviceInfo( + name=self._element.name, + identifiers={(DOMAIN, self._unique_id)}, + via_device=(DOMAIN, f"{self._prefix}_system"), + ) + + +class ElkAttachedEntity(ElkEntity): + """An elk entity that is attached to the elk system.""" + + @property + def device_info(self) -> DeviceInfo: + """Device info for the underlying ElkM1 system.""" + device_name = "ElkM1" + if self._prefix: + device_name += f" {self._prefix}" + device_info = DeviceInfo( + identifiers={(DOMAIN, f"{self._prefix}_system")}, + manufacturer="ELK Products, Inc.", + model="M1", + name=device_name, + sw_version=self._elk.panel.elkm1_version, + ) + if self._mac: + device_info[ATTR_CONNECTIONS] = {(CONNECTION_NETWORK_MAC, self._mac)} + return device_info diff --git a/homeassistant/components/elkm1/light.py b/homeassistant/components/elkm1/light.py index 17d525f6ddc..c041c9c9d65 100644 --- a/homeassistant/components/elkm1/light.py +++ b/homeassistant/components/elkm1/light.py @@ -12,7 +12,8 @@ from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEnti from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import ElkEntity, ElkM1ConfigEntry, create_elk_entities +from . import ElkM1ConfigEntry +from .entity import ElkEntity, create_elk_entities from .models import ELKM1Data diff --git a/homeassistant/components/elkm1/scene.py b/homeassistant/components/elkm1/scene.py index e4b738c9dbd..d8a1d83f326 100644 --- a/homeassistant/components/elkm1/scene.py +++ b/homeassistant/components/elkm1/scene.py @@ -10,7 +10,8 @@ from homeassistant.components.scene import Scene from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import ElkAttachedEntity, ElkEntity, ElkM1ConfigEntry, create_elk_entities +from . import ElkM1ConfigEntry +from .entity import ElkAttachedEntity, ElkEntity, create_elk_entities async def async_setup_entry( diff --git a/homeassistant/components/elkm1/sensor.py b/homeassistant/components/elkm1/sensor.py index 16f877719a7..e0231c86699 100644 --- a/homeassistant/components/elkm1/sensor.py +++ b/homeassistant/components/elkm1/sensor.py @@ -22,8 +22,9 @@ from homeassistant.helpers import entity_platform from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import VolDictType -from . import ElkAttachedEntity, ElkEntity, ElkM1ConfigEntry, create_elk_entities +from . import ElkM1ConfigEntry from .const import ATTR_VALUE, ELK_USER_CODE_SERVICE_SCHEMA +from .entity import ElkAttachedEntity, ElkEntity, create_elk_entities SERVICE_SENSOR_COUNTER_REFRESH = "sensor_counter_refresh" SERVICE_SENSOR_COUNTER_SET = "sensor_counter_set" diff --git a/homeassistant/components/elkm1/switch.py b/homeassistant/components/elkm1/switch.py index 70b38802a42..3e0f4849518 100644 --- a/homeassistant/components/elkm1/switch.py +++ b/homeassistant/components/elkm1/switch.py @@ -14,7 +14,8 @@ from homeassistant.components.switch import SwitchEntity from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import ElkAttachedEntity, ElkEntity, ElkM1ConfigEntry, create_elk_entities +from . import ElkM1ConfigEntry +from .entity import ElkAttachedEntity, ElkEntity, create_elk_entities from .models import ELKM1Data