diff --git a/kasa/feature.py b/kasa/feature.py index 18bed554..ad709424 100644 --- a/kasa/feature.py +++ b/kasa/feature.py @@ -211,14 +211,14 @@ class Feature: """Range of values if applicable.""" return self._get_property_value(self.range_getter) - @cached_property + @property def maximum_value(self) -> int: """Maximum value.""" if range := self.range: return range[1] return self.DEFAULT_MAX - @cached_property + @property def minimum_value(self) -> int: """Minimum value.""" if range := self.range: diff --git a/kasa/iot/iotbulb.py b/kasa/iot/iotbulb.py index 26c73096..81d647e8 100644 --- a/kasa/iot/iotbulb.py +++ b/kasa/iot/iotbulb.py @@ -429,7 +429,7 @@ class IotBulb(IotDevice): if not self._is_variable_color_temp: raise KasaException("Bulb does not support colortemp.") - valid_temperature_range = self.valid_temperature_range + 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( diff --git a/kasa/smart/modules/colortemperature.py b/kasa/smart/modules/colortemperature.py index fa3b7412..920fa6d2 100644 --- a/kasa/smart/modules/colortemperature.py +++ b/kasa/smart/modules/colortemperature.py @@ -3,16 +3,11 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING from ...feature import Feature from ...interfaces.light import ColorTempRange from ..smartmodule import SmartModule -if TYPE_CHECKING: - from ..smartdevice import SmartDevice - - _LOGGER = logging.getLogger(__name__) DEFAULT_TEMP_RANGE = [2500, 6500] @@ -23,11 +18,11 @@ class ColorTemperature(SmartModule): REQUIRED_COMPONENT = "color_temperature" - def __init__(self, device: SmartDevice, module: str): - super().__init__(device, module) + def _initialize_features(self): + """Initialize features.""" self._add_feature( Feature( - device, + self._device, "color_temperature", "Color temperature", container=self, @@ -61,7 +56,7 @@ class ColorTemperature(SmartModule): """Return current color temperature.""" return self.data["color_temp"] - async def set_color_temp(self, temp: int): + async def set_color_temp(self, temp: int, *, brightness=None): """Set the color temperature.""" valid_temperature_range = self.valid_temperature_range if temp < valid_temperature_range[0] or temp > valid_temperature_range[1]: @@ -70,8 +65,10 @@ class ColorTemperature(SmartModule): *valid_temperature_range, temp ) ) - - return await self.call("set_device_info", {"color_temp": temp}) + params = {"color_temp": temp} + if brightness: + params["brightness"] = brightness + return await self.call("set_device_info", params) async def _check_supported(self) -> bool: """Check the color_temp_range has more than one value.""" diff --git a/kasa/smart/modules/light.py b/kasa/smart/modules/light.py index 0a255bb2..8e0a37d8 100644 --- a/kasa/smart/modules/light.py +++ b/kasa/smart/modules/light.py @@ -107,7 +107,9 @@ class Light(SmartModule, LightInterface): """ if not self.is_variable_color_temp: raise KasaException("Bulb does not support colortemp.") - return await self._device.modules[Module.ColorTemperature].set_color_temp(temp) + return await self._device.modules[Module.ColorTemperature].set_color_temp( + temp, brightness=brightness + ) async def set_brightness( self, brightness: int, *, transition: int | None = None diff --git a/kasa/tests/test_common_modules.py b/kasa/tests/test_common_modules.py index beed8e8b..114615d4 100644 --- a/kasa/tests/test_common_modules.py +++ b/kasa/tests/test_common_modules.py @@ -12,6 +12,7 @@ from kasa.tests.device_fixtures import ( parametrize, parametrize_combine, plug_iot, + variable_temp_iot, ) led_smart = parametrize( @@ -36,6 +37,14 @@ dimmable_smart = parametrize( ) dimmable = parametrize_combine([dimmable_smart, dimmer_iot, dimmable_iot]) +variable_temp_smart = parametrize( + "variable temp smart", + component_filter="color_temperature", + protocol_filter={"SMART"}, +) + +variable_temp = parametrize_combine([variable_temp_iot, variable_temp_smart]) + light_preset_smart = parametrize( "has light preset smart", component_filter="preset", protocol_filter={"SMART"} ) @@ -147,6 +156,45 @@ async def test_light_brightness(dev: Device): await light.set_brightness(feature.maximum_value + 10) +@variable_temp +async def test_light_color_temp(dev: Device): + """Test color temp setter and getter.""" + assert isinstance(dev, Device) + + light = next(get_parent_and_child_modules(dev, Module.Light)) + assert light + if not light.is_variable_color_temp: + pytest.skip( + "Some smart light strips have color_temperature" + " component but min and max are the same" + ) + + # Test getting the value + feature = light._device.features["color_temperature"] + assert isinstance(feature.minimum_value, int) + assert isinstance(feature.maximum_value, int) + + await light.set_color_temp(feature.minimum_value + 10) + await dev.update() + assert light.color_temp == feature.minimum_value + 10 + + # Test setting brightness with color temp + await light.set_brightness(50) + await dev.update() + assert light.brightness == 50 + + await light.set_color_temp(feature.minimum_value + 20, brightness=60) + await dev.update() + assert light.color_temp == feature.minimum_value + 20 + assert light.brightness == 60 + + with pytest.raises(ValueError): + await light.set_color_temp(feature.minimum_value - 10) + + with pytest.raises(ValueError): + await light.set_color_temp(feature.maximum_value + 10) + + @light async def test_light_set_state(dev: Device): """Test brightness setter and getter."""