mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 08:47:57 +00:00
New paint_theme service added to the LIFX integration (#135667)
* New paint_theme service added to the LIFX integration Signed-off-by: Avi Miller <me@dje.li> Co-authored-by: J. Nick Koston <nick@koston.org> * Move effect selection into a dispatch table Signed-off-by: Avi Miller <me@dje.li> --------- Signed-off-by: Avi Miller <me@dje.li> Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
632c166201
commit
02ec1d1b71
@ -26,6 +26,9 @@
|
||||
},
|
||||
"effect_stop": {
|
||||
"service": "mdi:stop"
|
||||
},
|
||||
"paint_theme": {
|
||||
"service": "mdi:palette"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ from datetime import timedelta
|
||||
from typing import Any
|
||||
|
||||
import aiolifx_effects
|
||||
from aiolifx_themes.painter import ThemePainter
|
||||
from aiolifx_themes.themes import Theme, ThemeLibrary
|
||||
import voluptuous as vol
|
||||
|
||||
@ -42,6 +43,7 @@ SERVICE_EFFECT_MOVE = "effect_move"
|
||||
SERVICE_EFFECT_PULSE = "effect_pulse"
|
||||
SERVICE_EFFECT_SKY = "effect_sky"
|
||||
SERVICE_EFFECT_STOP = "effect_stop"
|
||||
SERVICE_PAINT_THEME = "paint_theme"
|
||||
|
||||
ATTR_CHANGE = "change"
|
||||
ATTR_CLOUD_SATURATION_MIN = "cloud_saturation_min"
|
||||
@ -83,6 +85,8 @@ EFFECT_SKY_DEFAULT_CLOUD_SATURATION_MAX = 180
|
||||
|
||||
EFFECT_SKY_SKY_TYPES = ["Sunrise", "Sunset", "Clouds"]
|
||||
|
||||
PAINT_THEME_DEFAULT_TRANSITION = 1
|
||||
|
||||
PULSE_MODE_BLINK = "blink"
|
||||
PULSE_MODE_BREATHE = "breathe"
|
||||
PULSE_MODE_PING = "ping"
|
||||
@ -201,6 +205,18 @@ LIFX_EFFECT_SKY_SCHEMA = cv.make_entity_service_schema(
|
||||
}
|
||||
)
|
||||
|
||||
LIFX_PAINT_THEME_SCHEMA = cv.make_entity_service_schema(
|
||||
{
|
||||
**LIFX_EFFECT_SCHEMA,
|
||||
ATTR_TRANSITION: vol.All(vol.Coerce(int), vol.Clamp(min=1, max=3600)),
|
||||
vol.Exclusive(ATTR_THEME, COLOR_GROUP): vol.Optional(
|
||||
vol.In(ThemeLibrary().themes)
|
||||
),
|
||||
vol.Exclusive(ATTR_PALETTE, COLOR_GROUP): vol.All(
|
||||
cv.ensure_list, [HSBK_SCHEMA]
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
SERVICES = (
|
||||
SERVICE_EFFECT_COLORLOOP,
|
||||
@ -210,6 +226,7 @@ SERVICES = (
|
||||
SERVICE_EFFECT_PULSE,
|
||||
SERVICE_EFFECT_SKY,
|
||||
SERVICE_EFFECT_STOP,
|
||||
SERVICE_PAINT_THEME,
|
||||
)
|
||||
|
||||
|
||||
@ -302,6 +319,222 @@ class LIFXManager:
|
||||
schema=LIFX_EFFECT_STOP_SCHEMA,
|
||||
)
|
||||
|
||||
self.hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_PAINT_THEME,
|
||||
service_handler,
|
||||
schema=LIFX_PAINT_THEME_SCHEMA,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def build_theme(theme_name: str = "exciting", palette: list | None = None) -> Theme:
|
||||
"""Either return the predefined theme or build one from the palette."""
|
||||
if palette is not None:
|
||||
theme = Theme()
|
||||
for hsbk in palette:
|
||||
theme.add_hsbk(hsbk[0], hsbk[1], hsbk[2], hsbk[3])
|
||||
else:
|
||||
theme = ThemeLibrary().get_theme(theme_name)
|
||||
|
||||
return theme
|
||||
|
||||
async def _start_effect_flame(
|
||||
self,
|
||||
bulbs: list[Light],
|
||||
coordinators: list[LIFXUpdateCoordinator],
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Start the firmware-based Flame effect."""
|
||||
|
||||
await asyncio.gather(
|
||||
*(
|
||||
coordinator.async_set_matrix_effect(
|
||||
effect=EFFECT_FLAME,
|
||||
speed=kwargs.get(ATTR_SPEED, EFFECT_FLAME_DEFAULT_SPEED),
|
||||
power_on=kwargs.get(ATTR_POWER_ON, True),
|
||||
)
|
||||
for coordinator in coordinators
|
||||
)
|
||||
)
|
||||
|
||||
async def _start_paint_theme(
|
||||
self,
|
||||
bulbs: list[Light],
|
||||
coordinators: list[LIFXUpdateCoordinator],
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Paint a theme across one or more LIFX bulbs."""
|
||||
theme_name = kwargs.get(ATTR_THEME, "exciting")
|
||||
palette = kwargs.get(ATTR_PALETTE)
|
||||
|
||||
theme = self.build_theme(theme_name, palette)
|
||||
|
||||
await ThemePainter(self.hass.loop).paint(
|
||||
theme,
|
||||
bulbs,
|
||||
duration=kwargs.get(ATTR_TRANSITION, PAINT_THEME_DEFAULT_TRANSITION),
|
||||
power_on=kwargs.get(ATTR_POWER_ON, True),
|
||||
)
|
||||
|
||||
async def _start_effect_morph(
|
||||
self,
|
||||
bulbs: list[Light],
|
||||
coordinators: list[LIFXUpdateCoordinator],
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Start the firmware-based Morph effect."""
|
||||
theme_name = kwargs.get(ATTR_THEME, "exciting")
|
||||
palette = kwargs.get(ATTR_PALETTE)
|
||||
|
||||
theme = self.build_theme(theme_name, palette)
|
||||
|
||||
await asyncio.gather(
|
||||
*(
|
||||
coordinator.async_set_matrix_effect(
|
||||
effect=EFFECT_MORPH,
|
||||
speed=kwargs.get(ATTR_SPEED, EFFECT_MORPH_DEFAULT_SPEED),
|
||||
palette=theme.colors,
|
||||
power_on=kwargs.get(ATTR_POWER_ON, True),
|
||||
)
|
||||
for coordinator in coordinators
|
||||
)
|
||||
)
|
||||
|
||||
async def _start_effect_move(
|
||||
self,
|
||||
bulbs: list[Light],
|
||||
coordinators: list[LIFXUpdateCoordinator],
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Start the firmware-based Move effect."""
|
||||
await asyncio.gather(
|
||||
*(
|
||||
coordinator.async_set_multizone_effect(
|
||||
effect=EFFECT_MOVE,
|
||||
speed=kwargs.get(ATTR_SPEED, EFFECT_MOVE_DEFAULT_SPEED),
|
||||
direction=kwargs.get(ATTR_DIRECTION, EFFECT_MOVE_DEFAULT_DIRECTION),
|
||||
theme_name=kwargs.get(ATTR_THEME),
|
||||
power_on=kwargs.get(ATTR_POWER_ON, False),
|
||||
)
|
||||
for coordinator in coordinators
|
||||
)
|
||||
)
|
||||
|
||||
async def _start_effect_pulse(
|
||||
self,
|
||||
bulbs: list[Light],
|
||||
coordinators: list[LIFXUpdateCoordinator],
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Start the software-based Pulse effect."""
|
||||
effect = aiolifx_effects.EffectPulse(
|
||||
power_on=bool(kwargs.get(ATTR_POWER_ON)),
|
||||
period=kwargs.get(ATTR_PERIOD),
|
||||
cycles=kwargs.get(ATTR_CYCLES),
|
||||
mode=kwargs.get(ATTR_MODE),
|
||||
hsbk=find_hsbk(self.hass, **kwargs),
|
||||
)
|
||||
await self.effects_conductor.start(effect, bulbs)
|
||||
|
||||
async def _start_effect_colorloop(
|
||||
self,
|
||||
bulbs: list[Light],
|
||||
coordinators: list[LIFXUpdateCoordinator],
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Start the software based Color Loop effect."""
|
||||
brightness = None
|
||||
saturation_max = None
|
||||
saturation_min = None
|
||||
|
||||
if ATTR_BRIGHTNESS in kwargs:
|
||||
brightness = convert_8_to_16(kwargs[ATTR_BRIGHTNESS])
|
||||
elif ATTR_BRIGHTNESS_PCT in kwargs:
|
||||
brightness = convert_8_to_16(round(255 * kwargs[ATTR_BRIGHTNESS_PCT] / 100))
|
||||
|
||||
if ATTR_SATURATION_MAX in kwargs:
|
||||
saturation_max = int(kwargs[ATTR_SATURATION_MAX] / 100 * 65535)
|
||||
|
||||
if ATTR_SATURATION_MIN in kwargs:
|
||||
saturation_min = int(kwargs[ATTR_SATURATION_MIN] / 100 * 65535)
|
||||
|
||||
effect = aiolifx_effects.EffectColorloop(
|
||||
power_on=bool(kwargs.get(ATTR_POWER_ON)),
|
||||
period=kwargs.get(ATTR_PERIOD),
|
||||
change=kwargs.get(ATTR_CHANGE),
|
||||
spread=kwargs.get(ATTR_SPREAD),
|
||||
transition=kwargs.get(ATTR_TRANSITION),
|
||||
brightness=brightness,
|
||||
saturation_max=saturation_max,
|
||||
saturation_min=saturation_min,
|
||||
)
|
||||
await self.effects_conductor.start(effect, bulbs)
|
||||
|
||||
async def _start_effect_sky(
|
||||
self,
|
||||
bulbs: list[Light],
|
||||
coordinators: list[LIFXUpdateCoordinator],
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Start the firmware-based Sky effect."""
|
||||
palette = kwargs.get(ATTR_PALETTE)
|
||||
if palette is not None:
|
||||
theme = Theme()
|
||||
for hsbk in palette:
|
||||
theme.add_hsbk(hsbk[0], hsbk[1], hsbk[2], hsbk[3])
|
||||
|
||||
speed = kwargs.get(ATTR_SPEED, EFFECT_SKY_DEFAULT_SPEED)
|
||||
sky_type = kwargs.get(ATTR_SKY_TYPE, EFFECT_SKY_DEFAULT_SKY_TYPE)
|
||||
|
||||
cloud_saturation_min = kwargs.get(
|
||||
ATTR_CLOUD_SATURATION_MIN,
|
||||
EFFECT_SKY_DEFAULT_CLOUD_SATURATION_MIN,
|
||||
)
|
||||
cloud_saturation_max = kwargs.get(
|
||||
ATTR_CLOUD_SATURATION_MAX,
|
||||
EFFECT_SKY_DEFAULT_CLOUD_SATURATION_MAX,
|
||||
)
|
||||
|
||||
await asyncio.gather(
|
||||
*(
|
||||
coordinator.async_set_matrix_effect(
|
||||
effect=EFFECT_SKY,
|
||||
speed=speed,
|
||||
sky_type=sky_type,
|
||||
cloud_saturation_min=cloud_saturation_min,
|
||||
cloud_saturation_max=cloud_saturation_max,
|
||||
palette=theme.colors,
|
||||
)
|
||||
for coordinator in coordinators
|
||||
)
|
||||
)
|
||||
|
||||
async def _start_effect_stop(
|
||||
self,
|
||||
bulbs: list[Light],
|
||||
coordinators: list[LIFXUpdateCoordinator],
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Stop any running software or firmware effect."""
|
||||
await self.effects_conductor.stop(bulbs)
|
||||
|
||||
for coordinator in coordinators:
|
||||
await coordinator.async_set_matrix_effect(effect=EFFECT_OFF, power_on=False)
|
||||
await coordinator.async_set_multizone_effect(
|
||||
effect=EFFECT_OFF, power_on=False
|
||||
)
|
||||
|
||||
_effect_dispatch = {
|
||||
SERVICE_EFFECT_COLORLOOP: _start_effect_colorloop,
|
||||
SERVICE_EFFECT_FLAME: _start_effect_flame,
|
||||
SERVICE_EFFECT_MORPH: _start_effect_morph,
|
||||
SERVICE_EFFECT_MOVE: _start_effect_move,
|
||||
SERVICE_EFFECT_PULSE: _start_effect_pulse,
|
||||
SERVICE_EFFECT_SKY: _start_effect_sky,
|
||||
SERVICE_EFFECT_STOP: _start_effect_stop,
|
||||
SERVICE_PAINT_THEME: _start_paint_theme,
|
||||
}
|
||||
|
||||
async def start_effect(
|
||||
self, entity_ids: set[str], service: str, **kwargs: Any
|
||||
) -> None:
|
||||
@ -318,137 +551,5 @@ class LIFXManager:
|
||||
coordinators.append(coordinator)
|
||||
bulbs.append(coordinator.device)
|
||||
|
||||
if service == SERVICE_EFFECT_FLAME:
|
||||
await asyncio.gather(
|
||||
*(
|
||||
coordinator.async_set_matrix_effect(
|
||||
effect=EFFECT_FLAME,
|
||||
speed=kwargs.get(ATTR_SPEED, EFFECT_FLAME_DEFAULT_SPEED),
|
||||
power_on=kwargs.get(ATTR_POWER_ON, True),
|
||||
)
|
||||
for coordinator in coordinators
|
||||
)
|
||||
)
|
||||
|
||||
elif service == SERVICE_EFFECT_MORPH:
|
||||
theme_name = kwargs.get(ATTR_THEME, "exciting")
|
||||
palette = kwargs.get(ATTR_PALETTE)
|
||||
|
||||
if palette is not None:
|
||||
theme = Theme()
|
||||
for hsbk in palette:
|
||||
theme.add_hsbk(hsbk[0], hsbk[1], hsbk[2], hsbk[3])
|
||||
else:
|
||||
theme = ThemeLibrary().get_theme(theme_name)
|
||||
|
||||
await asyncio.gather(
|
||||
*(
|
||||
coordinator.async_set_matrix_effect(
|
||||
effect=EFFECT_MORPH,
|
||||
speed=kwargs.get(ATTR_SPEED, EFFECT_MORPH_DEFAULT_SPEED),
|
||||
palette=theme.colors,
|
||||
power_on=kwargs.get(ATTR_POWER_ON, True),
|
||||
)
|
||||
for coordinator in coordinators
|
||||
)
|
||||
)
|
||||
|
||||
elif service == SERVICE_EFFECT_MOVE:
|
||||
await asyncio.gather(
|
||||
*(
|
||||
coordinator.async_set_multizone_effect(
|
||||
effect=EFFECT_MOVE,
|
||||
speed=kwargs.get(ATTR_SPEED, EFFECT_MOVE_DEFAULT_SPEED),
|
||||
direction=kwargs.get(
|
||||
ATTR_DIRECTION, EFFECT_MOVE_DEFAULT_DIRECTION
|
||||
),
|
||||
theme_name=kwargs.get(ATTR_THEME),
|
||||
power_on=kwargs.get(ATTR_POWER_ON, False),
|
||||
)
|
||||
for coordinator in coordinators
|
||||
)
|
||||
)
|
||||
|
||||
elif service == SERVICE_EFFECT_PULSE:
|
||||
effect = aiolifx_effects.EffectPulse(
|
||||
power_on=kwargs.get(ATTR_POWER_ON),
|
||||
period=kwargs.get(ATTR_PERIOD),
|
||||
cycles=kwargs.get(ATTR_CYCLES),
|
||||
mode=kwargs.get(ATTR_MODE),
|
||||
hsbk=find_hsbk(self.hass, **kwargs),
|
||||
)
|
||||
await self.effects_conductor.start(effect, bulbs)
|
||||
|
||||
elif service == SERVICE_EFFECT_COLORLOOP:
|
||||
brightness = None
|
||||
saturation_max = None
|
||||
saturation_min = None
|
||||
|
||||
if ATTR_BRIGHTNESS in kwargs:
|
||||
brightness = convert_8_to_16(kwargs[ATTR_BRIGHTNESS])
|
||||
elif ATTR_BRIGHTNESS_PCT in kwargs:
|
||||
brightness = convert_8_to_16(
|
||||
round(255 * kwargs[ATTR_BRIGHTNESS_PCT] / 100)
|
||||
)
|
||||
|
||||
if ATTR_SATURATION_MAX in kwargs:
|
||||
saturation_max = int(kwargs[ATTR_SATURATION_MAX] / 100 * 65535)
|
||||
|
||||
if ATTR_SATURATION_MIN in kwargs:
|
||||
saturation_min = int(kwargs[ATTR_SATURATION_MIN] / 100 * 65535)
|
||||
|
||||
effect = aiolifx_effects.EffectColorloop(
|
||||
power_on=kwargs.get(ATTR_POWER_ON),
|
||||
period=kwargs.get(ATTR_PERIOD),
|
||||
change=kwargs.get(ATTR_CHANGE),
|
||||
spread=kwargs.get(ATTR_SPREAD),
|
||||
transition=kwargs.get(ATTR_TRANSITION),
|
||||
brightness=brightness,
|
||||
saturation_max=saturation_max,
|
||||
saturation_min=saturation_min,
|
||||
)
|
||||
await self.effects_conductor.start(effect, bulbs)
|
||||
|
||||
elif service == SERVICE_EFFECT_SKY:
|
||||
palette = kwargs.get(ATTR_PALETTE)
|
||||
if palette is not None:
|
||||
theme = Theme()
|
||||
for hsbk in palette:
|
||||
theme.add_hsbk(hsbk[0], hsbk[1], hsbk[2], hsbk[3])
|
||||
|
||||
speed = kwargs.get(ATTR_SPEED, EFFECT_SKY_DEFAULT_SPEED)
|
||||
sky_type = kwargs.get(ATTR_SKY_TYPE, EFFECT_SKY_DEFAULT_SKY_TYPE)
|
||||
|
||||
cloud_saturation_min = kwargs.get(
|
||||
ATTR_CLOUD_SATURATION_MIN,
|
||||
EFFECT_SKY_DEFAULT_CLOUD_SATURATION_MIN,
|
||||
)
|
||||
cloud_saturation_max = kwargs.get(
|
||||
ATTR_CLOUD_SATURATION_MAX,
|
||||
EFFECT_SKY_DEFAULT_CLOUD_SATURATION_MAX,
|
||||
)
|
||||
|
||||
await asyncio.gather(
|
||||
*(
|
||||
coordinator.async_set_matrix_effect(
|
||||
effect=EFFECT_SKY,
|
||||
speed=speed,
|
||||
sky_type=sky_type,
|
||||
cloud_saturation_min=cloud_saturation_min,
|
||||
cloud_saturation_max=cloud_saturation_max,
|
||||
palette=theme.colors,
|
||||
)
|
||||
for coordinator in coordinators
|
||||
)
|
||||
)
|
||||
|
||||
elif service == SERVICE_EFFECT_STOP:
|
||||
await self.effects_conductor.stop(bulbs)
|
||||
|
||||
for coordinator in coordinators:
|
||||
await coordinator.async_set_matrix_effect(
|
||||
effect=EFFECT_OFF, power_on=False
|
||||
)
|
||||
await coordinator.async_set_multizone_effect(
|
||||
effect=EFFECT_OFF, power_on=False
|
||||
)
|
||||
if start_effect_func := self._effect_dispatch.get(service):
|
||||
await start_effect_func(self, bulbs, coordinators, **kwargs)
|
||||
|
@ -186,28 +186,46 @@ effect_move:
|
||||
options:
|
||||
- "autumn"
|
||||
- "blissful"
|
||||
- "bias_lighting"
|
||||
- "calaveras"
|
||||
- "cheerful"
|
||||
- "christmas"
|
||||
- "dream"
|
||||
- "energizing"
|
||||
- "epic"
|
||||
- "evening"
|
||||
- "exciting"
|
||||
- "fantasy"
|
||||
- "focusing"
|
||||
- "gentle"
|
||||
- "halloween"
|
||||
- "hanukkah"
|
||||
- "holly"
|
||||
- "independence_day"
|
||||
- "hygge"
|
||||
- "independence"
|
||||
- "intense"
|
||||
- "love"
|
||||
- "kwanzaa"
|
||||
- "mellow"
|
||||
- "party"
|
||||
- "peaceful"
|
||||
- "powerful"
|
||||
- "proud"
|
||||
- "pumpkin"
|
||||
- "relaxing"
|
||||
- "romance"
|
||||
- "santa"
|
||||
- "serene"
|
||||
- "shamrock"
|
||||
- "soothing"
|
||||
- "spacey"
|
||||
- "sports"
|
||||
- "spring"
|
||||
- "stardust"
|
||||
- "thanksgiving"
|
||||
- "tranquil"
|
||||
- "warming"
|
||||
- "zombie"
|
||||
power_on:
|
||||
default: true
|
||||
selector:
|
||||
@ -255,28 +273,46 @@ effect_morph:
|
||||
options:
|
||||
- "autumn"
|
||||
- "blissful"
|
||||
- "bias_lighting"
|
||||
- "calaveras"
|
||||
- "cheerful"
|
||||
- "christmas"
|
||||
- "dream"
|
||||
- "energizing"
|
||||
- "epic"
|
||||
- "evening"
|
||||
- "exciting"
|
||||
- "fantasy"
|
||||
- "focusing"
|
||||
- "gentle"
|
||||
- "halloween"
|
||||
- "hanukkah"
|
||||
- "holly"
|
||||
- "independence_day"
|
||||
- "hygge"
|
||||
- "independence"
|
||||
- "intense"
|
||||
- "love"
|
||||
- "kwanzaa"
|
||||
- "mellow"
|
||||
- "party"
|
||||
- "peaceful"
|
||||
- "powerful"
|
||||
- "proud"
|
||||
- "pumpkin"
|
||||
- "relaxing"
|
||||
- "romance"
|
||||
- "santa"
|
||||
- "serene"
|
||||
- "shamrock"
|
||||
- "soothing"
|
||||
- "spacey"
|
||||
- "sports"
|
||||
- "spring"
|
||||
- "stardust"
|
||||
- "thanksgiving"
|
||||
- "tranquil"
|
||||
- "warming"
|
||||
- "zombie"
|
||||
power_on:
|
||||
default: true
|
||||
selector:
|
||||
@ -338,3 +374,73 @@ effect_stop:
|
||||
entity:
|
||||
integration: lifx
|
||||
domain: light
|
||||
paint_theme:
|
||||
target:
|
||||
entity:
|
||||
integration: lifx
|
||||
domain: light
|
||||
fields:
|
||||
palette:
|
||||
example:
|
||||
- "[[0, 100, 100, 3500], [60, 100, 100, 3500]]"
|
||||
selector:
|
||||
object:
|
||||
theme:
|
||||
example: exciting
|
||||
default: exciting
|
||||
selector:
|
||||
select:
|
||||
mode: dropdown
|
||||
options:
|
||||
- "autumn"
|
||||
- "blissful"
|
||||
- "bias_lighting"
|
||||
- "calaveras"
|
||||
- "cheerful"
|
||||
- "christmas"
|
||||
- "dream"
|
||||
- "energizing"
|
||||
- "epic"
|
||||
- "evening"
|
||||
- "exciting"
|
||||
- "fantasy"
|
||||
- "focusing"
|
||||
- "gentle"
|
||||
- "halloween"
|
||||
- "hanukkah"
|
||||
- "holly"
|
||||
- "hygge"
|
||||
- "independence"
|
||||
- "intense"
|
||||
- "love"
|
||||
- "kwanzaa"
|
||||
- "mellow"
|
||||
- "party"
|
||||
- "peaceful"
|
||||
- "powerful"
|
||||
- "proud"
|
||||
- "pumpkin"
|
||||
- "relaxing"
|
||||
- "romance"
|
||||
- "santa"
|
||||
- "serene"
|
||||
- "shamrock"
|
||||
- "soothing"
|
||||
- "spacey"
|
||||
- "sports"
|
||||
- "spring"
|
||||
- "stardust"
|
||||
- "thanksgiving"
|
||||
- "tranquil"
|
||||
- "warming"
|
||||
- "zombie"
|
||||
transition:
|
||||
selector:
|
||||
number:
|
||||
min: 0
|
||||
max: 3600
|
||||
unit_of_measurement: seconds
|
||||
power_on:
|
||||
default: true
|
||||
selector:
|
||||
boolean:
|
||||
|
@ -209,7 +209,7 @@
|
||||
},
|
||||
"palette": {
|
||||
"name": "Palette",
|
||||
"description": "List of at least 2 and at most 16 colors as hue (0-360), saturation (0-100), brightness (0-100) and kelvin (1500-900) values to use for this effect. Overrides the theme attribute."
|
||||
"description": "List of at least 2 and at most 16 colors as hue (0-360), saturation (0-100), brightness (0-100) and kelvin (1500-9000) values to use for this effect. Overrides the theme attribute."
|
||||
},
|
||||
"theme": {
|
||||
"name": "[%key:component::lifx::entity::select::theme::name%]",
|
||||
@ -254,6 +254,28 @@
|
||||
"effect_stop": {
|
||||
"name": "Stop effect",
|
||||
"description": "Stops a running effect."
|
||||
},
|
||||
"paint_theme": {
|
||||
"name": "Paint Theme",
|
||||
"description": "Paint either a provided theme or custom palette across one or more LIFX lights.",
|
||||
"fields": {
|
||||
"palette": {
|
||||
"name": "Palette",
|
||||
"description": "List of at least 2 and at most 16 colors as hue (0-360), saturation (0-100), brightness (0-100) and kelvin (1500-9000) values to paint across the target lights. Overrides the theme attribute."
|
||||
},
|
||||
"theme": {
|
||||
"name": "[%key:component::lifx::entity::select::theme::name%]",
|
||||
"description": "Predefined color theme to paint. Overridden by the palette attribute."
|
||||
},
|
||||
"transition": {
|
||||
"name": "Transition",
|
||||
"description": "Duration in seconds to paint the theme."
|
||||
},
|
||||
"power_on": {
|
||||
"name": "Power on",
|
||||
"description": "Powered off lights will be turned on before painting the theme."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ from homeassistant.components.lifx.manager import (
|
||||
SERVICE_EFFECT_MORPH,
|
||||
SERVICE_EFFECT_MOVE,
|
||||
SERVICE_EFFECT_SKY,
|
||||
SERVICE_PAINT_THEME,
|
||||
)
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
@ -1045,6 +1046,104 @@ async def test_lightstrip_move_effect(hass: HomeAssistant) -> None:
|
||||
bulb.set_power.reset_mock()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_discovery")
|
||||
async def test_paint_theme_service(hass: HomeAssistant) -> None:
|
||||
"""Test the firmware flame and morph effects on a matrix device."""
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
bulb = _mocked_bulb()
|
||||
bulb.power_level = 0
|
||||
bulb.color = [65535, 65535, 65535, 65535]
|
||||
with (
|
||||
_patch_discovery(device=bulb),
|
||||
_patch_config_flow_try_connect(device=bulb),
|
||||
_patch_device(device=bulb),
|
||||
):
|
||||
await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity_id = "light.my_bulb"
|
||||
|
||||
bulb.power_level = 0
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_PAINT_THEME,
|
||||
{ATTR_ENTITY_ID: entity_id, ATTR_TRANSITION: 4, ATTR_THEME: "autumn"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
bulb.power_level = 65535
|
||||
|
||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30))
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == STATE_ON
|
||||
|
||||
assert len(bulb.set_power.calls) == 1
|
||||
assert len(bulb.set_color.calls) == 1
|
||||
call_dict = bulb.set_color.calls[0][1]
|
||||
call_dict.pop("callb")
|
||||
assert call_dict["value"] in [
|
||||
(5643, 65535, 32768, 3500),
|
||||
(15109, 65535, 32768, 3500),
|
||||
(8920, 65535, 32768, 3500),
|
||||
(10558, 65535, 32768, 3500),
|
||||
]
|
||||
assert call_dict["duration"] == 4000
|
||||
bulb.set_color.reset_mock()
|
||||
bulb.set_power.reset_mock()
|
||||
|
||||
bulb.power_level = 0
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_PAINT_THEME,
|
||||
{
|
||||
ATTR_ENTITY_ID: entity_id,
|
||||
ATTR_TRANSITION: 6,
|
||||
ATTR_PALETTE: [
|
||||
(0, 100, 255, 3500),
|
||||
(60, 100, 255, 3500),
|
||||
(120, 100, 255, 3500),
|
||||
(180, 100, 255, 3500),
|
||||
(240, 100, 255, 3500),
|
||||
(300, 100, 255, 3500),
|
||||
],
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
bulb.power_level = 65535
|
||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30))
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == STATE_ON
|
||||
|
||||
assert len(bulb.set_power.calls) == 1
|
||||
assert len(bulb.set_color.calls) == 1
|
||||
call_dict = bulb.set_color.calls[0][1]
|
||||
call_dict.pop("callb")
|
||||
hue = round(call_dict["value"][0] / 65535 * 360)
|
||||
sat = round(call_dict["value"][1] / 65535 * 100)
|
||||
bri = call_dict["value"][2] >> 8
|
||||
kel = call_dict["value"][3]
|
||||
assert (hue, sat, bri, kel) in [
|
||||
(0, 100, 255, 3500),
|
||||
(60, 100, 255, 3500),
|
||||
(120, 100, 255, 3500),
|
||||
(180, 100, 255, 3500),
|
||||
(240, 100, 255, 3500),
|
||||
(300, 100, 255, 3500),
|
||||
]
|
||||
assert call_dict["duration"] == 6000
|
||||
|
||||
bulb.set_color.reset_mock()
|
||||
bulb.set_power.reset_mock()
|
||||
|
||||
|
||||
async def test_color_light_with_temp(
|
||||
hass: HomeAssistant, mock_effect_conductor
|
||||
) -> None:
|
||||
|
Loading…
x
Reference in New Issue
Block a user