127 lines
4.4 KiB
Python
127 lines
4.4 KiB
Python
"""Matter entity base class."""
|
|
from __future__ import annotations
|
|
|
|
from abc import abstractmethod
|
|
from collections.abc import Callable
|
|
from dataclasses import dataclass
|
|
import logging
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
from matter_server.common.models.device_type_instance import MatterDeviceTypeInstance
|
|
from matter_server.common.models.events import EventType
|
|
from matter_server.common.models.node_device import AbstractMatterNodeDevice
|
|
|
|
from homeassistant.core import callback
|
|
from homeassistant.helpers.entity import DeviceInfo, Entity, EntityDescription
|
|
|
|
from .const import DOMAIN
|
|
|
|
if TYPE_CHECKING:
|
|
from matter_server.client import MatterClient
|
|
from matter_server.common.models.node import MatterAttribute
|
|
|
|
LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
@dataclass
|
|
class MatterEntityDescription:
|
|
"""Mixin to map a matter device to a Home Assistant entity."""
|
|
|
|
entity_cls: type[MatterEntity]
|
|
subscribe_attributes: tuple
|
|
|
|
|
|
@dataclass
|
|
class MatterEntityDescriptionBaseClass(EntityDescription, MatterEntityDescription):
|
|
"""For typing a base class that inherits from both entity descriptions."""
|
|
|
|
|
|
class MatterEntity(Entity):
|
|
"""Entity class for Matter devices."""
|
|
|
|
entity_description: MatterEntityDescriptionBaseClass
|
|
_attr_should_poll = False
|
|
_attr_has_entity_name = True
|
|
|
|
def __init__(
|
|
self,
|
|
matter_client: MatterClient,
|
|
node_device: AbstractMatterNodeDevice,
|
|
device_type_instance: MatterDeviceTypeInstance,
|
|
entity_description: MatterEntityDescriptionBaseClass,
|
|
) -> None:
|
|
"""Initialize the entity."""
|
|
self.matter_client = matter_client
|
|
self._node_device = node_device
|
|
self._device_type_instance = device_type_instance
|
|
self.entity_description = entity_description
|
|
node = device_type_instance.node
|
|
self._unsubscribes: list[Callable] = []
|
|
# for fast lookups we create a mapping to the attribute paths
|
|
self._attributes_map: dict[type, str] = {}
|
|
server_info = matter_client.server_info
|
|
# The server info is set when the client connects to the server.
|
|
assert server_info is not None
|
|
self._attr_unique_id = (
|
|
f"{server_info.compressed_fabric_id}-"
|
|
f"{node.unique_id}-"
|
|
f"{device_type_instance.endpoint}-"
|
|
f"{device_type_instance.device_type.device_type}"
|
|
)
|
|
|
|
@property
|
|
def device_info(self) -> DeviceInfo | None:
|
|
"""Return device info for device registry."""
|
|
return {"identifiers": {(DOMAIN, self._node_device.device_info().uniqueID)}}
|
|
|
|
async def async_added_to_hass(self) -> None:
|
|
"""Handle being added to Home Assistant."""
|
|
await super().async_added_to_hass()
|
|
|
|
# Subscribe to attribute updates.
|
|
for attr_cls in self.entity_description.subscribe_attributes:
|
|
if matter_attr := self.get_matter_attribute(attr_cls):
|
|
self._attributes_map[attr_cls] = matter_attr.path
|
|
self._unsubscribes.append(
|
|
self.matter_client.subscribe(
|
|
self._on_matter_event,
|
|
EventType.ATTRIBUTE_UPDATED,
|
|
self._device_type_instance.node.node_id,
|
|
matter_attr.path,
|
|
)
|
|
)
|
|
continue
|
|
# not sure if this can happen, but just in case log it.
|
|
LOGGER.warning("Attribute not found on device: %s", attr_cls)
|
|
|
|
# make sure to update the attributes once
|
|
self._update_from_device()
|
|
|
|
async def async_will_remove_from_hass(self) -> None:
|
|
"""Run when entity will be removed from hass."""
|
|
for unsub in self._unsubscribes:
|
|
unsub()
|
|
|
|
@callback
|
|
def _on_matter_event(self, event: EventType, data: Any = None) -> None:
|
|
"""Call on update."""
|
|
self._update_from_device()
|
|
self.async_write_ha_state()
|
|
|
|
@callback
|
|
@abstractmethod
|
|
def _update_from_device(self) -> None:
|
|
"""Update data from Matter device."""
|
|
|
|
@callback
|
|
def get_matter_attribute(self, attribute: type) -> MatterAttribute | None:
|
|
"""Lookup MatterAttribute instance on device instance by providing the attribute class."""
|
|
return next(
|
|
(
|
|
x
|
|
for x in self._device_type_instance.attributes
|
|
if x.attribute_type == attribute
|
|
),
|
|
None,
|
|
)
|