python-kasa/kasa/interfaces/light.py
2024-11-25 13:36:00 +00:00

214 lines
5.9 KiB
Python

"""Interact with a TPLink Light.
>>> from kasa import Discover, Module
>>>
>>> dev = await Discover.discover_single(
>>> "127.0.0.3",
>>> username="user@example.com",
>>> password="great_password"
>>> )
>>> await dev.update()
>>> print(dev.alias)
Living Room Bulb
Lights, like any other supported devices, can be turned on and off:
>>> print(dev.is_on)
>>> await dev.turn_on()
>>> await dev.update()
>>> print(dev.is_on)
True
Get the light module to interact:
>>> light = dev.modules[Module.Light]
You can use the ``is_``-prefixed properties to check for supported features:
>>> light.has_feature("brightness")
True
>>> light.has_feature("hsv")
True
>>> light.has_feature("color_temp")
True
All known bulbs support changing the brightness:
>>> light.brightness
100
>>> await light.set_brightness(50)
>>> await dev.update()
>>> light.brightness
50
Bulbs supporting color temperature can be queried for the supported range:
>>> if color_temp_feature := light.get_feature("color_temp"):
>>> print(f"{color_temp_feature.minimum_value}, {color_temp_feature.maximum_value}")
2500, 6500
>>> await light.set_color_temp(3000)
>>> await dev.update()
>>> light.color_temp
3000
Color bulbs can be adjusted by passing hue, saturation and value:
>>> await light.set_hsv(180, 100, 80)
>>> await dev.update()
>>> light.hsv
HSV(hue=180, saturation=100, value=80)
"""
from __future__ import annotations
from abc import ABC, abstractmethod
from collections.abc import Callable
from dataclasses import dataclass
from typing import Annotated, Any, NamedTuple
from warnings import warn
from ..exceptions import KasaException
from ..module import FeatureAttribute, Module
@dataclass
class LightState:
"""Class for smart light preset info."""
light_on: bool | None = None
brightness: int | None = None
hue: int | None = None
saturation: int | None = None
color_temp: int | None = None
transition: int | None = None
class ColorTempRange(NamedTuple):
"""Color temperature range."""
min: int
max: int
class HSV(NamedTuple):
"""Hue-saturation-value."""
hue: int
saturation: int
value: int
class Light(Module, ABC):
"""Base class for TP-Link Light."""
@property
@abstractmethod
def hsv(self) -> Annotated[HSV, FeatureAttribute()]:
"""Return the current HSV state of the bulb.
:return: hue, saturation and value (degrees, %, %)
"""
@property
@abstractmethod
def color_temp(self) -> Annotated[int, FeatureAttribute()]:
"""Whether the bulb supports color temperature changes."""
@property
@abstractmethod
def brightness(self) -> Annotated[int, FeatureAttribute()]:
"""Return the current brightness in percentage."""
@abstractmethod
async def set_hsv(
self,
hue: int,
saturation: int,
value: int | None = None,
*,
transition: int | None = None,
) -> Annotated[dict, FeatureAttribute()]:
"""Set new HSV.
Note, transition is not supported and will be ignored.
:param int hue: hue in degrees
:param int saturation: saturation in percentage [0,100]
:param int value: value in percentage [0, 100]
:param int transition: transition in milliseconds.
"""
@abstractmethod
async def set_color_temp(
self, temp: int, *, brightness: int | None = None, transition: int | None = None
) -> Annotated[dict, FeatureAttribute()]:
"""Set the color temperature of the device in kelvin.
Note, transition is not supported and will be ignored.
:param int temp: The new color temperature, in Kelvin
:param int transition: transition in milliseconds.
"""
@abstractmethod
async def set_brightness(
self, brightness: int, *, transition: int | None = None
) -> Annotated[dict, FeatureAttribute()]:
"""Set the brightness in percentage.
Note, transition is not supported and will be ignored.
:param int brightness: brightness in percent
:param int transition: transition in milliseconds.
"""
@property
@abstractmethod
def state(self) -> LightState:
"""Return the current light state."""
@abstractmethod
async def set_state(self, state: LightState) -> dict:
"""Set the light state."""
def _deprecated_valid_temperature_range(self) -> ColorTempRange:
if not (temp := self.get_feature("color_temp")):
raise KasaException("Color temperature not supported")
return ColorTempRange(temp.minimum_value, temp.maximum_value)
def _deprecated_attributes(self, dep_name: str) -> Callable | None:
map: dict[str, Callable] = {
"is_color": self.set_hsv,
"is_dimmable": self.set_brightness,
"is_variable_color_temp": self.set_color_temp,
}
return map.get(dep_name)
def __getattr__(self, name: str) -> Any:
if name == "valid_temperature_range":
res = self._deprecated_valid_temperature_range()
msg = (
"valid_temperature_range is deprecated, use "
"get_feature(self.set_color_temp) minimum_value "
" and maximum_value instead"
)
warn(msg, DeprecationWarning, stacklevel=2)
return res
if name == "has_effects":
msg = (
"has_effects is deprecated, use Module.LightEffect "
"in device.modules instead"
)
warn(msg, DeprecationWarning, stacklevel=2)
return Module.LightEffect in self._device.modules
if attr := self._deprecated_attributes(name):
msg = f"{name} is deprecated, use has_feature({attr}) instead"
warn(msg, DeprecationWarning, stacklevel=2)
return self.has_feature(attr)
raise AttributeError(f"Energy module has no attribute {name!r}")