mirror of
https://github.com/home-assistant/core.git
synced 2025-07-14 16:57:10 +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": {
|
"effect_stop": {
|
||||||
"service": "mdi:stop"
|
"service": "mdi:stop"
|
||||||
|
},
|
||||||
|
"paint_theme": {
|
||||||
|
"service": "mdi:palette"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ from datetime import timedelta
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import aiolifx_effects
|
import aiolifx_effects
|
||||||
|
from aiolifx_themes.painter import ThemePainter
|
||||||
from aiolifx_themes.themes import Theme, ThemeLibrary
|
from aiolifx_themes.themes import Theme, ThemeLibrary
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
@ -42,6 +43,7 @@ SERVICE_EFFECT_MOVE = "effect_move"
|
|||||||
SERVICE_EFFECT_PULSE = "effect_pulse"
|
SERVICE_EFFECT_PULSE = "effect_pulse"
|
||||||
SERVICE_EFFECT_SKY = "effect_sky"
|
SERVICE_EFFECT_SKY = "effect_sky"
|
||||||
SERVICE_EFFECT_STOP = "effect_stop"
|
SERVICE_EFFECT_STOP = "effect_stop"
|
||||||
|
SERVICE_PAINT_THEME = "paint_theme"
|
||||||
|
|
||||||
ATTR_CHANGE = "change"
|
ATTR_CHANGE = "change"
|
||||||
ATTR_CLOUD_SATURATION_MIN = "cloud_saturation_min"
|
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"]
|
EFFECT_SKY_SKY_TYPES = ["Sunrise", "Sunset", "Clouds"]
|
||||||
|
|
||||||
|
PAINT_THEME_DEFAULT_TRANSITION = 1
|
||||||
|
|
||||||
PULSE_MODE_BLINK = "blink"
|
PULSE_MODE_BLINK = "blink"
|
||||||
PULSE_MODE_BREATHE = "breathe"
|
PULSE_MODE_BREATHE = "breathe"
|
||||||
PULSE_MODE_PING = "ping"
|
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 = (
|
SERVICES = (
|
||||||
SERVICE_EFFECT_COLORLOOP,
|
SERVICE_EFFECT_COLORLOOP,
|
||||||
@ -210,6 +226,7 @@ SERVICES = (
|
|||||||
SERVICE_EFFECT_PULSE,
|
SERVICE_EFFECT_PULSE,
|
||||||
SERVICE_EFFECT_SKY,
|
SERVICE_EFFECT_SKY,
|
||||||
SERVICE_EFFECT_STOP,
|
SERVICE_EFFECT_STOP,
|
||||||
|
SERVICE_PAINT_THEME,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -302,6 +319,222 @@ class LIFXManager:
|
|||||||
schema=LIFX_EFFECT_STOP_SCHEMA,
|
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(
|
async def start_effect(
|
||||||
self, entity_ids: set[str], service: str, **kwargs: Any
|
self, entity_ids: set[str], service: str, **kwargs: Any
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -318,137 +551,5 @@ class LIFXManager:
|
|||||||
coordinators.append(coordinator)
|
coordinators.append(coordinator)
|
||||||
bulbs.append(coordinator.device)
|
bulbs.append(coordinator.device)
|
||||||
|
|
||||||
if service == SERVICE_EFFECT_FLAME:
|
if start_effect_func := self._effect_dispatch.get(service):
|
||||||
await asyncio.gather(
|
await start_effect_func(self, bulbs, coordinators, **kwargs)
|
||||||
*(
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
@ -186,28 +186,46 @@ effect_move:
|
|||||||
options:
|
options:
|
||||||
- "autumn"
|
- "autumn"
|
||||||
- "blissful"
|
- "blissful"
|
||||||
|
- "bias_lighting"
|
||||||
|
- "calaveras"
|
||||||
- "cheerful"
|
- "cheerful"
|
||||||
|
- "christmas"
|
||||||
- "dream"
|
- "dream"
|
||||||
- "energizing"
|
- "energizing"
|
||||||
- "epic"
|
- "epic"
|
||||||
|
- "evening"
|
||||||
- "exciting"
|
- "exciting"
|
||||||
|
- "fantasy"
|
||||||
- "focusing"
|
- "focusing"
|
||||||
|
- "gentle"
|
||||||
- "halloween"
|
- "halloween"
|
||||||
- "hanukkah"
|
- "hanukkah"
|
||||||
- "holly"
|
- "holly"
|
||||||
- "independence_day"
|
- "hygge"
|
||||||
|
- "independence"
|
||||||
- "intense"
|
- "intense"
|
||||||
|
- "love"
|
||||||
|
- "kwanzaa"
|
||||||
- "mellow"
|
- "mellow"
|
||||||
|
- "party"
|
||||||
- "peaceful"
|
- "peaceful"
|
||||||
- "powerful"
|
- "powerful"
|
||||||
|
- "proud"
|
||||||
|
- "pumpkin"
|
||||||
- "relaxing"
|
- "relaxing"
|
||||||
|
- "romance"
|
||||||
- "santa"
|
- "santa"
|
||||||
- "serene"
|
- "serene"
|
||||||
|
- "shamrock"
|
||||||
- "soothing"
|
- "soothing"
|
||||||
|
- "spacey"
|
||||||
- "sports"
|
- "sports"
|
||||||
- "spring"
|
- "spring"
|
||||||
|
- "stardust"
|
||||||
|
- "thanksgiving"
|
||||||
- "tranquil"
|
- "tranquil"
|
||||||
- "warming"
|
- "warming"
|
||||||
|
- "zombie"
|
||||||
power_on:
|
power_on:
|
||||||
default: true
|
default: true
|
||||||
selector:
|
selector:
|
||||||
@ -255,28 +273,46 @@ effect_morph:
|
|||||||
options:
|
options:
|
||||||
- "autumn"
|
- "autumn"
|
||||||
- "blissful"
|
- "blissful"
|
||||||
|
- "bias_lighting"
|
||||||
|
- "calaveras"
|
||||||
- "cheerful"
|
- "cheerful"
|
||||||
|
- "christmas"
|
||||||
- "dream"
|
- "dream"
|
||||||
- "energizing"
|
- "energizing"
|
||||||
- "epic"
|
- "epic"
|
||||||
|
- "evening"
|
||||||
- "exciting"
|
- "exciting"
|
||||||
|
- "fantasy"
|
||||||
- "focusing"
|
- "focusing"
|
||||||
|
- "gentle"
|
||||||
- "halloween"
|
- "halloween"
|
||||||
- "hanukkah"
|
- "hanukkah"
|
||||||
- "holly"
|
- "holly"
|
||||||
- "independence_day"
|
- "hygge"
|
||||||
|
- "independence"
|
||||||
- "intense"
|
- "intense"
|
||||||
|
- "love"
|
||||||
|
- "kwanzaa"
|
||||||
- "mellow"
|
- "mellow"
|
||||||
|
- "party"
|
||||||
- "peaceful"
|
- "peaceful"
|
||||||
- "powerful"
|
- "powerful"
|
||||||
|
- "proud"
|
||||||
|
- "pumpkin"
|
||||||
- "relaxing"
|
- "relaxing"
|
||||||
|
- "romance"
|
||||||
- "santa"
|
- "santa"
|
||||||
- "serene"
|
- "serene"
|
||||||
|
- "shamrock"
|
||||||
- "soothing"
|
- "soothing"
|
||||||
|
- "spacey"
|
||||||
- "sports"
|
- "sports"
|
||||||
- "spring"
|
- "spring"
|
||||||
|
- "stardust"
|
||||||
|
- "thanksgiving"
|
||||||
- "tranquil"
|
- "tranquil"
|
||||||
- "warming"
|
- "warming"
|
||||||
|
- "zombie"
|
||||||
power_on:
|
power_on:
|
||||||
default: true
|
default: true
|
||||||
selector:
|
selector:
|
||||||
@ -338,3 +374,73 @@ effect_stop:
|
|||||||
entity:
|
entity:
|
||||||
integration: lifx
|
integration: lifx
|
||||||
domain: light
|
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": {
|
"palette": {
|
||||||
"name": "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": {
|
"theme": {
|
||||||
"name": "[%key:component::lifx::entity::select::theme::name%]",
|
"name": "[%key:component::lifx::entity::select::theme::name%]",
|
||||||
@ -254,6 +254,28 @@
|
|||||||
"effect_stop": {
|
"effect_stop": {
|
||||||
"name": "Stop effect",
|
"name": "Stop effect",
|
||||||
"description": "Stops a running 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_MORPH,
|
||||||
SERVICE_EFFECT_MOVE,
|
SERVICE_EFFECT_MOVE,
|
||||||
SERVICE_EFFECT_SKY,
|
SERVICE_EFFECT_SKY,
|
||||||
|
SERVICE_PAINT_THEME,
|
||||||
)
|
)
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
ATTR_BRIGHTNESS,
|
ATTR_BRIGHTNESS,
|
||||||
@ -1045,6 +1046,104 @@ async def test_lightstrip_move_effect(hass: HomeAssistant) -> None:
|
|||||||
bulb.set_power.reset_mock()
|
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(
|
async def test_color_light_with_temp(
|
||||||
hass: HomeAssistant, mock_effect_conductor
|
hass: HomeAssistant, mock_effect_conductor
|
||||||
) -> None:
|
) -> None:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user