Move elkm1 base entity to separate module (#126052)

This commit is contained in:
epenet 2024-09-16 15:19:09 +02:00 committed by GitHub
parent a17dc3cb52
commit e3e93df187
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 159 additions and 135 deletions

View File

@ -3,8 +3,6 @@
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
from collections.abc import Iterable
from enum import Enum
import logging import logging
import re import re
from types import MappingProxyType from types import MappingProxyType
@ -17,7 +15,6 @@ import voluptuous as vol
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
ATTR_CONNECTIONS,
CONF_ENABLED, CONF_ENABLED,
CONF_EXCLUDE, CONF_EXCLUDE,
CONF_HOST, CONF_HOST,
@ -33,8 +30,6 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant, ServiceCall, callback from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError
from homeassistant.helpers import config_validation as cv 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.event import async_track_time_interval
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
@ -430,126 +425,3 @@ def _create_elk_services(hass: HomeAssistant) -> None:
hass.services.async_register( hass.services.async_register(
DOMAIN, "set_time", _set_time_service, SET_TIME_SERVICE_SCHEMA 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

View File

@ -33,13 +33,14 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import VolDictType from homeassistant.helpers.typing import VolDictType
from . import ElkAttachedEntity, ElkEntity, ElkM1ConfigEntry, create_elk_entities from . import ElkM1ConfigEntry
from .const import ( from .const import (
ATTR_CHANGED_BY_ID, ATTR_CHANGED_BY_ID,
ATTR_CHANGED_BY_KEYPAD, ATTR_CHANGED_BY_KEYPAD,
ATTR_CHANGED_BY_TIME, ATTR_CHANGED_BY_TIME,
ELK_USER_CODE_SERVICE_SCHEMA, ELK_USER_CODE_SERVICE_SCHEMA,
) )
from .entity import ElkAttachedEntity, ElkEntity, create_elk_entities
from .models import ELKM1Data from .models import ELKM1Data
DISPLAY_MESSAGE_SERVICE_SCHEMA: VolDictType = { DISPLAY_MESSAGE_SERVICE_SCHEMA: VolDictType = {

View File

@ -12,7 +12,8 @@ from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback 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( async def async_setup_entry(

View File

@ -22,7 +22,9 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue 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 = [ SUPPORT_HVAC = [
HVACMode.OFF, HVACMode.OFF,

View File

@ -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

View File

@ -12,7 +12,8 @@ from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEnti
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback 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 from .models import ELKM1Data

View File

@ -10,7 +10,8 @@ from homeassistant.components.scene import Scene
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback 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( async def async_setup_entry(

View File

@ -22,8 +22,9 @@ from homeassistant.helpers import entity_platform
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import VolDictType 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 .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_REFRESH = "sensor_counter_refresh"
SERVICE_SENSOR_COUNTER_SET = "sensor_counter_set" SERVICE_SENSOR_COUNTER_SET = "sensor_counter_set"

View File

@ -14,7 +14,8 @@ from homeassistant.components.switch import SwitchEntity
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback 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 from .models import ELKM1Data