Add matter switch platform (#83149)

pull/83438/head
Martin Hjelmare 2022-12-06 21:57:24 +01:00 committed by GitHub
parent 552a87dfcc
commit 511fd293b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 282 additions and 0 deletions

View File

@ -8,6 +8,7 @@ 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 .sensor import DEVICE_ENTITY as SENSOR_DEVICE_ENTITY
from .switch import DEVICE_ENTITY as SWITCH_DEVICE_ENTITY
if TYPE_CHECKING:
from matter_server.common.models.device_types import DeviceType
@ -25,4 +26,5 @@ DEVICE_PLATFORM: dict[
Platform.BINARY_SENSOR: BINARY_SENSOR_DEVICE_ENTITY,
Platform.LIGHT: LIGHT_DEVICE_ENTITY,
Platform.SENSOR: SENSOR_DEVICE_ENTITY,
Platform.SWITCH: SWITCH_DEVICE_ENTITY,
}

View File

@ -0,0 +1,88 @@
"""Matter switches."""
from __future__ import annotations
from dataclasses import dataclass
from functools import partial
from typing import TYPE_CHECKING, Any
from chip.clusters import Objects as clusters
from matter_server.common.models import device_types
from homeassistant.components.switch import (
SwitchDeviceClass,
SwitchEntity,
SwitchEntityDescription,
)
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 switches from Config Entry."""
matter: MatterAdapter = hass.data[DOMAIN][config_entry.entry_id]
matter.register_platform_handler(Platform.SWITCH, async_add_entities)
class MatterSwitch(MatterEntity, SwitchEntity):
"""Representation of a Matter switch."""
entity_description: MatterSwitchEntityDescription
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn switch on."""
await self.matter_client.send_device_command(
node_id=self._device_type_instance.node.node_id,
endpoint=self._device_type_instance.endpoint,
command=clusters.OnOff.Commands.On(),
)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn switch off."""
await self.matter_client.send_device_command(
node_id=self._device_type_instance.node.node_id,
endpoint=self._device_type_instance.endpoint,
command=clusters.OnOff.Commands.Off(),
)
@callback
def _update_from_device(self) -> None:
"""Update from device."""
self._attr_is_on = self._device_type_instance.get_cluster(clusters.OnOff).onOff
@dataclass
class MatterSwitchEntityDescription(
SwitchEntityDescription,
MatterEntityDescriptionBaseClass,
):
"""Matter Switch entity description."""
# You can't set default values on inherited data classes
MatterSwitchEntityDescriptionFactory = partial(
MatterSwitchEntityDescription, entity_cls=MatterSwitch
)
DEVICE_ENTITY: dict[
type[device_types.DeviceType],
MatterEntityDescriptionBaseClass | list[MatterEntityDescriptionBaseClass],
] = {
device_types.OnOffPlugInUnit: MatterSwitchEntityDescriptionFactory(
key=device_types.OnOffPlugInUnit,
subscribe_attributes=(clusters.OnOff.Attributes.OnOff,),
device_class=SwitchDeviceClass.OUTLET,
),
}

View File

@ -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,

View File

@ -0,0 +1,85 @@
"""Test Matter switches."""
from unittest.mock import MagicMock, call
from chip.clusters import Objects as clusters
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="switch_node")
async def switch_node_fixture(
hass: HomeAssistant, matter_client: MagicMock
) -> MatterNode:
"""Fixture for a switch node."""
return await setup_integration_with_node_fixture(
hass, "on-off-plugin-unit", matter_client
)
async def test_turn_on(
hass: HomeAssistant,
matter_client: MagicMock,
switch_node: MatterNode,
) -> None:
"""Test turning on a switch."""
state = hass.states.get("switch.mock_onoff_plugin_unit")
assert state
assert state.state == "off"
await hass.services.async_call(
"switch",
"turn_on",
{
"entity_id": "switch.mock_onoff_plugin_unit",
},
blocking=True,
)
assert matter_client.send_device_command.call_count == 1
assert matter_client.send_device_command.call_args == call(
node_id=switch_node.node_id,
endpoint=1,
command=clusters.OnOff.Commands.On(),
)
set_node_attribute(switch_node, 1, 6, 0, True)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("switch.mock_onoff_plugin_unit")
assert state
assert state.state == "on"
async def test_turn_off(
hass: HomeAssistant,
matter_client: MagicMock,
switch_node: MatterNode,
) -> None:
"""Test turning off a switch."""
state = hass.states.get("switch.mock_onoff_plugin_unit")
assert state
assert state.state == "off"
await hass.services.async_call(
"switch",
"turn_off",
{
"entity_id": "switch.mock_onoff_plugin_unit",
},
blocking=True,
)
assert matter_client.send_device_command.call_count == 1
assert matter_client.send_device_command.call_args == call(
node_id=switch_node.node_id,
endpoint=1,
command=clusters.OnOff.Commands.Off(),
)