"""Light preset module.""" from __future__ import annotations from collections.abc import Sequence from dataclasses import asdict, dataclass from typing import TYPE_CHECKING from mashumaro.config import BaseConfig from ...exceptions import KasaException from ...interfaces import LightPreset as LightPresetInterface from ...interfaces import LightState from ...json import DataClassJSONMixin from ...module import Module from ..iotmodule import IotModule if TYPE_CHECKING: pass # type ignore can be removed after migration mashumaro: # error: Signature of "__replace__" incompatible with supertype "LightState" @dataclass(kw_only=True, repr=False) class IotLightPreset(DataClassJSONMixin, LightState): # type: ignore[override] """Light configuration preset.""" class Config(BaseConfig): """Config class.""" omit_none = True index: int brightness: int # These are not available for effect mode presets on light strips hue: int | None = None saturation: int | None = None color_temp: int | None = None # Variables for effect mode presets custom: int | None = None id: str | None = None mode: int | None = None class LightPreset(IotModule, LightPresetInterface): """Class for setting light presets.""" _presets: dict[str, IotLightPreset] _preset_list: list[str] async def _post_update_hook(self) -> None: """Update the internal presets.""" self._presets = { f"Light preset {index+1}": IotLightPreset.from_dict(vals) for index, vals in enumerate(self.data["preferred_state"]) # Devices may list some light effects along with normal presets but these # are handled by the LightEffect module so exclude preferred states with id if "id" not in vals } 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', 'Preset 1', 'Preset 2', ...] """ return self._preset_list @property def preset_states_list(self) -> Sequence[IotLightPreset]: """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[Module.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 or not light.is_color) and (preset.saturation == s or not light.is_color) ): 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[Module.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 light.set_state(preset) @property def has_save_preset(self) -> bool: """Return True if the device supports updating presets.""" return True async def save_preset( self, preset_name: str, preset_state: LightState, ) -> dict: """Update the preset with preset_name with the new preset_info.""" if len(self._presets) == 0: raise KasaException("Device does not supported saving presets") 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) state = asdict(preset_state) state = {k: v for k, v in state.items() if v is not None} state["index"] = index return await self.call("set_preferred_state", state) def query(self) -> dict: """Return the base query.""" return {} @property # type: ignore def _deprecated_presets(self) -> list[IotLightPreset]: """Return a list of available bulb setting presets.""" return [ IotLightPreset(**vals) for vals in self._device.sys_info["preferred_state"] if "id" not in vals ] async def _deprecated_save_preset(self, preset: IotLightPreset) -> dict: """Save a setting preset. You can either construct a preset object manually, or pass an existing one obtained using :func:`presets`. """ if len(self._presets) == 0: raise KasaException("Device does not supported saving presets") if preset.index >= len(self._presets): raise KasaException("Invalid preset index") return await self.call("set_preferred_state", preset.to_dict())