8000 Bulbs: allow specifying transition for state changes by rytilahti · Pull Request #70 · python-kasa/python-kasa · GitHub
[go: up one dir, main page]

Skip to content

Bulbs: allow specifying transition for state changes #70

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 36 additions & 15 deletions kasa/smartbulb.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,11 @@ async def get_light_state(self) -> Dict[str, Dict]:
# 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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really part of this PR, but shouldn't we further specify what is in the output Dict?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That could be added, it'd be Dict[str, Union[str,Dict]] or Dict[str, Union[str,Dict[str,Union[str,int]]] (which is not very beautiful :-)

Here's an example what it returns:

{'on_off': 0, 'dft_on_state': {'mode': 'normal', 'hue': 0, 'saturation': 0, 'color_temp': 2000, 'brightness': 23}}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, that wouldn't be easy to read 😅

"""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
)
Expand Down Expand Up @@ -174,12 +177,15 @@ def _raise_for_invalid_brightness(self, value):
)

@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.")
Expand All @@ -203,7 +209,8 @@ async def set_hsv(self, hue: int, saturation: int, value: int):
"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
Expand All @@ -216,8 +223,12 @@ def color_temp(self) -> int:
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.")

Expand All @@ -229,7 +240,7 @@ async def set_color_temp(self, temp: int) -> None:
)

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
Expand All @@ -242, 8000 15 +253,19 @@ def brightness(self) -> int:
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
Expand All @@ -275,13 +290,19 @@ def is_on(self) -> bool:
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.

:param int transition: transition in milliseconds.
"""
return await self.set_light_state({"on_off": 0}, transition=transition)

async def turn_on(self) -> None:
"""Turn the bulb on."""
await self.set_light_state({"on_off": 1})
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
Expand Down
4 changes: 2 additions & 2 deletions kasa/smartdevice.py
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ async def reboot(self, delay=1) -> None:
"""
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.")

Expand All @@ -503,7 +503,7 @@ def is_off(self) -> bool:
"""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.")

Expand Down
39 changes: 39 additions & 0 deletions kasa/tests/test_bulb.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down
0