Refactor matter device entity value conversion (#90368)
This commit is contained in:
parent
fb4b35709d
commit
182af87f97
5 changed files with 60 additions and 39 deletions
|
@ -1,6 +1,8 @@
|
|||
"""Matter binary sensors."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from chip.clusters import Objects as clusters
|
||||
from chip.clusters.Objects import uint
|
||||
from chip.clusters.Types import Nullable, NullValue
|
||||
|
@ -15,7 +17,7 @@ from homeassistant.const import Platform
|
|||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .entity import MatterEntity
|
||||
from .entity import MatterEntity, MatterEntityDescription
|
||||
from .helpers import get_matter
|
||||
from .models import MatterDiscoverySchema
|
||||
|
||||
|
@ -30,9 +32,18 @@ async def async_setup_entry(
|
|||
matter.register_platform_handler(Platform.BINARY_SENSOR, async_add_entities)
|
||||
|
||||
|
||||
@dataclass
|
||||
class MatterBinarySensorEntityDescription(
|
||||
BinarySensorEntityDescription, MatterEntityDescription
|
||||
):
|
||||
"""Describe Matter binary sensor entities."""
|
||||
|
||||
|
||||
class MatterBinarySensor(MatterEntity, BinarySensorEntity):
|
||||
"""Representation of a Matter binary sensor."""
|
||||
|
||||
entity_description: MatterBinarySensorEntityDescription
|
||||
|
||||
@callback
|
||||
def _update_from_device(self) -> None:
|
||||
"""Update from device."""
|
||||
|
@ -40,7 +51,7 @@ class MatterBinarySensor(MatterEntity, BinarySensorEntity):
|
|||
value = self.get_matter_attribute_value(self._entity_info.primary_attribute)
|
||||
if value in (None, NullValue):
|
||||
value = None
|
||||
elif value_convert := self._entity_info.measurement_to_ha:
|
||||
elif value_convert := self.entity_description.measurement_to_ha:
|
||||
value = value_convert(value)
|
||||
self._attr_is_on = value
|
||||
|
||||
|
@ -51,52 +62,53 @@ DISCOVERY_SCHEMAS = [
|
|||
# instead of generic occupancy sensor
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.BINARY_SENSOR,
|
||||
entity_description=BinarySensorEntityDescription(
|
||||
entity_description=MatterBinarySensorEntityDescription(
|
||||
key="HueMotionSensor",
|
||||
device_class=BinarySensorDeviceClass.MOTION,
|
||||
name="Motion",
|
||||
measurement_to_ha=lambda x: (x & 1 == 1) if x is not None else None,
|
||||
),
|
||||
entity_class=MatterBinarySensor,
|
||||
required_attributes=(clusters.OccupancySensing.Attributes.Occupancy,),
|
||||
vendor_id=(4107,),
|
||||
product_name=("Hue motion sensor",),
|
||||
measurement_to_ha=lambda x: (x & 1 == 1) if x is not None else None,
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.BINARY_SENSOR,
|
||||
entity_description=BinarySensorEntityDescription(
|
||||
entity_description=MatterBinarySensorEntityDescription(
|
||||
key="ContactSensor",
|
||||
device_class=BinarySensorDeviceClass.DOOR,
|
||||
name="Contact",
|
||||
# value is inverted on matter to what we expect
|
||||
measurement_to_ha=lambda x: not x,
|
||||
),
|
||||
entity_class=MatterBinarySensor,
|
||||
required_attributes=(clusters.BooleanState.Attributes.StateValue,),
|
||||
# value is inverted on matter to what we expect
|
||||
measurement_to_ha=lambda x: not x,
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.BINARY_SENSOR,
|
||||
entity_description=BinarySensorEntityDescription(
|
||||
entity_description=MatterBinarySensorEntityDescription(
|
||||
key="OccupancySensor",
|
||||
device_class=BinarySensorDeviceClass.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,
|
||||
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(
|
||||
platform=Platform.BINARY_SENSOR,
|
||||
entity_description=BinarySensorEntityDescription(
|
||||
entity_description=MatterBinarySensorEntityDescription(
|
||||
key="BatteryChargeLevel",
|
||||
device_class=BinarySensorDeviceClass.BATTERY,
|
||||
name="Battery Status",
|
||||
measurement_to_ha=lambda x: x
|
||||
!= clusters.PowerSource.Enums.BatChargeLevel.kOk,
|
||||
),
|
||||
entity_class=MatterBinarySensor,
|
||||
required_attributes=(clusters.PowerSource.Attributes.BatChargeLevel,),
|
||||
# only add binary battery sensor if a regular percentage based is not available
|
||||
absent_attributes=(clusters.PowerSource.Attributes.BatPercentRemaining,),
|
||||
measurement_to_ha=lambda x: x != clusters.PowerSource.Enums.BatChargeLevel.kOk,
|
||||
),
|
||||
]
|
||||
|
|
|
@ -23,7 +23,7 @@ DISCOVERY_SCHEMAS: dict[Platform, list[MatterDiscoverySchema]] = {
|
|||
Platform.SENSOR: SENSOR_SCHEMAS,
|
||||
Platform.SWITCH: SWITCH_SCHEMAS,
|
||||
}
|
||||
SUPPORTED_PLATFORMS = tuple(DISCOVERY_SCHEMAS.keys())
|
||||
SUPPORTED_PLATFORMS = tuple(DISCOVERY_SCHEMAS)
|
||||
|
||||
|
||||
@callback
|
||||
|
@ -109,7 +109,6 @@ def async_discover_entities(
|
|||
attributes_to_watch=attributes_to_watch,
|
||||
entity_description=schema.entity_description,
|
||||
entity_class=schema.entity_class,
|
||||
measurement_to_ha=schema.measurement_to_ha,
|
||||
)
|
||||
|
||||
# prevent re-discovery of the same attributes
|
||||
|
|
|
@ -3,6 +3,7 @@ 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, cast
|
||||
|
||||
|
@ -11,7 +12,7 @@ from matter_server.common.helpers.util import create_attribute_path
|
|||
from matter_server.common.models import EventType, ServerInfoMessage
|
||||
|
||||
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 .helpers import get_device_id
|
||||
|
@ -25,6 +26,14 @@ if TYPE_CHECKING:
|
|||
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):
|
||||
"""Entity class for Matter devices."""
|
||||
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
"""Models used for the Matter integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from chip.clusters import Objects as clusters
|
||||
from chip.clusters.Objects import ClusterAttributeDescriptor
|
||||
|
@ -37,9 +35,6 @@ class MatterEntityInfo:
|
|||
# entity class to use to instantiate the entity
|
||||
entity_class: type
|
||||
|
||||
# [optional] function to call to convert the value from the primary attribute
|
||||
measurement_to_ha: Callable[[SensorValueTypes], SensorValueTypes] | None = None
|
||||
|
||||
@property
|
||||
def primary_attribute(self) -> type[ClusterAttributeDescriptor]:
|
||||
"""Return Primary Attribute belonging to the entity."""
|
||||
|
@ -50,7 +45,8 @@ class MatterEntityInfo:
|
|||
class MatterDiscoverySchema:
|
||||
"""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)
|
||||
|
@ -95,6 +91,3 @@ class MatterDiscoverySchema:
|
|||
# [optional] bool to specify if this primary value may be discovered
|
||||
# by multiple platforms
|
||||
allow_multi: bool = False
|
||||
|
||||
# [optional] function to call to convert the value from the primary attribute
|
||||
measurement_to_ha: Callable[[Any], Any] | None = None
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
"""Matter sensors."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from chip.clusters import Objects as clusters
|
||||
from chip.clusters.Types import Nullable, NullValue
|
||||
|
||||
|
@ -22,7 +24,7 @@ from homeassistant.const import (
|
|||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .entity import MatterEntity
|
||||
from .entity import MatterEntity, MatterEntityDescription
|
||||
from .helpers import get_matter
|
||||
from .models import MatterDiscoverySchema
|
||||
|
||||
|
@ -37,10 +39,16 @@ async def async_setup_entry(
|
|||
matter.register_platform_handler(Platform.SENSOR, async_add_entities)
|
||||
|
||||
|
||||
@dataclass
|
||||
class MatterSensorEntityDescription(SensorEntityDescription, MatterEntityDescription):
|
||||
"""Describe Matter sensor entities."""
|
||||
|
||||
|
||||
class MatterSensor(MatterEntity, SensorEntity):
|
||||
"""Representation of a Matter sensor."""
|
||||
|
||||
_attr_state_class = SensorStateClass.MEASUREMENT
|
||||
entity_description: MatterSensorEntityDescription
|
||||
|
||||
@callback
|
||||
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)
|
||||
if value in (None, NullValue):
|
||||
value = None
|
||||
elif value_convert := self._entity_info.measurement_to_ha:
|
||||
elif value_convert := self.entity_description.measurement_to_ha:
|
||||
value = value_convert(value)
|
||||
self._attr_native_value = value
|
||||
|
||||
|
@ -58,77 +66,77 @@ class MatterSensor(MatterEntity, SensorEntity):
|
|||
DISCOVERY_SCHEMAS = [
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.SENSOR,
|
||||
entity_description=SensorEntityDescription(
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="TemperatureSensor",
|
||||
name="Temperature",
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
measurement_to_ha=lambda x: x / 100,
|
||||
),
|
||||
entity_class=MatterSensor,
|
||||
required_attributes=(clusters.TemperatureMeasurement.Attributes.MeasuredValue,),
|
||||
measurement_to_ha=lambda x: x / 100,
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.SENSOR,
|
||||
entity_description=SensorEntityDescription(
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="PressureSensor",
|
||||
name="Pressure",
|
||||
native_unit_of_measurement=UnitOfPressure.KPA,
|
||||
device_class=SensorDeviceClass.PRESSURE,
|
||||
measurement_to_ha=lambda x: x / 10,
|
||||
),
|
||||
entity_class=MatterSensor,
|
||||
required_attributes=(clusters.PressureMeasurement.Attributes.MeasuredValue,),
|
||||
measurement_to_ha=lambda x: x / 10,
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.SENSOR,
|
||||
entity_description=SensorEntityDescription(
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="FlowSensor",
|
||||
name="Flow",
|
||||
native_unit_of_measurement=UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR,
|
||||
device_class=SensorDeviceClass.WATER, # what is the device class here ?
|
||||
measurement_to_ha=lambda x: x / 10,
|
||||
),
|
||||
entity_class=MatterSensor,
|
||||
required_attributes=(clusters.FlowMeasurement.Attributes.MeasuredValue,),
|
||||
measurement_to_ha=lambda x: x / 10,
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.SENSOR,
|
||||
entity_description=SensorEntityDescription(
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="HumiditySensor",
|
||||
name="Humidity",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
measurement_to_ha=lambda x: x / 100,
|
||||
),
|
||||
entity_class=MatterSensor,
|
||||
required_attributes=(
|
||||
clusters.RelativeHumidityMeasurement.Attributes.MeasuredValue,
|
||||
),
|
||||
measurement_to_ha=lambda x: x / 100,
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.SENSOR,
|
||||
entity_description=SensorEntityDescription(
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="LightSensor",
|
||||
name="Illuminance",
|
||||
native_unit_of_measurement=LIGHT_LUX,
|
||||
device_class=SensorDeviceClass.ILLUMINANCE,
|
||||
measurement_to_ha=lambda x: round(pow(10, ((x - 1) / 10000)), 1),
|
||||
),
|
||||
entity_class=MatterSensor,
|
||||
required_attributes=(clusters.IlluminanceMeasurement.Attributes.MeasuredValue,),
|
||||
measurement_to_ha=lambda x: round(pow(10, ((x - 1) / 10000)), 1),
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.SENSOR,
|
||||
entity_description=SensorEntityDescription(
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="PowerSource",
|
||||
name="Battery",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
# value has double precision
|
||||
measurement_to_ha=lambda x: int(x / 2),
|
||||
),
|
||||
entity_class=MatterSensor,
|
||||
required_attributes=(clusters.PowerSource.Attributes.BatPercentRemaining,),
|
||||
# value has double precision
|
||||
measurement_to_ha=lambda x: int(x / 2),
|
||||
),
|
||||
]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue