Skip to content

Commit

Permalink
Refactor matter device entity value conversion
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinHjelmare committed Mar 27, 2023
1 parent 9f04c23 commit 674cb21
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 39 deletions.
36 changes: 24 additions & 12 deletions homeassistant/components/matter/binary_sensor.py
@@ -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
Expand All @@ -15,7 +17,7 @@
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

Expand All @@ -30,17 +32,26 @@ 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."""
value: bool | uint | int | Nullable | None
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

Expand All @@ -51,52 +62,53 @@ def _update_from_device(self) -> None:
# 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,
),
]
3 changes: 1 addition & 2 deletions homeassistant/components/matter/discovery.py
Expand Up @@ -23,7 +23,7 @@
Platform.SENSOR: SENSOR_SCHEMAS,
Platform.SWITCH: SWITCH_SCHEMAS,
}
SUPPORTED_PLATFORMS = tuple(DISCOVERY_SCHEMAS.keys())
SUPPORTED_PLATFORMS = tuple(DISCOVERY_SCHEMAS)


@callback
Expand Down Expand Up @@ -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
Expand Down
11 changes: 10 additions & 1 deletion homeassistant/components/matter/entity.py
Expand Up @@ -3,6 +3,7 @@

from abc import abstractmethod
from collections.abc import Callable
from dataclasses import dataclass
import logging
from typing import TYPE_CHECKING, Any, cast

Expand All @@ -11,7 +12,7 @@
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
Expand All @@ -25,6 +26,14 @@
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."""

Expand Down
11 changes: 2 additions & 9 deletions homeassistant/components/matter/models.py
@@ -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
Expand Down Expand Up @@ -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."""
Expand All @@ -50,7 +45,8 @@ def primary_attribute(self) -> type[ClusterAttributeDescriptor]:
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)
Expand Down Expand Up @@ -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
38 changes: 23 additions & 15 deletions homeassistant/components/matter/sensor.py
@@ -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

Expand All @@ -22,7 +24,7 @@
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

Expand All @@ -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:
Expand All @@ -49,7 +57,7 @@ def _update_from_device(self) -> None:
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

Expand All @@ -58,77 +66,77 @@ def _update_from_device(self) -> None:
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),
),
]

0 comments on commit 674cb21

Please sign in to comment.