python-kasa/kasa/iot/modules/lightpreset.py
Teemu R 472008e818
Drop python3.8 support (#992)
Drop support for soon-to-be eol'd python 3.8.
This will allow some minor cleanups & makes it easier to add support for
timezones.

Related to
https://github.com/python-kasa/python-kasa/issues/980#issuecomment-2170889543
2024-06-19 20:24:12 +02:00

158 lines
5.4 KiB
Python

"""Light preset module."""
from __future__ import annotations
from collections.abc import Sequence
from dataclasses import asdict
from typing import TYPE_CHECKING, Optional
from pydantic.v1 import BaseModel, Field
from ...exceptions import KasaException
from ...interfaces import LightPreset as LightPresetInterface
from ...interfaces import LightState
from ...module import Module
from ..iotmodule import IotModule
if TYPE_CHECKING:
pass
class IotLightPreset(BaseModel, LightState):
"""Light configuration preset."""
index: int = Field(kw_only=True)
brightness: int = Field(kw_only=True)
# These are not available for effect mode presets on light strips
hue: Optional[int] = Field(kw_only=True, default=None) # noqa: UP007
saturation: Optional[int] = Field(kw_only=True, default=None) # noqa: UP007
color_temp: Optional[int] = Field(kw_only=True, default=None) # noqa: UP007
# Variables for effect mode presets
custom: Optional[int] = Field(kw_only=True, default=None) # noqa: UP007
id: Optional[str] = Field(kw_only=True, default=None) # noqa: UP007
mode: Optional[int] = Field(kw_only=True, default=None) # noqa: UP007
class LightPreset(IotModule, LightPresetInterface):
"""Class for setting light presets."""
_presets: dict[str, IotLightPreset]
_preset_list: list[str]
def _post_update_hook(self):
"""Update the internal presets."""
self._presets = {
f"Light preset {index+1}": IotLightPreset(**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,
) -> None:
"""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}")
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,
) -> None:
"""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):
"""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):
"""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.dict(exclude_none=True))