"""Platform for Miele select entity.""" from __future__ import annotations from collections.abc import Callable from dataclasses import dataclass from enum import IntEnum import logging from typing import Final from aiohttp import ClientResponseError from homeassistant.components.select import SelectEntity, SelectEntityDescription from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError, ServiceValidationError from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.typing import StateType from .const import DOMAIN, MieleAppliance from .coordinator import MieleConfigEntry from .entity import MieleDevice, MieleEntity PARALLEL_UPDATES = 1 _LOGGER = logging.getLogger(__name__) class MieleModes(IntEnum): """Modes for fridge/freezer.""" NORMAL = 0 SABBATH = 1 PARTY = 2 HOLIDAY = 3 @dataclass(frozen=True, kw_only=True) class MieleSelectDescription(SelectEntityDescription): """Class describing Miele select entities.""" value_fn: Callable[[MieleDevice], StateType] @dataclass class MieleSelectDefinition: """Class for defining select entities.""" types: tuple[MieleAppliance, ...] description: MieleSelectDescription SELECT_TYPES: Final[tuple[MieleSelectDefinition, ...]] = ( MieleSelectDefinition( types=( MieleAppliance.FREEZER, MieleAppliance.FRIDGE, MieleAppliance.FRIDGE_FREEZER, ), description=MieleSelectDescription( key="fridge_freezer_modes", value_fn=lambda value: 1, translation_key="fridge_freezer_mode", ), ), ) async def async_setup_entry( hass: HomeAssistant, config_entry: MieleConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up the select platform.""" coordinator = config_entry.runtime_data added_devices: set[str] = set() def _async_add_new_devices() -> None: nonlocal added_devices new_devices_set, current_devices = coordinator.async_add_devices(added_devices) added_devices = current_devices async_add_entities( MieleSelectMode(coordinator, device_id, definition.description) for device_id, device in coordinator.data.devices.items() for definition in SELECT_TYPES if device_id in new_devices_set and device.device_type in definition.types ) config_entry.async_on_unload(coordinator.async_add_listener(_async_add_new_devices)) _async_add_new_devices() class MieleSelectMode(MieleEntity, SelectEntity): """Representation of a Select mode entity.""" entity_description: MieleSelectDescription @property def options(self) -> list[str]: """Return the list of available options.""" return sorted( {MieleModes(x).name.lower() for x in self.action.modes} | {self.current_option} ) @property def current_option(self) -> str: """Retrieve currently selected option.""" # There is no direct mapping from Miele 3rd party API, so we infer the # current mode based on available modes in action.modes action_modes = set(self.action.modes) if action_modes in ({1}, {1, 2}, {1, 3}, {1, 2, 3}): return MieleModes.NORMAL.name.lower() if action_modes in ({0}, {0, 2}, {0, 3}, {0, 2, 3}): return MieleModes.SABBATH.name.lower() if action_modes in ({0, 1}, {0, 1, 3}): return MieleModes.PARTY.name.lower() if action_modes == {0, 1, 2}: return MieleModes.HOLIDAY.name.lower() return MieleModes.NORMAL.name.lower() async def async_select_option(self, option: str) -> None: """Set the selected option.""" new_mode = MieleModes[option.upper()].value if new_mode not in self.action.modes: _LOGGER.debug("Option '%s' is not available for %s", option, self.entity_id) raise ServiceValidationError( translation_domain=DOMAIN, translation_key="invalid_option", translation_placeholders={ "option": option, "entity": self.entity_id, }, ) try: await self.api.send_action( self._device_id, {"modes": new_mode}, ) except ClientResponseError as err: _LOGGER.debug("Error setting select state for %s: %s", self.entity_id, err) raise HomeAssistantError( translation_domain=DOMAIN, translation_key="set_state_error", translation_placeholders={ "entity": self.entity_id, }, ) from err # Refresh data as API does not push changes for modes updates await self.coordinator.async_request_refresh()