From db23ad852d048e16b70068ce77eb97905caedd70 Mon Sep 17 00:00:00 2001 From: Artem Sorokin Date: Wed, 11 Dec 2024 14:56:52 +0300 Subject: [PATCH 1/6] Add Matter power/energy sensor for draft electrical measurement cluster --- homeassistant/components/matter/sensor.py | 80 +++++ tests/components/matter/conftest.py | 1 + .../fixtures/nodes/yandex_smart_socket.json | 278 ++++++++++++++++++ .../matter/snapshots/test_sensor.ambr | 162 ++++++++++ 4 files changed, 521 insertions(+) create mode 100644 tests/components/matter/fixtures/nodes/yandex_smart_socket.json diff --git a/homeassistant/components/matter/sensor.py b/homeassistant/components/matter/sensor.py index e10f081d49713..135466a664515 100644 --- a/homeassistant/components/matter/sensor.py +++ b/homeassistant/components/matter/sensor.py @@ -9,6 +9,7 @@ from chip.clusters import Objects as clusters from chip.clusters.Types import Nullable, NullValue from matter_server.common.custom_clusters import ( + DraftElectricalMeasurementCluster, EveCluster, NeoCluster, ThirdRealityMeteringCluster, @@ -104,6 +105,31 @@ def _update_from_device(self) -> None: self._attr_native_value = value +class MatterDraftElectricalMeasurementSensor(MatterEntity, SensorEntity): + """Representation of a Matter sensor for Matter 1.0 draft ElectricalMeasurement cluster.""" + + entity_description: MatterSensorEntityDescription + + @callback + def _update_from_device(self) -> None: + """Update from device.""" + raw_value: Nullable | float | None + divisor: Nullable | float | None + multiplier: Nullable | float | None + + raw_value, divisor, multiplier = ( + self.get_matter_attribute_value(self._entity_info.attributes_to_watch[0]), + self.get_matter_attribute_value(self._entity_info.attributes_to_watch[1]), + self.get_matter_attribute_value(self._entity_info.attributes_to_watch[2]), + ) + for value in (raw_value, divisor, multiplier): + if value in (None, NullValue): + self._attr_native_value = None + return + + self._attr_native_value = (raw_value / divisor) * multiplier + + class MatterOperationalStateSensor(MatterSensor): """Representation of a sensor for Matter Operational State.""" @@ -623,6 +649,60 @@ def _update_from_device(self) -> None: clusters.ElectricalEnergyMeasurement.Attributes.CumulativeEnergyImported, ), ), + MatterDiscoverySchema( + platform=Platform.SENSOR, + entity_description=MatterSensorEntityDescription( + key="ElectricalMeasurementActivePower", + device_class=SensorDeviceClass.POWER, + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=UnitOfPower.WATT, + suggested_display_precision=2, + state_class=SensorStateClass.MEASUREMENT, + ), + entity_class=MatterDraftElectricalMeasurementSensor, + required_attributes=( + DraftElectricalMeasurementCluster.Attributes.ActivePower, + DraftElectricalMeasurementCluster.Attributes.AcPowerDivisor, + DraftElectricalMeasurementCluster.Attributes.AcPowerMultiplier, + ), + absent_clusters=(clusters.ElectricalPowerMeasurement,), + ), + MatterDiscoverySchema( + platform=Platform.SENSOR, + entity_description=MatterSensorEntityDescription( + key="ElectricalMeasurementRmsVoltage", + device_class=SensorDeviceClass.VOLTAGE, + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=UnitOfElectricPotential.VOLT, + suggested_display_precision=0, + state_class=SensorStateClass.MEASUREMENT, + ), + entity_class=MatterDraftElectricalMeasurementSensor, + required_attributes=( + DraftElectricalMeasurementCluster.Attributes.RmsVoltage, + DraftElectricalMeasurementCluster.Attributes.AcVoltageDivisor, + DraftElectricalMeasurementCluster.Attributes.AcVoltageMultiplier, + ), + absent_clusters=(clusters.ElectricalPowerMeasurement,), + ), + MatterDiscoverySchema( + platform=Platform.SENSOR, + entity_description=MatterSensorEntityDescription( + key="ElectricalMeasurementRmsCurrent", + device_class=SensorDeviceClass.CURRENT, + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, + suggested_display_precision=2, + state_class=SensorStateClass.MEASUREMENT, + ), + entity_class=MatterDraftElectricalMeasurementSensor, + required_attributes=( + DraftElectricalMeasurementCluster.Attributes.RmsCurrent, + DraftElectricalMeasurementCluster.Attributes.AcCurrentDivisor, + DraftElectricalMeasurementCluster.Attributes.AcCurrentMultiplier, + ), + absent_clusters=(clusters.ElectricalPowerMeasurement,), + ), MatterDiscoverySchema( platform=Platform.SENSOR, entity_description=MatterSensorEntityDescription( diff --git a/tests/components/matter/conftest.py b/tests/components/matter/conftest.py index bbafec48e10fa..6ba13836c04aa 100644 --- a/tests/components/matter/conftest.py +++ b/tests/components/matter/conftest.py @@ -115,6 +115,7 @@ async def integration_fixture( "window_covering_pa_lift", "window_covering_pa_tilt", "window_covering_tilt", + "yandex_smart_socket", ] ) async def matter_devices( diff --git a/tests/components/matter/fixtures/nodes/yandex_smart_socket.json b/tests/components/matter/fixtures/nodes/yandex_smart_socket.json new file mode 100644 index 0000000000000..26cdf38414f7d --- /dev/null +++ b/tests/components/matter/fixtures/nodes/yandex_smart_socket.json @@ -0,0 +1,278 @@ +{ + "node_id": 4, + "date_commissioned": "2024-12-05T10:54:31.635203", + "last_interview": "2024-12-05T12:16:52.038776", + "interview_version": 6, + "available": true, + "is_bridge": false, + "attributes": { + "0/29/0": [ + { + "0": 22, + "1": 1 + } + ], + "0/29/1": [29, 31, 40, 42, 48, 49, 51, 52, 54, 60, 62, 63], + "0/29/2": [41], + "0/29/3": [1], + "0/29/65532": 0, + "0/29/65533": 2, + "0/29/65528": [], + "0/29/65529": [], + "0/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], + "0/31/0": [ + { + "1": 5, + "2": 2, + "3": [112233], + "4": null, + "254": 3 + } + ], + "0/31/1": [], + "0/31/2": 4, + "0/31/3": 3, + "0/31/4": 4, + "0/31/65532": 0, + "0/31/65533": 1, + "0/31/65528": [], + "0/31/65529": [], + "0/31/65531": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533], + "0/40/0": 17, + "0/40/1": "Yandex", + "0/40/2": 5130, + "0/40/3": "YNDX-00540", + "0/40/4": 540, + "0/40/5": "", + "0/40/6": "XX", + "0/40/7": 0, + "0/40/8": "v0.4", + "0/40/9": 18, + "0/40/10": "8.0.r13402545-18", + "0/40/15": "HP000RM000V4RW", + "0/40/17": true, + "0/40/18": "E4480D32A5480B29", + "0/40/19": { + "0": 3, + "1": 3 + }, + "0/40/65532": 0, + "0/40/65533": 1, + "0/40/65528": [], + "0/40/65529": [], + "0/40/65531": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 17, 18, 19, 65528, 65529, 65531, + 65532, 65533 + ], + "0/42/0": [], + "0/42/1": true, + "0/42/2": 1, + "0/42/3": null, + "0/42/65532": 0, + "0/42/65533": 1, + "0/42/65528": [], + "0/42/65529": [0], + "0/42/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], + "0/48/0": 0, + "0/48/1": { + "0": 60, + "1": 900 + }, + "0/48/2": 0, + "0/48/3": 0, + "0/48/4": true, + "0/48/65532": 0, + "0/48/65533": 1, + "0/48/65528": [1, 3, 5], + "0/48/65529": [0, 2, 4], + "0/48/65531": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533], + "0/49/0": 1, + "0/49/1": [ + { + "0": "**REDACTED**", + "1": true + } + ], + "0/49/2": 10, + "0/49/3": 30, + "0/49/4": true, + "0/49/5": 0, + "0/49/6": "**REDACTED**", + "0/49/7": null, + "0/49/65532": 1, + "0/49/65533": 1, + "0/49/65528": [1, 5, 7], + "0/49/65529": [0, 2, 4, 6, 8], + "0/49/65531": [0, 1, 2, 3, 4, 5, 6, 7, 65528, 65529, 65531, 65532, 65533], + "0/51/0": [ + { + "0": "WIFI_STA_DEF", + "1": true, + "2": null, + "3": null, + "4": "PAtP8Nse", + "5": ["wKgAEw=="], + "6": ["/oAAAAAAAAA+C0///vDbHg==", "/YrmoeskHZU+C0///vDbHg=="], + "7": 1 + } + ], + "0/51/1": 4, + "0/51/2": 124, + "0/51/3": 0, + "0/51/4": 1, + "0/51/5": [], + "0/51/6": [], + "0/51/7": [], + "0/51/8": false, + "0/51/65532": 0, + "0/51/65533": 1, + "0/51/65528": [], + "0/51/65529": [0], + "0/51/65531": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 65528, 65529, 65531, 65532, 65533 + ], + "0/52/1": 79260, + "0/52/2": 171268, + "0/52/65532": 0, + "0/52/65533": 1, + "0/52/65528": [], + "0/52/65529": [], + "0/52/65531": [1, 2, 65528, 65529, 65531, 65532, 65533], + "0/54/0": "eJoYDvok", + "0/54/1": 4, + "0/54/2": 3, + "0/54/3": 11, + "0/54/4": -53, + "0/54/65532": 0, + "0/54/65533": 1, + "0/54/65528": [], + "0/54/65529": [], + "0/54/65531": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533], + "0/60/0": 0, + "0/60/1": null, + "0/60/2": null, + "0/60/65532": 0, + "0/60/65533": 1, + "0/60/65528": [], + "0/60/65529": [0, 1, 2], + "0/60/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533], + "0/62/0": [ + { + "1": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVASQRBBgkBwEkCAEwCUEEvOt9COzrjgf+b8q6FcKeKfbtqJybToVtEF0jiidbqg8FPmTIPTm1kU9hEiE6sd2N/GWSQHRoMi3YNl19h1PM3zcKNQEoARgkAgE2AwQCBAEYMAQUSu0+nQ/nOzrUNECyeBAqGPVu33YwBRS2PEiS/N109emRL3DTMaiWoWrEShgwC0DoGPCGt0HeGYnTS4TS2R7vbNhiFuuIrUQuxY5phP/UXBZosBDQTsnRTbMof18OkeO68MEcLXdIXjBJvBDaP/TsGA==", + "2": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEEz8nO5tz0gMFM5TW4YjYXGxkhr/UKHZg1rCa21StYqGd0wGaP7a5eMR+2BY20D1b11R7i6teWKnAaW+WqY0vQTjcKNQEpARgkAmAwBBS2PEiS/N109emRL3DTMaiWoWrESjAFFG//dFS5V0Y6/QdSQcC+z7idKKeJGDALQGFBsf7Ecq44e7NN8dCZIoJMUG16rmwD4ZtHtD4JPTxYabEreeblNF2ZDSgbo+A8sfz7Ci37WjznxbEj96vR8MgY", + "254": 3 + } + ], + "0/62/1": [ + { + "1": "BLB0QnDldRPfV2xt6Nd/34ja8uaWwvsLYZsF3yCdIwyB/krYZ0u1uBS0FTo7E3iqvN0cDZ7fbhw0OUsKTVZ9Y10=", + "2": 65521, + "3": 1, + "4": 4, + "5": "", + "254": 3 + } + ], + "0/62/2": 5, + "0/62/3": 4, + "0/62/4": [ + "FTABAQAkAgE3AyYU3a/iASYVn7wdXBgmBKaW1SwkBQA3BiYU3a/iASYVn7wdXBgkBwEkCAEwCUEE/OyhHiUZDgJ7iUVCKouxsZgI0DGBcK8E+vbDIHD5gfeFPNuT5sXN8aHlsEl7fZhfjbdEbIFudeJKIr5uf7+PLTcKNQEpARgkAmAwBBQtr6wAOFJ7UJLwYUKvomZh5wPaszAFFC2vrAA4UntQkvBhQq+iZmHnA9qzGDALQM5/1ziQdNcMURJqGH+j9wt7w/wPyeq8zf+u3FGgmmfhBSouJw4f+TIJLk7m/eQD0p2Q5rSDEuuwI2VBTxxeuWgY", + "FTABAQAkAgE3AycUe5hjm9Wdt4YmFewk8wUYJgTGN00tJAUANwYnFHuYY5vVnbeGJhXsJPMFGCQHASQIATAJQQR56PnGPW5p1dXhHDSVnjoah8C2+JYHzPAm5tvYgup9gf7DukH2TxxLdDEaBdD4hgQj/R8hrMYSmj8XmHQ8HhdZNwo1ASkBGCQCYDAEFN8wYcjYskj9OSQoEXkOn0QmWDrkMAUU3zBhyNiySP05JCgReQ6fRCZYOuQYMAtA+j7ir4H1KYIxAe49jhZr/Gg7pDUKtIcYyUVJD0g9egIYHShM1y1j3BsOQTBX6mnLPp4FS4AtNsUgaM+XPKSFSxg=", + "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQUARgkBwEkCAEwCUEEsHRCcOV1E99XbG3o13/fiNry5pbC+wthmwXfIJ0jDIH+SthnS7W4FLQVOjsTeKq83RwNnt9uHDQ5SwpNVn1jXTcKNQEpARgkAmAwBBRv/3RUuVdGOv0HUkHAvs+4nSiniTAFFG//dFS5V0Y6/QdSQcC+z7idKKeJGDALQKrvVhoinxo07C2nI/zakt4xUZKgab6DVI4mBXYoPQXaZM8jmEqWboPnLBUGbr9UAnqEc9yARHwlC77eXN1BCdUY", + "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQUARgkBwEkCAEwCUEEMmvMdDf/h+u7fawdjIe6gXEeWuszCShR8ulsHMLnJYTMHVrkztOcj4cHw6haH/q909aVmL3xLlbEC2lZtmZClDcKNQEpARgkAmAwBBRoZjEcSXeh6IFBtW0A2OilJBdeYjAFFGhmMRxJd6HogUG1bQDY6KUkF15iGDALQJm5+/SkVrR4iBpGVqZZGOH+DpS+cQYqceN1+JSnDFwxJe+khYxFifMohSQ5NLlTiJQTZWYpqMKMZHT36pWWADUY" + ], + "0/62/5": 3, + "0/62/65532": 0, + "0/62/65533": 1, + "0/62/65528": [1, 3, 5, 8], + "0/62/65529": [0, 2, 4, 6, 7, 9, 10, 11], + "0/62/65531": [0, 1, 2, 3, 4, 5, 65528, 65529, 65531, 65532, 65533], + "0/63/0": [], + "0/63/1": [], + "0/63/2": 4, + "0/63/3": 3, + "0/63/65532": 0, + "0/63/65533": 2, + "0/63/65528": [2, 5], + "0/63/65529": [0, 1, 3, 4], + "0/63/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], + "1/3/0": 0, + "1/3/1": 2, + "1/3/65532": 0, + "1/3/65533": 4, + "1/3/65528": [], + "1/3/65529": [0, 64], + "1/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533], + "1/4/0": 128, + "1/4/65532": 1, + "1/4/65533": 4, + "1/4/65528": [0, 1, 2, 3], + "1/4/65529": [0, 1, 2, 3, 4, 5], + "1/4/65531": [0, 65528, 65529, 65531, 65532, 65533], + "1/6/0": true, + "1/6/16384": true, + "1/6/16385": 0, + "1/6/16386": 0, + "1/6/16387": 0, + "1/6/65532": 1, + "1/6/65533": 4, + "1/6/65528": [], + "1/6/65529": [0, 1, 2, 64, 65, 66], + "1/6/65531": [ + 0, 16384, 16385, 16386, 16387, 65528, 65529, 65531, 65532, 65533 + ], + "1/29/0": [ + { + "0": 266, + "1": 2 + } + ], + "1/29/1": [3, 4, 6, 29, 2820, 336264194, 336264195], + "1/29/2": [], + "1/29/3": [], + "1/29/65532": 0, + "1/29/65533": 2, + "1/29/65528": [], + "1/29/65529": [], + "1/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], + "1/2820/0": 9, + "1/2820/1285": 2170, + "1/2820/1288": 592, + "1/2820/1291": 560, + "1/2820/1536": 1, + "1/2820/1537": 10, + "1/2820/1538": 1, + "1/2820/1539": 1000, + "1/2820/1540": 1, + "1/2820/1541": 8, + "1/2820/2049": 2530, + "1/2820/2050": 16300, + "1/2820/65532": 0, + "1/2820/65533": 3, + "1/2820/65528": [], + "1/2820/65529": [], + "1/2820/65531": [ + 0, 1285, 1288, 1291, 1536, 1537, 1538, 1539, 1540, 1541, 2049, 2050, + 65528, 65529, 65531, 65532, 65533 + ], + "1/336264194/336199680": 44, + "1/336264194/336199681": 0, + "1/336264194/336199682": 0, + "1/336264194/336199698": 70, + "1/336264194/65532": 0, + "1/336264194/65533": 1, + "1/336264194/65528": [], + "1/336264194/65529": [], + "1/336264194/65531": [ + 65528, 65529, 65531, 336199680, 336199681, 336199682, 336199698, 65532, + 65533 + ], + "1/336264195/336199680": 0, + "1/336264195/65532": 0, + "1/336264195/65533": 1, + "1/336264195/65528": [], + "1/336264195/65529": [], + "1/336264195/65531": [65528, 65529, 65531, 336199680, 65532, 65533] + }, + "attribute_subscriptions": [] +} diff --git a/tests/components/matter/snapshots/test_sensor.ambr b/tests/components/matter/snapshots/test_sensor.ambr index 96346b906c31f..6789540decbb5 100644 --- a/tests/components/matter/snapshots/test_sensor.ambr +++ b/tests/components/matter/snapshots/test_sensor.ambr @@ -2831,3 +2831,165 @@ 'state': '21.0', }) # --- +# name: test_sensors[yandex_smart_socket][sensor.yndx_00540_current-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.yndx_00540_current', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Current', + 'platform': 'matter', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00000000000004D2-0000000000000004-MatterNodeDevice-1-ElectricalMeasurementRmsCurrent-2820-1288', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[yandex_smart_socket][sensor.yndx_00540_current-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'current', + 'friendly_name': 'YNDX-00540 Current', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.yndx_00540_current', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.592', + }) +# --- +# name: test_sensors[yandex_smart_socket][sensor.yndx_00540_power-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.yndx_00540_power', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Power', + 'platform': 'matter', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00000000000004D2-0000000000000004-MatterNodeDevice-1-ElectricalMeasurementActivePower-2820-1291', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[yandex_smart_socket][sensor.yndx_00540_power-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'YNDX-00540 Power', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.yndx_00540_power', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '70.0', + }) +# --- +# name: test_sensors[yandex_smart_socket][sensor.yndx_00540_voltage-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.yndx_00540_voltage', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Voltage', + 'platform': 'matter', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00000000000004D2-0000000000000004-MatterNodeDevice-1-ElectricalMeasurementRmsVoltage-2820-1285', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[yandex_smart_socket][sensor.yndx_00540_voltage-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'voltage', + 'friendly_name': 'YNDX-00540 Voltage', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.yndx_00540_voltage', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '217.0', + }) +# --- From 06420435eaf5b5a2b2f779a96092ebc8dcfc1f2d Mon Sep 17 00:00:00 2001 From: Artem Sorokin Date: Thu, 9 Jan 2025 23:02:20 +0300 Subject: [PATCH 2/6] Add rounding and extra checks --- homeassistant/components/matter/sensor.py | 10 +++++++--- tests/components/matter/snapshots/test_sensor.ambr | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/matter/sensor.py b/homeassistant/components/matter/sensor.py index a9eeb75e33bf5..2b217054d330b 100644 --- a/homeassistant/components/matter/sensor.py +++ b/homeassistant/components/matter/sensor.py @@ -122,12 +122,16 @@ def _update_from_device(self) -> None: self.get_matter_attribute_value(self._entity_info.attributes_to_watch[1]), self.get_matter_attribute_value(self._entity_info.attributes_to_watch[2]), ) - for value in (raw_value, divisor, multiplier): - if value in (None, NullValue): + + for value in (divisor, multiplier): + if value in (None, NullValue, 0): self._attr_native_value = None return - self._attr_native_value = (raw_value / divisor) * multiplier + if raw_value in (None, NullValue): + self._attr_native_value = None + else: + self._attr_native_value = round(raw_value / divisor * multiplier, 2) class MatterOperationalStateSensor(MatterSensor): diff --git a/tests/components/matter/snapshots/test_sensor.ambr b/tests/components/matter/snapshots/test_sensor.ambr index 202ed83dcce5d..dfe1649ef2fe8 100644 --- a/tests/components/matter/snapshots/test_sensor.ambr +++ b/tests/components/matter/snapshots/test_sensor.ambr @@ -3194,7 +3194,7 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.592', + 'state': '0.59', }) # --- # name: test_sensors[yandex_smart_socket][sensor.yndx_00540_power-entry] From 5d60ae758cee2ac38d5ae0cbb130dadeffd36e8e Mon Sep 17 00:00:00 2001 From: Artem Sorokin Date: Sat, 25 Jan 2025 15:41:03 +0300 Subject: [PATCH 3/6] Add missing snapshots for tests --- .../matter/snapshots/test_button.ambr | 47 +++++++++++++++ .../matter/snapshots/test_select.ambr | 59 +++++++++++++++++++ .../matter/snapshots/test_switch.ambr | 47 +++++++++++++++ 3 files changed, 153 insertions(+) diff --git a/tests/components/matter/snapshots/test_button.ambr b/tests/components/matter/snapshots/test_button.ambr index bcba0da808e2f..cf9ddc2805fc7 100644 --- a/tests/components/matter/snapshots/test_button.ambr +++ b/tests/components/matter/snapshots/test_button.ambr @@ -3041,3 +3041,50 @@ 'state': 'unknown', }) # --- +# name: test_buttons[yandex_smart_socket][button.yndx_00540_identify-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'button', + 'entity_category': , + 'entity_id': 'button.yndx_00540_identify', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Identify', + 'platform': 'matter', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00000000000004D2-0000000000000004-MatterNodeDevice-1-IdentifyButton-3-65529', + 'unit_of_measurement': None, + }) +# --- +# name: test_buttons[yandex_smart_socket][button.yndx_00540_identify-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'identify', + 'friendly_name': 'YNDX-00540 Identify', + }), + 'context': , + 'entity_id': 'button.yndx_00540_identify', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- diff --git a/tests/components/matter/snapshots/test_select.ambr b/tests/components/matter/snapshots/test_select.ambr index 19a90503086e7..9a2639ba7e11a 100644 --- a/tests/components/matter/snapshots/test_select.ambr +++ b/tests/components/matter/snapshots/test_select.ambr @@ -1852,3 +1852,62 @@ 'state': 'Quick', }) # --- +# name: test_selects[yandex_smart_socket][select.yndx_00540_power_on_behavior_on_startup-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'on', + 'off', + 'toggle', + 'previous', + ]), + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'select', + 'entity_category': , + 'entity_id': 'select.yndx_00540_power_on_behavior_on_startup', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Power-on behavior on startup', + 'platform': 'matter', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'startup_on_off', + 'unique_id': '00000000000004D2-0000000000000004-MatterNodeDevice-1-MatterStartUpOnOff-6-16387', + 'unit_of_measurement': None, + }) +# --- +# name: test_selects[yandex_smart_socket][select.yndx_00540_power_on_behavior_on_startup-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'YNDX-00540 Power-on behavior on startup', + 'options': list([ + 'on', + 'off', + 'toggle', + 'previous', + ]), + }), + 'context': , + 'entity_id': 'select.yndx_00540_power_on_behavior_on_startup', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- diff --git a/tests/components/matter/snapshots/test_switch.ambr b/tests/components/matter/snapshots/test_switch.ambr index 612e81580a598..8277ee28838f7 100644 --- a/tests/components/matter/snapshots/test_switch.ambr +++ b/tests/components/matter/snapshots/test_switch.ambr @@ -421,3 +421,50 @@ 'state': 'on', }) # --- +# name: test_switches[yandex_smart_socket][switch.yndx_00540-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': None, + 'entity_id': 'switch.yndx_00540', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': None, + 'platform': 'matter', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '00000000000004D2-0000000000000004-MatterNodeDevice-1-MatterPlug-6-0', + 'unit_of_measurement': None, + }) +# --- +# name: test_switches[yandex_smart_socket][switch.yndx_00540-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'outlet', + 'friendly_name': 'YNDX-00540', + }), + 'context': , + 'entity_id': 'switch.yndx_00540', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- From 2b8a262c99cc9186e4ec6fd20c2fb92f673205ce Mon Sep 17 00:00:00 2001 From: Artem Sorokin Date: Sun, 26 Jan 2025 10:13:36 +0300 Subject: [PATCH 4/6] Test for zero/null values --- tests/components/matter/test_sensor.py | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/components/matter/test_sensor.py b/tests/components/matter/test_sensor.py index 630809a957db8..6a451c1e0ccc7 100644 --- a/tests/components/matter/test_sensor.py +++ b/tests/components/matter/test_sensor.py @@ -339,3 +339,31 @@ async def test_operational_state_sensor( state = hass.states.get("sensor.dishwasher_operational_state") assert state assert state.state == "extra_state" + + +@pytest.mark.parametrize("node_fixture", ["yandex_smart_socket"]) +async def test_yandex_smart_socket_power_sensor( + hass: HomeAssistant, + matter_client: MagicMock, + matter_node: MatterNode, +) -> None: + """Test Yandex Smart Socket.""" + state = hass.states.get("sensor.yndx_00540_power") + assert state + assert state.state == "70.0" + + # AcPowerDivisor + set_node_attribute(matter_node, 1, 2820, 1541, 0) + await trigger_subscription_callback(hass, matter_client) + + state = hass.states.get("sensor.yndx_00540_power") + assert state + assert state.state == "unknown" + + # ActivePower + set_node_attribute(matter_node, 1, 2820, 1291, None) + await trigger_subscription_callback(hass, matter_client) + + state = hass.states.get("sensor.yndx_00540_power") + assert state + assert state.state == "unknown" From 8d531d2a68b370960e7fb73980d37c1b7aad8837 Mon Sep 17 00:00:00 2001 From: Artem Sorokin Date: Mon, 27 Jan 2025 22:07:05 +0300 Subject: [PATCH 5/6] Improve naming in tests --- tests/components/matter/test_sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/components/matter/test_sensor.py b/tests/components/matter/test_sensor.py index 534bf375e9df6..8a5fbf48a494e 100644 --- a/tests/components/matter/test_sensor.py +++ b/tests/components/matter/test_sensor.py @@ -354,12 +354,12 @@ async def test_operational_state_sensor( @pytest.mark.parametrize("node_fixture", ["yandex_smart_socket"]) -async def test_yandex_smart_socket_power_sensor( +async def test_draft_electrical_measurement_sensor( hass: HomeAssistant, matter_client: MagicMock, matter_node: MatterNode, ) -> None: - """Test Yandex Smart Socket.""" + """Test Draft Electrical Measurement cluster sensors, using Yandex Smart Socket fixture.""" state = hass.states.get("sensor.yndx_00540_power") assert state assert state.state == "70.0" From 8f90a95c35de265f7a76f064233c577b8ddd8380 Mon Sep 17 00:00:00 2001 From: Artem Sorokin Date: Mon, 27 Jan 2025 23:02:59 +0300 Subject: [PATCH 6/6] Update unique_id for identify button --- tests/components/matter/snapshots/test_button.ambr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/components/matter/snapshots/test_button.ambr b/tests/components/matter/snapshots/test_button.ambr index 866e285d03ab8..dbbc984ab2f61 100644 --- a/tests/components/matter/snapshots/test_button.ambr +++ b/tests/components/matter/snapshots/test_button.ambr @@ -1848,7 +1848,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '00000000000004D2-0000000000000004-MatterNodeDevice-1-IdentifyButton-3-65529', + 'unique_id': '00000000000004D2-0000000000000004-MatterNodeDevice-1-IdentifyButton-3-1', 'unit_of_measurement': None, }) # ---