Bulbs: allow specifying transition for state changes (#70)

All state changing functions now allow defining transition time in milliseconds
This commit is contained in:
Teemu R 2020-06-14 20:21:55 +02:00 committed by GitHub
parent dd073fa8c8
commit 99e0c4a418
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 77 additions and 17 deletions

View File

@ -142,8 +142,11 @@ class SmartBulb(SmartDevice):
# TODO: add warning and refer to use light.state? # TODO: add warning and refer to use light.state?
return await self._query_helper(self.LIGHT_SERVICE, "get_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.""" """Set the light state."""
if transition is not None:
state["transition_period"] = transition
light_state = await self._query_helper( light_state = await self._query_helper(
self.LIGHT_SERVICE, "transition_light_state", state self.LIGHT_SERVICE, "transition_light_state", state
) )
@ -174,12 +177,15 @@ class SmartBulb(SmartDevice):
) )
@requires_update @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. """Set new HSV.
:param int hue: hue in degrees :param int hue: hue in degrees
:param int saturation: saturation in percentage [0,100] :param int saturation: saturation in percentage [0,100]
:param int value: value in percentage [0, 100] :param int value: value in percentage [0, 100]
:param int transition: transition in milliseconds.
""" """
if not self.is_color: if not self.is_color:
raise SmartDeviceException("Bulb does not support color.") raise SmartDeviceException("Bulb does not support color.")
@ -203,7 +209,8 @@ class SmartBulb(SmartDevice):
"brightness": value, "brightness": value,
"color_temp": 0, "color_temp": 0,
} }
await self.set_light_state(light_state)
return await self.set_light_state(light_state, transition=transition)
@property # type: ignore @property # type: ignore
@requires_update @requires_update
@ -216,8 +223,12 @@ class SmartBulb(SmartDevice):
return int(light_state["color_temp"]) return int(light_state["color_temp"])
@requires_update @requires_update
async def set_color_temp(self, temp: int) -> None: async def set_color_temp(self, temp: int, *, transition: int = None) -> Dict:
"""Set the color temperature of the device in kelvin.""" """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: if not self.is_variable_color_temp:
raise SmartDeviceException("Bulb does not support colortemp.") raise SmartDeviceException("Bulb does not support colortemp.")
@ -229,7 +240,7 @@ class SmartBulb(SmartDevice):
) )
light_state = {"color_temp": temp} 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 @property # type: ignore
@requires_update @requires_update
@ -242,15 +253,19 @@ class SmartBulb(SmartDevice):
return int(light_state["brightness"]) return int(light_state["brightness"])
@requires_update @requires_update
async def set_brightness(self, brightness: int) -> None: async def set_brightness(self, brightness: int, *, transition: int = None) -> Dict:
"""Set the brightness in percentage.""" """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 if not self.is_dimmable: # pragma: no cover
raise SmartDeviceException("Bulb is not dimmable.") raise SmartDeviceException("Bulb is not dimmable.")
self._raise_for_invalid_brightness(brightness) self._raise_for_invalid_brightness(brightness)
light_state = {"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 @property # type: ignore
@requires_update @requires_update
@ -275,13 +290,19 @@ class SmartBulb(SmartDevice):
light_state = self.light_state light_state = self.light_state
return bool(light_state["on_off"]) return bool(light_state["on_off"])
async def turn_off(self) -> None: async def turn_off(self, *, transition: int = None) -> Dict:
"""Turn the bulb off.""" """Turn the bulb off.
await self.set_light_state({"on_off": 0})
async def turn_on(self) -> None: :param int transition: transition in milliseconds.
"""Turn the bulb on.""" """
await self.set_light_state({"on_off": 1}) 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 @property # type: ignore
@requires_update @requires_update

View File

@ -493,7 +493,7 @@ class SmartDevice:
""" """
await self._query_helper("system", "reboot", {"delay": delay}) await self._query_helper("system", "reboot", {"delay": delay})
async def turn_off(self) -> None: async def turn_off(self) -> Dict:
"""Turn off the device.""" """Turn off the device."""
raise NotImplementedError("Device subclass needs to implement this.") raise NotImplementedError("Device subclass needs to implement this.")
@ -503,7 +503,7 @@ class SmartDevice:
"""Return True if device is off.""" """Return True if device is off."""
return not self.is_on return not self.is_on
async def turn_on(self) -> None: async def turn_on(self) -> Dict:
"""Turn device on.""" """Turn device on."""
raise NotImplementedError("Device subclass needs to implement this.") raise NotImplementedError("Device subclass needs to implement this.")

View File

@ -69,6 +69,17 @@ async def test_hsv(dev, turn_on):
assert brightness == 1 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 @color_bulb
@turn_on @turn_on
async def test_invalid_hsv(dev, 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 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 @variable_temp
async def test_unknown_temp_range(dev, monkeypatch): async def test_unknown_temp_range(dev, monkeypatch):
with pytest.raises(SmartDeviceException): with pytest.raises(SmartDeviceException):
@ -166,6 +185,26 @@ async def test_dimmable_brightness(dev, turn_on):
await dev.set_brightness("foo") 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 @dimmable
async def test_invalid_brightness(dev): async def test_invalid_brightness(dev):
assert dev.is_dimmable assert dev.is_dimmable