mirror of https://github.com/home-assistant/core
395 lines
14 KiB
Python
395 lines
14 KiB
Python
"""Provides a switch for Home Connect."""
|
|
|
|
import contextlib
|
|
import logging
|
|
from typing import Any
|
|
|
|
from homeconnect.api import HomeConnectError
|
|
|
|
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.exceptions import ServiceValidationError
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
|
|
from . import HomeConnectConfigEntry, get_dict_from_home_connect_error
|
|
from .const import (
|
|
ATTR_ALLOWED_VALUES,
|
|
ATTR_CONSTRAINTS,
|
|
ATTR_VALUE,
|
|
BSH_ACTIVE_PROGRAM,
|
|
BSH_CHILD_LOCK_STATE,
|
|
BSH_OPERATION_STATE,
|
|
BSH_POWER_OFF,
|
|
BSH_POWER_ON,
|
|
BSH_POWER_STANDBY,
|
|
BSH_POWER_STATE,
|
|
DOMAIN,
|
|
REFRIGERATION_DISPENSER,
|
|
REFRIGERATION_SUPERMODEFREEZER,
|
|
REFRIGERATION_SUPERMODEREFRIGERATOR,
|
|
SVE_TRANSLATION_PLACEHOLDER_APPLIANCE_NAME,
|
|
SVE_TRANSLATION_PLACEHOLDER_ENTITY_ID,
|
|
SVE_TRANSLATION_PLACEHOLDER_SETTING_KEY,
|
|
SVE_TRANSLATION_PLACEHOLDER_VALUE,
|
|
)
|
|
from .entity import HomeConnectDevice, HomeConnectEntity
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
APPLIANCES_WITH_PROGRAMS = (
|
|
"CleaningRobot",
|
|
"CoffeeMaker",
|
|
"Dishwasher",
|
|
"Dryer",
|
|
"Hood",
|
|
"Oven",
|
|
"WarmingDrawer",
|
|
"Washer",
|
|
"WasherDryer",
|
|
)
|
|
|
|
|
|
SWITCHES = (
|
|
SwitchEntityDescription(
|
|
key=BSH_CHILD_LOCK_STATE,
|
|
translation_key="child_lock",
|
|
),
|
|
SwitchEntityDescription(
|
|
key="ConsumerProducts.CoffeeMaker.Setting.CupWarmer",
|
|
translation_key="cup_warmer",
|
|
),
|
|
SwitchEntityDescription(
|
|
key=REFRIGERATION_SUPERMODEFREEZER,
|
|
translation_key="freezer_super_mode",
|
|
),
|
|
SwitchEntityDescription(
|
|
key=REFRIGERATION_SUPERMODEREFRIGERATOR,
|
|
translation_key="refrigerator_super_mode",
|
|
),
|
|
SwitchEntityDescription(
|
|
key="Refrigeration.Common.Setting.EcoMode",
|
|
translation_key="eco_mode",
|
|
),
|
|
SwitchEntityDescription(
|
|
key="Cooking.Oven.Setting.SabbathMode",
|
|
translation_key="sabbath_mode",
|
|
),
|
|
SwitchEntityDescription(
|
|
key="Refrigeration.Common.Setting.SabbathMode",
|
|
translation_key="sabbath_mode",
|
|
),
|
|
SwitchEntityDescription(
|
|
key="Refrigeration.Common.Setting.VacationMode",
|
|
translation_key="vacation_mode",
|
|
),
|
|
SwitchEntityDescription(
|
|
key="Refrigeration.Common.Setting.FreshMode",
|
|
translation_key="fresh_mode",
|
|
),
|
|
SwitchEntityDescription(
|
|
key=REFRIGERATION_DISPENSER,
|
|
translation_key="dispenser_enabled",
|
|
),
|
|
SwitchEntityDescription(
|
|
key="Refrigeration.Common.Setting.Door.AssistantFridge",
|
|
translation_key="door_assistant_fridge",
|
|
),
|
|
SwitchEntityDescription(
|
|
key="Refrigeration.Common.Setting.Door.AssistantFreezer",
|
|
translation_key="door_assistant_freezer",
|
|
),
|
|
)
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant,
|
|
entry: HomeConnectConfigEntry,
|
|
async_add_entities: AddEntitiesCallback,
|
|
) -> None:
|
|
"""Set up the Home Connect switch."""
|
|
|
|
def get_entities() -> list[SwitchEntity]:
|
|
"""Get a list of entities."""
|
|
entities: list[SwitchEntity] = []
|
|
for device in entry.runtime_data.devices:
|
|
if device.appliance.type in APPLIANCES_WITH_PROGRAMS:
|
|
with contextlib.suppress(HomeConnectError):
|
|
programs = device.appliance.get_programs_available()
|
|
if programs:
|
|
entities.extend(
|
|
HomeConnectProgramSwitch(device, program)
|
|
for program in programs
|
|
)
|
|
entities.append(HomeConnectPowerSwitch(device))
|
|
entities.extend(
|
|
HomeConnectSwitch(device, description)
|
|
for description in SWITCHES
|
|
if description.key in device.appliance.status
|
|
)
|
|
|
|
return entities
|
|
|
|
async_add_entities(await hass.async_add_executor_job(get_entities), True)
|
|
|
|
|
|
class HomeConnectSwitch(HomeConnectEntity, SwitchEntity):
|
|
"""Generic switch class for Home Connect Binary Settings."""
|
|
|
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
|
"""Turn on setting."""
|
|
|
|
_LOGGER.debug("Turning on %s", self.entity_description.key)
|
|
try:
|
|
await self.hass.async_add_executor_job(
|
|
self.device.appliance.set_setting, self.entity_description.key, True
|
|
)
|
|
except HomeConnectError as err:
|
|
self._attr_available = False
|
|
raise ServiceValidationError(
|
|
translation_domain=DOMAIN,
|
|
translation_key="turn_on",
|
|
translation_placeholders={
|
|
**get_dict_from_home_connect_error(err),
|
|
SVE_TRANSLATION_PLACEHOLDER_ENTITY_ID: self.entity_id,
|
|
SVE_TRANSLATION_PLACEHOLDER_SETTING_KEY: self.bsh_key,
|
|
},
|
|
) from err
|
|
|
|
self._attr_available = True
|
|
self.async_entity_update()
|
|
|
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
|
"""Turn off setting."""
|
|
|
|
_LOGGER.debug("Turning off %s", self.entity_description.key)
|
|
try:
|
|
await self.hass.async_add_executor_job(
|
|
self.device.appliance.set_setting, self.entity_description.key, False
|
|
)
|
|
except HomeConnectError as err:
|
|
_LOGGER.error("Error while trying to turn off: %s", err)
|
|
self._attr_available = False
|
|
raise ServiceValidationError(
|
|
translation_domain=DOMAIN,
|
|
translation_key="turn_off",
|
|
translation_placeholders={
|
|
**get_dict_from_home_connect_error(err),
|
|
SVE_TRANSLATION_PLACEHOLDER_ENTITY_ID: self.entity_id,
|
|
SVE_TRANSLATION_PLACEHOLDER_SETTING_KEY: self.bsh_key,
|
|
},
|
|
) from err
|
|
|
|
self._attr_available = True
|
|
self.async_entity_update()
|
|
|
|
async def async_update(self) -> None:
|
|
"""Update the switch's status."""
|
|
|
|
self._attr_is_on = self.device.appliance.status.get(
|
|
self.entity_description.key, {}
|
|
).get(ATTR_VALUE)
|
|
self._attr_available = True
|
|
_LOGGER.debug(
|
|
"Updated %s, new state: %s",
|
|
self.entity_description.key,
|
|
self._attr_is_on,
|
|
)
|
|
|
|
|
|
class HomeConnectProgramSwitch(HomeConnectEntity, SwitchEntity):
|
|
"""Switch class for Home Connect."""
|
|
|
|
def __init__(self, device: HomeConnectDevice, program_name: str) -> None:
|
|
"""Initialize the entity."""
|
|
desc = " ".join(["Program", program_name.split(".")[-1]])
|
|
if device.appliance.type == "WasherDryer":
|
|
desc = " ".join(
|
|
["Program", program_name.split(".")[-3], program_name.split(".")[-1]]
|
|
)
|
|
super().__init__(device, SwitchEntityDescription(key=program_name))
|
|
self._attr_name = f"{device.appliance.name} {desc}"
|
|
self._attr_unique_id = f"{device.appliance.haId}-{desc}"
|
|
self._attr_has_entity_name = False
|
|
self.program_name = program_name
|
|
|
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
|
"""Start the program."""
|
|
_LOGGER.debug("Tried to turn on program %s", self.program_name)
|
|
try:
|
|
await self.hass.async_add_executor_job(
|
|
self.device.appliance.start_program, self.program_name
|
|
)
|
|
except HomeConnectError as err:
|
|
raise ServiceValidationError(
|
|
translation_domain=DOMAIN,
|
|
translation_key="start_program",
|
|
translation_placeholders={
|
|
**get_dict_from_home_connect_error(err),
|
|
"program": self.program_name,
|
|
},
|
|
) from err
|
|
self.async_entity_update()
|
|
|
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
|
"""Stop the program."""
|
|
_LOGGER.debug("Tried to stop program %s", self.program_name)
|
|
try:
|
|
await self.hass.async_add_executor_job(self.device.appliance.stop_program)
|
|
except HomeConnectError as err:
|
|
raise ServiceValidationError(
|
|
translation_domain=DOMAIN,
|
|
translation_key="stop_program",
|
|
translation_placeholders={
|
|
**get_dict_from_home_connect_error(err),
|
|
"program": self.program_name,
|
|
},
|
|
) from err
|
|
self.async_entity_update()
|
|
|
|
async def async_update(self) -> None:
|
|
"""Update the switch's status."""
|
|
state = self.device.appliance.status.get(BSH_ACTIVE_PROGRAM, {})
|
|
if state.get(ATTR_VALUE) == self.program_name:
|
|
self._attr_is_on = True
|
|
else:
|
|
self._attr_is_on = False
|
|
_LOGGER.debug("Updated, new state: %s", self._attr_is_on)
|
|
|
|
|
|
class HomeConnectPowerSwitch(HomeConnectEntity, SwitchEntity):
|
|
"""Power switch class for Home Connect."""
|
|
|
|
power_off_state: str | None
|
|
|
|
def __init__(self, device: HomeConnectDevice) -> None:
|
|
"""Initialize the entity."""
|
|
super().__init__(
|
|
device,
|
|
SwitchEntityDescription(key=BSH_POWER_STATE, translation_key="power"),
|
|
)
|
|
if (
|
|
power_state := device.appliance.status.get(BSH_POWER_STATE, {}).get(
|
|
ATTR_VALUE
|
|
)
|
|
) and power_state in [BSH_POWER_OFF, BSH_POWER_STANDBY]:
|
|
self.power_off_state = power_state
|
|
|
|
async def async_added_to_hass(self) -> None:
|
|
"""Add the entity to the hass instance."""
|
|
await super().async_added_to_hass()
|
|
if not hasattr(self, "power_off_state"):
|
|
await self.async_fetch_power_off_state()
|
|
|
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
|
"""Switch the device on."""
|
|
_LOGGER.debug("Tried to switch on %s", self.name)
|
|
try:
|
|
await self.hass.async_add_executor_job(
|
|
self.device.appliance.set_setting, BSH_POWER_STATE, BSH_POWER_ON
|
|
)
|
|
except HomeConnectError as err:
|
|
self._attr_is_on = False
|
|
raise ServiceValidationError(
|
|
translation_domain=DOMAIN,
|
|
translation_key="power_on",
|
|
translation_placeholders={
|
|
**get_dict_from_home_connect_error(err),
|
|
SVE_TRANSLATION_PLACEHOLDER_APPLIANCE_NAME: self.device.appliance.name,
|
|
},
|
|
) from err
|
|
self.async_entity_update()
|
|
|
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
|
"""Switch the device off."""
|
|
if not hasattr(self, "power_off_state"):
|
|
raise ServiceValidationError(
|
|
translation_domain=DOMAIN,
|
|
translation_key="unable_to_retrieve_turn_off",
|
|
translation_placeholders={
|
|
SVE_TRANSLATION_PLACEHOLDER_APPLIANCE_NAME: self.device.appliance.name
|
|
},
|
|
)
|
|
|
|
if self.power_off_state is None:
|
|
raise ServiceValidationError(
|
|
translation_domain=DOMAIN,
|
|
translation_key="turn_off_not_supported",
|
|
translation_placeholders={
|
|
SVE_TRANSLATION_PLACEHOLDER_APPLIANCE_NAME: self.device.appliance.name
|
|
},
|
|
)
|
|
_LOGGER.debug("tried to switch off %s", self.name)
|
|
try:
|
|
await self.hass.async_add_executor_job(
|
|
self.device.appliance.set_setting,
|
|
BSH_POWER_STATE,
|
|
self.power_off_state,
|
|
)
|
|
except HomeConnectError as err:
|
|
self._attr_is_on = True
|
|
raise ServiceValidationError(
|
|
translation_domain=DOMAIN,
|
|
translation_key="power_off",
|
|
translation_placeholders={
|
|
**get_dict_from_home_connect_error(err),
|
|
SVE_TRANSLATION_PLACEHOLDER_APPLIANCE_NAME: self.device.appliance.name,
|
|
SVE_TRANSLATION_PLACEHOLDER_VALUE: self.power_off_state,
|
|
},
|
|
) from err
|
|
self.async_entity_update()
|
|
|
|
async def async_update(self) -> None:
|
|
"""Update the switch's status."""
|
|
if (
|
|
self.device.appliance.status.get(BSH_POWER_STATE, {}).get(ATTR_VALUE)
|
|
== BSH_POWER_ON
|
|
):
|
|
self._attr_is_on = True
|
|
elif (
|
|
hasattr(self, "power_off_state")
|
|
and self.device.appliance.status.get(BSH_POWER_STATE, {}).get(ATTR_VALUE)
|
|
== self.power_off_state
|
|
):
|
|
self._attr_is_on = False
|
|
elif self.device.appliance.status.get(BSH_OPERATION_STATE, {}).get(
|
|
ATTR_VALUE, None
|
|
) in [
|
|
"BSH.Common.EnumType.OperationState.Ready",
|
|
"BSH.Common.EnumType.OperationState.DelayedStart",
|
|
"BSH.Common.EnumType.OperationState.Run",
|
|
"BSH.Common.EnumType.OperationState.Pause",
|
|
"BSH.Common.EnumType.OperationState.ActionRequired",
|
|
"BSH.Common.EnumType.OperationState.Aborting",
|
|
"BSH.Common.EnumType.OperationState.Finished",
|
|
]:
|
|
self._attr_is_on = True
|
|
elif (
|
|
self.device.appliance.status.get(BSH_OPERATION_STATE, {}).get(ATTR_VALUE)
|
|
== "BSH.Common.EnumType.OperationState.Inactive"
|
|
):
|
|
self._attr_is_on = False
|
|
else:
|
|
self._attr_is_on = None
|
|
_LOGGER.debug("Updated, new state: %s", self._attr_is_on)
|
|
|
|
async def async_fetch_power_off_state(self) -> None:
|
|
"""Fetch the power off state."""
|
|
try:
|
|
data = await self.hass.async_add_executor_job(
|
|
self.device.appliance.get, f"/settings/{self.bsh_key}"
|
|
)
|
|
except HomeConnectError as err:
|
|
_LOGGER.error("An error occurred: %s", err)
|
|
return
|
|
if not data or not (
|
|
allowed_values := data.get(ATTR_CONSTRAINTS, {}).get(ATTR_ALLOWED_VALUES)
|
|
):
|
|
return
|
|
|
|
if BSH_POWER_OFF in allowed_values:
|
|
self.power_off_state = BSH_POWER_OFF
|
|
elif BSH_POWER_STANDBY in allowed_values:
|
|
self.power_off_state = BSH_POWER_STANDBY
|
|
else:
|
|
self.power_off_state = None
|