Add Matter discovery schemas for BooleanState sensors (#117870)

Co-authored-by: Stefan Agner <stefan@agner.ch>
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
Co-authored-by: Marcel van der Veldt <m.vanderveldt@outlook.com>
This commit is contained in:
Ludovic BOUÉ 2024-06-21 18:57:18 +02:00 committed by GitHub
parent cb563f25fa
commit 2ad5b1c3a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 280 additions and 34 deletions

View File

@ -7,6 +7,7 @@ from dataclasses import dataclass
from chip.clusters import Objects as clusters from chip.clusters import Objects as clusters
from chip.clusters.Objects import uint from chip.clusters.Objects import uint
from chip.clusters.Types import Nullable, NullValue from chip.clusters.Types import Nullable, NullValue
from matter_server.client.models import device_types
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass, BinarySensorDeviceClass,
@ -73,17 +74,6 @@ DISCOVERY_SCHEMAS = [
vendor_id=(4107,), vendor_id=(4107,),
product_name=("Hue motion sensor",), 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( MatterDiscoverySchema(
platform=Platform.BINARY_SENSOR, platform=Platform.BINARY_SENSOR,
entity_description=MatterBinarySensorEntityDescription( entity_description=MatterBinarySensorEntityDescription(
@ -109,4 +99,50 @@ DISCOVERY_SCHEMAS = [
# only add binary battery sensor if a regular percentage based is not available # only add binary battery sensor if a regular percentage based is not available
absent_attributes=(clusters.PowerSource.Attributes.BatPercentRemaining,), 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,),
),
] ]

View File

@ -45,6 +45,17 @@
} }
}, },
"entity": { "entity": {
"binary_sensor": {
"water_leak": {
"name": "Water leak"
},
"water_freeze": {
"name": "Water freeze"
},
"rain": {
"name": "Rain"
}
},
"climate": { "climate": {
"thermostat": { "thermostat": {
"name": "Thermostat" "name": "Thermostat"

View File

@ -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": []
}

View File

@ -32,29 +32,6 @@ def binary_sensor_platform() -> Generator[None]:
yield 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") @pytest.fixture(name="occupancy_sensor_node")
async def occupancy_sensor_node_fixture( async def occupancy_sensor_node_fixture(
hass: HomeAssistant, matter_client: MagicMock hass: HomeAssistant, matter_client: MagicMock
@ -87,6 +64,43 @@ async def test_occupancy_sensor(
assert state.state == "off" 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 # This tests needs to be adjusted to remove lingering tasks
@pytest.mark.parametrize("expected_lingering_tasks", [True]) @pytest.mark.parametrize("expected_lingering_tasks", [True])
async def test_battery_sensor( async def test_battery_sensor(