"""Module for light effects.""" from __future__ import annotations import logging from collections.abc import Sequence from dataclasses import asdict from typing import TYPE_CHECKING from ...interfaces import LightPreset as LightPresetInterface from ...interfaces import LightState from ..smartmodule import SmartModule, allow_update_after if TYPE_CHECKING: from ..smartdevice import SmartDevice _LOGGER = logging.getLogger(__name__) class LightPreset(SmartModule, LightPresetInterface): """Implementation of light presets.""" REQUIRED_COMPONENT = "preset" QUERY_GETTER_NAME = "get_preset_rules" MINIMUM_UPDATE_INTERVAL_SECS = 60 SYS_INFO_STATE_KEY = "preset_state" _presets: dict[str, LightState] _preset_list: list[str] def __init__(self, device: SmartDevice, module: str) -> None: super().__init__(device, module) self._state_in_sysinfo = self.SYS_INFO_STATE_KEY in device.sys_info self._brightness_only: bool = False async def _post_update_hook(self) -> None: """Update the internal presets.""" index = 0 self._presets = {} state_key = "states" if not self._state_in_sysinfo else self.SYS_INFO_STATE_KEY if preset_states := self.data.get(state_key): for preset_state in preset_states: if "brightness" not in preset_state: # Some devices can store effects as a preset. These will be ignored # and handled in the effects module if "lighting_effect" not in preset_state: _LOGGER.info( "Unexpected keys %s in preset", list(preset_state.keys()) ) continue color_temp = preset_state.get("color_temp") hue = preset_state.get("hue") saturation = preset_state.get("saturation") self._presets[f"Light preset {index + 1}"] = LightState( brightness=preset_state["brightness"], color_temp=color_temp, hue=hue, saturation=saturation, ) if color_temp is None and hue is None and saturation is None: self._brightness_only = True index = index + 1 elif preset_brightnesses := self.data.get("brightness"): self._brightness_only = True for preset_brightness in preset_brightnesses: self._presets[f"Brightness preset {index + 1}"] = LightState( brightness=preset_brightness, ) index = index + 1 self._preset_list = [self.PRESET_NOT_SET] self._preset_list.extend(self._presets.keys()) @property def preset_list(self) -> list[str]: """Return built-in effects list. Example: ['Off', 'Light preset 1', 'Light preset 2', ...] """ return self._preset_list @property def preset_states_list(self) -> Sequence[LightState]: """Return built-in effects list. Example: ['Off', 'Preset 1', 'Preset 2', ...] """ return list(self._presets.values()) @property def preset(self) -> str: """Return current preset name.""" light = self._device.modules[SmartModule.Light] brightness = light.brightness color_temp = light.color_temp if light.is_variable_color_temp else None h, s = (light.hsv.hue, light.hsv.saturation) if light.is_color else (None, None) for preset_name, preset in self._presets.items(): if ( preset.brightness == brightness and ( preset.color_temp == color_temp or not light.is_variable_color_temp ) and preset.hue == h and preset.saturation == s ): return preset_name return self.PRESET_NOT_SET async def set_preset( self, preset_name: str, ) -> dict: """Set a light preset for the device.""" light = self._device.modules[SmartModule.Light] if preset_name == self.PRESET_NOT_SET: if light.is_color: preset = LightState(hue=0, saturation=0, brightness=100) else: preset = LightState(brightness=100) elif (preset := self._presets.get(preset_name)) is None: # type: ignore[assignment] raise ValueError(f"{preset_name} is not a valid preset: {self.preset_list}") return await self._device.modules[SmartModule.Light].set_state(preset) @allow_update_after async def save_preset( self, preset_name: str, preset_state: LightState, ) -> dict: """Update the preset with preset_name with the new preset_info.""" if preset_name not in self._presets: raise ValueError(f"{preset_name} is not a valid preset: {self.preset_list}") index = list(self._presets.keys()).index(preset_name) if self._brightness_only: bright_list = [state.brightness for state in self._presets.values()] bright_list[index] = preset_state.brightness return await self.call("set_preset_rules", {"brightness": bright_list}) else: state_params = asdict(preset_state) new_info = {k: v for k, v in state_params.items() if v is not None} return await self.call( "edit_preset_rules", {"index": index, "state": new_info} ) @property def has_save_preset(self) -> bool: """Return True if the device supports updating presets.""" return True def query(self) -> dict: """Query to execute during the update cycle.""" if self._state_in_sysinfo: # Child lights can have states in the child info return {} if self.supported_version < 3: return {self.QUERY_GETTER_NAME: None} return {self.QUERY_GETTER_NAME: {"start_index": 0}} async def _check_supported(self) -> bool: """Additional check to see if the module is supported by the device. Parent devices that report components of children such as ks240 will not have the brightness value is sysinfo. """ # Look in _device.sys_info here because self.data is either sys_info or # get_preset_rules depending on whether it's a child device or not. return "brightness" in self._device.sys_info