python-kasa/kasa/iot/modules/lightpreset.py
2024-11-20 08:37:04 +00:00

168 lines
5.5 KiB
Python

"""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())