Refactor matter device entity value conversion (#90368)

This commit is contained in:
Martin Hjelmare 2023-03-27 22:21:56 +02:00 committed by GitHub
parent fb4b35709d
commit 182af87f97
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 60 additions and 39 deletions

View File

@ -1,6 +1,8 @@
"""Matter binary sensors.""" """Matter binary sensors."""
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass
from chip.clusters import Objects as clusters from chip.clusters import Objects as clusters
from chip.clusters.Objects import uint from chip.clusters.Objects import uint
from chip.clusters.Types import Nullable, NullValue from chip.clusters.Types import Nullable, NullValue
@ -15,7 +17,7 @@ from homeassistant.const import Platform
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .entity import MatterEntity from .entity import MatterEntity, MatterEntityDescription
from .helpers import get_matter from .helpers import get_matter
from .models import MatterDiscoverySchema from .models import MatterDiscoverySchema
@ -30,9 +32,18 @@ async def async_setup_entry(
matter.register_platform_handler(Platform.BINARY_SENSOR, async_add_entities) matter.register_platform_handler(Platform.BINARY_SENSOR, async_add_entities)
@dataclass
class MatterBinarySensorEntityDescription(
BinarySensorEntityDescription, MatterEntityDescription
):
"""Describe Matter binary sensor entities."""
class MatterBinarySensor(MatterEntity, BinarySensorEntity): class MatterBinarySensor(MatterEntity, BinarySensorEntity):
"""Representation of a Matter binary sensor.""" """Representation of a Matter binary sensor."""
entity_description: MatterBinarySensorEntityDescription
@callback @callback
def _update_from_device(self) -> None: def _update_from_device(self) -> None:
"""Update from device.""" """Update from device."""
@ -40,7 +51,7 @@ class MatterBinarySensor(MatterEntity, BinarySensorEntity):
value = self.get_matter_attribute_value(self._entity_info.primary_attribute) value = self.get_matter_attribute_value(self._entity_info.primary_attribute)
if value in (None, NullValue): if value in (None, NullValue):
value = None value = None
elif value_convert := self._entity_info.measurement_to_ha: elif value_convert := self.entity_description.measurement_to_ha:
value = value_convert(value) value = value_convert(value)
self._attr_is_on = value self._attr_is_on = value
@ -51,52 +62,53 @@ DISCOVERY_SCHEMAS = [
# instead of generic occupancy sensor # instead of generic occupancy sensor
MatterDiscoverySchema( MatterDiscoverySchema(
platform=Platform.BINARY_SENSOR, platform=Platform.BINARY_SENSOR,
entity_description=BinarySensorEntityDescription( entity_description=MatterBinarySensorEntityDescription(
key="HueMotionSensor", key="HueMotionSensor",
device_class=BinarySensorDeviceClass.MOTION, device_class=BinarySensorDeviceClass.MOTION,
name="Motion", name="Motion",
measurement_to_ha=lambda x: (x & 1 == 1) if x is not None else None,
), ),
entity_class=MatterBinarySensor, entity_class=MatterBinarySensor,
required_attributes=(clusters.OccupancySensing.Attributes.Occupancy,), required_attributes=(clusters.OccupancySensing.Attributes.Occupancy,),
vendor_id=(4107,), vendor_id=(4107,),
product_name=("Hue motion sensor",), product_name=("Hue motion sensor",),
measurement_to_ha=lambda x: (x & 1 == 1) if x is not None else None,
), ),
MatterDiscoverySchema( MatterDiscoverySchema(
platform=Platform.BINARY_SENSOR, platform=Platform.BINARY_SENSOR,
entity_description=BinarySensorEntityDescription( entity_description=MatterBinarySensorEntityDescription(
key="ContactSensor", key="ContactSensor",
device_class=BinarySensorDeviceClass.DOOR, device_class=BinarySensorDeviceClass.DOOR,
name="Contact", name="Contact",
# value is inverted on matter to what we expect
measurement_to_ha=lambda x: not x,
), ),
entity_class=MatterBinarySensor, entity_class=MatterBinarySensor,
required_attributes=(clusters.BooleanState.Attributes.StateValue,), required_attributes=(clusters.BooleanState.Attributes.StateValue,),
# value is inverted on matter to what we expect
measurement_to_ha=lambda x: not x,
), ),
MatterDiscoverySchema( MatterDiscoverySchema(
platform=Platform.BINARY_SENSOR, platform=Platform.BINARY_SENSOR,
entity_description=BinarySensorEntityDescription( entity_description=MatterBinarySensorEntityDescription(
key="OccupancySensor", key="OccupancySensor",
device_class=BinarySensorDeviceClass.OCCUPANCY, device_class=BinarySensorDeviceClass.OCCUPANCY,
name="Occupancy", name="Occupancy",
# The first bit = if occupied
measurement_to_ha=lambda x: (x & 1 == 1) if x is not None else None,
), ),
entity_class=MatterBinarySensor, entity_class=MatterBinarySensor,
required_attributes=(clusters.OccupancySensing.Attributes.Occupancy,), required_attributes=(clusters.OccupancySensing.Attributes.Occupancy,),
# The first bit = if occupied
measurement_to_ha=lambda x: (x & 1 == 1) if x is not None else None,
), ),
MatterDiscoverySchema( MatterDiscoverySchema(
platform=Platform.BINARY_SENSOR, platform=Platform.BINARY_SENSOR,
entity_description=BinarySensorEntityDescription( entity_description=MatterBinarySensorEntityDescription(
key="BatteryChargeLevel", key="BatteryChargeLevel",
device_class=BinarySensorDeviceClass.BATTERY, device_class=BinarySensorDeviceClass.BATTERY,
name="Battery Status", name="Battery Status",
measurement_to_ha=lambda x: x
!= clusters.PowerSource.Enums.BatChargeLevel.kOk,
), ),
entity_class=MatterBinarySensor, entity_class=MatterBinarySensor,
required_attributes=(clusters.PowerSource.Attributes.BatChargeLevel,), required_attributes=(clusters.PowerSource.Attributes.BatChargeLevel,),
# only add binary battery sensor if a regular percentage based is not available # only add binary battery sensor if a regular percentage based is not available
absent_attributes=(clusters.PowerSource.Attributes.BatPercentRemaining,), absent_attributes=(clusters.PowerSource.Attributes.BatPercentRemaining,),
measurement_to_ha=lambda x: x != clusters.PowerSource.Enums.BatChargeLevel.kOk,
), ),
] ]

View File

@ -23,7 +23,7 @@ DISCOVERY_SCHEMAS: dict[Platform, list[MatterDiscoverySchema]] = {
Platform.SENSOR: SENSOR_SCHEMAS, Platform.SENSOR: SENSOR_SCHEMAS,
Platform.SWITCH: SWITCH_SCHEMAS, Platform.SWITCH: SWITCH_SCHEMAS,
} }
SUPPORTED_PLATFORMS = tuple(DISCOVERY_SCHEMAS.keys()) SUPPORTED_PLATFORMS = tuple(DISCOVERY_SCHEMAS)
@callback @callback
@ -109,7 +109,6 @@ def async_discover_entities(
attributes_to_watch=attributes_to_watch, attributes_to_watch=attributes_to_watch,
entity_description=schema.entity_description, entity_description=schema.entity_description,
entity_class=schema.entity_class, entity_class=schema.entity_class,
measurement_to_ha=schema.measurement_to_ha,
) )
# prevent re-discovery of the same attributes # prevent re-discovery of the same attributes

