Fix changing brightness when effect is active (#1019)

This PR changes the behavior of `brightness` module if an effect is
active.
Currently, changing the brightness disables the effect when the
brightness is changed, this fixes that.
This will also improve the `set_effect` interface to use the current
brightness when an effect is activated.

* light_strip_effect: passing `bAdjusted` with the changed properties
changes the brightness.
* light_effect: the brightness is stored only in the rule, so we modify
it when adjusting the brightness. This is also done during the initial
effect activation.

---------

Co-authored-by: Steven B <51370195+sdb9696@users.noreply.github.com>
This commit is contained in:
Teemu R
2024-07-01 13:59:24 +02:00
committed by GitHub
parent 2687c71c4b
commit b31a2ede7f
10 changed files with 321 additions and 21 deletions

View File

@@ -39,3 +39,45 @@ async def test_light_effect(dev: Device, mocker: MockerFixture):
with pytest.raises(ValueError):
await light_effect.set_effect("foobar")
@light_effect
@pytest.mark.parametrize("effect_active", [True, False])
async def test_light_effect_brightness(
dev: Device, effect_active: bool, mocker: MockerFixture
):
"""Test that light module uses light_effect for brightness when active."""
light_module = dev.modules[Module.Light]
light_effect = dev.modules[Module.SmartLightEffect]
light_effect_set_brightness = mocker.spy(light_effect, "set_brightness")
mock_light_effect_call = mocker.patch.object(light_effect, "call")
brightness = dev.modules[Module.Brightness]
brightness_set_brightness = mocker.spy(brightness, "set_brightness")
mock_brightness_call = mocker.patch.object(brightness, "call")
mocker.patch.object(
type(light_effect),
"is_active",
new_callable=mocker.PropertyMock,
return_value=effect_active,
)
if effect_active: # Set the rule L1 active for testing
light_effect.data["current_rule_id"] = "L1"
await light_module.set_brightness(10)
if effect_active:
assert light_effect.is_active
assert light_effect.brightness == dev.brightness
light_effect_set_brightness.assert_called_with(10)
mock_light_effect_call.assert_called_with(
"edit_dynamic_light_effect_rule", mocker.ANY
)
else:
assert not light_effect.is_active
brightness_set_brightness.assert_called_with(10)
mock_brightness_call.assert_called_with("set_device_info", {"brightness": 10})

View File

@@ -0,0 +1,101 @@
from __future__ import annotations
from itertools import chain
import pytest
from pytest_mock import MockerFixture
from kasa import Device, Feature, Module
from kasa.smart.modules import LightEffect, LightStripEffect
from kasa.tests.device_fixtures import parametrize
light_strip_effect = parametrize(
"has light strip effect",
component_filter="light_strip_lighting_effect",
protocol_filter={"SMART"},
)
@light_strip_effect
async def test_light_strip_effect(dev: Device, mocker: MockerFixture):
"""Test light strip effect."""
light_effect = dev.modules.get(Module.LightEffect)
assert isinstance(light_effect, LightStripEffect)
brightness = dev.modules[Module.Brightness]
feature = dev.features["light_effect"]
assert feature.type == Feature.Type.Choice
call = mocker.spy(light_effect, "call")
light = dev.modules[Module.Light]
light_call = mocker.spy(light, "call")
assert feature.choices == light_effect.effect_list
assert feature.choices
for effect in chain(reversed(feature.choices), feature.choices):
await light_effect.set_effect(effect)
if effect == LightEffect.LIGHT_EFFECTS_OFF:
light_call.assert_called()
continue
# Start with the current effect data
params = light_effect.data["lighting_effect"]
enable = effect != LightEffect.LIGHT_EFFECTS_OFF
params["enable"] = enable
if enable:
params = light_effect._effect_mapping[effect]
params["enable"] = enable
params["brightness"] = brightness.brightness # use the existing brightness
call.assert_called_with("set_lighting_effect", params)
await dev.update()
assert light_effect.effect == effect
assert feature.value == effect
with pytest.raises(ValueError):
await light_effect.set_effect("foobar")
@light_strip_effect
@pytest.mark.parametrize("effect_active", [True, False])
async def test_light_effect_brightness(
dev: Device, effect_active: bool, mocker: MockerFixture
):
"""Test that light module uses light_effect for brightness when active."""
light_module = dev.modules[Module.Light]
light_effect = dev.modules[Module.SmartLightEffect]
light_effect_set_brightness = mocker.spy(light_effect, "set_brightness")
mock_light_effect_call = mocker.patch.object(light_effect, "call")
brightness = dev.modules[Module.Brightness]
brightness_set_brightness = mocker.spy(brightness, "set_brightness")
mock_brightness_call = mocker.patch.object(brightness, "call")
mocker.patch.object(
type(light_effect),
"is_active",
new_callable=mocker.PropertyMock,
return_value=effect_active,
)
await light_module.set_brightness(10)
if effect_active:
assert light_effect.is_active
assert light_effect.brightness == dev.brightness
light_effect_set_brightness.assert_called_with(10)
mock_light_effect_call.assert_called_with(
"set_lighting_effect", {"brightness": 10, "bAdjusted": True}
)
else:
assert not light_effect.is_active
brightness_set_brightness.assert_called_with(10)
mock_brightness_call.assert_called_with("set_device_info", {"brightness": 10})