mirror of
https://github.com/home-assistant/core.git
synced 2025-07-17 10:17:09 +00:00
Add support for custom effects to tplink light strips (#68502)
This commit is contained in:
parent
d75f577b88
commit
fb41734342
@ -1,10 +1,12 @@
|
|||||||
"""Support for TPLink lights."""
|
"""Support for TPLink lights."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Sequence
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, cast
|
from typing import Any, Final, cast
|
||||||
|
|
||||||
from kasa import SmartBulb, SmartLightStrip
|
from kasa import SmartBulb, SmartLightStrip
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
ATTR_BRIGHTNESS,
|
ATTR_BRIGHTNESS,
|
||||||
@ -22,6 +24,8 @@ from homeassistant.components.light import (
|
|||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
from homeassistant.helpers import entity_platform
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.util.color import (
|
from homeassistant.util.color import (
|
||||||
color_temperature_kelvin_to_mired as kelvin_to_mired,
|
color_temperature_kelvin_to_mired as kelvin_to_mired,
|
||||||
@ -35,6 +39,97 @@ from .entity import CoordinatedTPLinkEntity, async_refresh_after
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
SERVICE_RANDOM_EFFECT = "random_effect"
|
||||||
|
SERVICE_SEQUENCE_EFFECT = "sequence_effect"
|
||||||
|
|
||||||
|
HUE = vol.Range(min=0, max=360)
|
||||||
|
SAT = vol.Range(min=0, max=100)
|
||||||
|
VAL = vol.Range(min=0, max=100)
|
||||||
|
TRANSITION = vol.Range(min=0, max=6000)
|
||||||
|
HSV_SEQUENCE = vol.ExactSequence((HUE, SAT, VAL))
|
||||||
|
|
||||||
|
BASE_EFFECT_DICT: Final = {
|
||||||
|
vol.Optional("brightness", default=100): vol.All(
|
||||||
|
vol.Coerce(int), vol.Range(min=0, max=100)
|
||||||
|
),
|
||||||
|
vol.Optional("duration", default=0): vol.All(
|
||||||
|
vol.Coerce(int), vol.Range(min=0, max=5000)
|
||||||
|
),
|
||||||
|
vol.Optional("transition", default=0): vol.All(vol.Coerce(int), TRANSITION),
|
||||||
|
vol.Optional("segments", default=[0]): vol.All(
|
||||||
|
cv.ensure_list_csv,
|
||||||
|
vol.Length(min=1, max=80),
|
||||||
|
[vol.All(vol.Coerce(int), vol.Range(min=0, max=80))],
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
SEQUENCE_EFFECT_DICT: Final = {
|
||||||
|
**BASE_EFFECT_DICT,
|
||||||
|
vol.Required("sequence"): vol.All(
|
||||||
|
cv.ensure_list,
|
||||||
|
vol.Length(min=1, max=16),
|
||||||
|
[vol.All(vol.Coerce(tuple), HSV_SEQUENCE)],
|
||||||
|
),
|
||||||
|
vol.Optional("spread", default=1): vol.All(
|
||||||
|
vol.Coerce(int), vol.Range(min=1, max=16)
|
||||||
|
),
|
||||||
|
vol.Optional("direction", default=4): vol.All(
|
||||||
|
vol.Coerce(int), vol.Range(min=1, max=4)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
RANDOM_EFFECT_DICT: Final = {
|
||||||
|
**BASE_EFFECT_DICT,
|
||||||
|
vol.Optional("fadeoff", default=0): vol.All(
|
||||||
|
vol.Coerce(int), vol.Range(min=0, max=3000)
|
||||||
|
),
|
||||||
|
vol.Optional("hue_range"): vol.All(
|
||||||
|
cv.ensure_list_csv, [vol.Coerce(int)], vol.ExactSequence((HUE, HUE))
|
||||||
|
),
|
||||||
|
vol.Optional("saturation_range"): vol.All(
|
||||||
|
cv.ensure_list_csv, [vol.Coerce(int)], vol.ExactSequence((SAT, SAT))
|
||||||
|
),
|
||||||
|
vol.Optional("brightness_range"): vol.All(
|
||||||
|
cv.ensure_list_csv, [vol.Coerce(int)], vol.ExactSequence((VAL, VAL))
|
||||||
|
),
|
||||||
|
vol.Optional("transition_range"): vol.All(
|
||||||
|
cv.ensure_list_csv,
|
||||||
|
[vol.Coerce(int)],
|
||||||
|
vol.ExactSequence((TRANSITION, TRANSITION)),
|
||||||
|
),
|
||||||
|
vol.Required("init_states"): vol.All(
|
||||||
|
cv.ensure_list_csv, [vol.Coerce(int)], HSV_SEQUENCE
|
||||||
|
),
|
||||||
|
vol.Optional("random_seed", default=100): vol.All(
|
||||||
|
vol.Coerce(int), vol.Range(min=1, max=100)
|
||||||
|
),
|
||||||
|
vol.Required("backgrounds"): vol.All(
|
||||||
|
cv.ensure_list,
|
||||||
|
vol.Length(min=1, max=16),
|
||||||
|
[vol.All(vol.Coerce(tuple), HSV_SEQUENCE)],
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _async_build_base_effect(
|
||||||
|
brightness: int,
|
||||||
|
duration: int,
|
||||||
|
transition: int,
|
||||||
|
segments: list[int],
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"custom": 1,
|
||||||
|
"id": "yMwcNpLxijmoKamskHCvvravpbnIqAIN",
|
||||||
|
"brightness": brightness,
|
||||||
|
"name": "Custom",
|
||||||
|
"segments": segments,
|
||||||
|
"expansion_strategy": 1,
|
||||||
|
"enable": 1,
|
||||||
|
"duration": duration,
|
||||||
|
"transition": transition,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -51,6 +146,17 @@ async def async_setup_entry(
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
platform = entity_platform.async_get_current_platform()
|
||||||
|
platform.async_register_entity_service(
|
||||||
|
SERVICE_RANDOM_EFFECT,
|
||||||
|
RANDOM_EFFECT_DICT,
|
||||||
|
"async_set_random_effect",
|
||||||
|
)
|
||||||
|
platform.async_register_entity_service(
|
||||||
|
SERVICE_SEQUENCE_EFFECT,
|
||||||
|
SEQUENCE_EFFECT_DICT,
|
||||||
|
"async_set_sequence_effect",
|
||||||
|
)
|
||||||
elif coordinator.device.is_bulb or coordinator.device.is_dimmer:
|
elif coordinator.device.is_bulb or coordinator.device.is_dimmer:
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
[TPLinkSmartBulb(cast(SmartBulb, coordinator.device), coordinator)]
|
[TPLinkSmartBulb(cast(SmartBulb, coordinator.device), coordinator)]
|
||||||
@ -219,6 +325,7 @@ class TPLinkSmartLightStrip(TPLinkSmartBulb):
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the smart light strip."""
|
"""Initialize the smart light strip."""
|
||||||
super().__init__(device, coordinator)
|
super().__init__(device, coordinator)
|
||||||
|
self._last_custom_effect: dict[str, Any] = {}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self) -> int:
|
def supported_features(self) -> int:
|
||||||
@ -259,9 +366,73 @@ class TPLinkSmartLightStrip(TPLinkSmartBulb):
|
|||||||
):
|
):
|
||||||
if not self.device.effect["custom"]:
|
if not self.device.effect["custom"]:
|
||||||
await self.device.set_effect(self.device.effect["name"])
|
await self.device.set_effect(self.device.effect["name"])
|
||||||
|
elif self._last_custom_effect:
|
||||||
|
await self.device.set_custom_effect(self._last_custom_effect)
|
||||||
# The device does not remember custom effects
|
# The device does not remember custom effects
|
||||||
# so we must set a default value or it can never turn back on
|
# so we must set a default value or it can never turn back on
|
||||||
else:
|
else:
|
||||||
await self.device.set_hsv(0, 0, 100, transition=transition)
|
await self.device.set_hsv(0, 0, 100, transition=transition)
|
||||||
else:
|
else:
|
||||||
await self._async_turn_on_with_brightness(brightness, transition)
|
await self._async_turn_on_with_brightness(brightness, transition)
|
||||||
|
|
||||||
|
async def async_set_random_effect(
|
||||||
|
self,
|
||||||
|
brightness: int,
|
||||||
|
duration: int,
|
||||||
|
transition: int,
|
||||||
|
segments: list[int],
|
||||||
|
fadeoff: int,
|
||||||
|
init_states: tuple[int, int, int],
|
||||||
|
random_seed: int,
|
||||||
|
backgrounds: Sequence[tuple[int, int, int]],
|
||||||
|
hue_range: tuple[int, int] | None = None,
|
||||||
|
saturation_range: tuple[int, int] | None = None,
|
||||||
|
brightness_range: tuple[int, int] | None = None,
|
||||||
|
transition_range: tuple[int, int] | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Set a random effect."""
|
||||||
|
effect: dict[str, Any] = {
|
||||||
|
**_async_build_base_effect(brightness, duration, transition, segments),
|
||||||
|
"type": "random",
|
||||||
|
"init_states": [init_states],
|
||||||
|
"random_seed": random_seed,
|
||||||
|
"backgrounds": backgrounds,
|
||||||
|
}
|
||||||
|
if fadeoff:
|
||||||
|
effect["fadeoff"] = fadeoff
|
||||||
|
if hue_range:
|
||||||
|
effect["hue_range"] = hue_range
|
||||||
|
if saturation_range:
|
||||||
|
effect["saturation_range"] = saturation_range
|
||||||
|
if brightness_range:
|
||||||
|
effect["brightness_range"] = brightness_range
|
||||||
|
effect["brightness"] = min(
|
||||||
|
brightness_range[1], max(brightness, brightness_range[0])
|
||||||
|
)
|
||||||
|
if transition_range:
|
||||||
|
effect["transition_range"] = transition_range
|
||||||
|
effect["transition"] = 0
|
||||||
|
self._last_custom_effect = effect
|
||||||
|
await self.device.set_custom_effect(effect)
|
||||||
|
|
||||||
|
async def async_set_sequence_effect(
|
||||||
|
self,
|
||||||
|
brightness: int,
|
||||||
|
duration: int,
|
||||||
|
transition: int,
|
||||||
|
segments: list[int],
|
||||||
|
sequence: Sequence[tuple[int, int, int]],
|
||||||
|
spread: int,
|
||||||
|
direction: int,
|
||||||
|
) -> None:
|
||||||
|
"""Set a sequence effect."""
|
||||||
|
effect: dict[str, Any] = {
|
||||||
|
**_async_build_base_effect(brightness, duration, transition, segments),
|
||||||
|
"type": "sequence",
|
||||||
|
"sequence": sequence,
|
||||||
|
"repeat_times": 0,
|
||||||
|
"spread": spread,
|
||||||
|
"direction": direction,
|
||||||
|
}
|
||||||
|
self._last_custom_effect = effect
|
||||||
|
await self.device.set_custom_effect(effect)
|
||||||
|
183
homeassistant/components/tplink/services.yaml
Normal file
183
homeassistant/components/tplink/services.yaml
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
sequence_effect:
|
||||||
|
description: Set a sequence effect
|
||||||
|
target:
|
||||||
|
entity:
|
||||||
|
integration: tplink
|
||||||
|
domain: light
|
||||||
|
fields:
|
||||||
|
sequence:
|
||||||
|
description: List of HSV sequences (Max 16)
|
||||||
|
example: |
|
||||||
|
- [340, 20, 50]
|
||||||
|
- [20, 50, 50]
|
||||||
|
- [0, 100, 50]
|
||||||
|
required: true
|
||||||
|
selector:
|
||||||
|
object:
|
||||||
|
segments:
|
||||||
|
description: List of Segments (0 for all)
|
||||||
|
example: 0, 2, 4, 6, 8
|
||||||
|
default: 0
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
object:
|
||||||
|
brightness:
|
||||||
|
description: Initial brightness
|
||||||
|
example: 80
|
||||||
|
default: 100
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
number:
|
||||||
|
min: 1
|
||||||
|
step: 1
|
||||||
|
max: 100
|
||||||
|
unit_of_measurement: "%"
|
||||||
|
duration:
|
||||||
|
description: Duration
|
||||||
|
example: 0
|
||||||
|
default: 0
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
number:
|
||||||
|
min: 0
|
||||||
|
step: 1
|
||||||
|
max: 5000
|
||||||
|
unit_of_measurement: "ms"
|
||||||
|
transition:
|
||||||
|
description: Transition
|
||||||
|
example: 2000
|
||||||
|
default: 0
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
number:
|
||||||
|
min: 0
|
||||||
|
step: 1
|
||||||
|
max: 6000
|
||||||
|
unit_of_measurement: "ms"
|
||||||
|
spread:
|
||||||
|
description: Speed of spread
|
||||||
|
example: 1
|
||||||
|
default: 0
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
number:
|
||||||
|
min: 0
|
||||||
|
step: 1
|
||||||
|
max: 16
|
||||||
|
direction:
|
||||||
|
description: Direction
|
||||||
|
example: 1
|
||||||
|
default: 4
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
number:
|
||||||
|
min: 1
|
||||||
|
step: 1
|
||||||
|
max: 4
|
||||||
|
random_effect:
|
||||||
|
description: Set a random effect
|
||||||
|
target:
|
||||||
|
entity:
|
||||||
|
integration: tplink
|
||||||
|
domain: light
|
||||||
|
fields:
|
||||||
|
init_states:
|
||||||
|
description: Initial HSV sequence
|
||||||
|
example: [199, 99, 96]
|
||||||
|
required: true
|
||||||
|
selector:
|
||||||
|
object:
|
||||||
|
backgrounds:
|
||||||
|
description: List of HSV sequences (Max 16)
|
||||||
|
example: |
|
||||||
|
- [199, 89, 50]
|
||||||
|
- [160, 50, 50]
|
||||||
|
- [180, 100, 50]
|
||||||
|
required: true
|
||||||
|
selector:
|
||||||
|
object:
|
||||||
|
segments:
|
||||||
|
description: List of segments (0 for all)
|
||||||
|
example: 0, 2, 4, 6, 8
|
||||||
|
default: 0
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
object:
|
||||||
|
brightness:
|
||||||
|
description: Initial brightness
|
||||||
|
example: 90
|
||||||
|
default: 100
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
number:
|
||||||
|
min: 1
|
||||||
|
step: 1
|
||||||
|
max: 100
|
||||||
|
unit_of_measurement: "%"
|
||||||
|
duration:
|
||||||
|
description: Duration
|
||||||
|
example: 0
|
||||||
|
default: 0
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
number:
|
||||||
|
min: 0
|
||||||
|
step: 1
|
||||||
|
max: 5000
|
||||||
|
unit_of_measurement: "ms"
|
||||||
|
transition:
|
||||||
|
description: Transition
|
||||||
|
example: 2000
|
||||||
|
default: 0
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
number:
|
||||||
|
min: 0
|
||||||
|
step: 1
|
||||||
|
max: 6000
|
||||||
|
unit_of_measurement: "ms"
|
||||||
|
fadeoff:
|
||||||
|
description: Fade off
|
||||||
|
example: 2000
|
||||||
|
default: 0
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
number:
|
||||||
|
min: 0
|
||||||
|
step: 1
|
||||||
|
max: 3000
|
||||||
|
unit_of_measurement: "ms"
|
||||||
|
hue_range:
|
||||||
|
description: Range of hue
|
||||||
|
example: 340, 360
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
object:
|
||||||
|
saturation_range:
|
||||||
|
description: Range of saturation
|
||||||
|
example: 40, 95
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
object:
|
||||||
|
brightness_range:
|
||||||
|
description: Range of brightness
|
||||||
|
example: 90, 100
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
object:
|
||||||
|
transition_range:
|
||||||
|
description: Range of transition
|
||||||
|
example: 2000, 6000
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
object:
|
||||||
|
random_seed:
|
||||||
|
description: Random seed
|
||||||
|
example: 80
|
||||||
|
default: 100
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
number:
|
||||||
|
min: 1
|
||||||
|
step: 1
|
||||||
|
max: 100
|
@ -458,6 +458,151 @@ async def test_smart_strip_effects(hass: HomeAssistant) -> None:
|
|||||||
assert state.attributes[ATTR_EFFECT_LIST] is None
|
assert state.attributes[ATTR_EFFECT_LIST] is None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_smart_strip_custom_random_effect(hass: HomeAssistant) -> None:
|
||||||
|
"""Test smart strip custom random effects."""
|
||||||
|
already_migrated_config_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN, data={}, unique_id=MAC_ADDRESS
|
||||||
|
)
|
||||||
|
already_migrated_config_entry.add_to_hass(hass)
|
||||||
|
strip = _mocked_smart_light_strip()
|
||||||
|
|
||||||
|
with _patch_discovery(device=strip), _patch_single_discovery(device=strip):
|
||||||
|
await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
entity_id = "light.my_bulb"
|
||||||
|
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state.state == STATE_ON
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
"random_effect",
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: entity_id,
|
||||||
|
"init_states": [340, 20, 50],
|
||||||
|
"backgrounds": [[340, 20, 50], [20, 50, 50], [0, 100, 50]],
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
strip.set_custom_effect.assert_called_once_with(
|
||||||
|
{
|
||||||
|
"custom": 1,
|
||||||
|
"id": "yMwcNpLxijmoKamskHCvvravpbnIqAIN",
|
||||||
|
"brightness": 100,
|
||||||
|
"name": "Custom",
|
||||||
|
"segments": [0],
|
||||||
|
"expansion_strategy": 1,
|
||||||
|
"enable": 1,
|
||||||
|
"duration": 0,
|
||||||
|
"transition": 0,
|
||||||
|
"type": "random",
|
||||||
|
"init_states": [[340, 20, 50]],
|
||||||
|
"random_seed": 100,
|
||||||
|
"backgrounds": [(340, 20, 50), (20, 50, 50), (0, 100, 50)],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
strip.set_custom_effect.reset_mock()
|
||||||
|
|
||||||
|
strip.effect = {
|
||||||
|
"custom": 1,
|
||||||
|
"id": "yMwcNpLxijmoKamskHCvvravpbnIqAIN",
|
||||||
|
"brightness": 100,
|
||||||
|
"name": "Custom",
|
||||||
|
"enable": 1,
|
||||||
|
}
|
||||||
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=10))
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state.state == STATE_ON
|
||||||
|
|
||||||
|
strip.is_off = True
|
||||||
|
strip.is_on = False
|
||||||
|
strip.effect = {
|
||||||
|
"custom": 1,
|
||||||
|
"id": "yMwcNpLxijmoKamskHCvvravpbnIqAIN",
|
||||||
|
"brightness": 100,
|
||||||
|
"name": "Custom",
|
||||||
|
"enable": 0,
|
||||||
|
}
|
||||||
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=20))
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state.state == STATE_OFF
|
||||||
|
assert ATTR_EFFECT not in state.attributes
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
LIGHT_DOMAIN,
|
||||||
|
"turn_on",
|
||||||
|
{ATTR_ENTITY_ID: entity_id},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
strip.set_custom_effect.assert_called_once_with(
|
||||||
|
{
|
||||||
|
"custom": 1,
|
||||||
|
"id": "yMwcNpLxijmoKamskHCvvravpbnIqAIN",
|
||||||
|
"brightness": 100,
|
||||||
|
"name": "Custom",
|
||||||
|
"segments": [0],
|
||||||
|
"expansion_strategy": 1,
|
||||||
|
"enable": 1,
|
||||||
|
"duration": 0,
|
||||||
|
"transition": 0,
|
||||||
|
"type": "random",
|
||||||
|
"init_states": [[340, 20, 50]],
|
||||||
|
"random_seed": 100,
|
||||||
|
"backgrounds": [(340, 20, 50), (20, 50, 50), (0, 100, 50)],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
strip.set_custom_effect.reset_mock()
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
"random_effect",
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: entity_id,
|
||||||
|
"init_states": [340, 20, 50],
|
||||||
|
"backgrounds": [[340, 20, 50], [20, 50, 50], [0, 100, 50]],
|
||||||
|
"random_seed": 50,
|
||||||
|
"brightness": 80,
|
||||||
|
"duration": 5000,
|
||||||
|
"transition": 2000,
|
||||||
|
"fadeoff": 3000,
|
||||||
|
"hue_range": [0, 360],
|
||||||
|
"saturation_range": [0, 100],
|
||||||
|
"brightness_range": [0, 100],
|
||||||
|
"transition_range": [2000, 3000],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
strip.set_custom_effect.assert_called_once_with(
|
||||||
|
{
|
||||||
|
"custom": 1,
|
||||||
|
"id": "yMwcNpLxijmoKamskHCvvravpbnIqAIN",
|
||||||
|
"brightness": 80,
|
||||||
|
"name": "Custom",
|
||||||
|
"segments": [0],
|
||||||
|
"expansion_strategy": 1,
|
||||||
|
"enable": 1,
|
||||||
|
"duration": 5000,
|
||||||
|
"transition": 0,
|
||||||
|
"type": "random",
|
||||||
|
"init_states": [[340, 20, 50]],
|
||||||
|
"random_seed": 50,
|
||||||
|
"backgrounds": [(340, 20, 50), (20, 50, 50), (0, 100, 50)],
|
||||||
|
"fadeoff": 3000,
|
||||||
|
"hue_range": [0, 360],
|
||||||
|
"saturation_range": [0, 100],
|
||||||
|
"brightness_range": [0, 100],
|
||||||
|
"transition_range": [2000, 3000],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
strip.set_custom_effect.reset_mock()
|
||||||
|
|
||||||
|
|
||||||
async def test_smart_strip_custom_random_effect_at_start(hass: HomeAssistant) -> None:
|
async def test_smart_strip_custom_random_effect_at_start(hass: HomeAssistant) -> None:
|
||||||
"""Test smart strip custom random effects at startup."""
|
"""Test smart strip custom random effects at startup."""
|
||||||
already_migrated_config_entry = MockConfigEntry(
|
already_migrated_config_entry = MockConfigEntry(
|
||||||
@ -489,3 +634,50 @@ async def test_smart_strip_custom_random_effect_at_start(hass: HomeAssistant) ->
|
|||||||
)
|
)
|
||||||
strip.set_hsv.assert_called_with(0, 0, 100, transition=None)
|
strip.set_hsv.assert_called_with(0, 0, 100, transition=None)
|
||||||
strip.set_hsv.reset_mock()
|
strip.set_hsv.reset_mock()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_smart_strip_custom_sequence_effect(hass: HomeAssistant) -> None:
|
||||||
|
"""Test smart strip custom sequence effects."""
|
||||||
|
already_migrated_config_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN, data={}, unique_id=MAC_ADDRESS
|
||||||
|
)
|
||||||
|
already_migrated_config_entry.add_to_hass(hass)
|
||||||
|
strip = _mocked_smart_light_strip()
|
||||||
|
|
||||||
|
with _patch_discovery(device=strip), _patch_single_discovery(device=strip):
|
||||||
|
await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
entity_id = "light.my_bulb"
|
||||||
|
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state.state == STATE_ON
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
"sequence_effect",
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: entity_id,
|
||||||
|
"sequence": [[340, 20, 50], [20, 50, 50], [0, 100, 50]],
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
strip.set_custom_effect.assert_called_once_with(
|
||||||
|
{
|
||||||
|
"custom": 1,
|
||||||
|
"id": "yMwcNpLxijmoKamskHCvvravpbnIqAIN",
|
||||||
|
"brightness": 100,
|
||||||
|
"name": "Custom",
|
||||||
|
"segments": [0],
|
||||||
|
"expansion_strategy": 1,
|
||||||
|
"enable": 1,
|
||||||
|
"duration": 0,
|
||||||
|
"transition": 0,
|
||||||
|
"type": "sequence",
|
||||||
|
"sequence": [(340, 20, 50), (20, 50, 50), (0, 100, 50)],
|
||||||
|
"repeat_times": 0,
|
||||||
|
"spread": 1,
|
||||||
|
"direction": 4,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
strip.set_custom_effect.reset_mock()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user