From 2ad5b1c3a6140a49d1113e86e46b68165cf26884 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20BOU=C3=89?= Date: Fri, 21 Jun 2024 18:57:18 +0200 Subject: [PATCH] Add Matter discovery schemas for BooleanState sensors (#117870) Co-authored-by: Stefan Agner Co-authored-by: Franck Nijhof Co-authored-by: Marcel van der Veldt --- .../components/matter/binary_sensor.py | 58 ++++-- homeassistant/components/matter/strings.json | 11 ++ .../matter/fixtures/nodes/leak-sensor.json | 185 ++++++++++++++++++ tests/components/matter/test_binary_sensor.py | 60 +++--- 4 files changed, 280 insertions(+), 34 deletions(-) create mode 100644 tests/components/matter/fixtures/nodes/leak-sensor.json diff --git a/homeassistant/components/matter/binary_sensor.py b/homeassistant/components/matter/binary_sensor.py index 23ac2195355..b71c35c9cce 100644 --- a/homeassistant/components/matter/binary_sensor.py +++ b/homeassistant/components/matter/binary_sensor.py @@ -7,6 +7,7 @@ from dataclasses import dataclass from chip.clusters import Objects as clusters from chip.clusters.Objects import uint from chip.clusters.Types import Nullable, NullValue +from matter_server.client.models import device_types from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, @@ -73,17 +74,6 @@ DISCOVERY_SCHEMAS = [ vendor_id=(4107,), product_name=("Hue motion sensor",), ), - MatterDiscoverySchema( - platform=Platform.BINARY_SENSOR, - entity_description=MatterBinarySensorEntityDescription( - key="ContactSensor", - device_class=BinarySensorDeviceClass.DOOR, - # 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,), - ), MatterDiscoverySchema( platform=Platform.BINARY_SENSOR, entity_description=MatterBinarySensorEntityDescription( @@ -109,4 +99,50 @@ DISCOVERY_SCHEMAS = [ # only add binary battery sensor if a regular percentage based is not available absent_attributes=(clusters.PowerSource.Attributes.BatPercentRemaining,), ), + # BooleanState sensors (tied to device type) + MatterDiscoverySchema( + platform=Platform.BINARY_SENSOR, + entity_description=MatterBinarySensorEntityDescription( + key="ContactSensor", + device_class=BinarySensorDeviceClass.DOOR, + # 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,), + device_type=(device_types.ContactSensor,), + ), + MatterDiscoverySchema( + platform=Platform.BINARY_SENSOR, + entity_description=MatterBinarySensorEntityDescription( + key="WaterLeakDetector", + translation_key="water_leak", + device_class=BinarySensorDeviceClass.MOISTURE, + ), + entity_class=MatterBinarySensor, + required_attributes=(clusters.BooleanState.Attributes.StateValue,), + device_type=(device_types.WaterLeakDetector,), + ), + MatterDiscoverySchema( + platform=Platform.BINARY_SENSOR, + entity_description=MatterBinarySensorEntityDescription( + key="WaterFreezeDetector", + translation_key="water_freeze", + device_class=BinarySensorDeviceClass.COLD, + ), + entity_class=MatterBinarySensor, + required_attributes=(clusters.BooleanState.Attributes.StateValue,), + device_type=(device_types.WaterFreezeDetector,), + ), + MatterDiscoverySchema( + platform=Platform.BINARY_SENSOR, + entity_description=MatterBinarySensorEntityDescription( + key="RainSensor", + translation_key="rain", + device_class=BinarySensorDeviceClass.MOISTURE, + ), + entity_class=MatterBinarySensor, + required_attributes=(clusters.BooleanState.Attributes.StateValue,), + device_type=(device_types.RainSensor,), + ), ] diff --git a/homeassistant/components/matter/strings.json b/homeassistant/components/matter/strings.json index db71feab9c4..e94ab2e1780 100644 --- a/homeassistant/components/matter/strings.json +++ b/homeassistant/components/matter/strings.json @@ -45,6 +45,17 @@ } }, "entity": { + "binary_sensor": { + "water_leak": { + "name": "Water leak" + }, + "water_freeze": { + "name": "Water freeze" + }, + "rain": { + "name": "Rain" + } + }, "climate": { "thermostat": { "name": "Thermostat" diff --git a/tests/components/matter/fixtures/nodes/leak-sensor.json b/tests/components/matter/fixtures/nodes/leak-sensor.json new file mode 100644 index 00000000000..35cfb281e11 --- /dev/null +++ b/tests/components/matter/fixtures/nodes/leak-sensor.json @@ -0,0 +1,185 @@ +{ + "node_id": 32, + "date_commissioned": "2024-06-21T14:13:02.370603", + "last_interview": "2024-06-21T14:14:49.941142", + "interview_version": 6, + "available": true, + "is_bridge": true, + "attributes": { + "0/29/0": [ + { + "0": 22, + "1": 1 + } + ], + "0/29/1": [29, 31, 40, 43, 44, 48, 49, 50, 51, 52, 56, 60, 62, 63], + "0/29/2": [31], + "0/29/3": [1, 2], + "0/29/65528": [], + "0/29/65529": [], + "0/29/65530": [], + "0/29/65531": [0, 1, 2, 3, 65528, 65529, 65530, 65531, 65532, 65533], + "0/29/65532": 0, + "0/29/65533": 1, + "0/31/65528": [], + "0/31/65529": [], + "0/31/65530": [], + "0/31/65531": [0, 2, 3, 4, 65528, 65529, 65530, 65531, 65532, 65533], + "0/31/65532": 0, + "0/31/65533": 1, + "0/40/0": 1, + "0/40/1": "Mock", + "0/40/2": 65521, + "0/40/3": "Water Leak Detector", + "0/40/4": 32768, + "0/40/5": "Water Leak Detector", + "0/40/6": "", + "0/40/7": 0, + "0/40/8": "", + "0/40/9": 234946562, + "0/40/10": "14.1.0.2", + "0/40/15": "", + "0/40/17": true, + "0/40/18": "", + "0/40/19": { + "0": 3, + "1": 3 + }, + "0/40/65528": [], + "0/40/65529": [], + "0/40/65530": [], + "0/40/65531": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 17, 18, 19, 65528, 65529, 65530, + 65531, 65532, 65533 + ], + "0/40/65532": 0, + "0/40/65533": 2, + "0/43/0": "en", + "0/43/1": ["en"], + "0/43/65528": [], + "0/43/65529": [], + "0/43/65530": [], + "0/43/65531": [0, 1, 65528, 65529, 65530, 65531, 65532, 65533], + "0/43/65532": 0, + "0/43/65533": 1, + "0/44/0": 1, + "0/44/1": 4, + "0/44/2": [], + "0/44/65528": [], + "0/44/65529": [], + "0/44/65530": [], + "0/44/65531": [0, 1, 2, 65528, 65529, 65530, 65531, 65532, 65533], + "0/44/65532": 0, + "0/44/65533": 1, + "0/48/0": 0, + "0/48/1": { + "0": 60, + "1": 900 + }, + "0/48/2": 2, + "0/48/3": 2, + "0/48/4": false, + "0/48/65528": [], + "0/48/65529": [], + "0/48/65530": [], + "0/48/65531": [0, 1, 2, 3, 4, 65528, 65529, 65530, 65531, 65532, 65533], + "0/48/65532": 0, + "0/48/65533": 1, + "0/49/3": 30, + "0/49/65528": [], + "0/49/65529": [], + "0/49/65530": [], + "0/49/65531": [3, 4, 65528, 65529, 65530, 65531, 65532, 65533], + "0/49/65532": 4, + "0/49/65533": 1, + "0/50/65528": [], + "0/50/65529": [], + "0/50/65530": [], + "0/50/65531": [65528, 65529, 65530, 65531, 65532, 65533], + "0/50/65532": 0, + "0/50/65533": 1, + "0/51/1": 7, + "0/51/2": 17, + "0/51/8": false, + "0/51/65528": [], + "0/51/65529": [], + "0/51/65530": [], + "0/51/65531": [0, 1, 2, 8, 65528, 65529, 65530, 65531, 65532, 65533], + "0/51/65532": 0, + "0/51/65533": 1, + "0/52/65528": [], + "0/52/65529": [], + "0/52/65530": [], + "0/52/65531": [65528, 65529, 65530, 65531, 65532, 65533], + "0/52/65532": 0, + "0/52/65533": 1, + "0/56/0": 1718979287000000, + "0/56/1": 3, + "0/56/7": 1718982887000000, + "0/56/65528": [], + "0/56/65529": [], + "0/56/65530": [], + "0/56/65531": [0, 1, 7, 65528, 65529, 65530, 65531, 65532, 65533], + "0/56/65532": 0, + "0/56/65533": 2, + "0/60/0": 0, + "0/60/65528": [], + "0/60/65529": [], + "0/60/65530": [], + "0/60/65531": [0, 1, 2, 65528, 65529, 65530, 65531, 65532, 65533], + "0/60/65532": 0, + "0/60/65533": 1, + "0/62/2": 5, + "0/62/3": 3, + "0/62/5": 3, + "0/62/65528": [], + "0/62/65529": [], + "0/62/65530": [], + "0/62/65531": [0, 1, 2, 3, 4, 5, 65528, 65529, 65530, 65531, 65532, 65533], + "0/62/65532": 0, + "0/62/65533": 1, + "0/63/65528": [], + "0/63/65529": [], + "0/63/65530": [], + "0/63/65531": [65528, 65529, 65530, 65531, 65532, 65533], + "0/63/65532": 0, + "0/63/65533": 2, + "1/3/0": 0, + "1/3/1": 0, + "1/3/65528": [], + "1/3/65529": [], + "1/3/65530": [], + "1/3/65531": [0, 1, 65528, 65529, 65530, 65531, 65532, 65533], + "1/3/65532": 0, + "1/3/65533": 4, + "1/4/65528": [], + "1/4/65529": [], + "1/4/65530": [], + "1/4/65531": [0, 65528, 65529, 65530, 65531, 65532, 65533], + "1/4/65532": 0, + "1/4/65533": 4, + "1/29/0": [ + { + "0": 67, + "1": 1 + } + ], + "1/29/1": [3, 4, 5, 29, 57, 69], + "1/29/2": [], + "1/29/3": [], + "1/29/65528": [], + "1/29/65529": [], + "1/29/65530": [], + "1/29/65531": [0, 1, 2, 3, 65528, 65529, 65530, 65531, 65532, 65533], + "1/29/65532": 0, + "1/29/65533": 1, + "1/69/0": true, + "1/69/65528": [], + "1/69/65529": [], + "1/69/65530": [], + "1/69/65531": [0, 65528, 65529, 65530, 65531, 65532, 65533], + "1/69/65532": 0, + "1/69/65533": 1 + }, + "attribute_subscriptions": [] +} diff --git a/tests/components/matter/test_binary_sensor.py b/tests/components/matter/test_binary_sensor.py index 24928520ee5..becedc0af62 100644 --- a/tests/components/matter/test_binary_sensor.py +++ b/tests/components/matter/test_binary_sensor.py @@ -32,29 +32,6 @@ def binary_sensor_platform() -> Generator[None]: yield -# This tests needs to be adjusted to remove lingering tasks -@pytest.mark.parametrize("expected_lingering_tasks", [True]) -async def test_contact_sensor( - hass: HomeAssistant, - matter_client: MagicMock, - eve_contact_sensor_node: MatterNode, -) -> None: - """Test contact sensor.""" - 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( hass: HomeAssistant, matter_client: MagicMock @@ -87,6 +64,43 @@ async def test_occupancy_sensor( assert state.state == "off" +# This tests needs to be adjusted to remove lingering tasks +@pytest.mark.parametrize("expected_lingering_tasks", [True]) +@pytest.mark.parametrize( + ("fixture", "entity_id"), + [ + ("eve-contact-sensor", "binary_sensor.eve_door_door"), + ("leak-sensor", "binary_sensor.water_leak_detector_water_leak"), + ], +) +async def test_boolean_state_sensors( + hass: HomeAssistant, + matter_client: MagicMock, + fixture: str, + entity_id: str, +) -> None: + """Test if binary sensors get created from devices with Boolean State cluster.""" + node = await setup_integration_with_node_fixture( + hass, + fixture, + matter_client, + ) + state = hass.states.get(entity_id) + assert state + assert state.state == "on" + + # invert the value + cur_attr_value = node.get_attribute_value(1, 69, 0) + set_node_attribute(node, 1, 69, 0, not cur_attr_value) + await trigger_subscription_callback( + hass, matter_client, data=(node.node_id, "1/69/0", not cur_attr_value) + ) + + state = hass.states.get(entity_id) + 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(