mirror of
https://github.com/home-assistant/core.git
synced 2025-07-15 09:17:10 +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 .binary_sensor import DEVICE_ENTITY as BINARY_SENSOR_DEVICE_ENTITY
|
||||
from .light import DEVICE_ENTITY as LIGHT_DEVICE_ENTITY
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -20,5 +21,6 @@ DEVICE_PLATFORM: dict[
|
||||
MatterEntityDescriptionBaseClass | list[MatterEntityDescriptionBaseClass],
|
||||
],
|
||||
] = {
|
||||
Platform.BINARY_SENSOR: BINARY_SENSOR_DEVICE_ENTITY,
|
||||
Platform.LIGHT: LIGHT_DEVICE_ENTITY,
|
||||
}
|
||||
|
@ -4,6 +4,113 @@
|
||||
"last_interview": "2022-11-29T21:23:48.485057",
|
||||
"interview_version": 1,
|
||||
"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": {
|
||||
"node_id": 1,
|
||||
"endpoint": 0,
|
||||
@ -541,5 +648,9 @@
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
|
@ -4,6 +4,113 @@
|
||||
"last_interview": "2022-11-29T21:23:48.485057",
|
||||
"interview_version": 1,
|
||||
"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": {
|
||||
"node_id": 1,
|
||||
"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