core/homeassistant/components/matter/select.py

332 lines
12 KiB
Python

"""Matter ModeSelect Cluster Support."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from typing import TYPE_CHECKING, cast
from chip.clusters import Objects as clusters
from chip.clusters.ClusterObjects import ClusterAttributeDescriptor, ClusterCommand
from chip.clusters.Types import Nullable
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory, Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .entity import MatterEntity, MatterEntityDescription
from .helpers import get_matter
from .models import MatterDiscoverySchema
type SelectCluster = (
clusters.ModeSelect
| clusters.OvenMode
| clusters.LaundryWasherMode
| clusters.RefrigeratorAndTemperatureControlledCabinetMode
| clusters.RvcRunMode
| clusters.RvcCleanMode
| clusters.DishwasherMode
| clusters.EnergyEvseMode
| clusters.DeviceEnergyManagementMode
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Matter ModeSelect from Config Entry."""
matter = get_matter(hass)
matter.register_platform_handler(Platform.SELECT, async_add_entities)
@dataclass(frozen=True)
class MatterSelectEntityDescription(SelectEntityDescription, MatterEntityDescription):
"""Describe Matter select entities."""
@dataclass(frozen=True, kw_only=True)
class MatterListSelectEntityDescription(MatterSelectEntityDescription):
"""Describe Matter select entities for MatterListSelectEntity."""
# command: a callback to create the command to send to the device
# the callback's argument will be the index of the selected list value
command: Callable[[int], ClusterCommand]
# list attribute: the attribute descriptor to get the list of values (= list of strings)
list_attribute: type[ClusterAttributeDescriptor]
class MatterAttributeSelectEntity(MatterEntity, SelectEntity):
"""Representation of a select entity from Matter Attribute read/write."""
entity_description: MatterSelectEntityDescription
async def async_select_option(self, option: str) -> None:
"""Change the selected mode."""
value_convert = self.entity_description.ha_to_native_value
if TYPE_CHECKING:
assert value_convert is not None
await self.write_attribute(
value=value_convert(option),
)
@callback
def _update_from_device(self) -> None:
"""Update from device."""
value: Nullable | int | None
value = self.get_matter_attribute_value(self._entity_info.primary_attribute)
value_convert = self.entity_description.measurement_to_ha
if TYPE_CHECKING:
assert value_convert is not None
self._attr_current_option = value_convert(value)
class MatterModeSelectEntity(MatterAttributeSelectEntity):
"""Representation of a select entity from Matter (Mode) Cluster attribute(s)."""
async def async_select_option(self, option: str) -> None:
"""Change the selected mode."""
cluster: SelectCluster = self._endpoint.get_cluster(
self._entity_info.primary_attribute.cluster_id
)
# select the mode ID from the label string
for mode in cluster.supportedModes:
if mode.label != option:
continue
await self.send_device_command(
cluster.Commands.ChangeToMode(newMode=mode.mode),
)
break
@callback
def _update_from_device(self) -> None:
"""Update from device."""
# NOTE: cluster can be ModeSelect or a variant of that,
# such as DishwasherMode. They all have the same characteristics.
cluster: SelectCluster = self._endpoint.get_cluster(
self._entity_info.primary_attribute.cluster_id
)
modes = {mode.mode: mode.label for mode in cluster.supportedModes}
self._attr_options = list(modes.values())
self._attr_current_option = modes.get(cluster.currentMode)
# handle optional Description attribute as descriptive name for the mode
if desc := getattr(cluster, "description", None):
self._attr_name = desc
class MatterListSelectEntity(MatterEntity, SelectEntity):
"""Representation of a select entity from Matter list and selected item Cluster attribute(s)."""
entity_description: MatterListSelectEntityDescription
async def async_select_option(self, option: str) -> None:
"""Change the selected option."""
option_id = self._attr_options.index(option)
await self.send_device_command(
self.entity_description.command(option_id),
)
@callback
def _update_from_device(self) -> None:
"""Update from device."""
list_values = cast(
list[str],
self.get_matter_attribute_value(self.entity_description.list_attribute),
)
self._attr_options = list_values
current_option_idx: int = self.get_matter_attribute_value(
self._entity_info.primary_attribute
)
try:
self._attr_current_option = list_values[current_option_idx]
except IndexError:
self._attr_current_option = None
# Discovery schema(s) to map Matter Attributes to HA entities
DISCOVERY_SCHEMAS = [
MatterDiscoverySchema(
platform=Platform.SELECT,
entity_description=MatterSelectEntityDescription(
key="MatterModeSelect",
entity_category=EntityCategory.CONFIG,
translation_key="mode",
),
entity_class=MatterModeSelectEntity,
required_attributes=(
clusters.ModeSelect.Attributes.CurrentMode,
clusters.ModeSelect.Attributes.SupportedModes,
),
),
MatterDiscoverySchema(
platform=Platform.SELECT,
entity_description=MatterSelectEntityDescription(
key="MatterOvenMode",
translation_key="mode",
),
entity_class=MatterModeSelectEntity,
required_attributes=(
clusters.OvenMode.Attributes.CurrentMode,
clusters.OvenMode.Attributes.SupportedModes,
),
),
MatterDiscoverySchema(
platform=Platform.SELECT,
entity_description=MatterSelectEntityDescription(
key="MatterLaundryWasherMode",
translation_key="mode",
),
entity_class=MatterModeSelectEntity,
required_attributes=(
clusters.LaundryWasherMode.Attributes.CurrentMode,
clusters.LaundryWasherMode.Attributes.SupportedModes,
),
),
MatterDiscoverySchema(
platform=Platform.SELECT,
entity_description=MatterSelectEntityDescription(
key="MatterRefrigeratorAndTemperatureControlledCabinetMode",
translation_key="mode",
),
entity_class=MatterModeSelectEntity,
required_attributes=(
clusters.RefrigeratorAndTemperatureControlledCabinetMode.Attributes.CurrentMode,
clusters.RefrigeratorAndTemperatureControlledCabinetMode.Attributes.SupportedModes,
),
),
MatterDiscoverySchema(
platform=Platform.SELECT,
entity_description=MatterSelectEntityDescription(
key="MatterRvcCleanMode",
translation_key="clean_mode",
),
entity_class=MatterModeSelectEntity,
required_attributes=(
clusters.RvcCleanMode.Attributes.CurrentMode,
clusters.RvcCleanMode.Attributes.SupportedModes,
),
),
MatterDiscoverySchema(
platform=Platform.SELECT,
entity_description=MatterSelectEntityDescription(
key="MatterDishwasherMode",
translation_key="mode",
),
entity_class=MatterModeSelectEntity,
required_attributes=(
clusters.DishwasherMode.Attributes.CurrentMode,
clusters.DishwasherMode.Attributes.SupportedModes,
),
),
MatterDiscoverySchema(
platform=Platform.SELECT,
entity_description=MatterSelectEntityDescription(
key="MatterEnergyEvseMode",
translation_key="mode",
),
entity_class=MatterModeSelectEntity,
required_attributes=(
clusters.EnergyEvseMode.Attributes.CurrentMode,
clusters.EnergyEvseMode.Attributes.SupportedModes,
),
),
MatterDiscoverySchema(
platform=Platform.SELECT,
entity_description=MatterSelectEntityDescription(
key="MatterDeviceEnergyManagementMode",
translation_key="mode",
),
entity_class=MatterModeSelectEntity,
required_attributes=(
clusters.DeviceEnergyManagementMode.Attributes.CurrentMode,
clusters.DeviceEnergyManagementMode.Attributes.SupportedModes,
),
),
MatterDiscoverySchema(
platform=Platform.SELECT,
entity_description=MatterSelectEntityDescription(
key="MatterStartUpOnOff",
entity_category=EntityCategory.CONFIG,
translation_key="startup_on_off",
options=["on", "off", "toggle", "previous"],
measurement_to_ha={
0: "off",
1: "on",
2: "toggle",
None: "previous",
}.get,
ha_to_native_value={
"off": 0,
"on": 1,
"toggle": 2,
"previous": None,
}.get,
),
entity_class=MatterAttributeSelectEntity,
required_attributes=(clusters.OnOff.Attributes.StartUpOnOff,),
# allow None value for previous state
allow_none_value=True,
),
MatterDiscoverySchema(
platform=Platform.SELECT,
entity_description=MatterSelectEntityDescription(
key="SmokeCOSmokeSensitivityLevel",
entity_category=EntityCategory.CONFIG,
translation_key="sensitivity_level",
options=["high", "standard", "low"],
measurement_to_ha={
0: "high",
1: "standard",
2: "low",
}.get,
ha_to_native_value={
"high": 0,
"standard": 1,
"low": 2,
}.get,
),
entity_class=MatterAttributeSelectEntity,
required_attributes=(clusters.SmokeCoAlarm.Attributes.SmokeSensitivityLevel,),
),
MatterDiscoverySchema(
platform=Platform.SELECT,
entity_description=MatterSelectEntityDescription(
key="TrvTemperatureDisplayMode",
entity_category=EntityCategory.CONFIG,
translation_key="temperature_display_mode",
options=["Celsius", "Fahrenheit"],
measurement_to_ha={
0: "Celsius",
1: "Fahrenheit",
}.get,
ha_to_native_value={
"Celsius": 0,
"Fahrenheit": 1,
}.get,
),
entity_class=MatterAttributeSelectEntity,
required_attributes=(
clusters.ThermostatUserInterfaceConfiguration.Attributes.TemperatureDisplayMode,
),
),
MatterDiscoverySchema(
platform=Platform.SELECT,
entity_description=MatterListSelectEntityDescription(
key="TemperatureControlSelectedTemperatureLevel",
translation_key="temperature_level",
command=lambda selected_index: clusters.TemperatureControl.Commands.SetTemperature(
targetTemperatureLevel=selected_index
),
list_attribute=clusters.TemperatureControl.Attributes.SupportedTemperatureLevels,
),
entity_class=MatterListSelectEntity,
required_attributes=(
clusters.TemperatureControl.Attributes.SelectedTemperatureLevel,
clusters.TemperatureControl.Attributes.SupportedTemperatureLevels,
),
),
]