From 9d96a021c78ad86e4ed32da618eaf54a08430191 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 6 Dec 2022 21:39:00 +0100 Subject: [PATCH] Add matter sensor platform (#83147) --- .../components/matter/device_platform.py | 2 + homeassistant/components/matter/sensor.py | 167 +++++++++++++++++ .../matter/fixtures/nodes/flow-sensor.json | 107 +++++++++++ .../fixtures/nodes/humidity-sensor.json | 157 +++++++++++++--- .../matter/fixtures/nodes/light-sensor.json | 107 +++++++++++ .../fixtures/nodes/pressure-sensor.json | 107 +++++++++++ .../fixtures/nodes/temperature-sensor.json | 107 +++++++++++ tests/components/matter/test_sensor.py | 169 ++++++++++++++++++ 8 files changed, 898 insertions(+), 25 deletions(-) create mode 100644 homeassistant/components/matter/sensor.py create mode 100644 tests/components/matter/test_sensor.py diff --git a/homeassistant/components/matter/device_platform.py b/homeassistant/components/matter/device_platform.py index 3a4d11ab95f..02267b02880 100644 --- a/homeassistant/components/matter/device_platform.py +++ b/homeassistant/components/matter/device_platform.py @@ -7,6 +7,7 @@ from homeassistant.const import Platform from .binary_sensor import DEVICE_ENTITY as BINARY_SENSOR_DEVICE_ENTITY from .light import DEVICE_ENTITY as LIGHT_DEVICE_ENTITY +from .sensor import DEVICE_ENTITY as SENSOR_DEVICE_ENTITY if TYPE_CHECKING: from matter_server.common.models.device_types import DeviceType @@ -23,4 +24,5 @@ DEVICE_PLATFORM: dict[ ] = { Platform.BINARY_SENSOR: BINARY_SENSOR_DEVICE_ENTITY, Platform.LIGHT: LIGHT_DEVICE_ENTITY, + Platform.SENSOR: SENSOR_DEVICE_ENTITY, } diff --git a/homeassistant/components/matter/sensor.py b/homeassistant/components/matter/sensor.py new file mode 100644 index 00000000000..2b659fc1304 --- /dev/null +++ b/homeassistant/components/matter/sensor.py @@ -0,0 +1,167 @@ +"""Matter sensors.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +from functools import partial +from typing import TYPE_CHECKING, Any + +from chip.clusters import Objects as clusters +from chip.clusters.Types import Nullable, NullValue +from matter_server.common.models import device_types +from matter_server.common.models.device_type_instance import MatterDeviceTypeInstance + +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, + SensorStateClass, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + LIGHT_LUX, + PERCENTAGE, + VOLUME_FLOW_RATE_CUBIC_METERS_PER_HOUR, + Platform, + UnitOfPressure, + UnitOfTemperature, +) +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .entity import MatterEntity, MatterEntityDescriptionBaseClass + +if TYPE_CHECKING: + from .adapter import MatterAdapter + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up Matter sensors from Config Entry.""" + matter: MatterAdapter = hass.data[DOMAIN][config_entry.entry_id] + matter.register_platform_handler(Platform.SENSOR, async_add_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: + """Update from device.""" + measurement: Nullable | float | None + measurement = _get_attribute_value( + self._device_type_instance, + # We always subscribe to a single value + self.entity_description.subscribe_attributes[0], + ) + + if measurement is NullValue or measurement is None: + measurement = None + else: + measurement = self.entity_description.measurement_to_ha(measurement) + + self._attr_native_value = measurement + + +def _get_attribute_value( + device_type_instance: MatterDeviceTypeInstance, + attribute: clusters.ClusterAttributeDescriptor, +) -> Any: + """Return the value of an attribute.""" + # Find the cluster for this attribute. We don't have a lookup table yet. + cluster_cls: clusters.Cluster = next( + cluster + for cluster in device_type_instance.device_type.clusters + if cluster.id == attribute.cluster_id + ) + + # Find the attribute descriptor so we know the instance variable to fetch + attribute_descriptor: clusters.ClusterObjectFieldDescriptor = next( + descriptor + for descriptor in cluster_cls.descriptor.Fields + if descriptor.Tag == attribute.attribute_id + ) + + cluster_data = device_type_instance.get_cluster(cluster_cls) + return getattr(cluster_data, attribute_descriptor.Label) + + +@dataclass +class MatterSensorEntityDescriptionMixin: + """Required fields for sensor device mapping.""" + + measurement_to_ha: Callable[[float], float] + + +@dataclass +class MatterSensorEntityDescription( + SensorEntityDescription, + MatterEntityDescriptionBaseClass, + MatterSensorEntityDescriptionMixin, +): + """Matter Sensor entity description.""" + + +# You can't set default values on inherited data classes +MatterSensorEntityDescriptionFactory = partial( + MatterSensorEntityDescription, entity_cls=MatterSensor +) + + +DEVICE_ENTITY: dict[ + type[device_types.DeviceType], + MatterEntityDescriptionBaseClass | list[MatterEntityDescriptionBaseClass], +] = { + device_types.TemperatureSensor: MatterSensorEntityDescriptionFactory( + key=device_types.TemperatureSensor, + name="Temperature", + measurement_to_ha=lambda x: x / 100, + subscribe_attributes=( + clusters.TemperatureMeasurement.Attributes.MeasuredValue, + ), + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + ), + device_types.PressureSensor: MatterSensorEntityDescriptionFactory( + key=device_types.PressureSensor, + name="Pressure", + measurement_to_ha=lambda x: x / 10, + subscribe_attributes=(clusters.PressureMeasurement.Attributes.MeasuredValue,), + native_unit_of_measurement=UnitOfPressure.KPA, + device_class=SensorDeviceClass.PRESSURE, + ), + device_types.FlowSensor: MatterSensorEntityDescriptionFactory( + key=device_types.FlowSensor, + name="Flow", + measurement_to_ha=lambda x: x / 10, + subscribe_attributes=(clusters.FlowMeasurement.Attributes.MeasuredValue,), + native_unit_of_measurement=VOLUME_FLOW_RATE_CUBIC_METERS_PER_HOUR, + ), + device_types.HumiditySensor: MatterSensorEntityDescriptionFactory( + key=device_types.HumiditySensor, + name="Humidity", + measurement_to_ha=lambda x: x / 100, + subscribe_attributes=( + clusters.RelativeHumidityMeasurement.Attributes.MeasuredValue, + ), + native_unit_of_measurement=PERCENTAGE, + device_class=SensorDeviceClass.HUMIDITY, + ), + device_types.LightSensor: MatterSensorEntityDescriptionFactory( + key=device_types.LightSensor, + name="Light", + measurement_to_ha=lambda x: round(pow(10, ((x - 1) / 10000)), 1), + subscribe_attributes=( + clusters.IlluminanceMeasurement.Attributes.MeasuredValue, + ), + native_unit_of_measurement=LIGHT_LUX, + device_class=SensorDeviceClass.ILLUMINANCE, + ), +} diff --git a/tests/components/matter/fixtures/nodes/flow-sensor.json b/tests/components/matter/fixtures/nodes/flow-sensor.json index 04135f8aa82..92f4c5b73b2 100644 --- a/tests/components/matter/fixtures/nodes/flow-sensor.json +++ b/tests/components/matter/fixtures/nodes/flow-sensor.json @@ -4,6 +4,113 @@ "last_interview": "2022-11-29T21:23:48.485057", "interview_version": 1, "attributes": { + "0/29/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.DeviceTypeList", + "attribute_name": "DeviceTypeList", + "value": [ + { + "type": 22, + "revision": 1 + } + ] + }, + "0/29/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ServerList", + "attribute_name": "ServerList", + "value": [ + 4, 29, 31, 40, 42, 43, 44, 48, 49, 50, 51, 52, 53, 54, 55, 59, 60, 62, + 63, 64, 65 + ] + }, + "0/29/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClientList", + "attribute_name": "ClientList", + "value": [41] + }, + "0/29/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.PartsList", + "attribute_name": "PartsList", + "value": [1] + }, + "0/29/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/29/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/29/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/29/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "0/29/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533] + }, "0/40/0": { "node_id": 1, "endpoint": 0, diff --git a/tests/components/matter/fixtures/nodes/humidity-sensor.json b/tests/components/matter/fixtures/nodes/humidity-sensor.json index f29227ce14b..34b95263de5 100644 --- a/tests/components/matter/fixtures/nodes/humidity-sensor.json +++ b/tests/components/matter/fixtures/nodes/humidity-sensor.json @@ -4,6 +4,113 @@ "last_interview": "2022-11-29T21:23:48.485057", "interview_version": 1, "attributes": { + "0/29/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.DeviceTypeList", + "attribute_name": "DeviceTypeList", + "value": [ + { + "type": 22, + "revision": 1 + } + ] + }, + "0/29/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ServerList", + "attribute_name": "ServerList", + "value": [ + 4, 29, 31, 40, 42, 43, 44, 48, 49, 50, 51, 52, 53, 54, 55, 59, 60, 62, + 63, 64, 65 + ] + }, + "0/29/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClientList", + "attribute_name": "ClientList", + "value": [41] + }, + "0/29/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.PartsList", + "attribute_name": "PartsList", + "value": [1] + }, + "0/29/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/29/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/29/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/29/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "0/29/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533] + }, "0/40/0": { "node_id": 1, "endpoint": 0, @@ -404,7 +511,7 @@ }, "1/29/65531": { "node_id": 1, - "endpoint": 0, + "endpoint": 1, "cluster_id": 29, "cluster_type": "chip.clusters.Objects.Descriptor", "cluster_name": "Descriptor", @@ -413,9 +520,9 @@ "attribute_name": "AttributeList", "value": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533] }, - "0/1029/0": { - "node_id": 3, - "endpoint": 0, + "1/1029/0": { + "node_id": 1, + "endpoint": 1, "cluster_id": 1029, "cluster_type": "chip.clusters.Objects.RelativeHumidityMeasurement", "cluster_name": "RelativeHumidityMeasurement", @@ -424,9 +531,9 @@ "attribute_name": "MeasuredValue", "value": 0 }, - "0/1029/1": { - "node_id": 3, - "endpoint": 0, + "1/1029/1": { + "node_id": 1, + "endpoint": 1, "cluster_id": 1029, "cluster_type": "chip.clusters.Objects.RelativeHumidityMeasurement", "cluster_name": "RelativeHumidityMeasurement", @@ -435,9 +542,9 @@ "attribute_name": "MinMeasuredValue", "value": 0 }, - "0/1029/2": { - "node_id": 3, - "endpoint": 0, + "1/1029/2": { + "node_id": 1, + "endpoint": 1, "cluster_id": 1029, "cluster_type": "chip.clusters.Objects.RelativeHumidityMeasurement", "cluster_name": "RelativeHumidityMeasurement", @@ -446,9 +553,9 @@ "attribute_name": "MaxMeasuredValue", "value": 10000 }, - "0/1029/65532": { - "node_id": 3, - "endpoint": 0, + "1/1029/65532": { + "node_id": 1, + "endpoint": 1, "cluster_id": 1029, "cluster_type": "chip.clusters.Objects.RelativeHumidityMeasurement", "cluster_name": "RelativeHumidityMeasurement", @@ -457,9 +564,9 @@ "attribute_name": "FeatureMap", "value": 0 }, - "0/1029/65533": { - "node_id": 3, - "endpoint": 0, + "1/1029/65533": { + "node_id": 1, + "endpoint": 1, "cluster_id": 1029, "cluster_type": "chip.clusters.Objects.RelativeHumidityMeasurement", "cluster_name": "RelativeHumidityMeasurement", @@ -468,9 +575,9 @@ "attribute_name": "ClusterRevision", "value": 3 }, - "0/1029/65528": { - "node_id": 3, - "endpoint": 0, + "1/1029/65528": { + "node_id": 1, + "endpoint": 1, "cluster_id": 1029, "cluster_type": "chip.clusters.Objects.RelativeHumidityMeasurement", "cluster_name": "RelativeHumidityMeasurement", @@ -479,9 +586,9 @@ "attribute_name": "GeneratedCommandList", "value": [] }, - "0/1029/65529": { - "node_id": 3, - "endpoint": 0, + "1/1029/65529": { + "node_id": 1, + "endpoint": 1, "cluster_id": 1029, "cluster_type": "chip.clusters.Objects.RelativeHumidityMeasurement", "cluster_name": "RelativeHumidityMeasurement", @@ -490,9 +597,9 @@ "attribute_name": "AcceptedCommandList", "value": [] }, - "0/1029/65531": { - "node_id": 3, - "endpoint": 0, + "1/1029/65531": { + "node_id": 1, + "endpoint": 1, "cluster_id": 1029, "cluster_type": "chip.clusters.Objects.RelativeHumidityMeasurement", "cluster_name": "RelativeHumidityMeasurement", diff --git a/tests/components/matter/fixtures/nodes/light-sensor.json b/tests/components/matter/fixtures/nodes/light-sensor.json index 1e7b09c752b..152292d4589 100644 --- a/tests/components/matter/fixtures/nodes/light-sensor.json +++ b/tests/components/matter/fixtures/nodes/light-sensor.json @@ -4,6 +4,113 @@ "last_interview": "2022-11-29T21:23:48.485057", "interview_version": 1, "attributes": { + "0/29/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.DeviceTypeList", + "attribute_name": "DeviceTypeList", + "value": [ + { + "type": 22, + "revision": 1 + } + ] + }, + "0/29/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ServerList", + "attribute_name": "ServerList", + "value": [ + 4, 29, 31, 40, 42, 43, 44, 48, 49, 50, 51, 52, 53, 54, 55, 59, 60, 62, + 63, 64, 65 + ] + }, + "0/29/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClientList", + "attribute_name": "ClientList", + "value": [41] + }, + "0/29/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.PartsList", + "attribute_name": "PartsList", + "value": [1] + }, + "0/29/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/29/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/29/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/29/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "0/29/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533] + }, "0/40/0": { "node_id": 1, "endpoint": 0, diff --git a/tests/components/matter/fixtures/nodes/pressure-sensor.json b/tests/components/matter/fixtures/nodes/pressure-sensor.json index 9422f0c659b..e338933fbc8 100644 --- a/tests/components/matter/fixtures/nodes/pressure-sensor.json +++ b/tests/components/matter/fixtures/nodes/pressure-sensor.json @@ -4,6 +4,113 @@ "last_interview": "2022-11-29T21:23:48.485057", "interview_version": 1, "attributes": { + "0/29/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.DeviceTypeList", + "attribute_name": "DeviceTypeList", + "value": [ + { + "type": 22, + "revision": 1 + } + ] + }, + "0/29/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ServerList", + "attribute_name": "ServerList", + "value": [ + 4, 29, 31, 40, 42, 43, 44, 48, 49, 50, 51, 52, 53, 54, 55, 59, 60, 62, + 63, 64, 65 + ] + }, + "0/29/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClientList", + "attribute_name": "ClientList", + "value": [41] + }, + "0/29/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.PartsList", + "attribute_name": "PartsList", + "value": [1] + }, + "0/29/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/29/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/29/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/29/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "0/29/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533] + }, "0/40/0": { "node_id": 1, "endpoint": 0, diff --git a/tests/components/matter/fixtures/nodes/temperature-sensor.json b/tests/components/matter/fixtures/nodes/temperature-sensor.json index 3631992def8..5e451f31fd2 100644 --- a/tests/components/matter/fixtures/nodes/temperature-sensor.json +++ b/tests/components/matter/fixtures/nodes/temperature-sensor.json @@ -4,6 +4,113 @@ "last_interview": "2022-11-29T21:23:48.485057", "interview_version": 1, "attributes": { + "0/29/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.DeviceTypeList", + "attribute_name": "DeviceTypeList", + "value": [ + { + "type": 22, + "revision": 1 + } + ] + }, + "0/29/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ServerList", + "attribute_name": "ServerList", + "value": [ + 4, 29, 31, 40, 42, 43, 44, 48, 49, 50, 51, 52, 53, 54, 55, 59, 60, 62, + 63, 64, 65 + ] + }, + "0/29/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClientList", + "attribute_name": "ClientList", + "value": [41] + }, + "0/29/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.PartsList", + "attribute_name": "PartsList", + "value": [1] + }, + "0/29/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/29/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/29/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/29/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "0/29/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533] + }, "0/40/0": { "node_id": 1, "endpoint": 0, diff --git a/tests/components/matter/test_sensor.py b/tests/components/matter/test_sensor.py new file mode 100644 index 00000000000..ccdf09b8dc9 --- /dev/null +++ b/tests/components/matter/test_sensor.py @@ -0,0 +1,169 @@ +"""Test Matter sensors.""" +from unittest.mock import MagicMock + +from matter_server.common.models.node import MatterNode +import pytest + +from homeassistant.core import HomeAssistant + +from .common import ( + set_node_attribute, + setup_integration_with_node_fixture, + trigger_subscription_callback, +) + + +@pytest.fixture(name="flow_sensor_node") +async def flow_sensor_node_fixture( + hass: HomeAssistant, matter_client: MagicMock +) -> MatterNode: + """Fixture for a flow sensor node.""" + return await setup_integration_with_node_fixture(hass, "flow-sensor", matter_client) + + +@pytest.fixture(name="humidity_sensor_node") +async def humidity_sensor_node_fixture( + hass: HomeAssistant, matter_client: MagicMock +) -> MatterNode: + """Fixture for a humidity sensor node.""" + return await setup_integration_with_node_fixture( + hass, "humidity-sensor", matter_client + ) + + +@pytest.fixture(name="light_sensor_node") +async def light_sensor_node_fixture( + hass: HomeAssistant, matter_client: MagicMock +) -> MatterNode: + """Fixture for a light sensor node.""" + return await setup_integration_with_node_fixture( + hass, "light-sensor", matter_client + ) + + +@pytest.fixture(name="pressure_sensor_node") +async def pressure_sensor_node_fixture( + hass: HomeAssistant, matter_client: MagicMock +) -> MatterNode: + """Fixture for a pressure sensor node.""" + return await setup_integration_with_node_fixture( + hass, "pressure-sensor", matter_client + ) + + +@pytest.fixture(name="temperature_sensor_node") +async def temperature_sensor_node_fixture( + hass: HomeAssistant, matter_client: MagicMock +) -> MatterNode: + """Fixture for a temperature sensor node.""" + return await setup_integration_with_node_fixture( + hass, "temperature-sensor", matter_client + ) + + +async def test_sensor_null_value( + hass: HomeAssistant, + matter_client: MagicMock, + flow_sensor_node: MatterNode, +) -> None: + """Test flow sensor.""" + state = hass.states.get("sensor.mock_flow_sensor_flow") + assert state + assert state.state == "0.0" + + set_node_attribute(flow_sensor_node, 1, 1028, 0, None) + await trigger_subscription_callback(hass, matter_client) + + state = hass.states.get("sensor.mock_flow_sensor_flow") + assert state + assert state.state == "unknown" + + +async def test_flow_sensor( + hass: HomeAssistant, + matter_client: MagicMock, + flow_sensor_node: MatterNode, +) -> None: + """Test flow sensor.""" + state = hass.states.get("sensor.mock_flow_sensor_flow") + assert state + assert state.state == "0.0" + + set_node_attribute(flow_sensor_node, 1, 1028, 0, 20) + await trigger_subscription_callback(hass, matter_client) + + state = hass.states.get("sensor.mock_flow_sensor_flow") + assert state + assert state.state == "2.0" + + +async def test_humidity_sensor( + hass: HomeAssistant, + matter_client: MagicMock, + humidity_sensor_node: MatterNode, +) -> None: + """Test humidity sensor.""" + state = hass.states.get("sensor.mock_humidity_sensor_humidity") + assert state + assert state.state == "0.0" + + set_node_attribute(humidity_sensor_node, 1, 1029, 0, 4000) + await trigger_subscription_callback(hass, matter_client) + + state = hass.states.get("sensor.mock_humidity_sensor_humidity") + assert state + assert state.state == "40.0" + + +async def test_light_sensor( + hass: HomeAssistant, + matter_client: MagicMock, + light_sensor_node: MatterNode, +) -> None: + """Test light sensor.""" + state = hass.states.get("sensor.mock_light_sensor_light") + assert state + assert state.state == "1.3" + + set_node_attribute(light_sensor_node, 1, 1024, 0, 3000) + await trigger_subscription_callback(hass, matter_client) + + state = hass.states.get("sensor.mock_light_sensor_light") + assert state + assert state.state == "2.0" + + +async def test_pressure_sensor( + hass: HomeAssistant, + matter_client: MagicMock, + pressure_sensor_node: MatterNode, +) -> None: + """Test pressure sensor.""" + state = hass.states.get("sensor.mock_pressure_sensor_pressure") + assert state + assert state.state == "0.0" + + set_node_attribute(pressure_sensor_node, 1, 1027, 0, 1010) + await trigger_subscription_callback(hass, matter_client) + + state = hass.states.get("sensor.mock_pressure_sensor_pressure") + assert state + assert state.state == "101.0" + + +async def test_temperature_sensor( + hass: HomeAssistant, + matter_client: MagicMock, + temperature_sensor_node: MatterNode, +) -> None: + """Test temperature sensor.""" + state = hass.states.get("sensor.mock_temperature_sensor_temperature") + assert state + assert state.state == "21.0" + + set_node_attribute(temperature_sensor_node, 1, 1026, 0, 2500) + await trigger_subscription_callback(hass, matter_client) + + state = hass.states.get("sensor.mock_temperature_sensor_temperature") + assert state + assert state.state == "25.0"