mirror of
https://github.com/home-assistant/core.git
synced 2025-07-12 07:47:08 +00:00
Add effects feature to Hue lights (#68567)
This commit is contained in:
parent
8c10963bc0
commit
dbef90654f
@ -6,11 +6,13 @@ from typing import Any
|
|||||||
from aiohue import HueBridgeV2
|
from aiohue import HueBridgeV2
|
||||||
from aiohue.v2.controllers.events import EventType
|
from aiohue.v2.controllers.events import EventType
|
||||||
from aiohue.v2.controllers.lights import LightsController
|
from aiohue.v2.controllers.lights import LightsController
|
||||||
|
from aiohue.v2.models.feature import EffectStatus, TimedEffectStatus
|
||||||
from aiohue.v2.models.light import Light
|
from aiohue.v2.models.light import Light
|
||||||
|
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
ATTR_BRIGHTNESS,
|
ATTR_BRIGHTNESS,
|
||||||
ATTR_COLOR_TEMP,
|
ATTR_COLOR_TEMP,
|
||||||
|
ATTR_EFFECT,
|
||||||
ATTR_FLASH,
|
ATTR_FLASH,
|
||||||
ATTR_TRANSITION,
|
ATTR_TRANSITION,
|
||||||
ATTR_XY_COLOR,
|
ATTR_XY_COLOR,
|
||||||
@ -19,6 +21,7 @@ from homeassistant.components.light import (
|
|||||||
COLOR_MODE_ONOFF,
|
COLOR_MODE_ONOFF,
|
||||||
COLOR_MODE_XY,
|
COLOR_MODE_XY,
|
||||||
FLASH_SHORT,
|
FLASH_SHORT,
|
||||||
|
SUPPORT_EFFECT,
|
||||||
SUPPORT_FLASH,
|
SUPPORT_FLASH,
|
||||||
SUPPORT_TRANSITION,
|
SUPPORT_TRANSITION,
|
||||||
LightEntity,
|
LightEntity,
|
||||||
@ -36,6 +39,8 @@ from .helpers import (
|
|||||||
normalize_hue_transition,
|
normalize_hue_transition,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
EFFECT_NONE = "None"
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -86,6 +91,21 @@ class HueLight(HueBaseEntity, LightEntity):
|
|||||||
self._supported_color_modes.add(COLOR_MODE_BRIGHTNESS)
|
self._supported_color_modes.add(COLOR_MODE_BRIGHTNESS)
|
||||||
# support transition if brightness control
|
# support transition if brightness control
|
||||||
self._attr_supported_features |= SUPPORT_TRANSITION
|
self._attr_supported_features |= SUPPORT_TRANSITION
|
||||||
|
# get list of supported effects (combine effects and timed_effects)
|
||||||
|
self._attr_effect_list = []
|
||||||
|
if effects := resource.effects:
|
||||||
|
self._attr_effect_list = [
|
||||||
|
x.value for x in effects.status_values if x != EffectStatus.NO_EFFECT
|
||||||
|
]
|
||||||
|
if timed_effects := resource.timed_effects:
|
||||||
|
self._attr_effect_list += [
|
||||||
|
x.value
|
||||||
|
for x in timed_effects.status_values
|
||||||
|
if x != TimedEffectStatus.NO_EFFECT
|
||||||
|
]
|
||||||
|
if len(self._attr_effect_list) > 0:
|
||||||
|
self._attr_effect_list.insert(0, EFFECT_NONE)
|
||||||
|
self._attr_supported_features |= SUPPORT_EFFECT
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def brightness(self) -> int | None:
|
def brightness(self) -> int | None:
|
||||||
@ -155,6 +175,17 @@ class HueLight(HueBaseEntity, LightEntity):
|
|||||||
"dynamics": self.resource.dynamics.status.value,
|
"dynamics": self.resource.dynamics.status.value,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def effect(self) -> str | None:
|
||||||
|
"""Return the current effect."""
|
||||||
|
if effects := self.resource.effects:
|
||||||
|
if effects.status != EffectStatus.NO_EFFECT:
|
||||||
|
return effects.status.value
|
||||||
|
if timed_effects := self.resource.timed_effects:
|
||||||
|
if timed_effects.status != TimedEffectStatus.NO_EFFECT:
|
||||||
|
return timed_effects.status.value
|
||||||
|
return EFFECT_NONE
|
||||||
|
|
||||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Turn the device on."""
|
"""Turn the device on."""
|
||||||
transition = normalize_hue_transition(kwargs.get(ATTR_TRANSITION))
|
transition = normalize_hue_transition(kwargs.get(ATTR_TRANSITION))
|
||||||
@ -162,6 +193,17 @@ class HueLight(HueBaseEntity, LightEntity):
|
|||||||
color_temp = normalize_hue_colortemp(kwargs.get(ATTR_COLOR_TEMP))
|
color_temp = normalize_hue_colortemp(kwargs.get(ATTR_COLOR_TEMP))
|
||||||
brightness = normalize_hue_brightness(kwargs.get(ATTR_BRIGHTNESS))
|
brightness = normalize_hue_brightness(kwargs.get(ATTR_BRIGHTNESS))
|
||||||
flash = kwargs.get(ATTR_FLASH)
|
flash = kwargs.get(ATTR_FLASH)
|
||||||
|
effect = effect_str = kwargs.get(ATTR_EFFECT)
|
||||||
|
if effect_str == EFFECT_NONE:
|
||||||
|
effect = EffectStatus.NO_EFFECT
|
||||||
|
elif effect_str is not None:
|
||||||
|
# work out if we got a regular effect or timed effect
|
||||||
|
effect = EffectStatus(effect_str)
|
||||||
|
if effect == EffectStatus.UNKNOWN:
|
||||||
|
effect = TimedEffectStatus(effect_str)
|
||||||
|
if transition is None:
|
||||||
|
# a transition is required for timed effect, default to 10 minutes
|
||||||
|
transition = 600000
|
||||||
|
|
||||||
if flash is not None:
|
if flash is not None:
|
||||||
await self.async_set_flash(flash)
|
await self.async_set_flash(flash)
|
||||||
@ -179,6 +221,7 @@ class HueLight(HueBaseEntity, LightEntity):
|
|||||||
color_xy=xy_color,
|
color_xy=xy_color,
|
||||||
color_temp=color_temp,
|
color_temp=color_temp,
|
||||||
transition_time=transition,
|
transition_time=transition,
|
||||||
|
effect=effect,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -36,6 +36,8 @@ async def test_lights(hass, mock_bridge_v2, v2_resources_test_data):
|
|||||||
assert light_1.attributes["min_mireds"] == 153
|
assert light_1.attributes["min_mireds"] == 153
|
||||||
assert light_1.attributes["max_mireds"] == 500
|
assert light_1.attributes["max_mireds"] == 500
|
||||||
assert light_1.attributes["dynamics"] == "dynamic_palette"
|
assert light_1.attributes["dynamics"] == "dynamic_palette"
|
||||||
|
assert light_1.attributes["effect_list"] == ["None", "candle", "fire"]
|
||||||
|
assert light_1.attributes["effect"] == "None"
|
||||||
|
|
||||||
# test light which supports color temperature only
|
# test light which supports color temperature only
|
||||||
light_2 = hass.states.get("light.hue_light_with_color_temperature_only")
|
light_2 = hass.states.get("light.hue_light_with_color_temperature_only")
|
||||||
@ -49,6 +51,7 @@ async def test_lights(hass, mock_bridge_v2, v2_resources_test_data):
|
|||||||
assert light_2.attributes["min_mireds"] == 153
|
assert light_2.attributes["min_mireds"] == 153
|
||||||
assert light_2.attributes["max_mireds"] == 454
|
assert light_2.attributes["max_mireds"] == 454
|
||||||
assert light_2.attributes["dynamics"] == "none"
|
assert light_2.attributes["dynamics"] == "none"
|
||||||
|
assert light_2.attributes["effect_list"] == ["None", "candle", "sunrise"]
|
||||||
|
|
||||||
# test light which supports color only
|
# test light which supports color only
|
||||||
light_3 = hass.states.get("light.hue_light_with_color_only")
|
light_3 = hass.states.get("light.hue_light_with_color_only")
|
||||||
@ -164,6 +167,39 @@ async def test_light_turn_on_service(hass, mock_bridge_v2, v2_resources_test_dat
|
|||||||
assert len(mock_bridge_v2.mock_requests) == 6
|
assert len(mock_bridge_v2.mock_requests) == 6
|
||||||
assert mock_bridge_v2.mock_requests[5]["json"]["color_temperature"]["mirek"] == 500
|
assert mock_bridge_v2.mock_requests[5]["json"]["color_temperature"]["mirek"] == 500
|
||||||
|
|
||||||
|
# test enable effect
|
||||||
|
await hass.services.async_call(
|
||||||
|
"light",
|
||||||
|
"turn_on",
|
||||||
|
{"entity_id": test_light_id, "effect": "candle"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert len(mock_bridge_v2.mock_requests) == 7
|
||||||
|
assert mock_bridge_v2.mock_requests[6]["json"]["effects"]["effect"] == "candle"
|
||||||
|
|
||||||
|
# test disable effect
|
||||||
|
await hass.services.async_call(
|
||||||
|
"light",
|
||||||
|
"turn_on",
|
||||||
|
{"entity_id": test_light_id, "effect": "None"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert len(mock_bridge_v2.mock_requests) == 8
|
||||||
|
assert mock_bridge_v2.mock_requests[7]["json"]["effects"]["effect"] == "no_effect"
|
||||||
|
|
||||||
|
# test timed effect
|
||||||
|
await hass.services.async_call(
|
||||||
|
"light",
|
||||||
|
"turn_on",
|
||||||
|
{"entity_id": test_light_id, "effect": "sunrise", "transition": 6},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert len(mock_bridge_v2.mock_requests) == 9
|
||||||
|
assert (
|
||||||
|
mock_bridge_v2.mock_requests[8]["json"]["timed_effects"]["effect"] == "sunrise"
|
||||||
|
)
|
||||||
|
assert mock_bridge_v2.mock_requests[8]["json"]["timed_effects"]["duration"] == 6000
|
||||||
|
|
||||||
|
|
||||||
async def test_light_turn_off_service(hass, mock_bridge_v2, v2_resources_test_data):
|
async def test_light_turn_off_service(hass, mock_bridge_v2, v2_resources_test_data):
|
||||||
"""Test calling the turn off service on a light."""
|
"""Test calling the turn off service on a light."""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user