Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge entity description and device mapping #39

Merged
merged 5 commits into from
Jun 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions custom_components/matter_experimental/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,23 +185,25 @@ async def setup_node(self, node: MatterNode) -> None:
created = False

for platform, devices in DEVICE_PLATFORM.items():
device_mappings = devices.get(device.device_type)
entity_descriptions = devices.get(device.device_type)

if device_mappings is None:
if entity_descriptions is None:
continue

if not isinstance(device_mappings, list):
device_mappings = [device_mappings]
if not isinstance(entity_descriptions, list):
entity_descriptions = [entity_descriptions]

entities = []
for device_mapping in device_mappings:
for entity_description in entity_descriptions:
self.logger.debug(
"Creating %s entity for %s (%s)",
platform,
device.device_type.__name__,
hex(device.device_type.device_type),
)
entities.append(device_mapping.entity_cls(device, device_mapping))
entities.append(
entity_description.entity_cls(device, entity_description)
)

self.platform_handlers[platform](entities)
created = True
Expand Down
36 changes: 26 additions & 10 deletions custom_components/matter_experimental/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""Matter switches."""
from __future__ import annotations

from dataclasses import dataclass
from functools import partial
from typing import TYPE_CHECKING

from homeassistant.components.binary_sensor import (
Expand All @@ -17,8 +19,8 @@
from matter_server.vendor.chip.clusters import Objects as clusters

from .const import DOMAIN
from .device_platform_helper import DeviceMapping
from .entity import MatterEntity
from .entity_description import MatterEntityDescription

if TYPE_CHECKING:
from matter_server.client.matter import Matter
Expand All @@ -37,13 +39,15 @@ async def async_setup_entry(
class MatterBinarySensor(MatterEntity, BinarySensorEntity):
"""Representation of a Matter binary sensor."""

entity_description: MatterBinarySensorEntityDescription

@callback
def _update_from_device(self) -> None:
"""Update from device."""
self._attr_is_on = self._device.get_cluster(clusters.BooleanState).stateValue


class MatterOccupancySensor(MatterEntity, BinarySensorEntity):
class MatterOccupancySensor(MatterBinarySensor):
"""Representation of a Matter occupancy sensor."""

_attr_device_class = BinarySensorDeviceClass.OCCUPANCY
Expand All @@ -56,18 +60,30 @@ def _update_from_device(self) -> None:
self._attr_is_on = occupancy & 1 == 1


@dataclass
class MatterBinarySensorEntityDescription(
BinarySensorEntityDescription,
MatterEntityDescription,
):
"""Matter Binary Sensor entity description."""


# You can't set default values on inherited data classes
MatterSensorEntityDescriptionFactory = partial(
MatterBinarySensorEntityDescription, entity_cls=MatterBinarySensor
)

DEVICE_ENTITY: dict[
type[device_types.DeviceType], DeviceMapping | list[DeviceMapping]
type[device_types.DeviceType],
MatterEntityDescription | list[MatterEntityDescription],
] = {
device_types.ContactSensor: DeviceMapping(
entity_cls=MatterBinarySensor,
device_types.ContactSensor: MatterSensorEntityDescriptionFactory(
key=device_types.ContactSensor,
subscribe_attributes=(clusters.BooleanState.Attributes.StateValue,),
entity_description=BinarySensorEntityDescription(
key=None,
device_class=BinarySensorDeviceClass.DOOR,
),
device_class=BinarySensorDeviceClass.DOOR,
),
device_types.OccupancySensor: DeviceMapping(
device_types.OccupancySensor: MatterSensorEntityDescriptionFactory(
key=device_types.OccupancySensor,
entity_cls=MatterOccupancySensor,
subscribe_attributes=(clusters.OccupancySensing.Attributes.Occupancy,),
),
Expand Down
5 changes: 3 additions & 2 deletions custom_components/matter_experimental/device_platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@
if TYPE_CHECKING:
from matter_server.vendor.device_types import DeviceType

from .device_platform_helper import DeviceMapping
from .entity_description import MatterEntityDescription


DEVICE_PLATFORM: dict[
Platform, dict[type[DeviceType], DeviceMapping | list[DeviceMapping]]
Platform,
dict[type[DeviceType], MatterEntityDescription | list[MatterEntityDescription]],
] = {
Platform.BINARY_SENSOR: BINARY_SENSOR_DEVICE_ENTITY,
Platform.LIGHT: LIGHT_DEVICE_ENTITY,
Expand Down
18 changes: 0 additions & 18 deletions custom_components/matter_experimental/device_platform_helper.py

This file was deleted.

17 changes: 9 additions & 8 deletions custom_components/matter_experimental/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,20 @@
from matter_server.client.model.device import MatterDevice

from .const import DOMAIN
from .device_platform_helper import DeviceMapping
from .entity_description import MatterEntityDescriptionBaseClass


class MatterEntity(entity.Entity):

entity_description: MatterEntityDescriptionBaseClass
_attr_should_poll = False
_unsubscribe: Callable[..., Coroutine[Any, Any, None]] | None = None

def __init__(self, device: MatterDevice, mapping: DeviceMapping) -> None:
def __init__(
self, device: MatterDevice, entity_description: MatterEntityDescriptionBaseClass
) -> None:
self._device = device
self._device_mapping = mapping
if mapping.entity_description:
self.entity_description = mapping.entity_description
self.entity_description = entity_description
self._attr_unique_id = f"{device.node.matter.client.server_info.compressedFabricId}-{device.node.unique_id}-{device.endpoint_id}-{device.device_type.device_type}"

@property
Expand Down Expand Up @@ -54,19 +55,19 @@ async def init_matter_device(self) -> None:

self._attr_name = name

if not self._device_mapping.subscribe_attributes:
if not self.entity_description.subscribe_attributes:
self._update_from_device()
return

try:
# Subscribe to updates.
self._unsubscribe = await self._device.subscribe_updates(
self._device_mapping.subscribe_attributes, self._subscription_update
self.entity_description.subscribe_attributes, self._subscription_update
)

# Fetch latest info from the device.
await self._device.update_attributes(
self._device_mapping.subscribe_attributes
self.entity_description.subscribe_attributes
)
except FailedCommand as err:
self._device.node.matter.adapter.logger.warning(
Expand Down
23 changes: 23 additions & 0 deletions custom_components/matter_experimental/entity_description.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Matter entity description base class."""
from __future__ import annotations
balloob marked this conversation as resolved.
Show resolved Hide resolved

