mirror of
https://github.com/python-kasa/python-kasa.git
synced 2024-12-22 19:23:34 +00:00
Add LightEffect module for smart light strips (#918)
Implements the `light_strip_lighting_effect` components for `smart` devices. Uses a new list of effects captured from a L900 which are similar to the `iot` effects but include some additional properties and a few extra effects. Assumes that a device only implements `light_strip_lighting_effect` or `light_effect` but not both.
This commit is contained in:
parent
67b5d7de83
commit
133a839f22
@ -911,11 +911,12 @@ async def effect(dev: Device, ctx, effect):
|
|||||||
echo("Device does not support effects")
|
echo("Device does not support effects")
|
||||||
return
|
return
|
||||||
if effect is None:
|
if effect is None:
|
||||||
raise click.BadArgumentUsage(
|
echo(
|
||||||
"Setting an effect requires a named built-in effect: "
|
f"Light effect: {light_effect.effect}\n"
|
||||||
+ f"{light_effect.effect_list}",
|
+ f"Available Effects: {light_effect.effect_list}"
|
||||||
ctx,
|
|
||||||
)
|
)
|
||||||
|
return light_effect.effect
|
||||||
|
|
||||||
if effect not in light_effect.effect_list:
|
if effect not in light_effect.effect_list:
|
||||||
raise click.BadArgumentUsage(
|
raise click.BadArgumentUsage(
|
||||||
f"Effect must be one of: {light_effect.effect_list}", ctx
|
f"Effect must be one of: {light_effect.effect_list}", ctx
|
||||||
|
@ -21,13 +21,11 @@ class LightEffect(IotModule, LightEffectInterface):
|
|||||||
'id': '',
|
'id': '',
|
||||||
'name': ''}
|
'name': ''}
|
||||||
"""
|
"""
|
||||||
if (
|
eff = self.data["lighting_effect_state"]
|
||||||
(state := self.data.get("lighting_effect_state"))
|
name = eff["name"]
|
||||||
and state.get("enable")
|
if eff["enable"]:
|
||||||
and (name := state.get("name"))
|
|
||||||
and name in EFFECT_NAMES_V1
|
|
||||||
):
|
|
||||||
return name
|
return name
|
||||||
|
|
||||||
return self.LIGHT_EFFECTS_OFF
|
return self.LIGHT_EFFECTS_OFF
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -67,6 +65,7 @@ class LightEffect(IotModule, LightEffectInterface):
|
|||||||
raise ValueError(f"The effect {effect} is not a built in effect.")
|
raise ValueError(f"The effect {effect} is not a built in effect.")
|
||||||
else:
|
else:
|
||||||
effect_dict = EFFECT_MAPPING_V1[effect]
|
effect_dict = EFFECT_MAPPING_V1[effect]
|
||||||
|
|
||||||
if brightness is not None:
|
if brightness is not None:
|
||||||
effect_dict["brightness"] = brightness
|
effect_dict["brightness"] = brightness
|
||||||
if transition is not None:
|
if transition is not None:
|
||||||
|
429
kasa/smart/effects.py
Normal file
429
kasa/smart/effects.py
Normal file
@ -0,0 +1,429 @@
|
|||||||
|
"""Module for light strip light effects."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
|
EFFECT_AURORA = {
|
||||||
|
"custom": 0,
|
||||||
|
"id": "TapoStrip_1MClvV18i15Jq3bvJVf0eP",
|
||||||
|
"brightness": 100,
|
||||||
|
"name": "Aurora",
|
||||||
|
"enable": 1,
|
||||||
|
"segments": [0],
|
||||||
|
"expansion_strategy": 1,
|
||||||
|
"display_colors": [
|
||||||
|
[120, 100, 100],
|
||||||
|
[240, 100, 100],
|
||||||
|
[260, 100, 100],
|
||||||
|
[280, 100, 100],
|
||||||
|
],
|
||||||
|
"type": "sequence",
|
||||||
|
"duration": 0,
|
||||||
|
"transition": 1500,
|
||||||
|
"direction": 4,
|
||||||
|
"spread": 7,
|
||||||
|
"repeat_times": 0,
|
||||||
|
"sequence": [[120, 100, 100], [240, 100, 100], [260, 100, 100], [280, 100, 100]],
|
||||||
|
}
|
||||||
|
EFFECT_BUBBLING_CAULDRON = {
|
||||||
|
"custom": 0,
|
||||||
|
"id": "TapoStrip_6DlumDwO2NdfHppy50vJtu",
|
||||||
|
"brightness": 100,
|
||||||
|
"name": "Bubbling Cauldron",
|
||||||
|
"enable": 1,
|
||||||
|
"segments": [0],
|
||||||
|
"expansion_strategy": 1,
|
||||||
|
"display_colors": [[100, 100, 100], [270, 100, 100]],
|
||||||
|
"type": "random",
|
||||||
|
"hue_range": [100, 270],
|
||||||
|
"saturation_range": [80, 100],
|
||||||
|
"brightness_range": [50, 100],
|
||||||
|
"duration": 0,
|
||||||
|
"transition": 200,
|
||||||
|
"init_states": [[270, 100, 100]],
|
||||||
|
"fadeoff": 1000,
|
||||||
|
"random_seed": 24,
|
||||||
|
"backgrounds": [[270, 40, 50]],
|
||||||
|
}
|
||||||
|
EFFECT_CANDY_CANE = {
|
||||||
|
"custom": 0,
|
||||||
|
"id": "TapoStrip_6Dy0Nc45vlhFPEzG021Pe9",
|
||||||
|
"brightness": 100,
|
||||||
|
"name": "Candy Cane",
|
||||||
|
"enable": 1,
|
||||||
|
"segments": [0],
|
||||||
|
"expansion_strategy": 1,
|
||||||
|
"display_colors": [[0, 0, 100], [0, 81, 100]],
|
||||||
|
"type": "sequence",
|
||||||
|
"duration": 700,
|
||||||
|
"transition": 500,
|
||||||
|
"direction": 1,
|
||||||
|
"spread": 1,
|
||||||
|
"repeat_times": 0,
|
||||||
|
"sequence": [
|
||||||
|
[0, 0, 100],
|
||||||
|
[0, 0, 100],
|
||||||
|
[360, 81, 100],
|
||||||
|
[0, 0, 100],
|
||||||
|
[0, 0, 100],
|
||||||
|
[360, 81, 100],
|
||||||
|
[360, 81, 100],
|
||||||
|
[0, 0, 100],
|
||||||
|
[0, 0, 100],
|
||||||
|
[360, 81, 100],
|
||||||
|
[360, 81, 100],
|
||||||
|
[360, 81, 100],
|
||||||
|
[360, 81, 100],
|
||||||
|
[0, 0, 100],
|
||||||
|
[0, 0, 100],
|
||||||
|
[360, 81, 100],
|
||||||
|
],
|
||||||
|
}
|
||||||
|
EFFECT_CHRISTMAS = {
|
||||||
|
"custom": 0,
|
||||||
|
"id": "TapoStrip_5zkiG6avJ1IbhjiZbRlWvh",
|
||||||
|
"brightness": 100,
|
||||||
|
"name": "Christmas",
|
||||||
|
"enable": 1,
|
||||||
|
"segments": [0],
|
||||||
|
"expansion_strategy": 1,
|
||||||
|
"display_colors": [[136, 98, 100], [350, 97, 100]],
|
||||||
|
"type": "random",
|
||||||
|
"hue_range": [136, 146],
|
||||||
|
"saturation_range": [90, 100],
|
||||||
|
"brightness_range": [50, 100],
|
||||||
|
"duration": 5000,
|
||||||
|
"transition": 0,
|
||||||
|
"init_states": [[136, 0, 100]],
|
||||||
|
"fadeoff": 2000,
|
||||||
|
"random_seed": 100,
|
||||||
|
"backgrounds": [[136, 98, 75], [136, 0, 0], [350, 0, 100], [350, 97, 94]],
|
||||||
|
}
|
||||||
|
EFFECT_FLICKER = {
|
||||||
|
"custom": 0,
|
||||||
|
"id": "TapoStrip_4HVKmMc6vEzjm36jXaGwMs",
|
||||||
|
"brightness": 100,
|
||||||
|
"name": "Flicker",
|
||||||
|
"enable": 1,
|
||||||
|
"segments": [1],
|
||||||
|
"expansion_strategy": 1,
|
||||||
|
"display_colors": [[30, 81, 100], [40, 100, 100]],
|
||||||
|
"type": "random",
|
||||||
|
"hue_range": [30, 40],
|
||||||
|
"saturation_range": [100, 100],
|
||||||
|
"brightness_range": [50, 100],
|
||||||
|
"duration": 0,
|
||||||
|
"transition": 0,
|
||||||
|
"transition_range": [375, 500],
|
||||||
|
"init_states": [[30, 81, 80]],
|
||||||
|
}
|
||||||
|
EFFECT_GRANDMAS_CHRISTMAS_LIGHTS = {
|
||||||
|
"custom": 0,
|
||||||
|
"id": "TapoStrip_3Gk6CmXOXbjCiwz9iD543C",
|
||||||
|
"brightness": 100,
|
||||||
|
"name": "Grandma's Christmas Lights",
|
||||||
|
"enable": 1,
|
||||||
|
"segments": [0],
|
||||||
|
"expansion_strategy": 1,
|
||||||
|
"display_colors": [[30, 100, 100], [240, 100, 100], [130, 100, 100], [0, 100, 100]],
|
||||||
|
"type": "sequence",
|
||||||
|
"duration": 5000,
|
||||||
|
"transition": 100,
|
||||||
|
"direction": 1,
|
||||||
|
"spread": 1,
|
||||||
|
"repeat_times": 0,
|
||||||
|
"sequence": [
|
||||||
|
[30, 100, 100],
|
||||||
|
[30, 0, 0],
|
||||||
|
[30, 0, 0],
|
||||||
|
[240, 100, 100],
|
||||||
|
[240, 0, 0],
|
||||||
|
[240, 0, 0],
|
||||||
|
[240, 0, 100],
|
||||||
|
[240, 0, 0],
|
||||||
|
[240, 0, 0],
|
||||||
|
[130, 100, 100],
|
||||||
|
[130, 0, 0],
|
||||||
|
[130, 0, 0],
|
||||||
|
[0, 100, 100],
|
||||||
|
[0, 0, 0],
|
||||||
|
[0, 0, 0],
|
||||||
|
],
|
||||||
|
}
|
||||||
|
EFFECT_HANUKKAH = {
|
||||||
|
"custom": 0,
|
||||||
|
"id": "TapoStrip_2YTk4wramLKv5XZ9KFDVYm",
|
||||||
|
"brightness": 100,
|
||||||
|
"name": "Hanukkah",
|
||||||
|
"enable": 1,
|
||||||
|
"segments": [1],
|
||||||
|
"expansion_strategy": 1,
|
||||||
|
"display_colors": [[200, 100, 100]],
|
||||||
|
"type": "random",
|
||||||
|
"hue_range": [200, 210],
|
||||||
|
"saturation_range": [0, 100],
|
||||||
|
"brightness_range": [50, 100],
|
||||||
|
"duration": 1500,
|
||||||
|
"transition": 0,
|
||||||
|
"transition_range": [400, 500],
|
||||||
|
"init_states": [[35, 81, 80]],
|
||||||
|
}
|
||||||
|
EFFECT_HAUNTED_MANSION = {
|
||||||
|
"custom": 0,
|
||||||
|
"id": "TapoStrip_4rJ6JwC7I9st3tQ8j4lwlI",
|
||||||
|
"brightness": 100,
|
||||||
|
"name": "Haunted Mansion",
|
||||||
|
"enable": 1,
|
||||||
|
"segments": [80],
|
||||||
|
"expansion_strategy": 2,
|
||||||
|
"display_colors": [[44, 9, 100]],
|
||||||
|
"type": "random",
|
||||||
|
"hue_range": [45, 45],
|
||||||
|
"saturation_range": [10, 10],
|
||||||
|
"brightness_range": [0, 80],
|
||||||
|
"duration": 0,
|
||||||
|
"transition": 0,
|
||||||
|
"transition_range": [50, 1500],
|
||||||
|
"init_states": [[45, 10, 100]],
|
||||||
|
"fadeoff": 200,
|
||||||
|
"random_seed": 1,
|
||||||
|
"backgrounds": [[45, 10, 100]],
|
||||||
|
}
|
||||||
|
EFFECT_ICICLE = {
|
||||||
|
"custom": 0,
|
||||||
|
"id": "TapoStrip_7UcYLeJbiaxVIXCxr21tpx",
|
||||||
|
"brightness": 100,
|
||||||
|
"name": "Icicle",
|
||||||
|
"enable": 1,
|
||||||
|
"segments": [0],
|
||||||
|
"expansion_strategy": 1,
|
||||||
|
"display_colors": [[190, 100, 100]],
|
||||||
|
"type": "sequence",
|
||||||
|
"duration": 0,
|
||||||
|
"transition": 400,
|
||||||
|
"direction": 4,
|
||||||
|
"spread": 3,
|
||||||
|
"repeat_times": 0,
|
||||||
|
"sequence": [
|
||||||
|
[190, 100, 70],
|
||||||
|
[190, 100, 70],
|
||||||
|
[190, 30, 50],
|
||||||
|
[190, 100, 70],
|
||||||
|
[190, 100, 70],
|
||||||
|
],
|
||||||
|
}
|
||||||
|
EFFECT_LIGHTNING = {
|
||||||
|
"custom": 0,
|
||||||
|
"id": "TapoStrip_7OGzfSfnOdhoO2ri4gOHWn",
|
||||||
|
"brightness": 100,
|
||||||
|
"name": "Lightning",
|
||||||
|
"enable": 1,
|
||||||
|
"segments": [7],
|
||||||
|
"expansion_strategy": 1,
|
||||||
|
"display_colors": [[210, 9, 100], [200, 50, 100], [200, 100, 100]],
|
||||||
|
"type": "random",
|
||||||
|
"hue_range": [240, 240],
|
||||||
|
"saturation_range": [10, 11],
|
||||||
|
"brightness_range": [90, 100],
|
||||||
|
"duration": 0,
|
||||||
|
"transition": 50,
|
||||||
|
"init_states": [[240, 30, 100]],
|
||||||
|
"fadeoff": 150,
|
||||||
|
"random_seed": 50,
|
||||||
|
"backgrounds": [[200, 100, 100], [200, 50, 10], [210, 10, 50], [240, 10, 0]],
|
||||||
|
}
|
||||||
|
EFFECT_OCEAN = {
|
||||||
|
"custom": 0,
|
||||||
|
"id": "TapoStrip_0fOleCdwSgR0nfjkReeYfw",
|
||||||
|
"brightness": 100,
|
||||||
|
"name": "Ocean",
|
||||||
|
"enable": 1,
|
||||||
|
"segments": [0],
|
||||||
|
"expansion_strategy": 1,
|
||||||
|
"display_colors": [[198, 84, 100]],
|
||||||
|
"type": "sequence",
|
||||||
|
"duration": 0,
|
||||||
|
"transition": 2000,
|
||||||
|
"direction": 3,
|
||||||
|
"spread": 16,
|
||||||
|
"repeat_times": 0,
|
||||||
|
"sequence": [[198, 84, 30], [198, 70, 30], [198, 10, 30]],
|
||||||
|
}
|
||||||
|
EFFECT_RAINBOW = {
|
||||||
|
"custom": 0,
|
||||||
|
"id": "TapoStrip_7CC5y4lsL8pETYvmz7UOpQ",
|
||||||
|
"brightness": 100,
|
||||||
|
"name": "Rainbow",
|
||||||
|
"enable": 1,
|
||||||
|
"segments": [0],
|
||||||
|
"expansion_strategy": 1,
|
||||||
|
"display_colors": [
|
||||||
|
[0, 100, 100],
|
||||||
|
[100, 100, 100],
|
||||||
|
[200, 100, 100],
|
||||||
|
[300, 100, 100],
|
||||||
|
],
|
||||||
|
"type": "sequence",
|
||||||
|
"duration": 0,
|
||||||
|
"transition": 1500,
|
||||||
|
"direction": 1,
|
||||||
|
"spread": 12,
|
||||||
|
"repeat_times": 0,
|
||||||
|
"sequence": [[0, 100, 100], [100, 100, 100], [200, 100, 100], [300, 100, 100]],
|
||||||
|
}
|
||||||
|
EFFECT_RAINDROP = {
|
||||||
|
"custom": 0,
|
||||||
|
"id": "TapoStrip_1t2nWlTBkV8KXBZ0TWvBjs",
|
||||||
|
"brightness": 100,
|
||||||
|
"name": "Raindrop",
|
||||||
|
"enable": 1,
|
||||||
|
"segments": [0],
|
||||||
|
"expansion_strategy": 1,
|
||||||
|
"display_colors": [[200, 9, 100], [200, 19, 100]],
|
||||||
|
"type": "random",
|
||||||
|
"hue_range": [200, 200],
|
||||||
|
"saturation_range": [10, 20],
|
||||||
|
"brightness_range": [10, 30],
|
||||||
|
"duration": 0,
|
||||||
|
"transition": 1000,
|
||||||
|
"init_states": [[200, 40, 100]],
|
||||||
|
"fadeoff": 1000,
|
||||||
|
"random_seed": 24,
|
||||||
|
"backgrounds": [[200, 40, 0]],
|
||||||
|
}
|
||||||
|
EFFECT_SPRING = {
|
||||||
|
"custom": 0,
|
||||||
|
"id": "TapoStrip_1nL6GqZ5soOxj71YDJOlZL",
|
||||||
|
"brightness": 100,
|
||||||
|
"name": "Spring",
|
||||||
|
"enable": 1,
|
||||||
|
"segments": [0],
|
||||||
|
"expansion_strategy": 1,
|
||||||
|
"display_colors": [[0, 30, 100], [130, 100, 100]],
|
||||||
|
"type": "random",
|
||||||
|
"hue_range": [0, 90],
|
||||||
|
"saturation_range": [30, 100],
|
||||||
|
"brightness_range": [90, 100],
|
||||||
|
"duration": 600,
|
||||||
|
"transition": 0,
|
||||||
|
"transition_range": [2000, 6000],
|
||||||
|
"init_states": [[80, 30, 100]],
|
||||||
|
"fadeoff": 1000,
|
||||||
|
"random_seed": 20,
|
||||||
|
"backgrounds": [[130, 100, 40]],
|
||||||
|
}
|
||||||
|
EFFECT_SUNRISE = {
|
||||||
|
"custom": 0,
|
||||||
|
"id": "TapoStrip_1OVSyXIsDxrt4j7OxyRvqi",
|
||||||
|
"brightness": 100,
|
||||||
|
"name": "Sunrise",
|
||||||
|
"enable": 1,
|
||||||
|
"segments": [0],
|
||||||
|
"expansion_strategy": 2,
|
||||||
|
"display_colors": [[0, 0, 100], [30, 95, 100], [0, 100, 100]],
|
||||||
|
"type": "pulse",
|
||||||
|
"duration": 600,
|
||||||
|
"transition": 60000,
|
||||||
|
"direction": 1,
|
||||||
|
"spread": 1,
|
||||||
|
"repeat_times": 1,
|
||||||
|
"run_time": 0,
|
||||||
|
"sequence": [
|
||||||
|
[0, 100, 5],
|
||||||
|
[0, 100, 5],
|
||||||
|
[10, 100, 6],
|
||||||
|
[15, 100, 7],
|
||||||
|
[20, 100, 8],
|
||||||
|
[20, 100, 10],
|
||||||
|
[30, 100, 12],
|
||||||
|
[30, 95, 15],
|
||||||
|
[30, 90, 20],
|
||||||
|
[30, 80, 25],
|
||||||
|
[30, 75, 30],
|
||||||
|
[30, 70, 40],
|
||||||
|
[30, 60, 50],
|
||||||
|
[30, 50, 60],
|
||||||
|
[30, 20, 70],
|
||||||
|
[30, 0, 100],
|
||||||
|
],
|
||||||
|
"trans_sequence": [],
|
||||||
|
}
|
||||||
|
EFFECT_SUNSET = {
|
||||||
|
"custom": 0,
|
||||||
|
"id": "TapoStrip_5NiN0Y8GAUD78p4neKk9EL",
|
||||||
|
"brightness": 100,
|
||||||
|
"name": "Sunset",
|
||||||
|
"enable": 1,
|
||||||
|
"segments": [0],
|
||||||
|
"expansion_strategy": 2,
|
||||||
|
"display_colors": [[0, 100, 100], [30, 95, 100], [0, 0, 100]],
|
||||||
|
"type": "pulse",
|
||||||
|
"duration": 600,
|
||||||
|
"transition": 60000,
|
||||||
|
"direction": 1,
|
||||||
|
"spread": 1,
|
||||||
|
"repeat_times": 1,
|
||||||
|
"run_time": 0,
|
||||||
|
"sequence": [
|
||||||
|
[30, 0, 100],
|
||||||
|
[30, 20, 100],
|
||||||
|
[30, 50, 99],
|
||||||
|
[30, 60, 98],
|
||||||
|
[30, 70, 97],
|
||||||
|
[30, 75, 95],
|
||||||
|
[30, 80, 93],
|
||||||
|
[30, 90, 90],
|
||||||
|
[30, 95, 85],
|
||||||
|
[30, 100, 80],
|
||||||
|
[20, 100, 70],
|
||||||
|
[20, 100, 60],
|
||||||
|
[15, 100, 50],
|
||||||
|
[10, 100, 40],
|
||||||
|
[0, 100, 30],
|
||||||
|
[0, 100, 0],
|
||||||
|
],
|
||||||
|
"trans_sequence": [],
|
||||||
|
}
|
||||||
|
EFFECT_VALENTINES = {
|
||||||
|
"custom": 0,
|
||||||
|
"id": "TapoStrip_2q1Vio9sSjHmaC7JS9d30l",
|
||||||
|
"brightness": 100,
|
||||||
|
"name": "Valentines",
|
||||||
|
"enable": 1,
|
||||||
|
"segments": [0],
|
||||||
|
"expansion_strategy": 1,
|
||||||
|
"display_colors": [[339, 19, 100], [19, 50, 100], [0, 100, 100], [339, 40, 100]],
|
||||||
|
"type": "random",
|
||||||
|
"hue_range": [340, 340],
|
||||||
|
"saturation_range": [30, 40],
|
||||||
|
"brightness_range": [90, 100],
|
||||||
|
"duration": 600,
|
||||||
|
"transition": 2000,
|
||||||
|
"init_states": [[340, 30, 100]],
|
||||||
|
"fadeoff": 3000,
|
||||||
|
"random_seed": 100,
|
||||||
|
"backgrounds": [[340, 20, 50], [20, 50, 50], [0, 100, 50]],
|
||||||
|
}
|
||||||
|
EFFECTS_LIST = [
|
||||||
|
EFFECT_AURORA,
|
||||||
|
EFFECT_BUBBLING_CAULDRON,
|
||||||
|
EFFECT_CANDY_CANE,
|
||||||
|
EFFECT_CHRISTMAS,
|
||||||
|
EFFECT_FLICKER,
|
||||||
|
EFFECT_GRANDMAS_CHRISTMAS_LIGHTS,
|
||||||
|
EFFECT_HANUKKAH,
|
||||||
|
EFFECT_HAUNTED_MANSION,
|
||||||
|
EFFECT_ICICLE,
|
||||||
|
EFFECT_LIGHTNING,
|
||||||
|
EFFECT_OCEAN,
|
||||||
|
EFFECT_RAINBOW,
|
||||||
|
EFFECT_RAINDROP,
|
||||||
|
EFFECT_SPRING,
|
||||||
|
EFFECT_SUNRISE,
|
||||||
|
EFFECT_SUNSET,
|
||||||
|
EFFECT_VALENTINES,
|
||||||
|
]
|
||||||
|
|
||||||
|
EFFECT_NAMES: list[str] = [cast(str, effect["name"]) for effect in EFFECTS_LIST]
|
||||||
|
EFFECT_MAPPING = {effect["name"]: effect for effect in EFFECTS_LIST}
|
@ -18,6 +18,7 @@ from .humiditysensor import HumiditySensor
|
|||||||
from .led import Led
|
from .led import Led
|
||||||
from .light import Light
|
from .light import Light
|
||||||
from .lighteffect import LightEffect
|
from .lighteffect import LightEffect
|
||||||
|
from .lightstripeffect import LightStripEffect
|
||||||
from .lighttransition import LightTransition
|
from .lighttransition import LightTransition
|
||||||
from .reportmode import ReportMode
|
from .reportmode import ReportMode
|
||||||
from .temperaturecontrol import TemperatureControl
|
from .temperaturecontrol import TemperatureControl
|
||||||
@ -44,6 +45,7 @@ __all__ = [
|
|||||||
"Cloud",
|
"Cloud",
|
||||||
"Light",
|
"Light",
|
||||||
"LightEffect",
|
"LightEffect",
|
||||||
|
"LightStripEffect",
|
||||||
"LightTransition",
|
"LightTransition",
|
||||||
"ColorTemperature",
|
"ColorTemperature",
|
||||||
"Color",
|
"Color",
|
||||||
|
109
kasa/smart/modules/lightstripeffect.py
Normal file
109
kasa/smart/modules/lightstripeffect.py
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
"""Module for light effects."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from ...interfaces.lighteffect import LightEffect as LightEffectInterface
|
||||||
|
from ..effects import EFFECT_MAPPING, EFFECT_NAMES
|
||||||
|
from ..smartmodule import SmartModule
|
||||||
|
|
||||||
|
|
||||||
|
class LightStripEffect(SmartModule, LightEffectInterface):
|
||||||
|
"""Implementation of dynamic light effects."""
|
||||||
|
|
||||||
|
REQUIRED_COMPONENT = "light_strip_lighting_effect"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
"""Name of the module.
|
||||||
|
|
||||||
|
By default smart modules are keyed in the module mapping by class name.
|
||||||
|
The name is overriden here as this module implements the same common interface
|
||||||
|
as the bulb light_effect and the assumption is a device only supports one
|
||||||
|
or the other.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return "LightEffect"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def effect(self) -> str:
|
||||||
|
"""Return effect state.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
{'brightness': 50,
|
||||||
|
'custom': 0,
|
||||||
|
'enable': 0,
|
||||||
|
'id': '',
|
||||||
|
'name': ''}
|
||||||
|
"""
|
||||||
|
eff = self.data["lighting_effect"]
|
||||||
|
name = eff["name"]
|
||||||
|
if eff["enable"]:
|
||||||
|
return name
|
||||||
|
return self.LIGHT_EFFECTS_OFF
|
||||||
|
|
||||||
|
@property
|
||||||
|
def effect_list(self) -> list[str]:
|
||||||
|
"""Return built-in effects list.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
['Aurora', 'Bubbling Cauldron', ...]
|
||||||
|
"""
|
||||||
|
effect_list = [self.LIGHT_EFFECTS_OFF]
|
||||||
|
effect_list.extend(EFFECT_NAMES)
|
||||||
|
return effect_list
|
||||||
|
|
||||||
|
async def set_effect(
|
||||||
|
self,
|
||||||
|
effect: str,
|
||||||
|
*,
|
||||||
|
brightness: int | None = None,
|
||||||
|
transition: int | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Set an effect on the device.
|
||||||
|
|
||||||
|
If brightness or transition is defined,
|
||||||
|
its value will be used instead of the effect-specific default.
|
||||||
|
|
||||||
|
See :meth:`effect_list` for available effects,
|
||||||
|
or use :meth:`set_custom_effect` for custom effects.
|
||||||
|
|
||||||
|
:param str effect: The effect to set
|
||||||
|
:param int brightness: The wanted brightness
|
||||||
|
:param int transition: The wanted transition time
|
||||||
|
"""
|
||||||
|
if effect == self.LIGHT_EFFECTS_OFF:
|
||||||
|
effect_dict = dict(self.data["lighting_effect"])
|
||||||
|
effect_dict["enable"] = 0
|
||||||
|
elif effect not in EFFECT_MAPPING:
|
||||||
|
raise ValueError(f"The effect {effect} is not a built in effect.")
|
||||||
|
else:
|
||||||
|
effect_dict = EFFECT_MAPPING[effect]
|
||||||
|
|
||||||
|
if brightness is not None:
|
||||||
|
effect_dict["brightness"] = brightness
|
||||||
|
if transition is not None:
|
||||||
|
effect_dict["transition"] = transition
|
||||||
|
|
||||||
|
await self.set_custom_effect(effect_dict)
|
||||||
|
|
||||||
|
async def set_custom_effect(
|
||||||
|
self,
|
||||||
|
effect_dict: dict,
|
||||||
|
) -> None:
|
||||||
|
"""Set a custom effect on the device.
|
||||||
|
|
||||||
|
:param str effect_dict: The custom effect dict to set
|
||||||
|
"""
|
||||||
|
return await self.call(
|
||||||
|
"set_lighting_effect",
|
||||||
|
effect_dict,
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_custom_effects(self) -> bool:
|
||||||
|
"""Return True if the device supports setting custom effects."""
|
||||||
|
return True
|
||||||
|
|
||||||
|
def query(self):
|
||||||
|
"""Return the base query."""
|
||||||
|
return {}
|
@ -176,7 +176,7 @@ class FakeSmartTransport(BaseTransport):
|
|||||||
"Method %s not implemented for children" % child_method
|
"Method %s not implemented for children" % child_method
|
||||||
)
|
)
|
||||||
|
|
||||||
def _set_light_effect(self, info, params):
|
def _set_dynamic_light_effect(self, info, params):
|
||||||
"""Set or remove values as per the device behaviour."""
|
"""Set or remove values as per the device behaviour."""
|
||||||
info["get_device_info"]["dynamic_light_effect_enable"] = params["enable"]
|
info["get_device_info"]["dynamic_light_effect_enable"] = params["enable"]
|
||||||
info["get_dynamic_light_effect_rules"]["enable"] = params["enable"]
|
info["get_dynamic_light_effect_rules"]["enable"] = params["enable"]
|
||||||
@ -189,6 +189,13 @@ class FakeSmartTransport(BaseTransport):
|
|||||||
if "current_rule_id" in info["get_dynamic_light_effect_rules"]:
|
if "current_rule_id" in info["get_dynamic_light_effect_rules"]:
|
||||||
del info["get_dynamic_light_effect_rules"]["current_rule_id"]
|
del info["get_dynamic_light_effect_rules"]["current_rule_id"]
|
||||||
|
|
||||||
|
def _set_light_strip_effect(self, info, params):
|
||||||
|
"""Set or remove values as per the device behaviour."""
|
||||||
|
info["get_device_info"]["lighting_effect"]["enable"] = params["enable"]
|
||||||
|
info["get_device_info"]["lighting_effect"]["name"] = params["name"]
|
||||||
|
info["get_device_info"]["lighting_effect"]["id"] = params["id"]
|
||||||
|
info["get_lighting_effect"] = copy.deepcopy(params)
|
||||||
|
|
||||||
def _set_led_info(self, info, params):
|
def _set_led_info(self, info, params):
|
||||||
"""Set or remove values as per the device behaviour."""
|
"""Set or remove values as per the device behaviour."""
|
||||||
info["get_led_info"]["led_status"] = params["led_rule"] != "never"
|
info["get_led_info"]["led_status"] = params["led_rule"] != "never"
|
||||||
@ -244,7 +251,10 @@ class FakeSmartTransport(BaseTransport):
|
|||||||
elif method in ["set_qs_info", "fw_download"]:
|
elif method in ["set_qs_info", "fw_download"]:
|
||||||
return {"error_code": 0}
|
return {"error_code": 0}
|
||||||
elif method == "set_dynamic_light_effect_rule_enable":
|
elif method == "set_dynamic_light_effect_rule_enable":
|
||||||
self._set_light_effect(info, params)
|
self._set_dynamic_light_effect(info, params)
|
||||||
|
return {"error_code": 0}
|
||||||
|
elif method == "set_lighting_effect":
|
||||||
|
self._set_light_strip_effect(info, params)
|
||||||
return {"error_code": 0}
|
return {"error_code": 0}
|
||||||
elif method == "set_led_info":
|
elif method == "set_led_info":
|
||||||
self._set_led_info(info, params)
|
self._set_led_info(info, params)
|
||||||
|
@ -390,12 +390,8 @@ async def test_light_effect(dev: Device, runner: CliRunner):
|
|||||||
assert light_effect.effect == light_effect.LIGHT_EFFECTS_OFF
|
assert light_effect.effect == light_effect.LIGHT_EFFECTS_OFF
|
||||||
|
|
||||||
res = await runner.invoke(effect, obj=dev)
|
res = await runner.invoke(effect, obj=dev)
|
||||||
msg = (
|
assert f"Light effect: {light_effect.effect}" in res.output
|
||||||
"Setting an effect requires a named built-in effect: "
|
assert res.exit_code == 0
|
||||||
+ f"{light_effect.effect_list}"
|
|
||||||
)
|
|
||||||
assert msg in res.output
|
|
||||||
assert res.exit_code == 2
|
|
||||||
|
|
||||||
res = await runner.invoke(effect, [light_effect.effect_list[1]], obj=dev)
|
res = await runner.invoke(effect, [light_effect.effect_list[1]], obj=dev)
|
||||||
assert f"Setting Effect: {light_effect.effect_list[1]}" in res.output
|
assert f"Setting Effect: {light_effect.effect_list[1]}" in res.output
|
||||||
|
@ -19,7 +19,14 @@ led = parametrize_combine([led_smart, plug_iot])
|
|||||||
light_effect_smart = parametrize(
|
light_effect_smart = parametrize(
|
||||||
"has light effect smart", component_filter="light_effect", protocol_filter={"SMART"}
|
"has light effect smart", component_filter="light_effect", protocol_filter={"SMART"}
|
||||||
)
|
)
|
||||||
light_effect = parametrize_combine([light_effect_smart, lightstrip_iot])
|
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]
|
||||||
|
)
|
||||||
|
|
||||||
dimmable_smart = parametrize(
|
dimmable_smart = parametrize(
|
||||||
"dimmable smart", component_filter="brightness", protocol_filter={"SMART"}
|
"dimmable smart", component_filter="brightness", protocol_filter={"SMART"}
|
||||||
|
Loading…
Reference in New Issue
Block a user