2024-05-10 18:29:28 +00:00
|
|
|
import pytest
|
|
|
|
from pytest_mock import MockerFixture
|
|
|
|
|
2024-05-19 10:20:18 +00:00
|
|
|
from kasa import Device, LightState, Module
|
2024-05-10 18:29:28 +00:00
|
|
|
from kasa.tests.device_fixtures import (
|
2024-05-19 10:20:18 +00:00
|
|
|
bulb_iot,
|
2024-05-22 13:33:55 +00:00
|
|
|
bulb_smart,
|
2024-05-13 16:34:44 +00:00
|
|
|
dimmable_iot,
|
|
|
|
dimmer_iot,
|
2024-06-10 04:59:37 +00:00
|
|
|
get_parent_and_child_modules,
|
2024-05-13 16:34:44 +00:00
|
|
|
lightstrip_iot,
|
2024-05-10 18:29:28 +00:00
|
|
|
parametrize,
|
|
|
|
parametrize_combine,
|
|
|
|
plug_iot,
|
2024-07-31 14:52:27 +00:00
|
|
|
variable_temp_iot,
|
2024-05-10 18:29:28 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
led_smart = parametrize(
|
|
|
|
"has led smart", component_filter="led", protocol_filter={"SMART"}
|
|
|
|
)
|
|
|
|
led = parametrize_combine([led_smart, plug_iot])
|
|
|
|
|
|
|
|
light_effect_smart = parametrize(
|
|
|
|
"has light effect smart", component_filter="light_effect", protocol_filter={"SMART"}
|
|
|
|
)
|
2024-05-15 05:16:57 +00:00
|
|
|
light_strip_effect_smart = parametrize(
|
|
|
|
"has light strip effect smart",
|
|
|
|
component_filter="light_strip_lighting_effect",
|
|
|
|
protocol_filter={"SMART"},
|
|
|
|
)
|
|
|
|
light_effect = parametrize_combine(
|
|
|
|
[light_effect_smart, light_strip_effect_smart, lightstrip_iot]
|
|
|
|
)
|
2024-05-13 16:34:44 +00:00
|
|
|
|
|
|
|
dimmable_smart = parametrize(
|
|
|
|
"dimmable smart", component_filter="brightness", protocol_filter={"SMART"}
|
|
|
|
)
|
|
|
|
dimmable = parametrize_combine([dimmable_smart, dimmer_iot, dimmable_iot])
|
2024-05-10 18:29:28 +00:00
|
|
|
|
2024-07-31 14:52:27 +00:00
|
|
|
variable_temp_smart = parametrize(
|
|
|
|
"variable temp smart",
|
|
|
|
component_filter="color_temperature",
|
|
|
|
protocol_filter={"SMART"},
|
|
|
|
)
|
|
|
|
|
|
|
|
variable_temp = parametrize_combine([variable_temp_iot, variable_temp_smart])
|
|
|
|
|
2024-05-19 10:20:18 +00:00
|
|
|
light_preset_smart = parametrize(
|
|
|
|
"has light preset smart", component_filter="preset", protocol_filter={"SMART"}
|
|
|
|
)
|
|
|
|
|
|
|
|
light_preset = parametrize_combine([light_preset_smart, bulb_iot])
|
|
|
|
|
2024-05-22 13:33:55 +00:00
|
|
|
light = parametrize_combine([bulb_smart, bulb_iot, dimmable])
|
|
|
|
|
2024-05-10 18:29:28 +00:00
|
|
|
|
|
|
|
@led
|
|
|
|
async def test_led_module(dev: Device, mocker: MockerFixture):
|
|
|
|
"""Test fan speed feature."""
|
|
|
|
led_module = dev.modules.get(Module.Led)
|
|
|
|
assert led_module
|
2024-05-13 16:34:44 +00:00
|
|
|
feat = dev.features["led"]
|
2024-05-10 18:29:28 +00:00
|
|
|
|
|
|
|
call = mocker.spy(led_module, "call")
|
|
|
|
await led_module.set_led(True)
|
|
|
|
assert call.call_count == 1
|
|
|
|
await dev.update()
|
|
|
|
assert led_module.led is True
|
|
|
|
assert feat.value is True
|
|
|
|
|
|
|
|
await led_module.set_led(False)
|
|
|
|
assert call.call_count == 2
|
|
|
|
await dev.update()
|
|
|
|
assert led_module.led is False
|
|
|
|
assert feat.value is False
|
|
|
|
|
|
|
|
await feat.set_value(True)
|
|
|
|
assert call.call_count == 3
|
|
|
|
await dev.update()
|
|
|
|
assert feat.value is True
|
|
|
|
assert led_module.led is True
|
|
|
|
|
|
|
|
|
|
|
|
@light_effect
|
|
|
|
async def test_light_effect_module(dev: Device, mocker: MockerFixture):
|
|
|
|
"""Test fan speed feature."""
|
|
|
|
light_effect_module = dev.modules[Module.LightEffect]
|
|
|
|
assert light_effect_module
|
2024-05-13 16:34:44 +00:00
|
|
|
feat = dev.features["light_effect"]
|
2024-05-10 18:29:28 +00:00
|
|
|
|
2024-06-07 10:29:26 +00:00
|
|
|
call = mocker.spy(dev, "_query_helper")
|
2024-05-10 18:29:28 +00:00
|
|
|
effect_list = light_effect_module.effect_list
|
|
|
|
assert "Off" in effect_list
|
|
|
|
assert effect_list.index("Off") == 0
|
|
|
|
assert len(effect_list) > 1
|
|
|
|
assert effect_list == feat.choices
|
|
|
|
|
|
|
|
assert light_effect_module.has_custom_effects is not None
|
|
|
|
|
|
|
|
await light_effect_module.set_effect("Off")
|
2024-07-01 11:59:24 +00:00
|
|
|
call.assert_called()
|
2024-05-10 18:29:28 +00:00
|
|
|
await dev.update()
|
|
|
|
assert light_effect_module.effect == "Off"
|
|
|
|
assert feat.value == "Off"
|
2024-07-01 11:59:24 +00:00
|
|
|
call.reset_mock()
|
2024-05-10 18:29:28 +00:00
|
|
|
|
|
|
|
second_effect = effect_list[1]
|
|
|
|
await light_effect_module.set_effect(second_effect)
|
2024-07-01 11:59:24 +00:00
|
|
|
call.assert_called()
|
2024-05-10 18:29:28 +00:00
|
|
|
await dev.update()
|
|
|
|
assert light_effect_module.effect == second_effect
|
|
|
|
assert feat.value == second_effect
|
2024-07-01 11:59:24 +00:00
|
|
|
call.reset_mock()
|
2024-05-10 18:29:28 +00:00
|
|
|
|
|
|
|
last_effect = effect_list[len(effect_list) - 1]
|
|
|
|
await light_effect_module.set_effect(last_effect)
|
2024-07-01 11:59:24 +00:00
|
|
|
call.assert_called()
|
2024-05-10 18:29:28 +00:00
|
|
|
await dev.update()
|
|
|
|
assert light_effect_module.effect == last_effect
|
|
|
|
assert feat.value == last_effect
|
2024-07-01 11:59:24 +00:00
|
|
|
call.reset_mock()
|
2024-05-10 18:29:28 +00:00
|
|
|
|
|
|
|
# Test feature set
|
|
|
|
await feat.set_value(second_effect)
|
2024-07-01 11:59:24 +00:00
|
|
|
call.assert_called()
|
2024-05-10 18:29:28 +00:00
|
|
|
await dev.update()
|
|
|
|
assert light_effect_module.effect == second_effect
|
|
|
|
assert feat.value == second_effect
|
2024-07-01 11:59:24 +00:00
|
|
|
call.reset_mock()
|
2024-05-10 18:29:28 +00:00
|
|
|
|
2024-08-30 15:30:07 +00:00
|
|
|
with pytest.raises(ValueError, match="The effect foobar is not a built in effect."):
|
2024-05-10 18:29:28 +00:00
|
|
|
await light_effect_module.set_effect("foobar")
|
2024-08-30 15:30:07 +00:00
|
|
|
call.assert_not_called()
|
2024-05-13 16:34:44 +00:00
|
|
|
|
|
|
|
|
2024-07-31 14:58:48 +00:00
|
|
|
@light_effect
|
|
|
|
async def test_light_effect_brightness(dev: Device, mocker: MockerFixture):
|
|
|
|
"""Test that light module uses light_effect for brightness when active."""
|
|
|
|
light_module = dev.modules[Module.Light]
|
|
|
|
|
|
|
|
light_effect = dev.modules[Module.LightEffect]
|
|
|
|
|
|
|
|
await light_effect.set_effect(light_effect.LIGHT_EFFECTS_OFF)
|
|
|
|
await light_module.set_brightness(50)
|
|
|
|
await dev.update()
|
|
|
|
assert light_effect.effect == light_effect.LIGHT_EFFECTS_OFF
|
|
|
|
assert light_module.brightness == 50
|
|
|
|
await light_effect.set_effect(light_effect.effect_list[1])
|
|
|
|
await dev.update()
|
|
|
|
# assert light_module.brightness == 100
|
|
|
|
|
|
|
|
await light_module.set_brightness(75)
|
|
|
|
await dev.update()
|
|
|
|
assert light_module.brightness == 75
|
|
|
|
|
|
|
|
await light_effect.set_effect(light_effect.LIGHT_EFFECTS_OFF)
|
|
|
|
await dev.update()
|
|
|
|
assert light_module.brightness == 50
|
|
|
|
|
|
|
|
|
2024-05-13 16:34:44 +00:00
|
|
|
@dimmable
|
|
|
|
async def test_light_brightness(dev: Device):
|
|
|
|
"""Test brightness setter and getter."""
|
|
|
|
assert isinstance(dev, Device)
|
2024-06-10 04:59:37 +00:00
|
|
|
light = next(get_parent_and_child_modules(dev, Module.Light))
|
2024-05-13 16:34:44 +00:00
|
|
|
assert light
|
|
|
|
|
|
|
|
# Test getting the value
|
2024-06-10 04:59:37 +00:00
|
|
|
feature = light._device.features["brightness"]
|
2024-05-13 16:34:44 +00:00
|
|
|
assert feature.minimum_value == 0
|
|
|
|
assert feature.maximum_value == 100
|
|
|
|
|
|
|
|
await light.set_brightness(10)
|
|
|
|
await dev.update()
|
|
|
|
assert light.brightness == 10
|
|
|
|
|
2024-08-30 15:30:07 +00:00
|
|
|
with pytest.raises(ValueError, match="Invalid brightness value: "):
|
2024-05-13 16:34:44 +00:00
|
|
|
await light.set_brightness(feature.minimum_value - 10)
|
|
|
|
|
2024-08-30 15:30:07 +00:00
|
|
|
with pytest.raises(ValueError, match="Invalid brightness value: "):
|
2024-05-13 16:34:44 +00:00
|
|
|
await light.set_brightness(feature.maximum_value + 10)
|
2024-05-19 10:20:18 +00:00
|
|
|
|
|
|
|
|
2024-07-31 14:52:27 +00:00
|
|
|
@variable_temp
|
|
|
|
async def test_light_color_temp(dev: Device):
|
|
|
|
"""Test color temp setter and getter."""
|
|
|
|
assert isinstance(dev, Device)
|
|
|
|
|
|
|
|
light = next(get_parent_and_child_modules(dev, Module.Light))
|
|
|
|
assert light
|
|
|
|
if not light.is_variable_color_temp:
|
|
|
|
pytest.skip(
|
|
|
|
"Some smart light strips have color_temperature"
|
|
|
|
" component but min and max are the same"
|
|
|
|
)
|
|
|
|
|
|
|
|
# Test getting the value
|
|
|
|
feature = light._device.features["color_temperature"]
|
|
|
|
assert isinstance(feature.minimum_value, int)
|
|
|
|
assert isinstance(feature.maximum_value, int)
|
|
|
|
|
|
|
|
await light.set_color_temp(feature.minimum_value + 10)
|
|
|
|
await dev.update()
|
|
|
|
assert light.color_temp == feature.minimum_value + 10
|
|
|
|
|
|
|
|
# Test setting brightness with color temp
|
|
|
|
await light.set_brightness(50)
|
|
|
|
await dev.update()
|
|
|
|
assert light.brightness == 50
|
|
|
|
|
|
|
|
await light.set_color_temp(feature.minimum_value + 20, brightness=60)
|
|
|
|
await dev.update()
|
|
|
|
assert light.color_temp == feature.minimum_value + 20
|
|
|
|
assert light.brightness == 60
|
|
|
|
|
2024-09-27 15:36:41 +00:00
|
|
|
with pytest.raises(ValueError, match=r"Temperature should be between \d+ and \d+"):
|
2024-07-31 14:52:27 +00:00
|
|
|
await light.set_color_temp(feature.minimum_value - 10)
|
|
|
|
|
2024-09-27 15:36:41 +00:00
|
|
|
with pytest.raises(ValueError, match=r"Temperature should be between \d+ and \d+"):
|
2024-07-31 14:52:27 +00:00
|
|
|
await light.set_color_temp(feature.maximum_value + 10)
|
|
|
|
|
|
|
|
|
2024-05-22 13:33:55 +00:00
|
|
|
@light
|
|
|
|
async def test_light_set_state(dev: Device):
|
|
|
|
"""Test brightness setter and getter."""
|
|
|
|
assert isinstance(dev, Device)
|
2024-06-10 04:59:37 +00:00
|
|
|
light = next(get_parent_and_child_modules(dev, Module.Light))
|
2024-05-22 13:33:55 +00:00
|
|
|
assert light
|
2024-07-31 14:58:48 +00:00
|
|
|
# For fixtures that have a light effect active switch off
|
|
|
|
if light_effect := light._device.modules.get(Module.LightEffect):
|
|
|
|
await light_effect.set_effect(light_effect.LIGHT_EFFECTS_OFF)
|
2024-05-22 13:33:55 +00:00
|
|
|
|
|
|
|
await light.set_state(LightState(light_on=False))
|
|
|
|
await dev.update()
|
|
|
|
assert light.state.light_on is False
|
|
|
|
|
|
|
|
await light.set_state(LightState(light_on=True))
|
|
|
|
await dev.update()
|
|
|
|
assert light.state.light_on is True
|
|
|
|
|
|
|
|
await light.set_state(LightState(brightness=0))
|
|
|
|
await dev.update()
|
|
|
|
assert light.state.light_on is False
|
|
|
|
|
|
|
|
await light.set_state(LightState(brightness=50))
|
|
|
|
await dev.update()
|
|
|
|
assert light.state.light_on is True
|
|
|
|
|
|
|
|
|
2024-05-19 10:20:18 +00:00
|
|
|
@light_preset
|
|
|
|
async def test_light_preset_module(dev: Device, mocker: MockerFixture):
|
|
|
|
"""Test light preset module."""
|
2024-06-10 04:59:37 +00:00
|
|
|
preset_mod = next(get_parent_and_child_modules(dev, Module.LightPreset))
|
2024-05-19 10:20:18 +00:00
|
|
|
assert preset_mod
|
2024-06-10 04:59:37 +00:00
|
|
|
light_mod = next(get_parent_and_child_modules(dev, Module.Light))
|
2024-05-19 10:20:18 +00:00
|
|
|
assert light_mod
|
2024-06-10 04:59:37 +00:00
|
|
|
feat = preset_mod._device.features["light_preset"]
|
2024-05-19 10:20:18 +00:00
|
|
|
|
|
|
|
preset_list = preset_mod.preset_list
|
|
|
|
assert "Not set" in preset_list
|
|
|
|
assert preset_list.index("Not set") == 0
|
|
|
|
assert preset_list == feat.choices
|
|
|
|
|
|
|
|
assert preset_mod.has_save_preset is True
|
|
|
|
|
|
|
|
await light_mod.set_brightness(33) # Value that should not be a preset
|
|
|
|
await dev.update()
|
|
|
|
assert preset_mod.preset == "Not set"
|
|
|
|
assert feat.value == "Not set"
|
|
|
|
|
|
|
|
if len(preset_list) == 1:
|
|
|
|
return
|
|
|
|
|
2024-05-22 13:33:55 +00:00
|
|
|
call = mocker.spy(light_mod, "set_state")
|
2024-05-19 10:20:18 +00:00
|
|
|
second_preset = preset_list[1]
|
|
|
|
await preset_mod.set_preset(second_preset)
|
|
|
|
assert call.call_count == 1
|
|
|
|
await dev.update()
|
|
|
|
assert preset_mod.preset == second_preset
|
|
|
|
assert feat.value == second_preset
|
|
|
|
|
|
|
|
last_preset = preset_list[len(preset_list) - 1]
|
|
|
|
await preset_mod.set_preset(last_preset)
|
|
|
|
assert call.call_count == 2
|
|
|
|
await dev.update()
|
|
|
|
assert preset_mod.preset == last_preset
|
|
|
|
assert feat.value == last_preset
|
|
|
|
|
|
|
|
# Test feature set
|
|
|
|
await feat.set_value(second_preset)
|
|
|
|
assert call.call_count == 3
|
|
|
|
await dev.update()
|
|
|
|
assert preset_mod.preset == second_preset
|
|
|
|
assert feat.value == second_preset
|
|
|
|
|
2024-08-30 15:30:07 +00:00
|
|
|
with pytest.raises(ValueError, match="foobar is not a valid preset"):
|
2024-05-19 10:20:18 +00:00
|
|
|
await preset_mod.set_preset("foobar")
|
2024-08-30 15:30:07 +00:00
|
|
|
assert call.call_count == 3
|
2024-05-19 10:20:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
@light_preset
|
|
|
|
async def test_light_preset_save(dev: Device, mocker: MockerFixture):
|
|
|
|
"""Test saving a new preset value."""
|
2024-06-10 04:59:37 +00:00
|
|
|
preset_mod = next(get_parent_and_child_modules(dev, Module.LightPreset))
|
2024-05-19 10:20:18 +00:00
|
|
|
assert preset_mod
|
|
|
|
preset_list = preset_mod.preset_list
|
|
|
|
if len(preset_list) == 1:
|
|
|
|
return
|
|
|
|
|
|
|
|
second_preset = preset_list[1]
|
|
|
|
if preset_mod.preset_states_list[0].hue is None:
|
|
|
|
new_preset = LightState(brightness=52)
|
|
|
|
else:
|
|
|
|
new_preset = LightState(brightness=52, color_temp=3000, hue=20, saturation=30)
|
|
|
|
await preset_mod.save_preset(second_preset, new_preset)
|
|
|
|
await dev.update()
|
|
|
|
new_preset_state = preset_mod.preset_states_list[0]
|
2024-08-30 15:30:07 +00:00
|
|
|
assert new_preset_state.brightness == new_preset.brightness
|
|
|
|
assert new_preset_state.hue == new_preset.hue
|
|
|
|
assert new_preset_state.saturation == new_preset.saturation
|
|
|
|
assert new_preset_state.color_temp == new_preset.color_temp
|