diff --git a/kasa/smartbulb.py b/kasa/smartbulb.py index e6d5d552..a06dcf11 100644 --- a/kasa/smartbulb.py +++ b/kasa/smartbulb.py @@ -142,8 +142,11 @@ class SmartBulb(SmartDevice): # TODO: add warning and refer to use light.state? return await self._query_helper(self.LIGHT_SERVICE, "get_light_state") - async def set_light_state(self, state: Dict) -> Dict: + async def set_light_state(self, state: Dict, *, transition: int = None) -> Dict: """Set the light state.""" + if transition is not None: + state["transition_period"] = transition + light_state = await self._query_helper( self.LIGHT_SERVICE, "transition_light_state", state ) @@ -174,12 +177,15 @@ class SmartBulb(SmartDevice): ) @requires_update - async def set_hsv(self, hue: int, saturation: int, value: int): + async def set_hsv( + self, hue: int, saturation: int, value: int, *, transition: int = None + ) -> Dict: """Set new HSV. :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. """ if not self.is_color: raise SmartDeviceException("Bulb does not support color.") @@ -203,7 +209,8 @@ class SmartBulb(SmartDevice): "brightness": value, "color_temp": 0, } - await self.set_light_state(light_state) + + return await self.set_light_state(light_state, transition=transition) @property # type: ignore @requires_update @@ -216,8 +223,12 @@ class SmartBulb(SmartDevice): return int(light_state["color_temp"]) @requires_update - async def set_color_temp(self, temp: int) -> None: - """Set the color temperature of the device in kelvin.""" + async def set_color_temp(self, temp: int, *, transition: int = None) -> Dict: + """Set the color temperature of the device in kelvin. + + :param int temp: The new color temperature, in Kelvin + :param int transition: transition in milliseconds. + """ if not self.is_variable_color_temp: raise SmartDeviceException("Bulb does not support colortemp.") @@ -229,7 +240,7 @@ class SmartBulb(SmartDevice): ) light_state = {"color_temp": temp} - await self.set_light_state(light_state) + return await self.set_light_state(light_state, transition=transition) @property # type: ignore @requires_update @@ -242,15 +253,19 @@ class SmartBulb(SmartDevice): return int(light_state["brightness"]) @requires_update - async def set_brightness(self, brightness: int) -> None: - """Set the brightness in percentage.""" + async def set_brightness(self, brightness: int, *, transition: int = None) -> Dict: + """Set the brightness in percentage. + + :param int brightness: brightness in percent + :param int transition: transition in milliseconds. + """ if not self.is_dimmable: # pragma: no cover raise SmartDeviceException("Bulb is not dimmable.") self._raise_for_invalid_brightness(brightness) light_state = {"brightness": brightness} - await self.set_light_state(light_state) + return await self.set_light_state(light_state, transition=transition) @property # type: ignore @requires_update @@ -275,13 +290,19 @@ class SmartBulb(SmartDevice): light_state = self.light_state return bool(light_state["on_off"]) - async def turn_off(self) -> None: - """Turn the bulb off.""" - await self.set_light_state({"on_off": 0}) + async def turn_off(self, *, transition: int = None) -> Dict: + """Turn the bulb off. - async def turn_on(self) -> None: - """Turn the bulb on.""" - await self.set_light_state({"on_off": 1}) + :param int transition: transition in milliseconds. + """ + return await self.set_light_state({"on_off": 0}, transition=transition) + + async def turn_on(self, *, transition: int = None) -> Dict: + """Turn the bulb on. + + :param int transition: transition in milliseconds. + """ + return await self.set_light_state({"on_off": 1}, transition=transition) @property # type: ignore @requires_update diff --git a/kasa/smartdevice.py b/kasa/smartdevice.py index cd2e8f5f..0ed263b4 100755 --- a/kasa/smartdevice.py +++ b/kasa/smartdevice.py @@ -493,7 +493,7 @@ class SmartDevice: """ await self._query_helper("system", "reboot", {"delay": delay}) - async def turn_off(self) -> None: + async def turn_off(self) -> Dict: """Turn off the device.""" raise NotImplementedError("Device subclass needs to implement this.") @@ -503,7 +503,7 @@ class SmartDevice: """Return True if device is off.""" return not self.is_on - async def turn_on(self) -> None: + async def turn_on(self) -> Dict: """Turn device on.""" raise NotImplementedError("Device subclass needs to implement this.") diff --git a/kasa/tests/test_bulb.py b/kasa/tests/test_bulb.py index c3365d9f..b84558b1 100644 --- a/kasa/tests/test_bulb.py +++ b/kasa/tests/test_bulb.py @@ -69,6 +69,17 @@ async def test_hsv(dev, turn_on): assert brightness == 1 +@color_bulb +async def test_set_hsv_transition(dev, mocker): + set_light_state = mocker.patch("kasa.SmartBulb.set_light_state") + await dev.set_hsv(10, 10, 100, transition=1000) + + set_light_state.assert_called_with( + {"hue": 10, "saturation": 10, "brightness": 100, "color_temp": 0}, + transition=1000, + ) + + @color_bulb @turn_on async def test_invalid_hsv(dev, turn_on): @@ -123,6 +134,14 @@ async def test_try_set_colortemp(dev, turn_on): assert dev.color_temp == 2700 +@variable_temp +async def test_set_color_temp_transition(dev, mocker): + set_light_state = mocker.patch("kasa.SmartBulb.set_light_state") + await dev.set_color_temp(2700, transition=100) + + set_light_state.assert_called_with({"color_temp": 2700}, transition=100) + + @variable_temp async def test_unknown_temp_range(dev, monkeypatch): with pytest.raises(SmartDeviceException): @@ -166,6 +185,26 @@ async def test_dimmable_brightness(dev, turn_on): await dev.set_brightness("foo") +@bulb +async def test_turn_on_transition(dev, mocker): + set_light_state = mocker.patch("kasa.SmartBulb.set_light_state") + await dev.turn_on(transition=1000) + + set_light_state.assert_called_with({"on_off": 1}, transition=1000) + + await dev.turn_off(transition=100) + + set_light_state.assert_called_with({"on_off": 0}, transition=100) + + +@bulb +async def test_dimmable_brightness_transition(dev, mocker): + set_light_state = mocker.patch("kasa.SmartBulb.set_light_state") + await dev.set_brightness(10, transition=1000) + + set_light_state.assert_called_with({"brightness": 10}, transition=1000) + + @dimmable async def test_invalid_brightness(dev): assert dev.is_dimmable