Add matter switch platform (#83149)
This commit is contained in:
parent
552a87dfcc
commit
511fd293b6
4 changed files with 282 additions and 0 deletions
|
@ -8,6 +8,7 @@ from homeassistant.const import Platform
|
||||||
from .binary_sensor import DEVICE_ENTITY as BINARY_SENSOR_DEVICE_ENTITY
|
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
|
||||||
from .sensor import DEVICE_ENTITY as SENSOR_DEVICE_ENTITY
|
from .sensor import DEVICE_ENTITY as SENSOR_DEVICE_ENTITY
|
||||||
|
from .switch import DEVICE_ENTITY as SWITCH_DEVICE_ENTITY
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from matter_server.common.models.device_types import DeviceType
|
from matter_server.common.models.device_types import DeviceType
|
||||||
|
@ -25,4 +26,5 @@ DEVICE_PLATFORM: dict[
|
||||||
Platform.BINARY_SENSOR: BINARY_SENSOR_DEVICE_ENTITY,
|
Platform.BINARY_SENSOR: BINARY_SENSOR_DEVICE_ENTITY,
|
||||||
Platform.LIGHT: LIGHT_DEVICE_ENTITY,
|
Platform.LIGHT: LIGHT_DEVICE_ENTITY,
|
||||||
Platform.SENSOR: SENSOR_DEVICE_ENTITY,
|
Platform.SENSOR: SENSOR_DEVICE_ENTITY,
|
||||||
|
Platform.SWITCH: SWITCH_DEVICE_ENTITY,
|
||||||
}
|
}
|
||||||
|
|
88
homeassistant/components/matter/switch.py
Normal file
88
homeassistant/components/matter/switch.py
Normal 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,
|
||||||
|
),
|
||||||
|
}
|
|
@ -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,
|
||||||
|
|
85
tests/components/matter/test_switch.py
Normal file
85
tests/components/matter/test_switch.py
Normal 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(),
|
||||||
|
)
|
Loading…
Add table
Add a link
Reference in a new issue