View File

@ -3,6 +3,7 @@ from __future__ import annotations
from abc import abstractmethod from abc import abstractmethod
from collections.abc import Callable from collections.abc import Callable
from dataclasses import dataclass
import logging import logging
from typing import TYPE_CHECKING, Any, cast from typing import TYPE_CHECKING, Any, cast
@ -11,7 +12,7 @@ from matter_server.common.helpers.util import create_attribute_path
from matter_server.common.models import EventType, ServerInfoMessage from matter_server.common.models import EventType, ServerInfoMessage
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.entity import DeviceInfo, Entity from homeassistant.helpers.entity import DeviceInfo, Entity, EntityDescription
from .const import DOMAIN, ID_TYPE_DEVICE_ID from .const import DOMAIN, ID_TYPE_DEVICE_ID
from .helpers import get_device_id from .helpers import get_device_id
@ -25,6 +26,14 @@ if TYPE_CHECKING:
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
@dataclass
class MatterEntityDescription(EntityDescription):
"""Describe the Matter entity."""
# convert the value from the primary attribute to the value used by HA
measurement_to_ha: Callable[[Any], Any] | None = None
class MatterEntity(Entity): class MatterEntity(Entity):
"""Entity class for Matter devices.""" """Entity class for Matter devices."""

View File

