mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-08-06 10:44:04 +00:00
Add LightEffectModule for dynamic light effects on SMART bulbs (#887)
Support the `light_effect` module which allows setting the effect to Off or Party or Relax. Uses the new `Feature.Type.Choice`. Does not currently allow editing of effects.
This commit is contained in:
@@ -176,6 +176,19 @@ class FakeSmartTransport(BaseTransport):
|
||||
"Method %s not implemented for children" % child_method
|
||||
)
|
||||
|
||||
def _set_light_effect(self, info, params):
|
||||
"""Set or remove values as per the device behaviour."""
|
||||
info["get_device_info"]["dynamic_light_effect_enable"] = params["enable"]
|
||||
info["get_dynamic_light_effect_rules"]["enable"] = params["enable"]
|
||||
if params["enable"]:
|
||||
info["get_device_info"]["dynamic_light_effect_id"] = params["id"]
|
||||
info["get_dynamic_light_effect_rules"]["current_rule_id"] = params["enable"]
|
||||
else:
|
||||
if "dynamic_light_effect_id" in info["get_device_info"]:
|
||||
del info["get_device_info"]["dynamic_light_effect_id"]
|
||||
if "current_rule_id" in info["get_dynamic_light_effect_rules"]:
|
||||
del info["get_dynamic_light_effect_rules"]["current_rule_id"]
|
||||
|
||||
def _send_request(self, request_dict: dict):
|
||||
method = request_dict["method"]
|
||||
params = request_dict["params"]
|
||||
@@ -223,6 +236,9 @@ class FakeSmartTransport(BaseTransport):
|
||||
return retval
|
||||
elif method == "set_qs_info":
|
||||
return {"error_code": 0}
|
||||
elif method == "set_dynamic_light_effect_rule_enable":
|
||||
self._set_light_effect(info, params)
|
||||
return {"error_code": 0}
|
||||
elif method[:4] == "set_":
|
||||
target_method = f"get_{method[4:]}"
|
||||
info[target_method].update(params)
|
||||
|
42
kasa/tests/smart/modules/test_light_effect.py
Normal file
42
kasa/tests/smart/modules/test_light_effect.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from itertools import chain
|
||||
from typing import cast
|
||||
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from kasa import Device, Feature
|
||||
from kasa.smart.modules import LightEffectModule
|
||||
from kasa.tests.device_fixtures import parametrize
|
||||
|
||||
light_effect = parametrize(
|
||||
"has light effect", component_filter="light_effect", protocol_filter={"SMART"}
|
||||
)
|
||||
|
||||
|
||||
@light_effect
|
||||
async def test_light_effect(dev: Device, mocker: MockerFixture):
|
||||
"""Test light effect."""
|
||||
light_effect = cast(LightEffectModule, dev.modules.get("LightEffectModule"))
|
||||
assert light_effect
|
||||
|
||||
feature = light_effect._module_features["light_effect"]
|
||||
assert feature.type == Feature.Type.Choice
|
||||
|
||||
call = mocker.spy(light_effect, "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)
|
||||
enable = effect != LightEffectModule.LIGHT_EFFECTS_OFF
|
||||
params: dict[str, bool | str] = {"enable": enable}
|
||||
if enable:
|
||||
params["id"] = light_effect._scenes_names_to_id[effect]
|
||||
call.assert_called_with("set_dynamic_light_effect_rule_enable", params)
|
||||
await dev.update()
|
||||
assert light_effect.effect == effect
|
||||
assert feature.value == effect
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await light_effect.set_effect("foobar")
|
@@ -689,6 +689,17 @@ async def test_feature(mocker, runner):
|
||||
assert res.exit_code == 0
|
||||
|
||||
|
||||
async def test_features_all(discovery_mock, mocker, runner):
|
||||
"""Test feature command on all fixtures."""
|
||||
res = await runner.invoke(
|
||||
cli,
|
||||
["--host", "127.0.0.123", "--debug", "feature"],
|
||||
catch_exceptions=False,
|
||||
)
|
||||
assert "== Features ==" in res.output
|
||||
assert res.exit_code == 0
|
||||
|
||||
|
||||
async def test_feature_single(mocker, runner):
|
||||
"""Test feature command returning single value."""
|
||||
dummy_device = await get_device_for_fixture_protocol(
|
||||
@@ -736,7 +747,7 @@ async def test_feature_set(mocker, runner):
|
||||
)
|
||||
|
||||
led_setter.assert_called_with(True)
|
||||
assert "Setting led to True" in res.output
|
||||
assert "Changing led from False to True" in res.output
|
||||
assert res.exit_code == 0
|
||||
|
||||
|
||||
@@ -762,14 +773,14 @@ async def test_feature_set_child(mocker, runner):
|
||||
"--child",
|
||||
child_id,
|
||||
"state",
|
||||
"False",
|
||||
"True",
|
||||
],
|
||||
catch_exceptions=False,
|
||||
)
|
||||
|
||||
get_child_device.assert_called()
|
||||
setter.assert_called_with(False)
|
||||
setter.assert_called_with(True)
|
||||
|
||||
assert f"Targeting child device {child_id}"
|
||||
assert "Setting state to False" in res.output
|
||||
assert "Changing state from False to True" in res.output
|
||||
assert res.exit_code == 0
|
||||
|
Reference in New Issue
Block a user