Add effects feature to Hue lights (#68567)

This commit is contained in:
Marcel van der Veldt 2022-03-23 23:13:01 +01:00 committed by GitHub
parent 8c10963bc0
commit dbef90654f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 2103 additions and 2013 deletions

View File

@ -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

View File

@ -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."""