@ -1,9 +1,7 @@
"""Models used for the Matter integration.""" """Models used for the Matter integration."""
from __future__ import annotations from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any
from chip.clusters import Objects as clusters from chip.clusters import Objects as clusters
from chip.clusters.Objects import ClusterAttributeDescriptor from chip.clusters.Objects import ClusterAttributeDescriptor
@ -37,9 +35,6 @@ class MatterEntityInfo:
# entity class to use to instantiate the entity # entity class to use to instantiate the entity
entity_class: type entity_class: type
# [optional] function to call to convert the value from the primary attribute
measurement_to_ha: Callable[[SensorValueTypes], SensorValueTypes] | None = None
@property @property
def primary_attribute(self) -> type[ClusterAttributeDescriptor]: def primary_attribute(self) -> type[ClusterAttributeDescriptor]:
"""Return Primary Attribute belonging to the entity.""" """Return Primary Attribute belonging to the entity."""
@ -50,7 +45,8 @@ class MatterEntityInfo:
class MatterDiscoverySchema: class MatterDiscoverySchema:
"""Matter discovery schema. """Matter discovery schema.
The Matter endpoint and it's (primary) Attribute for an entity must match these conditions. The Matter endpoint and its (primary) Attribute
for an entity must match these conditions.
""" """
# specify the hass platform for which this scheme applies (e.g. light, sensor) # specify the hass platform for which this scheme applies (e.g. light, sensor)
@ -95,6 +91,3 @@ class MatterDiscoverySchema:
# [optional] bool to specify if this primary value may be discovered # [optional] bool to specify if this primary value may be discovered
# by multiple platforms # by multiple platforms
allow_multi: bool = False allow_multi: bool = False
# [optional] function to call to convert the value from the primary attribute
measurement_to_ha: Callable[[Any], Any] | None = None

View File

