Add matter switch platform (#83149)
parent
552a87dfcc
commit
511fd293b6
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
"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,
|
||||
|
|
|
@ -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…
Reference in New Issue