from dataclasses import dataclass
from typing import TYPE_CHECKING

from homeassistant.helpers.entity import EntityDescription

if TYPE_CHECKING:
from .entity import MatterEntity


@dataclass
class MatterEntityDescription:
"""Mixin to map a matter device to a HA entity."""

entity_cls: type[MatterEntity]
subscribe_attributes: tuple


@dataclass
class MatterEntityDescriptionBaseClass(EntityDescription, MatterEntityDescription):
"""For typing a base class that inherits from both entity descriptions."""
42 changes: 31 additions & 11 deletions custom_components/matter_experimental/light.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
"""Matter light."""
from __future__ import annotations

from dataclasses import dataclass
from functools import partial
from typing import TYPE_CHECKING, Any

from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from matter_server.client.model.device import MatterDevice
from matter_server.vendor import device_types
from matter_server.vendor.chip.clusters import Objects as clusters

from .const import DOMAIN
from .device_platform_helper import DeviceMapping
from .entity import MatterEntity
from .entity_description import MatterEntityDescription
from .util import renormalize

if TYPE_CHECKING:
Expand All @@ -35,7 +38,9 @@ async def async_setup_entry(
class MatterLight(MatterEntity, LightEntity):
"""Representation of a Matter light."""

def __init__(self, device: MatterDevice, mapping: DeviceMapping) -> None:
entity_description: MatterLightEntityDescription

def __init__(self, device: MatterDevice, mapping: MatterEntityDescription) -> None:
"""Initialize the light."""
super().__init__(device, mapping)
if self._supports_brightness():
Expand All @@ -45,7 +50,7 @@ def _supports_brightness(self):
"""Return if device supports brightness."""
return (
clusters.LevelControl.Attributes.CurrentLevel
in self._device_mapping.subscribe_attributes
in self.entity_description.subscribe_attributes
)

async def async_turn_on(self, **kwargs: Any) -> None:
Expand Down Expand Up @@ -82,7 +87,7 @@ def _update_from_device(self) -> None:

if (
clusters.LevelControl.Attributes.CurrentLevel
in self._device_mapping.subscribe_attributes
in self.entity_description.subscribe_attributes
):
level_control = self._device.get_cluster(clusters.LevelControl)

Expand All @@ -96,22 +101,37 @@ def _update_from_device(self) -> None:
)


@dataclass
class MatterLightEntityDescription(
EntityDescription,
MatterEntityDescription,
):
"""Matter light entity description."""


# You can't set default values on inherited data classes
MatterLightEntityDescriptionFactory = partial(
MatterLightEntityDescription, entity_cls=MatterLight
)


DEVICE_ENTITY: dict[
type[device_types.DeviceType], DeviceMapping | list[DeviceMapping]
type[device_types.DeviceType],
MatterEntityDescription | list[MatterEntityDescription],
] = {
device_types.OnOffLight: DeviceMapping(
entity_cls=MatterLight,
device_types.OnOffLight: MatterLightEntityDescriptionFactory(
key=device_types.OnOffLight,
subscribe_attributes=(clusters.OnOff.Attributes.OnOff,),
),
device_types.DimmableLight: DeviceMapping(
entity_cls=MatterLight,
device_types.DimmableLight: MatterLightEntityDescriptionFactory(
key=device_types.DimmableLight,
subscribe_attributes=(
clusters.OnOff.Attributes.OnOff,
clusters.LevelControl.Attributes.CurrentLevel,
),
),
device_types.DimmablePlugInUnit: DeviceMapping(
entity_cls=MatterLight,
device_types.DimmablePlugInUnit: MatterLightEntityDescriptionFactory(
key=device_types.DimmablePlugInUnit,
subscribe_attributes=(
clusters.OnOff.Attributes.OnOff,
clusters.LevelControl.Attributes.CurrentLevel,
Expand Down
Loading