"""Base classes for KNX entities.""" from dataclasses import dataclass from typing import TYPE_CHECKING, Any from xknx.devices import Device as XknxDevice from homeassistant.const import CONF_ENTITY_CATEGORY, CONF_NAME, EntityCategory from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_platform import EntityPlatform from homeassistant.helpers.entity_registry import RegistryEntry from .const import DOMAIN from .storage.config_store import PlatformControllerBase from .storage.const import CONF_DEVICE_INFO if TYPE_CHECKING: from .knx_module import KNXModule @dataclass(slots=True, frozen=True) class KnxEntityIdentifier: """Class to identify KNX entities in KNX frontend.""" platform: str unique_id: str ui: bool # ui or yaml entity class KnxUiEntityPlatformController(PlatformControllerBase): """Class to manage dynamic adding and reloading of UI entities.""" def __init__( self, knx_module: KNXModule, entity_platform: EntityPlatform, entity_class: type[KnxUiEntity], ) -> None: """Initialize the UI platform.""" self._knx_module = knx_module self._entity_platform = entity_platform self._entity_class = entity_class async def create_entity(self, unique_id: str, config: dict[str, Any]) -> None: """Add a new UI entity.""" await self._entity_platform.async_add_entities( [self._entity_class(self._knx_module, unique_id, config)] ) async def update_entity( self, entity_entry: RegistryEntry, config: dict[str, Any] ) -> None: """Update an existing UI entities configuration.""" await self._entity_platform.async_remove_entity(entity_entry.entity_id) await self.create_entity(unique_id=entity_entry.unique_id, config=config) class _KnxEntityBase(Entity): """Representation of a KNX entity.""" _attr_should_poll = False _attr_unique_id: str _knx_module: KNXModule _device: XknxDevice _knx_entity_identifier: KnxEntityIdentifier | None = None @property def available(self) -> bool: """Return True if entity is available.""" return self._knx_module.connected async def async_update(self) -> None: """Request a state update from KNX bus.""" await self._device.sync() def after_update_callback(self, device: XknxDevice) -> None: """Call after device was updated.""" self.async_write_ha_state() async def async_added_to_hass(self) -> None: """Store register state change callback and start device object.""" self._device.register_device_updated_cb(self.after_update_callback) self._device.xknx.devices.async_add(self._device) if uid := self.unique_id: self._knx_entity_identifier = KnxEntityIdentifier( platform=self.platform_data.domain, unique_id=uid, ui=isinstance(self, KnxUiEntity), ) self._knx_module.add_to_group_address_entities( group_addresses=self._device.group_addresses(), identifier=self._knx_entity_identifier, ) # super call needed to have methods of multi-inherited classes called # eg. for restoring state (like _KNXSwitch) await super().async_added_to_hass() async def async_will_remove_from_hass(self) -> None: """Disconnect device object when removed.""" self._device.unregister_device_updated_cb(self.after_update_callback) self._device.xknx.devices.async_remove(self._device) if self._knx_entity_identifier: self._knx_module.remove_from_group_address_entities( group_addresses=self._device.group_addresses(), identifier=self._knx_entity_identifier, ) class KnxYamlEntity(_KnxEntityBase): """Representation of a KNX entity configured from YAML.""" def __init__( self, knx_module: KNXModule, unique_id: str, name: str, entity_category: EntityCategory | None, ) -> None: """Initialize the YAML entity.""" self._knx_module = knx_module self._attr_name = name or None self._attr_unique_id = unique_id self._attr_entity_category = entity_category class KnxUiEntity(_KnxEntityBase): """Representation of a KNX UI entity.""" _attr_has_entity_name = True def __init__( self, knx_module: KNXModule, unique_id: str, entity_config: dict[str, Any] ) -> None: """Initialize the UI entity.""" self._knx_module = knx_module self._attr_name = entity_config[CONF_NAME] self._attr_unique_id = unique_id if entity_category := entity_config.get(CONF_ENTITY_CATEGORY): self._attr_entity_category = EntityCategory(entity_category) if device_info := entity_config.get(CONF_DEVICE_INFO): self._attr_device_info = DeviceInfo(identifiers={(DOMAIN, device_info)})