mirror of
https://github.com/home-assistant/core.git
synced 2026-02-21 02:18:47 +00:00
520 lines
19 KiB
Python
520 lines
19 KiB
Python
"""Support for switches through the SmartThings cloud API."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Any, cast
|
|
|
|
from pysmartthings import Attribute, Capability, Command, SmartThings
|
|
|
|
from homeassistant.components.switch import (
|
|
DOMAIN as SWITCH_DOMAIN,
|
|
SwitchEntity,
|
|
SwitchEntityDescription,
|
|
)
|
|
from homeassistant.const import EntityCategory
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.exceptions import ServiceValidationError
|
|
from homeassistant.helpers import entity_registry as er
|
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
|
|
|
from . import FullDevice, SmartThingsConfigEntry
|
|
from .const import INVALID_SWITCH_CATEGORIES, MAIN
|
|
from .entity import SmartThingsEntity
|
|
from .util import deprecate_entity
|
|
|
|
CAPABILITIES = (
|
|
Capability.SWITCH_LEVEL,
|
|
Capability.COLOR_CONTROL,
|
|
Capability.COLOR_TEMPERATURE,
|
|
Capability.FAN_SPEED,
|
|
)
|
|
|
|
AC_CAPABILITIES = (
|
|
Capability.AIR_CONDITIONER_MODE,
|
|
Capability.AIR_CONDITIONER_FAN_MODE,
|
|
Capability.TEMPERATURE_MEASUREMENT,
|
|
Capability.THERMOSTAT_COOLING_SETPOINT,
|
|
)
|
|
|
|
MEDIA_PLAYER_CAPABILITIES = (
|
|
Capability.AUDIO_MUTE,
|
|
Capability.AUDIO_VOLUME,
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True, kw_only=True)
|
|
class SmartThingsSwitchEntityDescription(SwitchEntityDescription):
|
|
"""Describe a SmartThings switch entity."""
|
|
|
|
status_attribute: Attribute
|
|
component_translation_key: dict[str, str] | None = None
|
|
on_key: str | bool = "on"
|
|
on_command: Command = Command.ON
|
|
off_command: Command = Command.OFF
|
|
|
|
|
|
@dataclass(frozen=True, kw_only=True)
|
|
class SmartThingsCommandSwitchEntityDescription(SmartThingsSwitchEntityDescription):
|
|
"""Describe a SmartThings switch entity."""
|
|
|
|
command: Command
|
|
off_key: str | bool = "off"
|
|
|
|
|
|
@dataclass(frozen=True, kw_only=True)
|
|
class SmartThingsDishwasherWashingOptionSwitchEntityDescription(
|
|
SmartThingsCommandSwitchEntityDescription
|
|
):
|
|
"""Describe a SmartThings switch entity for a dishwasher washing option."""
|
|
|
|
on_key: str | bool = True
|
|
off_key: str | bool = False
|
|
|
|
|
|
SWITCH = SmartThingsSwitchEntityDescription(
|
|
key=Capability.SWITCH,
|
|
status_attribute=Attribute.SWITCH,
|
|
name=None,
|
|
)
|
|
CAPABILITY_TO_COMMAND_SWITCHES: dict[
|
|
Capability | str, SmartThingsCommandSwitchEntityDescription
|
|
] = {
|
|
Capability.SAMSUNG_CE_AIR_CONDITIONER_LIGHTING: SmartThingsCommandSwitchEntityDescription(
|
|
key=Capability.SAMSUNG_CE_AIR_CONDITIONER_LIGHTING,
|
|
translation_key="display_lighting",
|
|
status_attribute=Attribute.LIGHTING,
|
|
command=Command.SET_LIGHTING_LEVEL,
|
|
entity_category=EntityCategory.CONFIG,
|
|
),
|
|
Capability.CUSTOM_DRYER_WRINKLE_PREVENT: SmartThingsCommandSwitchEntityDescription(
|
|
key=Capability.CUSTOM_DRYER_WRINKLE_PREVENT,
|
|
translation_key="wrinkle_prevent",
|
|
status_attribute=Attribute.DRYER_WRINKLE_PREVENT,
|
|
command=Command.SET_DRYER_WRINKLE_PREVENT,
|
|
entity_category=EntityCategory.CONFIG,
|
|
),
|
|
Capability.SAMSUNG_CE_STEAM_CLOSET_AUTO_CYCLE_LINK: SmartThingsCommandSwitchEntityDescription(
|
|
key=Capability.SAMSUNG_CE_STEAM_CLOSET_AUTO_CYCLE_LINK,
|
|
translation_key="auto_cycle_link",
|
|
status_attribute=Attribute.STEAM_CLOSET_AUTO_CYCLE_LINK,
|
|
command=Command.SET_STEAM_CLOSET_AUTO_CYCLE_LINK,
|
|
entity_category=EntityCategory.CONFIG,
|
|
),
|
|
}
|
|
CAPABILITY_TO_SWITCHES: dict[Capability | str, SmartThingsSwitchEntityDescription] = {
|
|
Capability.SAMSUNG_CE_AIR_CONDITIONER_BEEP: SmartThingsSwitchEntityDescription(
|
|
key=Capability.SAMSUNG_CE_AIR_CONDITIONER_BEEP,
|
|
translation_key="sound_effect",
|
|
status_attribute=Attribute.BEEP,
|
|
on_key="on",
|
|
on_command=Command.ON,
|
|
off_command=Command.OFF,
|
|
entity_category=EntityCategory.CONFIG,
|
|
),
|
|
Capability.SAMSUNG_CE_WASHER_BUBBLE_SOAK: SmartThingsSwitchEntityDescription(
|
|
key=Capability.SAMSUNG_CE_WASHER_BUBBLE_SOAK,
|
|
translation_key="bubble_soak",
|
|
status_attribute=Attribute.STATUS,
|
|
entity_category=EntityCategory.CONFIG,
|
|
),
|
|
Capability.SWITCH: SmartThingsSwitchEntityDescription(
|
|
key=Capability.SWITCH,
|
|
status_attribute=Attribute.SWITCH,
|
|
component_translation_key={
|
|
"icemaker": "ice_maker",
|
|
"icemaker-02": "ice_maker_2",
|
|
},
|
|
),
|
|
Capability.SAMSUNG_CE_SABBATH_MODE: SmartThingsSwitchEntityDescription(
|
|
key=Capability.SAMSUNG_CE_SABBATH_MODE,
|
|
translation_key="sabbath_mode",
|
|
status_attribute=Attribute.STATUS,
|
|
entity_category=EntityCategory.CONFIG,
|
|
),
|
|
Capability.SAMSUNG_CE_POWER_COOL: SmartThingsSwitchEntityDescription(
|
|
key=Capability.SAMSUNG_CE_POWER_COOL,
|
|
translation_key="power_cool",
|
|
status_attribute=Attribute.ACTIVATED,
|
|
on_key="True",
|
|
on_command=Command.ACTIVATE,
|
|
off_command=Command.DEACTIVATE,
|
|
entity_category=EntityCategory.CONFIG,
|
|
),
|
|
Capability.SAMSUNG_CE_POWER_FREEZE: SmartThingsSwitchEntityDescription(
|
|
key=Capability.SAMSUNG_CE_POWER_FREEZE,
|
|
translation_key="power_freeze",
|
|
status_attribute=Attribute.ACTIVATED,
|
|
on_key="True",
|
|
on_command=Command.ACTIVATE,
|
|
off_command=Command.DEACTIVATE,
|
|
entity_category=EntityCategory.CONFIG,
|
|
),
|
|
Capability.SAMSUNG_CE_STEAM_CLOSET_SANITIZE_MODE: SmartThingsSwitchEntityDescription(
|
|
key=Capability.SAMSUNG_CE_STEAM_CLOSET_SANITIZE_MODE,
|
|
translation_key="sanitize",
|
|
status_attribute=Attribute.STATUS,
|
|
entity_category=EntityCategory.CONFIG,
|
|
),
|
|
Capability.SAMSUNG_CE_STEAM_CLOSET_KEEP_FRESH_MODE: SmartThingsSwitchEntityDescription(
|
|
key=Capability.SAMSUNG_CE_STEAM_CLOSET_KEEP_FRESH_MODE,
|
|
translation_key="keep_fresh_mode",
|
|
status_attribute=Attribute.STATUS,
|
|
entity_category=EntityCategory.CONFIG,
|
|
),
|
|
}
|
|
DISHWASHER_WASHING_OPTIONS_TO_SWITCHES: dict[
|
|
Attribute | str, SmartThingsDishwasherWashingOptionSwitchEntityDescription
|
|
] = {
|
|
Attribute.ADD_RINSE: SmartThingsDishwasherWashingOptionSwitchEntityDescription(
|
|
key=Attribute.ADD_RINSE,
|
|
translation_key="add_rinse",
|
|
status_attribute=Attribute.ADD_RINSE,
|
|
command=Command.SET_ADD_RINSE,
|
|
entity_category=EntityCategory.CONFIG,
|
|
),
|
|
Attribute.DRY_PLUS: SmartThingsDishwasherWashingOptionSwitchEntityDescription(
|
|
key=Attribute.DRY_PLUS,
|
|
translation_key="dry_plus",
|
|
status_attribute=Attribute.DRY_PLUS,
|
|
command=Command.SET_DRY_PLUS,
|
|
entity_category=EntityCategory.CONFIG,
|
|
),
|
|
Attribute.HEATED_DRY: SmartThingsDishwasherWashingOptionSwitchEntityDescription(
|
|
key=Attribute.HEATED_DRY,
|
|
translation_key="heated_dry",
|
|
status_attribute=Attribute.HEATED_DRY,
|
|
command=Command.SET_HEATED_DRY,
|
|
entity_category=EntityCategory.CONFIG,
|
|
),
|
|
Attribute.HIGH_TEMP_WASH: SmartThingsDishwasherWashingOptionSwitchEntityDescription(
|
|
key=Attribute.HIGH_TEMP_WASH,
|
|
translation_key="high_temp_wash",
|
|
status_attribute=Attribute.HIGH_TEMP_WASH,
|
|
command=Command.SET_HIGH_TEMP_WASH,
|
|
entity_category=EntityCategory.CONFIG,
|
|
),
|
|
Attribute.HOT_AIR_DRY: SmartThingsDishwasherWashingOptionSwitchEntityDescription(
|
|
key=Attribute.HOT_AIR_DRY,
|
|
translation_key="hot_air_dry",
|
|
status_attribute=Attribute.HOT_AIR_DRY,
|
|
command=Command.SET_HOT_AIR_DRY,
|
|
entity_category=EntityCategory.CONFIG,
|
|
),
|
|
Attribute.MULTI_TAB: SmartThingsDishwasherWashingOptionSwitchEntityDescription(
|
|
key=Attribute.MULTI_TAB,
|
|
translation_key="multi_tab",
|
|
status_attribute=Attribute.MULTI_TAB,
|
|
command=Command.SET_MULTI_TAB,
|
|
entity_category=EntityCategory.CONFIG,
|
|
),
|
|
Attribute.RINSE_PLUS: SmartThingsDishwasherWashingOptionSwitchEntityDescription(
|
|
key=Attribute.RINSE_PLUS,
|
|
translation_key="rinse_plus",
|
|
status_attribute=Attribute.RINSE_PLUS,
|
|
command=Command.SET_RINSE_PLUS,
|
|
entity_category=EntityCategory.CONFIG,
|
|
),
|
|
Attribute.SANITIZE: SmartThingsDishwasherWashingOptionSwitchEntityDescription(
|
|
key=Attribute.SANITIZE,
|
|
translation_key="sanitize",
|
|
status_attribute=Attribute.SANITIZE,
|
|
command=Command.SET_SANITIZE,
|
|
entity_category=EntityCategory.CONFIG,
|
|
),
|
|
Attribute.SANITIZING_WASH: SmartThingsDishwasherWashingOptionSwitchEntityDescription(
|
|
key=Attribute.SANITIZING_WASH,
|
|
translation_key="sanitizing_wash",
|
|
status_attribute=Attribute.SANITIZING_WASH,
|
|
command=Command.SET_SANITIZING_WASH,
|
|
entity_category=EntityCategory.CONFIG,
|
|
),
|
|
Attribute.SPEED_BOOSTER: SmartThingsDishwasherWashingOptionSwitchEntityDescription(
|
|
key=Attribute.SPEED_BOOSTER,
|
|
translation_key="speed_booster",
|
|
status_attribute=Attribute.SPEED_BOOSTER,
|
|
command=Command.SET_SPEED_BOOSTER,
|
|
entity_category=EntityCategory.CONFIG,
|
|
),
|
|
Attribute.STEAM_SOAK: SmartThingsDishwasherWashingOptionSwitchEntityDescription(
|
|
key=Attribute.STEAM_SOAK,
|
|
translation_key="steam_soak",
|
|
status_attribute=Attribute.STEAM_SOAK,
|
|
command=Command.SET_STEAM_SOAK,
|
|
entity_category=EntityCategory.CONFIG,
|
|
),
|
|
Attribute.STORM_WASH: SmartThingsDishwasherWashingOptionSwitchEntityDescription(
|
|
key=Attribute.STORM_WASH,
|
|
translation_key="storm_wash",
|
|
status_attribute=Attribute.STORM_WASH,
|
|
command=Command.SET_STORM_WASH,
|
|
entity_category=EntityCategory.CONFIG,
|
|
),
|
|
}
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant,
|
|
entry: SmartThingsConfigEntry,
|
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
|
) -> None:
|
|
"""Add switches for a config entry."""
|
|
entry_data = entry.runtime_data
|
|
entities: list[SmartThingsEntity] = [
|
|
SmartThingsCommandSwitch(
|
|
entry_data.client,
|
|
device,
|
|
description,
|
|
Capability(capability),
|
|
)
|
|
for device in entry_data.devices.values()
|
|
for capability, description in CAPABILITY_TO_COMMAND_SWITCHES.items()
|
|
if capability in device.status[MAIN]
|
|
]
|
|
entities.extend(
|
|
SmartThingsSwitch(
|
|
entry_data.client,
|
|
device,
|
|
description,
|
|
Capability(capability),
|
|
component,
|
|
)
|
|
for device in entry_data.devices.values()
|
|
for capability, description in CAPABILITY_TO_SWITCHES.items()
|
|
for component in device.status
|
|
if capability in device.status[component]
|
|
and (
|
|
(description.component_translation_key is None and component == MAIN)
|
|
or (
|
|
description.component_translation_key is not None
|
|
and component in description.component_translation_key
|
|
)
|
|
)
|
|
)
|
|
entities.extend(
|
|
SmartThingsDishwasherWashingOptionSwitch(
|
|
entry_data.client,
|
|
device,
|
|
DISHWASHER_WASHING_OPTIONS_TO_SWITCHES[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_SWITCHES
|
|
)
|
|
entity_registry = er.async_get(hass)
|
|
for device in entry_data.devices.values():
|
|
if (
|
|
Capability.SWITCH in device.status[MAIN]
|
|
and not any(
|
|
capability in device.status[MAIN] for capability in CAPABILITIES
|
|
)
|
|
and not all(
|
|
capability in device.status[MAIN] for capability in AC_CAPABILITIES
|
|
)
|
|
):
|
|
media_player = all(
|
|
capability in device.status[MAIN]
|
|
for capability in MEDIA_PLAYER_CAPABILITIES
|
|
)
|
|
appliance = (
|
|
device.device.components[MAIN].manufacturer_category
|
|
in INVALID_SWITCH_CATEGORIES
|
|
)
|
|
dhw = Capability.SAMSUNG_CE_EHS_FSV_SETTINGS in device.status[MAIN]
|
|
if media_player or appliance or dhw:
|
|
if appliance:
|
|
issue = "appliance"
|
|
version = "2025.10.0"
|
|
elif media_player:
|
|
issue = "media_player"
|
|
version = "2025.10.0"
|
|
else:
|
|
issue = "dhw"
|
|
version = "2025.12.0"
|
|
if deprecate_entity(
|
|
hass,
|
|
entity_registry,
|
|
SWITCH_DOMAIN,
|
|
f"{device.device.device_id}_{MAIN}_{Capability.SWITCH}_{Attribute.SWITCH}_{Attribute.SWITCH}",
|
|
f"deprecated_switch_{issue}",
|
|
version,
|
|
):
|
|
entities.append(
|
|
SmartThingsSwitch(
|
|
entry_data.client,
|
|
device,
|
|
SWITCH,
|
|
Capability.SWITCH,
|
|
)
|
|
)
|
|
continue
|
|
entities.append(
|
|
SmartThingsSwitch(
|
|
entry_data.client,
|
|
device,
|
|
SWITCH,
|
|
Capability.SWITCH,
|
|
)
|
|
)
|
|
async_add_entities(entities)
|
|
|
|
|
|
class SmartThingsSwitch(SmartThingsEntity, SwitchEntity):
|
|
"""Define a SmartThings switch."""
|
|
|
|
entity_description: SmartThingsSwitchEntityDescription
|
|
|
|
def __init__(
|
|
self,
|
|
client: SmartThings,
|
|
device: FullDevice,
|
|
entity_description: SmartThingsSwitchEntityDescription,
|
|
capability: Capability,
|
|
component: str = MAIN,
|
|
extra_capabilities: set[Capability] | None = None,
|
|
) -> None:
|
|
"""Initialize the switch."""
|
|
extra_capabilities = set() if extra_capabilities is None else extra_capabilities
|
|
super().__init__(
|
|
client, device, {capability} | extra_capabilities, component=component
|
|
)
|
|
self.entity_description = entity_description
|
|
self.switch_capability = capability
|
|
self._attr_unique_id = f"{device.device.device_id}_{component}_{capability}_{entity_description.status_attribute}_{entity_description.status_attribute}"
|
|
if (
|
|
translation_keys := entity_description.component_translation_key
|
|
) is not None and (
|
|
translation_key := translation_keys.get(component)
|
|
) is not None:
|
|
self._attr_translation_key = translation_key
|
|
|
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
|
"""Turn the switch off."""
|
|
await self.execute_device_command(
|
|
self.switch_capability,
|
|
self.entity_description.off_command,
|
|
)
|
|
|
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
|
"""Turn the switch on."""
|
|
await self.execute_device_command(
|
|
self.switch_capability,
|
|
self.entity_description.on_command,
|
|
)
|
|
|
|
def _current_state(self) -> Any:
|
|
return self.get_attribute_value(
|
|
self.switch_capability, self.entity_description.status_attribute
|
|
)
|
|
|
|
@property
|
|
def is_on(self) -> bool:
|
|
"""Return true if switch is on."""
|
|
return self._current_state() == self.entity_description.on_key
|
|
|
|
|
|
class SmartThingsCommandSwitch(SmartThingsSwitch):
|
|
"""Define a SmartThings command switch."""
|
|
|
|
entity_description: SmartThingsCommandSwitchEntityDescription
|
|
|
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
|
"""Turn the switch off."""
|
|
await self.execute_device_command(
|
|
self.switch_capability,
|
|
self.entity_description.command,
|
|
self.entity_description.off_key,
|
|
)
|
|
|
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
|
"""Turn the switch on."""
|
|
await self.execute_device_command(
|
|
self.switch_capability,
|
|
self.entity_description.command,
|
|
self.entity_description.on_key,
|
|
)
|
|
|
|
|
|
class SmartThingsDishwasherWashingOptionSwitch(SmartThingsCommandSwitch):
|
|
"""Define a SmartThings dishwasher washing option switch."""
|
|
|
|
def __init__(
|
|
self,
|
|
client: SmartThings,
|
|
device: FullDevice,
|
|
entity_description: SmartThingsSwitchEntityDescription,
|
|
) -> None:
|
|
"""Initialize the switch."""
|
|
super().__init__(
|
|
client,
|
|
device,
|
|
entity_description,
|
|
Capability.SAMSUNG_CE_DISHWASHER_WASHING_OPTIONS,
|
|
MAIN,
|
|
{
|
|
Capability.REMOTE_CONTROL_STATUS,
|
|
Capability.DISHWASHER_OPERATING_STATE,
|
|
Capability.SAMSUNG_CE_DISHWASHER_WASHING_COURSE,
|
|
Capability.SAMSUNG_CE_DISHWASHER_WASHING_COURSE_DETAILS,
|
|
},
|
|
)
|
|
|
|
def _validate_before_execute(self) -> None:
|
|
"""Validate that the switch command can be executed."""
|
|
if (
|
|
self.get_attribute_value(
|
|
Capability.REMOTE_CONTROL_STATUS, Attribute.REMOTE_CONTROL_ENABLED
|
|
)
|
|
== "false"
|
|
):
|
|
raise ServiceValidationError(
|
|
"Can only be updated when remote control is enabled"
|
|
)
|
|
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"
|
|
)
|
|
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_settable: list[bool] = next(
|
|
(
|
|
detail["options"][self.entity_description.status_attribute]["settable"]
|
|
for detail in course_details
|
|
if detail["courseName"] == selected_course
|
|
),
|
|
[],
|
|
)
|
|
if not course_settable:
|
|
raise ServiceValidationError("Option is not supported by selected cycle")
|
|
|
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
|
"""Turn the switch off."""
|
|
self._validate_before_execute()
|
|
await super().async_turn_off(**kwargs)
|
|
|
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
|
"""Turn the switch on."""
|
|
self._validate_before_execute()
|
|
await super().async_turn_on(**kwargs)
|
|
|
|
def _current_state(self) -> Any:
|
|
return super()._current_state()["value"]
|