"""Support for VeSync numeric entities.""" from collections.abc import Awaitable, Callable from dataclasses import dataclass import logging from typing import override from pyvesync.base_devices.vesyncbasedevice import VeSyncBaseDevice from pyvesync.device_container import DeviceContainer from homeassistant.components.number import ( NumberEntity, NumberEntityDescription, NumberMode, ) from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from .common import is_humidifier from .const import VS_DEVICES, VS_DISCOVERY from .coordinator import VesyncConfigEntry, VeSyncDataCoordinator from .entity import VeSyncBaseEntity _LOGGER = logging.getLogger(__name__) PARALLEL_UPDATES = 1 def _mist_levels(device: VeSyncBaseDevice) -> list[int]: """Check if the device supports mist level adjustment.""" if is_humidifier(device): return device.mist_levels raise HomeAssistantError("Device does not support mist level adjustment.") def _set_mist_level(device: VeSyncBaseDevice, value: float) -> Awaitable[bool]: """Set mist level on humidifier.""" if is_humidifier(device): return device.set_mist_level(int(value)) raise HomeAssistantError("Device does not support mist level adjustment.") @dataclass(frozen=True, kw_only=True) class VeSyncNumberEntityDescription(NumberEntityDescription): """Class to describe a Vesync number entity.""" exists_fn: Callable[[VeSyncBaseDevice], bool] = lambda _: True value_fn: Callable[[VeSyncBaseDevice], float] native_min_value_fn: Callable[[VeSyncBaseDevice], float] native_max_value_fn: Callable[[VeSyncBaseDevice], float] set_value_fn: Callable[[VeSyncBaseDevice, float], Awaitable[bool]] NUMBER_DESCRIPTIONS: list[VeSyncNumberEntityDescription] = [ VeSyncNumberEntityDescription( key="mist_level", translation_key="mist_level", native_min_value_fn=lambda device: min(_mist_levels(device)), native_max_value_fn=lambda device: max(_mist_levels(device)), native_step=1, mode=NumberMode.SLIDER, exists_fn=is_humidifier, set_value_fn=_set_mist_level, value_fn=lambda device: device.state.mist_virtual_level, ) ] async def async_setup_entry( hass: HomeAssistant, config_entry: VesyncConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up number entities.""" coordinator = config_entry.runtime_data @callback def discover(devices: list[VeSyncBaseDevice]) -> None: """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( config_entry.runtime_data.manager.devices, async_add_entities, coordinator ) @callback def _setup_entities( devices: DeviceContainer | list[VeSyncBaseDevice], async_add_entities: AddConfigEntryEntitiesCallback, coordinator: VeSyncDataCoordinator, ) -> None: """Add number entities.""" async_add_entities( VeSyncNumberEntity(dev, description, coordinator) for dev in devices for description in NUMBER_DESCRIPTIONS if description.exists_fn(dev) ) class VeSyncNumberEntity(VeSyncBaseEntity, NumberEntity): """A class to set numeric options on Vesync device.""" entity_description: VeSyncNumberEntityDescription def __init__( self, device: VeSyncBaseDevice, description: VeSyncNumberEntityDescription, coordinator: VeSyncDataCoordinator, ) -> None: """Initialize the VeSync number device.""" super().__init__(device, coordinator) self.entity_description = description self._attr_unique_id = f"{super().unique_id}-{description.key}" @property @override def native_value(self) -> float: """Return the value reported by the number.""" return self.entity_description.value_fn(self.device) @property @override def native_min_value(self) -> float: """Return the value reported by the number.""" return self.entity_description.native_min_value_fn(self.device) @property @override def native_max_value(self) -> float: """Return the value reported by the number.""" return self.entity_description.native_max_value_fn(self.device) @override async def async_set_native_value(self, value: float) -> None: """Set new value.""" if not await self.entity_description.set_value_fn(self.device, value): raise HomeAssistantError(self.device.last_response.message) self.async_write_ha_state()