@ -1,6 +1,8 @@
"""Matter sensors.""" """Matter sensors."""
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass
from chip.clusters import Objects as clusters from chip.clusters import Objects as clusters
from chip.clusters.Types import Nullable, NullValue from chip.clusters.Types import Nullable, NullValue
@ -22,7 +24,7 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .entity import MatterEntity from .entity import MatterEntity, MatterEntityDescription
from .helpers import get_matter from .helpers import get_matter
from .models import MatterDiscoverySchema from .models import MatterDiscoverySchema
@ -37,10 +39,16 @@ async def async_setup_entry(
matter.register_platform_handler(Platform.SENSOR, async_add_entities) matter.register_platform_handler(Platform.SENSOR, async_add_entities)
@dataclass
class MatterSensorEntityDescription(SensorEntityDescription, MatterEntityDescription):
"""Describe Matter sensor entities."""
class MatterSensor(MatterEntity, SensorEntity): class MatterSensor(MatterEntity, SensorEntity):
"""Representation of a Matter sensor.""" """Representation of a Matter sensor."""
_attr_state_class = SensorStateClass.MEASUREMENT _attr_state_class = SensorStateClass.MEASUREMENT
entity_description: MatterSensorEntityDescription
@callback @callback
def _update_from_device(self) -> None: def _update_from_device(self) -> None:
@ -49,7 +57,7 @@ class MatterSensor(MatterEntity, SensorEntity):
value = self.get_matter_attribute_value(self._entity_info.primary_attribute) value = self.get_matter_attribute_value(self._entity_info.primary_attribute)
if value in (None, NullValue): if value in (None, NullValue):
value = None value = None
elif value_convert := self._entity_info.measurement_to_ha: elif value_convert := self.entity_description.measurement_to_ha:
value = value_convert(value) value = value_convert(value)
self._attr_native_value = value self._attr_native_value = value
@ -58,77 +66,77 @@ class MatterSensor(MatterEntity, SensorEntity):
DISCOVERY_SCHEMAS = [ DISCOVERY_SCHEMAS = [
MatterDiscoverySchema( MatterDiscoverySchema(
platform=Platform.SENSOR, platform=Platform.SENSOR,
entity_description=SensorEntityDescription( entity_description=MatterSensorEntityDescription(
key="TemperatureSensor", key="TemperatureSensor",
name="Temperature", name="Temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS, native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE, device_class=SensorDeviceClass.TEMPERATURE,
measurement_to_ha=lambda x: x / 100,
), ),
entity_class=MatterSensor, entity_class=MatterSensor,
required_attributes=(clusters.TemperatureMeasurement.Attributes.MeasuredValue,), required_attributes=(clusters.TemperatureMeasurement.Attributes.MeasuredValue,),
measurement_to_ha=lambda x: x / 100,
), ),
MatterDiscoverySchema( MatterDiscoverySchema(
platform=Platform.SENSOR, platform=Platform.SENSOR,
entity_description=SensorEntityDescription( entity_description=MatterSensorEntityDescription(
key="PressureSensor", key="PressureSensor",
name="Pressure", name="Pressure",
native_unit_of_measurement=UnitOfPressure.KPA, native_unit_of_measurement=UnitOfPressure.KPA,
device_class=SensorDeviceClass.PRESSURE, device_class=SensorDeviceClass.PRESSURE,
measurement_to_ha=lambda x: x / 10,
), ),
entity_class=MatterSensor, entity_class=MatterSensor,
required_attributes=(clusters.PressureMeasurement.Attributes.MeasuredValue,), required_attributes=(clusters.PressureMeasurement.Attributes.MeasuredValue,),
measurement_to_ha=lambda x: x / 10,
), ),
MatterDiscoverySchema( MatterDiscoverySchema(
platform=Platform.SENSOR, platform=Platform.SENSOR,
entity_description=SensorEntityDescription( entity_description=MatterSensorEntityDescription(
key="FlowSensor", key="FlowSensor",
name="Flow", name="Flow",
native_unit_of_measurement=UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR, native_unit_of_measurement=UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR,
device_class=SensorDeviceClass.WATER, # what is the device class here ? device_class=SensorDeviceClass.WATER, # what is the device class here ?
measurement_to_ha=lambda x: x / 10,
), ),
entity_class=MatterSensor, entity_class=MatterSensor,
required_attributes=(clusters.FlowMeasurement.Attributes.MeasuredValue,), required_attributes=(clusters.FlowMeasurement.Attributes.MeasuredValue,),
measurement_to_ha=lambda x: x / 10,
), ),
MatterDiscoverySchema( MatterDiscoverySchema(
platform=Platform.SENSOR, platform=Platform.SENSOR,
entity_description=SensorEntityDescription( entity_description=MatterSensorEntityDescription(
key="HumiditySensor", key="HumiditySensor",
name="Humidity", name="Humidity",
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY, device_class=SensorDeviceClass.HUMIDITY,
measurement_to_ha=lambda x: x / 100,
), ),
entity_class=MatterSensor, entity_class=MatterSensor,
required_attributes=( required_attributes=(
clusters.RelativeHumidityMeasurement.Attributes.MeasuredValue, clusters.RelativeHumidityMeasurement.Attributes.MeasuredValue,
), ),
measurement_to_ha=lambda x: x / 100,
), ),
MatterDiscoverySchema( MatterDiscoverySchema(
platform=Platform.SENSOR, platform=Platform.SENSOR,
entity_description=SensorEntityDescription( entity_description=MatterSensorEntityDescription(
key="LightSensor", key="LightSensor",
name="Illuminance", name="Illuminance",
native_unit_of_measurement=LIGHT_LUX, native_unit_of_measurement=LIGHT_LUX,
device_class=SensorDeviceClass.ILLUMINANCE, device_class=SensorDeviceClass.ILLUMINANCE,
measurement_to_ha=lambda x: round(pow(10, ((x - 1) / 10000)), 1),
), ),
entity_class=MatterSensor, entity_class=MatterSensor,
required_attributes=(clusters.IlluminanceMeasurement.Attributes.MeasuredValue,), required_attributes=(clusters.IlluminanceMeasurement.Attributes.MeasuredValue,),
measurement_to_ha=lambda x: round(pow(10, ((x - 1) / 10000)), 1),
), ),
MatterDiscoverySchema( MatterDiscoverySchema(
platform=Platform.SENSOR, platform=Platform.SENSOR,
entity_description=SensorEntityDescription( entity_description=MatterSensorEntityDescription(
key="PowerSource", key="PowerSource",
name="Battery", name="Battery",
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.BATTERY, device_class=SensorDeviceClass.BATTERY,
# value has double precision
measurement_to_ha=lambda x: int(x / 2),
), ),
entity_class=MatterSensor, entity_class=MatterSensor,
required_attributes=(clusters.PowerSource.Attributes.BatPercentRemaining,), required_attributes=(clusters.PowerSource.Attributes.BatPercentRemaining,),
# value has double precision
measurement_to_ha=lambda x: int(x / 2),
), ),
] ]