diff --git a/kasa/feature.py b/kasa/feature.py index df28c952..c42debc7 100644 --- a/kasa/feature.py +++ b/kasa/feature.py @@ -41,6 +41,17 @@ class Feature: minimum_value: int = 0 #: Maximum value maximum_value: int = 2**16 # Arbitrary max + #: Attribute containing the name of the range getter property. + #: If set, this property will be used to set *minimum_value* and *maximum_value*. + range_getter: Optional[str] = None + + def __post_init__(self): + """Handle late-binding of members.""" + container = self.container if self.container is not None else self.device + if self.range_getter is not None: + self.minimum_value, self.maximum_value = getattr( + container, self.range_getter + ) @property def value(self): diff --git a/kasa/smart/modules/__init__.py b/kasa/smart/modules/__init__.py index dc4e0cf5..9d1af1c8 100644 --- a/kasa/smart/modules/__init__.py +++ b/kasa/smart/modules/__init__.py @@ -5,6 +5,7 @@ from .battery import BatterySensor from .brightness import Brightness from .childdevicemodule import ChildDeviceModule from .cloudmodule import CloudModule +from .colortemp import ColorTemperatureModule from .devicemodule import DeviceModule from .energymodule import EnergyModule from .firmware import Firmware @@ -31,4 +32,5 @@ __all__ = [ "Firmware", "CloudModule", "LightTransitionModule", + "ColorTemperatureModule", ] diff --git a/kasa/smart/modules/colortemp.py b/kasa/smart/modules/colortemp.py new file mode 100644 index 00000000..97388b8d --- /dev/null +++ b/kasa/smart/modules/colortemp.py @@ -0,0 +1,55 @@ +"""Implementation of color temp module.""" +from typing import TYPE_CHECKING, Dict + +from ...bulb import ColorTempRange +from ...feature import Feature +from ..smartmodule import SmartModule + +if TYPE_CHECKING: + from ..smartdevice import SmartDevice + + +class ColorTemperatureModule(SmartModule): + """Implementation of color temp module.""" + + REQUIRED_COMPONENT = "color_temperature" + + def __init__(self, device: "SmartDevice", module: str): + super().__init__(device, module) + self._add_feature( + Feature( + device, + "Color temperature", + container=self, + attribute_getter="color_temp", + attribute_setter="set_color_temp", + range_getter="valid_temperature_range", + ) + ) + + def query(self) -> Dict: + """Query to execute during the update cycle.""" + # Color temp is contained in the main device info response. + return {} + + @property + def valid_temperature_range(self) -> ColorTempRange: + """Return valid color-temp range.""" + return ColorTempRange(*self.data.get("color_temp_range")) + + @property + def color_temp(self): + """Return current color temperature.""" + return self.data["color_temp"] + + async def set_color_temp(self, temp: int): + """Set the color temperature.""" + valid_temperature_range = self.valid_temperature_range + if temp < valid_temperature_range[0] or temp > valid_temperature_range[1]: + raise ValueError( + "Temperature should be between {} and {}, was {}".format( + *valid_temperature_range, temp + ) + ) + + return await self.call("set_device_info", {"color_temp": temp}) diff --git a/kasa/tests/smart/features/__init__.py b/kasa/tests/smart/features/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/kasa/tests/test_feature_brightness.py b/kasa/tests/smart/features/test_brightness.py similarity index 100% rename from kasa/tests/test_feature_brightness.py rename to kasa/tests/smart/features/test_brightness.py diff --git a/kasa/tests/smart/features/test_colortemp.py b/kasa/tests/smart/features/test_colortemp.py new file mode 100644 index 00000000..8c899d6d --- /dev/null +++ b/kasa/tests/smart/features/test_colortemp.py @@ -0,0 +1,31 @@ +import pytest + +from kasa.smart import SmartDevice +from kasa.tests.conftest import parametrize + +brightness = parametrize("colortemp smart", component_filter="color_temperature") + + +@brightness +async def test_colortemp_component(dev: SmartDevice): + """Test brightness feature.""" + assert isinstance(dev, SmartDevice) + assert "color_temperature" in dev._components + + # Test getting the value + feature = dev.features["color_temperature"] + assert isinstance(feature.value, int) + assert isinstance(feature.minimum_value, int) + assert isinstance(feature.maximum_value, int) + + # Test setting the value + # We need to take the min here, as L9xx reports a range [9000, 9000]. + new_value = min(feature.minimum_value + 1, feature.maximum_value) + await feature.set_value(new_value) + assert feature.value == new_value + + with pytest.raises(ValueError): + await feature.set_value(feature.minimum_value - 10) + + with pytest.raises(ValueError): + await feature.set_value(feature.maximum_value + 10)