From 02192ddf82ee439d60253a60e218160fc2a0d765 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 4 Jul 2023 14:54:37 +0200 Subject: [PATCH] Set Matter battery sensors as diagnostic (#95794) * Set matter battery sensor to diagnostic * Update tests * Use new eve contact sensor dump across the board * Assert entity category * Complete typing --- .../components/matter/binary_sensor.py | 3 +- homeassistant/components/matter/sensor.py | 2 + tests/components/matter/conftest.py | 21 ++ .../matter/fixtures/nodes/contact-sensor.json | 90 ----- .../fixtures/nodes/eve-contact-sensor.json | 343 ++++++++++++++++++ tests/components/matter/test_binary_sensor.py | 78 +++- tests/components/matter/test_door_lock.py | 9 - tests/components/matter/test_sensor.py | 29 ++ 8 files changed, 455 insertions(+), 120 deletions(-) delete mode 100644 tests/components/matter/fixtures/nodes/contact-sensor.json create mode 100644 tests/components/matter/fixtures/nodes/eve-contact-sensor.json diff --git a/homeassistant/components/matter/binary_sensor.py b/homeassistant/components/matter/binary_sensor.py index 7c94c07c8cd..aabfc12eefb 100644 --- a/homeassistant/components/matter/binary_sensor.py +++ b/homeassistant/components/matter/binary_sensor.py @@ -13,7 +13,7 @@ from homeassistant.components.binary_sensor import ( BinarySensorEntityDescription, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import Platform +from homeassistant.const import EntityCategory, Platform from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -99,6 +99,7 @@ DISCOVERY_SCHEMAS = [ entity_description=MatterBinarySensorEntityDescription( key="BatteryChargeLevel", device_class=BinarySensorDeviceClass.BATTERY, + entity_category=EntityCategory.DIAGNOSTIC, measurement_to_ha=lambda x: x != clusters.PowerSource.Enums.BatChargeLevelEnum.kOk, ), diff --git a/homeassistant/components/matter/sensor.py b/homeassistant/components/matter/sensor.py index 027dcda65a7..5021ed7fa0d 100644 --- a/homeassistant/components/matter/sensor.py +++ b/homeassistant/components/matter/sensor.py @@ -16,6 +16,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( LIGHT_LUX, PERCENTAGE, + EntityCategory, Platform, UnitOfPressure, UnitOfTemperature, @@ -127,6 +128,7 @@ DISCOVERY_SCHEMAS = [ key="PowerSource", native_unit_of_measurement=PERCENTAGE, device_class=SensorDeviceClass.BATTERY, + entity_category=EntityCategory.DIAGNOSTIC, # value has double precision measurement_to_ha=lambda x: int(x / 2), ), diff --git a/tests/components/matter/conftest.py b/tests/components/matter/conftest.py index e4af252fccb..6a14148585a 100644 --- a/tests/components/matter/conftest.py +++ b/tests/components/matter/conftest.py @@ -5,12 +5,15 @@ import asyncio from collections.abc import AsyncGenerator, Generator from unittest.mock import AsyncMock, MagicMock, patch +from matter_server.client.models.node import MatterNode from matter_server.common.const import SCHEMA_VERSION from matter_server.common.models import ServerInfoMessage import pytest from homeassistant.core import HomeAssistant +from .common import setup_integration_with_node_fixture + from tests.common import MockConfigEntry MOCK_FABRIC_ID = 12341234 @@ -210,3 +213,21 @@ def update_addon_fixture() -> Generator[AsyncMock, None, None]: "homeassistant.components.hassio.addon_manager.async_update_addon" ) as update_addon: yield update_addon + + +@pytest.fixture(name="door_lock") +async def door_lock_fixture( + hass: HomeAssistant, matter_client: MagicMock +) -> MatterNode: + """Fixture for a door lock node.""" + return await setup_integration_with_node_fixture(hass, "door-lock", matter_client) + + +@pytest.fixture(name="eve_contact_sensor_node") +async def eve_contact_sensor_node_fixture( + hass: HomeAssistant, matter_client: MagicMock +) -> MatterNode: + """Fixture for a contact sensor node.""" + return await setup_integration_with_node_fixture( + hass, "eve-contact-sensor", matter_client + ) diff --git a/tests/components/matter/fixtures/nodes/contact-sensor.json b/tests/components/matter/fixtures/nodes/contact-sensor.json deleted file mode 100644 index 909f7be2ebe..00000000000 --- a/tests/components/matter/fixtures/nodes/contact-sensor.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "node_id": 1, - "date_commissioned": "2022-11-29T21:23:48.485051", - "last_interview": "2022-11-29T21:23:48.485057", - "interview_version": 2, - "attributes": { - "0/29/0": [ - { - "deviceType": 22, - "revision": 1 - } - ], - "0/29/1": [ - 4, 29, 31, 40, 42, 43, 44, 48, 49, 50, 51, 52, 53, 54, 55, 59, 60, 62, 63, - 64, 65 - ], - "0/29/2": [41], - "0/29/3": [1], - "0/29/65532": 0, - "0/29/65533": 1, - "0/29/65528": [], - "0/29/65529": [], - "0/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], - "0/40/0": 1, - "0/40/1": "Nabu Casa", - "0/40/2": 65521, - "0/40/3": "Mock ContactSensor", - "0/40/4": 32768, - "0/40/5": "Mock Contact sensor", - "0/40/6": "XX", - "0/40/7": 0, - "0/40/8": "v1.0", - "0/40/9": 1, - "0/40/10": "v1.0", - "0/40/11": "20221206", - "0/40/12": "", - "0/40/13": "", - "0/40/14": "", - "0/40/15": "TEST_SN", - "0/40/16": false, - "0/40/17": true, - "0/40/18": "mock-contact-sensor", - "0/40/19": { - "caseSessionsPerFabric": 3, - "subscriptionsPerFabric": 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, 11, 12, 13, 14, 15, 16, 17, 18, 19, - 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/29/0": [ - { - "deviceType": 21, - "revision": 1 - } - ], - "1/29/1": [ - 3, 4, 5, 6, 7, 8, 15, 29, 30, 37, 47, 59, 64, 65, 69, 80, 257, 258, 259, - 512, 513, 514, 516, 768, 1024, 1026, 1027, 1028, 1029, 1030, 1283, 1284, - 1285, 1286, 1287, 1288, 1289, 1290, 1291, 1292, 1293, 1294, 2820, - 4294048773 - ], - "1/29/2": [], - "1/29/3": [], - "1/29/65532": 0, - "1/29/65533": 1, - "1/29/65528": [], - "1/29/65529": [], - "1/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], - "1/69/0": true, - "1/69/65532": 0, - "1/69/65533": 1, - "1/69/65528": [], - "1/69/65529": [], - "1/69/65531": [0, 65528, 65529, 65531, 65532, 65533] - }, - "available": true, - "attribute_subscriptions": [] -} diff --git a/tests/components/matter/fixtures/nodes/eve-contact-sensor.json b/tests/components/matter/fixtures/nodes/eve-contact-sensor.json new file mode 100644 index 00000000000..b0eacfb621c --- /dev/null +++ b/tests/components/matter/fixtures/nodes/eve-contact-sensor.json @@ -0,0 +1,343 @@ +{ + "node_id": 1, + "date_commissioned": "2023-07-02T14:06:45.190550", + "last_interview": "2023-07-02T14:06:45.190553", + "interview_version": 4, + "available": true, + "is_bridge": false, + "attributes": { + "0/53/65532": 15, + "0/53/11": 26, + "0/53/3": 4895, + "0/53/47": 0, + "0/53/8": [ + { + "extAddress": 12872547289273451492, + "rloc16": 1024, + "routerId": 1, + "nextHop": 0, + "pathCost": 0, + "LQIIn": 3, + "LQIOut": 3, + "age": 142, + "allocated": true, + "linkEstablished": true + } + ], + "0/53/29": 1556, + "0/53/9": 2040160480, + "0/53/15": 1, + "0/53/40": 519, + "0/53/7": [ + { + "extAddress": 12872547289273451492, + "age": 654, + "rloc16": 1024, + "linkFrameCounter": 738, + "mleFrameCounter": 418, + "lqi": 3, + "averageRssi": -50, + "lastRssi": -51, + "frameErrorRate": 5, + "messageErrorRate": 0, + "rxOnWhenIdle": true, + "fullThreadDevice": true, + "fullNetworkData": true, + "isChild": false + } + ], + "0/53/33": 66, + "0/53/18": 1, + "0/53/45": 0, + "0/53/21": 0, + "0/53/36": 0, + "0/53/44": 0, + "0/53/50": 0, + "0/53/60": "AB//wA==", + "0/53/10": 68, + "0/53/53": 0, + "0/53/65528": [], + "0/53/4": 5980345540157460411, + "0/53/19": 1, + "0/53/62": [0, 0, 0, 0], + "0/53/54": 2, + "0/53/49": 0, + "0/53/23": 2597, + "0/53/20": 0, + "0/53/28": 1059, + "0/53/24": 17, + "0/53/22": 2614, + "0/53/17": 0, + "0/53/32": 0, + "0/53/14": 1, + "0/53/26": 2597, + "0/53/37": 0, + "0/53/65529": [0], + "0/53/34": 1, + "0/53/2": "MyHome1425454932", + "0/53/6": 0, + "0/53/43": 0, + "0/53/25": 2597, + "0/53/30": 0, + "0/53/41": 1, + "0/53/55": 4, + "0/53/42": 520, + "0/53/52": 0, + "0/53/61": { + "activeTimestampPresent": true, + "pendingTimestampPresent": false, + "masterKeyPresent": true, + "networkNamePresent": true, + "extendedPanIdPresent": true, + "meshLocalPrefixPresent": true, + "delayPresent": false, + "panIdPresent": true, + "channelPresent": true, + "pskcPresent": true, + "securityPolicyPresent": true, + "channelMaskPresent": true + }, + "0/53/48": 3, + "0/53/39": 529, + "0/53/35": 0, + "0/53/38": 0, + "0/53/31": 0, + "0/53/51": 0, + "0/53/65533": 1, + "0/53/59": { + "rotationTime": 672, + "flags": 8335 + }, + "0/53/46": 0, + "0/53/5": "QP1S/nSVYwAA", + "0/53/13": 1, + "0/53/27": 17, + "0/53/1": 2, + "0/53/0": 25, + "0/53/65531": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 59, + 60, 61, 62, 65528, 65529, 65531, 65532, 65533 + ], + "0/53/12": 121, + "0/53/16": 0, + "0/42/0": [ + { + "providerNodeID": 1773685588, + "endpoint": 0, + "fabricIndex": 1 + } + ], + "0/42/65528": [], + "0/42/65533": 1, + "0/42/1": true, + "0/42/2": 1, + "0/42/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], + "0/42/3": null, + "0/42/65532": 0, + "0/42/65529": [0], + "0/48/65532": 0, + "0/48/65528": [1, 3, 5], + "0/48/1": { + "failSafeExpiryLengthSeconds": 60, + "maxCumulativeFailsafeSeconds": 900 + }, + "0/48/4": true, + "0/48/65531": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533], + "0/48/2": 0, + "0/48/0": 0, + "0/48/3": 0, + "0/48/65529": [0, 2, 4], + "0/48/65533": 1, + "0/31/4": 3, + "0/31/65529": [], + "0/31/3": 3, + "0/31/65533": 1, + "0/31/65531": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533], + "0/31/1": [], + "0/31/0": [ + { + "privilege": 0, + "authMode": 0, + "subjects": null, + "targets": null, + "fabricIndex": 1 + }, + { + "privilege": 0, + "authMode": 0, + "subjects": null, + "targets": null, + "fabricIndex": 2 + }, + { + "privilege": 5, + "authMode": 2, + "subjects": [112233], + "targets": null, + "fabricIndex": 3 + } + ], + "0/31/65532": 0, + "0/31/65528": [], + "0/31/2": 4, + "0/49/2": 10, + "0/49/65528": [1, 5, 7], + "0/49/65533": 1, + "0/49/1": [ + { + "networkID": "Uv50lWMtT7s=", + "connected": true + } + ], + "0/49/3": 20, + "0/49/7": null, + "0/49/0": 1, + "0/49/6": null, + "0/49/65529": [0, 3, 4, 6, 8], + "0/49/5": 0, + "0/49/4": true, + "0/49/65531": [0, 1, 2, 3, 4, 5, 6, 7, 65528, 65529, 65531, 65532, 65533], + "0/49/65532": 2, + "0/63/0": [], + "0/63/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], + "0/63/65528": [2, 5], + "0/63/1": [], + "0/63/3": 3, + "0/63/65532": 0, + "0/63/65533": 1, + "0/63/65529": [0, 1, 3, 4], + "0/63/2": 3, + "0/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], + "0/29/65529": [], + "0/29/65532": 0, + "0/29/3": [1], + "0/29/2": [41], + "0/29/65533": 1, + "0/29/0": [ + { + "deviceType": 22, + "revision": 1 + } + ], + "0/29/1": [29, 31, 40, 42, 46, 48, 49, 51, 53, 60, 62, 63], + "0/29/65528": [], + "0/51/65531": [0, 1, 2, 3, 5, 6, 7, 8, 65528, 65529, 65531, 65532, 65533], + "0/51/0": [ + { + "name": "ieee802154", + "isOperational": true, + "offPremiseServicesReachableIPv4": null, + "offPremiseServicesReachableIPv6": null, + "hardwareAddress": "YtmXHFJ/dhk=", + "IPv4Addresses": [], + "IPv6Addresses": [ + "/RG+U41GAABynlpPU50e5g==", + "/oAAAAAAAABg2ZccUn92GQ==", + "/VL+dJVjAAB1cwmi02rvTA==" + ], + "type": 4 + } + ], + "0/51/65529": [0], + "0/51/7": [], + "0/51/3": 0, + "0/51/65533": 1, + "0/51/2": 653, + "0/51/6": [], + "0/51/1": 1, + "0/51/8": false, + "0/51/65532": 0, + "0/51/65528": [], + "0/51/5": [], + "0/40/9": 6650, + "0/40/65529": [], + "0/40/4": 77, + "0/40/1": "Eve Systems", + "0/40/5": "", + "0/40/15": "QV26L1A16199", + "0/40/8": "1.1", + "0/40/6": "**REDACTED**", + "0/40/3": "Eve Door", + "0/40/19": { + "caseSessionsPerFabric": 3, + "subscriptionsPerFabric": 3 + }, + "0/40/2": 4874, + "0/40/65532": 0, + "0/40/65528": [], + "0/40/65531": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 18, 19, 65528, 65529, 65531, 65532, + 65533 + ], + "0/40/7": 1, + "0/40/10": "3.2.1", + "0/40/0": 1, + "0/40/65533": 1, + "0/40/18": "4D97F6015F8E39C1", + "0/46/65529": [], + "0/46/0": [1], + "0/46/65528": [], + "0/46/65531": [0, 65528, 65529, 65531, 65532, 65533], + "0/46/65532": 0, + "0/46/65533": 1, + "0/60/65532": 0, + "0/60/0": 0, + "0/60/65528": [], + "0/60/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533], + "0/60/2": null, + "0/60/65529": [0, 1, 2], + "0/60/1": null, + "0/60/65533": 1, + "1/69/65529": [], + "1/69/65528": [], + "1/69/65531": [0, 65528, 65529, 65531, 65532, 65533], + "1/69/65533": 1, + "1/69/65532": 0, + "1/69/0": false, + "1/29/65529": [], + "1/29/1": [3, 29, 47, 69, 319486977], + "1/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], + "1/29/65533": 1, + "1/29/0": [ + { + "deviceType": 21, + "revision": 1 + } + ], + "1/29/65528": [], + "1/29/65532": 0, + "1/29/2": [], + "1/29/3": [], + "1/47/65531": [ + 0, 1, 2, 11, 12, 14, 15, 16, 18, 19, 25, 65528, 65529, 65531, 65532, 65533 + ], + "1/47/15": false, + "1/47/25": 1, + "1/47/2": "Battery", + "1/47/18": [], + "1/47/1": 0, + "1/47/14": 0, + "1/47/65533": 1, + "1/47/12": 200, + "1/47/19": "", + "1/47/11": 3558, + "1/47/65528": [], + "1/47/65529": [], + "1/47/0": 1, + "1/47/16": 2, + "1/47/65532": 10, + "1/3/65528": [], + "1/3/65529": [0], + "1/3/1": 2, + "1/3/0": 0, + "1/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533], + "1/3/65532": 0, + "1/3/65533": 4 + }, + "attribute_subscriptions": [ + [1, 69, 0], + [1, 47, 12] + ] +} diff --git a/tests/components/matter/test_binary_sensor.py b/tests/components/matter/test_binary_sensor.py index d7982e1d5ae..4dbb3b27b9c 100644 --- a/tests/components/matter/test_binary_sensor.py +++ b/tests/components/matter/test_binary_sensor.py @@ -1,10 +1,16 @@ """Test Matter binary sensors.""" -from unittest.mock import MagicMock +from collections.abc import Generator +from unittest.mock import MagicMock, patch from matter_server.client.models.node import MatterNode import pytest +from homeassistant.components.matter.binary_sensor import ( + DISCOVERY_SCHEMAS as BINARY_SENSOR_SCHEMAS, +) +from homeassistant.const import EntityCategory, Platform from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from .common import ( set_node_attribute, @@ -13,14 +19,16 @@ from .common import ( ) -@pytest.fixture(name="contact_sensor_node") -async def contact_sensor_node_fixture( - hass: HomeAssistant, matter_client: MagicMock -) -> MatterNode: - """Fixture for a contact sensor node.""" - return await setup_integration_with_node_fixture( - hass, "contact-sensor", matter_client - ) +@pytest.fixture(autouse=True) +def binary_sensor_platform() -> Generator[None, None, None]: + """Load only the binary sensor platform.""" + with patch( + "homeassistant.components.matter.discovery.DISCOVERY_SCHEMAS", + new={ + Platform.BINARY_SENSOR: BINARY_SENSOR_SCHEMAS, + }, + ): + yield # This tests needs to be adjusted to remove lingering tasks @@ -28,22 +36,23 @@ async def contact_sensor_node_fixture( async def test_contact_sensor( hass: HomeAssistant, matter_client: MagicMock, - contact_sensor_node: MatterNode, + eve_contact_sensor_node: MatterNode, ) -> None: """Test contact sensor.""" - state = hass.states.get("binary_sensor.mock_contact_sensor_door") - assert state - assert state.state == "off" - - set_node_attribute(contact_sensor_node, 1, 69, 0, False) - await trigger_subscription_callback( - hass, matter_client, data=(contact_sensor_node.node_id, "1/69/0", False) - ) - - state = hass.states.get("binary_sensor.mock_contact_sensor_door") + entity_id = "binary_sensor.eve_door_door" + state = hass.states.get(entity_id) assert state assert state.state == "on" + set_node_attribute(eve_contact_sensor_node, 1, 69, 0, True) + await trigger_subscription_callback( + hass, matter_client, data=(eve_contact_sensor_node.node_id, "1/69/0", True) + ) + + state = hass.states.get(entity_id) + assert state + assert state.state == "off" + @pytest.fixture(name="occupancy_sensor_node") async def occupancy_sensor_node_fixture( @@ -75,3 +84,32 @@ async def test_occupancy_sensor( state = hass.states.get("binary_sensor.mock_occupancy_sensor_occupancy") assert state assert state.state == "off" + + +# This tests needs to be adjusted to remove lingering tasks +@pytest.mark.parametrize("expected_lingering_tasks", [True]) +async def test_battery_sensor( + hass: HomeAssistant, + matter_client: MagicMock, + door_lock: MatterNode, +) -> None: + """Test battery sensor.""" + entity_id = "binary_sensor.mock_door_lock_battery" + state = hass.states.get(entity_id) + assert state + assert state.state == "off" + + set_node_attribute(door_lock, 1, 47, 14, 1) + await trigger_subscription_callback( + hass, matter_client, data=(door_lock.node_id, "1/47/14", 1) + ) + + state = hass.states.get(entity_id) + assert state + assert state.state == "on" + + entity_registry = er.async_get(hass) + entry = entity_registry.async_get(entity_id) + + assert entry + assert entry.entity_category == EntityCategory.DIAGNOSTIC diff --git a/tests/components/matter/test_door_lock.py b/tests/components/matter/test_door_lock.py index 003bfa3cf39..3eba65dc8ab 100644 --- a/tests/components/matter/test_door_lock.py +++ b/tests/components/matter/test_door_lock.py @@ -16,19 +16,10 @@ from homeassistant.core import HomeAssistant from .common import ( set_node_attribute, - setup_integration_with_node_fixture, trigger_subscription_callback, ) -@pytest.fixture(name="door_lock") -async def door_lock_fixture( - hass: HomeAssistant, matter_client: MagicMock -) -> MatterNode: - """Fixture for a door lock node.""" - return await setup_integration_with_node_fixture(hass, "door-lock", matter_client) - - # This tests needs to be adjusted to remove lingering tasks @pytest.mark.parametrize("expected_lingering_tasks", [True]) async def test_lock( diff --git a/tests/components/matter/test_sensor.py b/tests/components/matter/test_sensor.py index a2e97e188f6..2650f2b1a6f 100644 --- a/tests/components/matter/test_sensor.py +++ b/tests/components/matter/test_sensor.py @@ -4,7 +4,9 @@ from unittest.mock import MagicMock from matter_server.client.models.node import MatterNode import pytest +from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from .common import ( set_node_attribute, @@ -179,3 +181,30 @@ async def test_temperature_sensor( state = hass.states.get("sensor.mock_temperature_sensor_temperature") assert state assert state.state == "25.0" + + +# This tests needs to be adjusted to remove lingering tasks +@pytest.mark.parametrize("expected_lingering_tasks", [True]) +async def test_battery_sensor( + hass: HomeAssistant, + matter_client: MagicMock, + eve_contact_sensor_node: MatterNode, +) -> None: + """Test battery sensor.""" + entity_id = "sensor.eve_door_battery" + state = hass.states.get(entity_id) + assert state + assert state.state == "100" + + set_node_attribute(eve_contact_sensor_node, 1, 47, 12, 100) + await trigger_subscription_callback(hass, matter_client) + + state = hass.states.get(entity_id) + assert state + assert state.state == "50" + + entity_registry = er.async_get(hass) + entry = entity_registry.async_get(entity_id) + + assert entry + assert entry.entity_category == EntityCategory.DIAGNOSTIC