1
0
mirror of https://github.com/home-assistant/core.git synced 2026-02-21 02:18:47 +00:00
Files
2026-02-12 22:57:54 +01:00

461 lines
16 KiB
Python

"""Support for select entities through the SmartThings cloud API."""
from __future__ import annotations
from dataclasses import dataclass
from typing import cast
from pysmartthings import Attribute, Capability, Command, SmartThings
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import FullDevice, SmartThingsConfigEntry
from .const import MAIN
from .entity import SmartThingsEntity
LAMP_TO_HA = {
"extraHigh": "extra_high",
"high": "high",
"mid": "mid",
"low": "low",
"on": "on",
"off": "off",
}
WASHER_SOIL_LEVEL_TO_HA = {
"none": "none",
"heavy": "heavy",
"normal": "normal",
"light": "light",
"extraLight": "extra_light",
"extraHeavy": "extra_heavy",
"up": "up",
"down": "down",
}
WASHER_SPIN_LEVEL_TO_HA = {
"none": "none",
"rinseHold": "rinse_hold",
"noSpin": "no_spin",
"low": "low",
"extraLow": "extra_low",
"delicate": "delicate",
"medium": "medium",
"high": "high",
"extraHigh": "extra_high",
"200": "200",
"400": "400",
"600": "600",
"800": "800",
"1000": "1000",
"1200": "1200",
"1400": "1400",
"1600": "1600",
}
WASHER_WATER_TEMPERATURE_TO_HA = {
"none": "none",
"20": "20",
"30": "30",
"40": "40",
"50": "50",
"60": "60",
"65": "65",
"70": "70",
"75": "75",
"80": "80",
"90": "90",
"95": "95",
"tapCold": "tap_cold",
"cold": "cold",
"cool": "cool",
"ecoWarm": "eco_warm",
"warm": "warm",
"semiHot": "semi_hot",
"hot": "hot",
"extraHot": "extra_hot",
"extraLow": "extra_low",
"low": "low",
"mediumLow": "medium_low",
"medium": "medium",
"high": "high",
}
@dataclass(frozen=True, kw_only=True)
class SmartThingsSelectDescription(SelectEntityDescription):
"""Class describing SmartThings select entities."""
key: Capability
requires_remote_control_status: bool = False
options_attribute: Attribute
status_attribute: Attribute
command: Command
options_map: dict[str, str] | None = None
default_options: list[str] | None = None
extra_components: list[str] | None = None
capability_ignore_list: list[Capability] | None = None
value_is_integer: bool = False
CAPABILITIES_TO_SELECT: dict[Capability | str, SmartThingsSelectDescription] = {
Capability.DISHWASHER_OPERATING_STATE: SmartThingsSelectDescription(
key=Capability.DISHWASHER_OPERATING_STATE,
name=None,
translation_key="operating_state",
requires_remote_control_status=True,
options_attribute=Attribute.SUPPORTED_MACHINE_STATES,
status_attribute=Attribute.MACHINE_STATE,
command=Command.SET_MACHINE_STATE,
),
Capability.DRYER_OPERATING_STATE: SmartThingsSelectDescription(
key=Capability.DRYER_OPERATING_STATE,
name=None,
translation_key="operating_state",
requires_remote_control_status=True,
options_attribute=Attribute.SUPPORTED_MACHINE_STATES,
status_attribute=Attribute.MACHINE_STATE,
command=Command.SET_MACHINE_STATE,
default_options=["run", "pause", "stop"],
),
Capability.WASHER_OPERATING_STATE: SmartThingsSelectDescription(
key=Capability.WASHER_OPERATING_STATE,
name=None,
translation_key="operating_state",
requires_remote_control_status=True,
options_attribute=Attribute.SUPPORTED_MACHINE_STATES,
status_attribute=Attribute.MACHINE_STATE,
command=Command.SET_MACHINE_STATE,
default_options=["run", "pause", "stop"],
),
Capability.SAMSUNG_CE_AUTO_DISPENSE_DETERGENT: SmartThingsSelectDescription(
key=Capability.SAMSUNG_CE_AUTO_DISPENSE_DETERGENT,
translation_key="detergent_amount",
options_attribute=Attribute.SUPPORTED_AMOUNT,
status_attribute=Attribute.AMOUNT,
command=Command.SET_AMOUNT,
entity_category=EntityCategory.CONFIG,
),
Capability.SAMSUNG_CE_FLEXIBLE_AUTO_DISPENSE_DETERGENT: SmartThingsSelectDescription(
key=Capability.SAMSUNG_CE_FLEXIBLE_AUTO_DISPENSE_DETERGENT,
translation_key="flexible_detergent_amount",
options_attribute=Attribute.SUPPORTED_AMOUNT,
status_attribute=Attribute.AMOUNT,
command=Command.SET_AMOUNT,
entity_category=EntityCategory.CONFIG,
),
Capability.SAMSUNG_CE_LAMP: SmartThingsSelectDescription(
key=Capability.SAMSUNG_CE_LAMP,
translation_key="lamp",
options_attribute=Attribute.SUPPORTED_BRIGHTNESS_LEVEL,
status_attribute=Attribute.BRIGHTNESS_LEVEL,
command=Command.SET_BRIGHTNESS_LEVEL,
options_map=LAMP_TO_HA,
entity_category=EntityCategory.CONFIG,
extra_components=["hood"],
capability_ignore_list=[Capability.SAMSUNG_CE_CONNECTION_STATE],
),
Capability.CUSTOM_WASHER_SPIN_LEVEL: SmartThingsSelectDescription(
key=Capability.CUSTOM_WASHER_SPIN_LEVEL,
translation_key="spin_level",
options_attribute=Attribute.SUPPORTED_WASHER_SPIN_LEVEL,
status_attribute=Attribute.WASHER_SPIN_LEVEL,
command=Command.SET_WASHER_SPIN_LEVEL,
options_map=WASHER_SPIN_LEVEL_TO_HA,
entity_category=EntityCategory.CONFIG,
),
Capability.CUSTOM_WASHER_SOIL_LEVEL: SmartThingsSelectDescription(
key=Capability.CUSTOM_WASHER_SOIL_LEVEL,
translation_key="soil_level",
options_attribute=Attribute.SUPPORTED_WASHER_SOIL_LEVEL,
status_attribute=Attribute.WASHER_SOIL_LEVEL,
command=Command.SET_WASHER_SOIL_LEVEL,
options_map=WASHER_SOIL_LEVEL_TO_HA,
entity_category=EntityCategory.CONFIG,
),
Capability.CUSTOM_WASHER_WATER_TEMPERATURE: SmartThingsSelectDescription(
key=Capability.CUSTOM_WASHER_WATER_TEMPERATURE,
translation_key="water_temperature",
requires_remote_control_status=True,
options_attribute=Attribute.SUPPORTED_WASHER_WATER_TEMPERATURE,
status_attribute=Attribute.WASHER_WATER_TEMPERATURE,
command=Command.SET_WASHER_WATER_TEMPERATURE,
options_map=WASHER_WATER_TEMPERATURE_TO_HA,
entity_category=EntityCategory.CONFIG,
),
Capability.SAMSUNG_CE_DUST_FILTER_ALARM: SmartThingsSelectDescription(
key=Capability.SAMSUNG_CE_DUST_FILTER_ALARM,
translation_key="dust_filter_alarm",
options_attribute=Attribute.SUPPORTED_ALARM_THRESHOLDS,
status_attribute=Attribute.ALARM_THRESHOLD,
command=Command.SET_ALARM_THRESHOLD,
entity_category=EntityCategory.CONFIG,
value_is_integer=True,
),
}
DISHWASHER_WASHING_OPTIONS_TO_SELECT: dict[
Attribute | str, SmartThingsSelectDescription
] = {
Attribute.SELECTED_ZONE: SmartThingsSelectDescription(
key=Capability.SAMSUNG_CE_DISHWASHER_WASHING_OPTIONS,
translation_key="selected_zone",
options_attribute=Attribute.SELECTED_ZONE,
status_attribute=Attribute.SELECTED_ZONE,
command=Command.SET_SELECTED_ZONE,
entity_category=EntityCategory.CONFIG,
requires_remote_control_status=True,
),
Attribute.ZONE_BOOSTER: SmartThingsSelectDescription(
key=Capability.SAMSUNG_CE_DISHWASHER_WASHING_OPTIONS,
translation_key="zone_booster",
options_attribute=Attribute.ZONE_BOOSTER,
status_attribute=Attribute.ZONE_BOOSTER,
command=Command.SET_ZONE_BOOSTER,
entity_category=EntityCategory.CONFIG,
requires_remote_control_status=True,
),
}
async def async_setup_entry(
hass: HomeAssistant,
entry: SmartThingsConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Add select entities for a config entry."""
entry_data = entry.runtime_data
async_add_entities(
SmartThingsSelectEntity(entry_data.client, device, description, component)
for capability, description in CAPABILITIES_TO_SELECT.items()
for device in entry_data.devices.values()
for component in device.status
if capability in device.status[component]
and (
component == MAIN
or (
description.extra_components is not None
and component in description.extra_components
)
)
and (
description.capability_ignore_list is None
or any(
capability not in device.status[component]
for capability in description.capability_ignore_list
)
)
)
async_add_entities(
SmartThingsDishwasherWashingOptionSelectEntity(
entry_data.client,
device,
DISHWASHER_WASHING_OPTIONS_TO_SELECT[attribute],
)
for device in entry_data.devices.values()
for component in device.status
if component == MAIN
and Capability.SAMSUNG_CE_DISHWASHER_WASHING_OPTIONS in device.status[component]
for attribute in cast(
list[str],
device.status[component][Capability.SAMSUNG_CE_DISHWASHER_WASHING_OPTIONS][
Attribute.SUPPORTED_LIST
].value,
)
if attribute in DISHWASHER_WASHING_OPTIONS_TO_SELECT
)
class SmartThingsSelectEntity(SmartThingsEntity, SelectEntity):
"""Define a SmartThings select."""
entity_description: SmartThingsSelectDescription
def __init__(
self,
client: SmartThings,
device: FullDevice,
entity_description: SmartThingsSelectDescription,
component: str,
extra_capabilities: set[Capability] | None = None,
) -> None:
"""Initialize the instance."""
capabilities = {entity_description.key}
if entity_description.requires_remote_control_status:
capabilities.add(Capability.REMOTE_CONTROL_STATUS)
if extra_capabilities is not None:
capabilities.update(extra_capabilities)
super().__init__(client, device, capabilities, component=component)
self.entity_description = entity_description
self._attr_unique_id = f"{device.device.device_id}_{component}_{entity_description.key}_{entity_description.status_attribute}_{entity_description.status_attribute}"
@property
def options(self) -> list[str]:
"""Return the list of options."""
options: list[str] = (
self.get_attribute_value(
self.entity_description.key, self.entity_description.options_attribute
)
or self.entity_description.default_options
or []
)
if self.entity_description.options_map:
options = [
self.entity_description.options_map.get(option, option)
for option in options
]
if self.entity_description.value_is_integer:
options = [str(option) for option in options]
return options
@property
def current_option(self) -> str | None:
"""Return the current option."""
option = self.get_attribute_value(
self.entity_description.key, self.entity_description.status_attribute
)
if self.entity_description.options_map:
option = self.entity_description.options_map.get(option)
if self.entity_description.value_is_integer and option is not None:
option = str(option)
return option
def _validate_before_select(self) -> None:
"""Validate that the select can be used."""
if (
self.entity_description.requires_remote_control_status
and self.get_attribute_value(
Capability.REMOTE_CONTROL_STATUS, Attribute.REMOTE_CONTROL_ENABLED
)
== "false"
):
raise ServiceValidationError(
"Can only be updated when remote control is enabled"
)
async def async_select_option(self, option: str) -> None:
"""Select an option."""
self._validate_before_select()
new_option: str | int = option
if self.entity_description.options_map:
new_option = next(
(
key
for key, value in self.entity_description.options_map.items()
if value == option
),
new_option,
)
if self.entity_description.value_is_integer:
new_option = int(option)
await self.execute_device_command(
self.entity_description.key,
self.entity_description.command,
new_option,
)
class SmartThingsDishwasherWashingOptionSelectEntity(SmartThingsSelectEntity):
"""Define a SmartThings select for a dishwasher washing option."""
def __init__(
self,
client: SmartThings,
device: FullDevice,
entity_description: SmartThingsSelectDescription,
) -> None:
"""Initialize the switch."""
super().__init__(
client,
device,
entity_description,
MAIN,
{
Capability.DISHWASHER_OPERATING_STATE,
Capability.SAMSUNG_CE_DISHWASHER_OPERATION,
Capability.SAMSUNG_CE_DISHWASHER_WASHING_COURSE,
Capability.SAMSUNG_CE_DISHWASHER_WASHING_COURSE_DETAILS,
},
)
@property
def options(self) -> list[str]:
"""Return the list of options."""
device_options = self.get_attribute_value(
self.entity_description.key, self.entity_description.options_attribute
)["settable"]
selected_course = self.get_attribute_value(
Capability.SAMSUNG_CE_DISHWASHER_WASHING_COURSE, Attribute.WASHING_COURSE
)
course_details = self.get_attribute_value(
Capability.SAMSUNG_CE_DISHWASHER_WASHING_COURSE_DETAILS,
Attribute.PREDEFINED_COURSES,
)
course_options = set(
next(
(
detail["options"][self.entity_description.options_attribute][
"settable"
]
for detail in course_details
if detail["courseName"] == selected_course
),
[],
)
)
return [option for option in device_options if option in course_options]
@property
def current_option(self) -> str | None:
"""Return the current option."""
return self.get_attribute_value(
self.entity_description.key, self.entity_description.status_attribute
)["value"]
def _validate_before_select(self) -> None:
"""Validate that the select can be used."""
super()._validate_before_select()
if (
self.get_attribute_value(
Capability.DISHWASHER_OPERATING_STATE, Attribute.MACHINE_STATE
)
!= "stop"
):
raise ServiceValidationError(
"Can only be updated when dishwasher machine state is stop"
)
async def async_select_option(self, option: str) -> None:
"""Select an option."""
self._validate_before_select()
selected_course = self.get_attribute_value(
Capability.SAMSUNG_CE_DISHWASHER_WASHING_COURSE, Attribute.WASHING_COURSE
)
options = {
option: self.get_attribute_value(self.entity_description.key, option)[
"value"
]
for option in self.get_attribute_value(
self.entity_description.key, Attribute.SUPPORTED_LIST
)
}
options[self.entity_description.options_attribute] = option
await self.execute_device_command(
Capability.SAMSUNG_CE_DISHWASHER_OPERATION,
Command.CANCEL,
False,
)
await self.execute_device_command(
Capability.SAMSUNG_CE_DISHWASHER_WASHING_COURSE,
Command.SET_WASHING_COURSE,
selected_course,
)
await self.execute_device_command(
self.entity_description.key,
Command.SET_OPTIONS,
options,
)