1
0
mirror of https://github.com/home-assistant/core.git synced 2026-06-01 05:04:21 +01:00
Files
core/homeassistant/components/matter/switch.py
T
2026-04-30 21:14:48 +02:00

328 lines
12 KiB
Python

"""Matter switches."""
from collections.abc import Callable
from dataclasses import dataclass
from typing import Any
from chip.clusters import Objects as clusters
from chip.clusters.Objects import ClusterCommand, NullValue
from matter_server.client.models import device_types
from homeassistant.components.switch import (
SwitchDeviceClass,
SwitchEntity,
SwitchEntityDescription,
)
from homeassistant.const import EntityCategory, Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .entity import MatterEntity, MatterEntityDescription
from .helpers import MatterConfigEntry
from .models import MatterDiscoverySchema
EVSE_SUPPLY_STATE_MAP = {
clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabled: False,
clusters.EnergyEvse.Enums.SupplyStateEnum.kChargingEnabled: True,
clusters.EnergyEvse.Enums.SupplyStateEnum.kDischargingEnabled: False,
clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabledDiagnostics: False,
}
async def async_setup_entry(
hass: HomeAssistant,
config_entry: MatterConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Matter switches from Config Entry."""
matter = config_entry.runtime_data.adapter
matter.register_platform_handler(Platform.SWITCH, async_add_entities)
@dataclass(frozen=True, kw_only=True)
class MatterSwitchEntityDescription(SwitchEntityDescription, MatterEntityDescription):
"""Describe Matter Switch entities."""
inverted: bool = False
class MatterSwitch(MatterEntity, SwitchEntity):
"""Representation of a Matter switch."""
entity_description: MatterSwitchEntityDescription
_platform_translation_key = "switch"
def _get_command_for_value(self, value: bool) -> ClusterCommand:
"""Get the appropriate command for the desired value.
Applies inversion if needed (e.g., for inverted logic like mute).
"""
send_value = not value if self.entity_description.inverted else value
return (
clusters.OnOff.Commands.On()
if send_value
else clusters.OnOff.Commands.Off()
)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn switch on."""
await self.send_device_command(self._get_command_for_value(True))
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn switch off."""
await self.send_device_command(self._get_command_for_value(False))
@callback
def _update_from_device(self) -> None:
"""Update from device."""
value = self.get_matter_attribute_value(self._entity_info.primary_attribute)
if self.entity_description.inverted:
value = not value
self._attr_is_on = value
class MatterGenericCommandSwitch(MatterSwitch):
"""Representation of a Matter switch."""
entity_description: MatterGenericCommandSwitchEntityDescription
_platform_translation_key = "switch"
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn switch on."""
if self.entity_description.on_command:
# custom command defined to set the new value
await self.send_device_command(
self.entity_description.on_command(),
self.entity_description.command_timeout,
)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn switch off."""
if self.entity_description.off_command:
await self.send_device_command(
self.entity_description.off_command(),
self.entity_description.command_timeout,
)
@callback
def _update_from_device(self) -> None:
"""Update from device."""
value = self.get_matter_attribute_value(self._entity_info.primary_attribute)
if value_convert := self.entity_description.device_to_ha:
value = value_convert(value)
self._attr_is_on = value
async def send_device_command(
self,
command: ClusterCommand,
command_timeout: int | None = None,
**kwargs: Any,
) -> None:
"""Send device command with timeout."""
await self.matter_client.send_device_command(
node_id=self._endpoint.node.node_id,
endpoint_id=self._endpoint.endpoint_id,
command=command,
timed_request_timeout_ms=command_timeout,
**kwargs,
)
@dataclass(frozen=True, kw_only=True)
class MatterGenericCommandSwitchEntityDescription(MatterSwitchEntityDescription):
"""Describe Matter Generic command Switch entities."""
# command: a custom callback to create the command to send to the device
on_command: Callable[[], Any] | None = None
off_command: Callable[[], Any] | None = None
command_timeout: int | None = None
@dataclass(frozen=True, kw_only=True)
class MatterNumericSwitchEntityDescription(MatterSwitchEntityDescription):
"""Describe Matter Numeric Switch entities."""
class MatterNumericSwitch(MatterSwitch):
"""Representation of a Matter Enum Attribute as a Switch entity."""
entity_description: MatterNumericSwitchEntityDescription
async def _async_set_native_value(self, value: bool) -> None:
"""Update the current value."""
send_value: Any = value
if value_convert := self.entity_description.ha_to_device:
send_value = value_convert(value)
await self.write_attribute(value=send_value)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn switch on."""
await self._async_set_native_value(True)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn switch off."""
await self._async_set_native_value(False)
@callback
def _update_from_device(self) -> None:
"""Update from device."""
value = self.get_matter_attribute_value(self._entity_info.primary_attribute)
if value_convert := self.entity_description.device_to_ha:
value = value_convert(value)
self._attr_is_on = value
# Discovery schema(s) to map Matter Attributes to HA entities
DISCOVERY_SCHEMAS = [
MatterDiscoverySchema(
platform=Platform.SWITCH,
entity_description=MatterSwitchEntityDescription(
key="MatterPlug",
device_class=SwitchDeviceClass.OUTLET,
name=None,
),
entity_class=MatterSwitch,
required_attributes=(clusters.OnOff.Attributes.OnOff,),
device_type=(device_types.OnOffPlugInUnit,),
),
MatterDiscoverySchema(
platform=Platform.SWITCH,
entity_description=MatterSwitchEntityDescription(
key="MatterPowerToggle",
device_class=SwitchDeviceClass.SWITCH,
translation_key="power",
),
entity_class=MatterSwitch,
required_attributes=(clusters.OnOff.Attributes.OnOff,),
device_type=(
device_types.AirPurifier,
device_types.BasicVideoPlayer,
device_types.CastingVideoPlayer,
device_types.CookSurface,
device_types.Cooktop,
device_types.Dishwasher,
device_types.ExtractorHood,
device_types.LaundryDryer,
device_types.LaundryWasher,
device_types.Oven,
device_types.Pump,
device_types.PumpController,
device_types.Refrigerator,
device_types.RoboticVacuumCleaner,
device_types.RoomAirConditioner,
),
),
MatterDiscoverySchema(
platform=Platform.SWITCH,
entity_description=MatterSwitchEntityDescription(
key="MatterSwitch",
device_class=SwitchDeviceClass.OUTLET,
name=None,
),
entity_class=MatterSwitch,
required_attributes=(clusters.OnOff.Attributes.OnOff,),
not_device_type=(
device_types.ColorTemperatureLight,
device_types.DimmableLight,
device_types.ExtendedColorLight,
device_types.DimmerSwitch,
device_types.ColorDimmerSwitch,
device_types.OnOffLight,
device_types.AirPurifier,
device_types.BasicVideoPlayer,
device_types.CastingVideoPlayer,
device_types.CookSurface,
device_types.Cooktop,
device_types.Dishwasher,
device_types.ExtractorHood,
device_types.Fan,
device_types.LaundryDryer,
device_types.LaundryWasher,
device_types.Oven,
device_types.Pump,
device_types.PumpController,
device_types.Refrigerator,
device_types.RoboticVacuumCleaner,
device_types.RoomAirConditioner,
device_types.Speaker,
),
),
MatterDiscoverySchema(
platform=Platform.SWITCH,
entity_description=MatterSwitchEntityDescription(
key="MatterMuteToggle",
translation_key="speaker_mute",
inverted=True,
),
entity_class=MatterSwitch,
required_attributes=(clusters.OnOff.Attributes.OnOff,),
device_type=(device_types.Speaker,),
),
MatterDiscoverySchema(
platform=Platform.SWITCH,
entity_description=MatterNumericSwitchEntityDescription(
key="EveTrvChildLock",
entity_category=EntityCategory.CONFIG,
translation_key="child_lock",
device_to_ha={
0: False,
1: True,
}.get,
ha_to_device={
False: 0,
True: 1,
}.get,
),
entity_class=MatterNumericSwitch,
required_attributes=(
clusters.ThermostatUserInterfaceConfiguration.Attributes.KeypadLockout,
),
vendor_id=(4874,),
),
MatterDiscoverySchema(
platform=Platform.SWITCH,
entity_description=MatterNumericSwitchEntityDescription(
key="DoorLockEnablePrivacyModeButton",
entity_category=EntityCategory.CONFIG,
translation_key="privacy_mode_button",
device_to_ha=bool,
ha_to_device=int,
),
entity_class=MatterNumericSwitch,
required_attributes=(clusters.DoorLock.Attributes.EnablePrivacyModeButton,),
),
MatterDiscoverySchema(
platform=Platform.SWITCH,
entity_description=MatterGenericCommandSwitchEntityDescription(
key="EnergyEvseChargingSwitch",
translation_key="evse_charging_switch",
on_command=lambda: clusters.EnergyEvse.Commands.EnableCharging(
chargingEnabledUntil=NullValue,
minimumChargeCurrent=0,
maximumChargeCurrent=0,
),
off_command=clusters.EnergyEvse.Commands.Disable,
command_timeout=3000,
device_to_ha=EVSE_SUPPLY_STATE_MAP.get,
),
entity_class=MatterGenericCommandSwitch,
required_attributes=(
clusters.EnergyEvse.Attributes.SupplyState,
clusters.EnergyEvse.Attributes.AcceptedCommandList,
),
value_contains=clusters.EnergyEvse.Commands.EnableCharging.command_id,
allow_multi=True,
),
MatterDiscoverySchema(
platform=Platform.SWITCH,
entity_description=MatterNumericSwitchEntityDescription(
key="EveChildLock",
entity_category=EntityCategory.CONFIG,
translation_key="child_lock",
),
entity_class=MatterNumericSwitch,
required_attributes=(clusters.EveCluster.Attributes.ChildLock,),
),
]