mirror of
https://github.com/home-assistant/core.git
synced 2025-07-16 01:37:08 +00:00
Add matter binary sensor platform (#83144)
This commit is contained in:
parent
46669a1704
commit
e7a06046a7
95
homeassistant/components/matter/binary_sensor.py
Normal file
95
homeassistant/components/matter/binary_sensor.py
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
"""Matter binary sensors."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from functools import partial
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from chip.clusters import Objects as clusters
|
||||||
|
from matter_server.common.models import device_types
|
||||||
|
|
||||||
|
from homeassistant.components.binary_sensor import (
|
||||||
|
BinarySensorDeviceClass,
|
||||||
|
BinarySensorEntity,
|
||||||
|
BinarySensorEntityDescription,
|
||||||
|
)
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import Platform
|
||||||
|
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 binary sensor from Config Entry."""
|
||||||
|
matter: MatterAdapter = hass.data[DOMAIN][config_entry.entry_id]
|
||||||
|
matter.register_platform_handler(Platform.BINARY_SENSOR, async_add_entities)
|
||||||
|
|
||||||
|
|
||||||
|
class MatterBinarySensor(MatterEntity, BinarySensorEntity):
|
||||||
|
"""Representation of a Matter binary sensor."""
|
||||||
|
|
||||||
|
entity_description: MatterBinarySensorEntityDescription
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _update_from_device(self) -> None:
|
||||||
|
"""Update from device."""
|
||||||
|
self._attr_is_on = self._device_type_instance.get_cluster(
|
||||||
|
clusters.BooleanState
|
||||||
|
).stateValue
|
||||||
|
|
||||||
|
|
||||||
|
class MatterOccupancySensor(MatterBinarySensor):
|
||||||
|
"""Representation of a Matter occupancy sensor."""
|
||||||
|
|
||||||
|
_attr_device_class = BinarySensorDeviceClass.OCCUPANCY
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _update_from_device(self) -> None:
|
||||||
|
"""Update from device."""
|
||||||
|
occupancy = self._device_type_instance.get_cluster(
|
||||||
|
clusters.OccupancySensing
|
||||||
|
).occupancy
|
||||||
|
# The first bit = if occupied
|
||||||
|
self._attr_is_on = occupancy & 1 == 1
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MatterBinarySensorEntityDescription(
|
||||||
|
BinarySensorEntityDescription,
|
||||||
|
MatterEntityDescriptionBaseClass,
|
||||||
|
):
|
||||||
|
"""Matter Binary Sensor entity description."""
|
||||||
|
|
||||||
|
|
||||||
|
# You can't set default values on inherited data classes
|
||||||
|
MatterSensorEntityDescriptionFactory = partial(
|
||||||
|
MatterBinarySensorEntityDescription, entity_cls=MatterBinarySensor
|
||||||
|
)
|
||||||
|
|
||||||
|
DEVICE_ENTITY: dict[
|
||||||
|
type[device_types.DeviceType],
|
||||||
|
MatterEntityDescriptionBaseClass | list[MatterEntityDescriptionBaseClass],
|
||||||
|
] = {
|
||||||
|
device_types.ContactSensor: MatterSensorEntityDescriptionFactory(
|
||||||
|
key=device_types.ContactSensor,
|
||||||
|
name="Contact",
|
||||||
|
subscribe_attributes=(clusters.BooleanState.Attributes.StateValue,),
|
||||||
|
device_class=BinarySensorDeviceClass.DOOR,
|
||||||
|
),
|
||||||
|
device_types.OccupancySensor: MatterSensorEntityDescriptionFactory(
|
||||||
|
key=device_types.OccupancySensor,
|
||||||
|
name="Occupancy",
|
||||||
|
entity_cls=MatterOccupancySensor,
|
||||||
|
subscribe_attributes=(clusters.OccupancySensing.Attributes.Occupancy,),
|
||||||
|
),
|
||||||
|
}
|
@ -5,6 +5,7 @@ from typing import TYPE_CHECKING
|
|||||||
|
|
||||||
from homeassistant.const import Platform
|
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 .light import DEVICE_ENTITY as LIGHT_DEVICE_ENTITY
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -20,5 +21,6 @@ DEVICE_PLATFORM: dict[
|
|||||||
MatterEntityDescriptionBaseClass | list[MatterEntityDescriptionBaseClass],
|
MatterEntityDescriptionBaseClass | list[MatterEntityDescriptionBaseClass],
|
||||||
],
|
],
|
||||||
] = {
|
] = {
|
||||||
|
Platform.BINARY_SENSOR: BINARY_SENSOR_DEVICE_ENTITY,
|
||||||
Platform.LIGHT: LIGHT_DEVICE_ENTITY,
|
Platform.LIGHT: LIGHT_DEVICE_ENTITY,
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,113 @@
|
|||||||
"last_interview": "2022-11-29T21:23:48.485057",
|
"last_interview": "2022-11-29T21:23:48.485057",
|
||||||
"interview_version": 1,
|
"interview_version": 1,
|
||||||
"attributes": {
|
"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": {
|
"0/40/0": {
|
||||||
"node_id": 1,
|
"node_id": 1,
|
||||||
"endpoint": 0,
|
"endpoint": 0,
|
||||||
@ -541,5 +648,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"endpoints": [0, 1],
|
"endpoints": [0, 1],
|
||||||
|
"root_device_type_instance": null,
|
||||||
|
"aggregator_device_type_instance": null,
|
||||||
|
"device_type_instances": [null],
|
||||||
|
"node_devices": [null],
|
||||||
"_type": "matter_server.common.models.node.MatterNode"
|
"_type": "matter_server.common.models.node.MatterNode"
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,113 @@
|
|||||||
"last_interview": "2022-11-29T21:23:48.485057",
|
"last_interview": "2022-11-29T21:23:48.485057",
|
||||||
"interview_version": 1,
|
"interview_version": 1,
|
||||||
"attributes": {
|
"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": {
|
"0/40/0": {
|
||||||
"node_id": 1,
|
"node_id": 1,
|
||||||
"endpoint": 0,
|
"endpoint": 0,
|
||||||
|
69
tests/components/matter/test_binary_sensor.py
Normal file
69
tests/components/matter/test_binary_sensor.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
"""Test Matter binary 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="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
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_contact_sensor(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
matter_client: MagicMock,
|
||||||
|
contact_sensor_node: MatterNode,
|
||||||
|
) -> None:
|
||||||
|
"""Test contact sensor."""
|
||||||
|
state = hass.states.get("binary_sensor.mock_contact_sensor_contact")
|
||||||
|
assert state
|
||||||
|
assert state.state == "on"
|
||||||
|
|
||||||
|
set_node_attribute(contact_sensor_node, 1, 69, 0, False)
|
||||||
|
await trigger_subscription_callback(hass, matter_client)
|
||||||
|
|
||||||
|
state = hass.states.get("binary_sensor.mock_contact_sensor_contact")
|
||||||
|
assert state
|
||||||
|
assert state.state == "off"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="occupancy_sensor_node")
|
||||||
|
async def occupancy_sensor_node_fixture(
|
||||||
|
hass: HomeAssistant, matter_client: MagicMock
|
||||||
|
) -> MatterNode:
|
||||||
|
"""Fixture for a occupancy sensor node."""
|
||||||
|
return await setup_integration_with_node_fixture(
|
||||||
|
hass, "occupancy-sensor", matter_client
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_occupancy_sensor(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
matter_client: MagicMock,
|
||||||
|
occupancy_sensor_node: MatterNode,
|
||||||
|
) -> None:
|
||||||
|
"""Test occupancy sensor."""
|
||||||
|
state = hass.states.get("binary_sensor.mock_occupancy_sensor_occupancy")
|
||||||
|
assert state
|
||||||
|
assert state.state == "on"
|
||||||
|
|
||||||
|
set_node_attribute(occupancy_sensor_node, 1, 1030, 0, 0)
|
||||||
|
await trigger_subscription_callback(hass, matter_client)
|
||||||
|
|
||||||
|
state = hass.states.get("binary_sensor.mock_occupancy_sensor_occupancy")
|
||||||
|
assert state
|
||||||
|
assert state.state == "off"
|
Loading…
x
Reference in New Issue
Block a user