"""Support for VeSync numeric entities.""" from collections.abc import Callable from dataclasses import dataclass import logging from pyvesync.vesyncbasedevice import VeSyncBaseDevice from homeassistant.components.select import SelectEntity, SelectEntityDescription from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from .common import rgetattr from .const import ( DOMAIN, NIGHT_LIGHT_LEVEL_BRIGHT, NIGHT_LIGHT_LEVEL_DIM, NIGHT_LIGHT_LEVEL_OFF, VS_COORDINATOR, VS_DEVICES, VS_DISCOVERY, ) from .coordinator import VeSyncDataCoordinator from .entity import VeSyncBaseEntity _LOGGER = logging.getLogger(__name__) VS_TO_HA_NIGHT_LIGHT_LEVEL_MAP = { 100: NIGHT_LIGHT_LEVEL_BRIGHT, 50: NIGHT_LIGHT_LEVEL_DIM, 0: NIGHT_LIGHT_LEVEL_OFF, } HA_TO_VS_NIGHT_LIGHT_LEVEL_MAP = { v: k for k, v in VS_TO_HA_NIGHT_LIGHT_LEVEL_MAP.items() } @dataclass(frozen=True, kw_only=True) class VeSyncSelectEntityDescription(SelectEntityDescription): """Class to describe a Vesync select entity.""" exists_fn: Callable[[VeSyncBaseDevice], bool] current_option_fn: Callable[[VeSyncBaseDevice], str] select_option_fn: Callable[[VeSyncBaseDevice, str], bool] SELECT_DESCRIPTIONS: list[VeSyncSelectEntityDescription] = [ VeSyncSelectEntityDescription( key="night_light_level", translation_key="night_light_level", options=list(VS_TO_HA_NIGHT_LIGHT_LEVEL_MAP.values()), icon="mdi:brightness-6", exists_fn=lambda device: rgetattr(device, "night_light"), # The select_option service framework ensures that only options specified are # accepted. ServiceValidationError gets raised for invalid value. select_option_fn=lambda device, value: device.set_night_light_brightness( HA_TO_VS_NIGHT_LIGHT_LEVEL_MAP.get(value, 0) ), # Reporting "off" as the choice for unhandled level. current_option_fn=lambda device: VS_TO_HA_NIGHT_LIGHT_LEVEL_MAP.get( device.details.get("night_light_brightness"), NIGHT_LIGHT_LEVEL_OFF ), ), ] async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up select entities.""" coordinator = hass.data[DOMAIN][VS_COORDINATOR] @callback def discover(devices): """Add new devices to platform.""" _setup_entities(devices, async_add_entities, coordinator) config_entry.async_on_unload( async_dispatcher_connect(hass, VS_DISCOVERY.format(VS_DEVICES), discover) ) _setup_entities(hass.data[DOMAIN][VS_DEVICES], async_add_entities, coordinator) @callback def _setup_entities( devices: list[VeSyncBaseDevice], async_add_entities: AddConfigEntryEntitiesCallback, coordinator: VeSyncDataCoordinator, ): """Add select entities.""" async_add_entities( VeSyncSelectEntity(dev, description, coordinator) for dev in devices for description in SELECT_DESCRIPTIONS if description.exists_fn(dev) ) class VeSyncSelectEntity(VeSyncBaseEntity, SelectEntity): """A class to set numeric options on Vesync device.""" entity_description: VeSyncSelectEntityDescription def __init__( self, device: VeSyncBaseDevice, description: VeSyncSelectEntityDescription, coordinator: VeSyncDataCoordinator, ) -> None: """Initialize the VeSync select device.""" super().__init__(device, coordinator) self.entity_description = description self._attr_unique_id = f"{super().unique_id}-{description.key}" @property def current_option(self) -> str | None: """Return an option.""" return self.entity_description.current_option_fn(self.device) async def async_select_option(self, option: str) -> None: """Set an option.""" if await self.hass.async_add_executor_job( self.entity_description.select_option_fn, self.device, option ): await self.coordinator.async_request_refresh()