Fix short flash effect in Hue integration (#62988)

pull/62999/head
Marcel van der Veldt 2021-12-29 14:21:38 +01:00 committed by GitHub
parent 4d8c9fc1ab
commit c5bdf858a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 144 additions and 8 deletions

View File

@ -19,6 +19,7 @@ from homeassistant.components.light import (
COLOR_MODE_COLOR_TEMP,
COLOR_MODE_ONOFF,
COLOR_MODE_XY,
FLASH_SHORT,
SUPPORT_FLASH,
SUPPORT_TRANSITION,
LightEntity,
@ -37,7 +38,6 @@ ALLOWED_ERRORS = [
'device (groupedLight) is "soft off", command (on) may not have effect',
"device (light) has communication issues, command (on) may not have effect",
'device (light) is "soft off", command (on) may not have effect',
"attribute (supportedAlertActions) cannot be written",
]
@ -155,6 +155,11 @@ class GroupedHueLight(HueBaseEntity, LightEntity):
brightness = normalize_hue_brightness(kwargs.get(ATTR_BRIGHTNESS))
flash = kwargs.get(ATTR_FLASH)
if flash is not None:
await self.async_set_flash(flash)
# flash can not be sent with other commands at the same time
return
# NOTE: a grouped_light can only handle turn on/off
# To set other features, you'll have to control the attached lights
if (
@ -194,6 +199,12 @@ class GroupedHueLight(HueBaseEntity, LightEntity):
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the light off."""
transition = normalize_hue_transition(kwargs.get(ATTR_TRANSITION))
flash = kwargs.get(ATTR_FLASH)
if flash is not None:
await self.async_set_flash(flash)
# flash can not be sent with other commands at the same time
return
# NOTE: a grouped_light can only handle turn on/off
# To set other features, you'll have to control the attached lights
@ -220,6 +231,19 @@ class GroupedHueLight(HueBaseEntity, LightEntity):
]
)
async def async_set_flash(self, flash: str) -> None:
"""Send flash command to light."""
await asyncio.gather(
*[
self.bridge.async_request_call(
self.api.lights.set_flash,
id=light.id,
short=flash == FLASH_SHORT,
)
for light in self.controller.get_lights(self.resource.id)
]
)
@callback
def on_update(self) -> None:
"""Call on update event."""

View File

@ -6,7 +6,6 @@ from typing import Any
from aiohue import HueBridgeV2
from aiohue.v2.controllers.events import EventType
from aiohue.v2.controllers.lights import LightsController
from aiohue.v2.models.feature import AlertEffectType
from aiohue.v2.models.light import Light
from homeassistant.components.light import (
@ -19,6 +18,7 @@ from homeassistant.components.light import (
COLOR_MODE_COLOR_TEMP,
COLOR_MODE_ONOFF,
COLOR_MODE_XY,
FLASH_SHORT,
SUPPORT_FLASH,
SUPPORT_TRANSITION,
LightEntity,
@ -35,7 +35,6 @@ from .helpers import normalize_hue_brightness, normalize_hue_transition
ALLOWED_ERRORS = [
"device (light) has communication issues, command (on) may not have effect",
'device (light) is "soft off", command (on) may not have effect',
"attribute (supportedAlertActions) cannot be written",
]
@ -73,7 +72,8 @@ class HueLight(HueBaseEntity, LightEntity):
) -> None:
"""Initialize the light."""
super().__init__(bridge, controller, resource)
self._attr_supported_features |= SUPPORT_FLASH
if self.resource.alert and self.resource.alert.action_values:
self._attr_supported_features |= SUPPORT_FLASH
self.resource = resource
self.controller = controller
self._supported_color_modes = set()
@ -162,6 +162,14 @@ class HueLight(HueBaseEntity, LightEntity):
brightness = normalize_hue_brightness(kwargs.get(ATTR_BRIGHTNESS))
flash = kwargs.get(ATTR_FLASH)
if flash is not None:
await self.async_set_flash(flash)
# flash can not be sent with other commands at the same time or result will be flaky
# Hue's default behavior is that a light returns to its previous state for short
# flash (identify) and the light is kept turned on for long flash (breathe effect)
# Why is this flash alert/effect hidden in the turn_on/off commands ?
return
await self.bridge.async_request_call(
self.controller.set_state,
id=self.resource.id,
@ -170,7 +178,6 @@ class HueLight(HueBaseEntity, LightEntity):
color_xy=xy_color,
color_temp=color_temp,
transition_time=transition,
alert=AlertEffectType.BREATHE if flash is not None else None,
allowed_errors=ALLOWED_ERRORS,
)
@ -179,11 +186,25 @@ class HueLight(HueBaseEntity, LightEntity):
transition = normalize_hue_transition(kwargs.get(ATTR_TRANSITION))
flash = kwargs.get(ATTR_FLASH)
if flash is not None:
await self.async_set_flash(flash)
# flash can not be sent with other commands at the same time or result will be flaky
# Hue's default behavior is that a light returns to its previous state for short
# flash (identify) and the light is kept turned on for long flash (breathe effect)
return
await self.bridge.async_request_call(
self.controller.set_state,
id=self.resource.id,
on=False,
transition_time=transition,
alert=AlertEffectType.BREATHE if flash is not None else None,
allowed_errors=ALLOWED_ERRORS,
)
async def async_set_flash(self, flash: str) -> None:
"""Send flash command to light."""
await self.bridge.async_request_call(
self.controller.set_flash,
id=self.resource.id,
short=flash == FLASH_SHORT,
)

View File

@ -121,7 +121,7 @@ async def test_light_turn_on_service(hass, mock_bridge_v2, v2_resources_test_dat
assert mock_bridge_v2.mock_requests[1]["json"]["on"]["on"] is True
assert mock_bridge_v2.mock_requests[1]["json"]["dynamics"]["duration"] == 200
# test again with sending flash/alert
# test again with sending long flash
await hass.services.async_call(
"light",
"turn_on",
@ -129,9 +129,18 @@ async def test_light_turn_on_service(hass, mock_bridge_v2, v2_resources_test_dat
blocking=True,
)
assert len(mock_bridge_v2.mock_requests) == 3
assert mock_bridge_v2.mock_requests[2]["json"]["on"]["on"] is True
assert mock_bridge_v2.mock_requests[2]["json"]["alert"]["action"] == "breathe"
# test again with sending short flash
await hass.services.async_call(
"light",
"turn_on",
{"entity_id": test_light_id, "flash": "short"},
blocking=True,
)
assert len(mock_bridge_v2.mock_requests) == 4
assert mock_bridge_v2.mock_requests[3]["json"]["identify"]["action"] == "identify"
async def test_light_turn_off_service(hass, mock_bridge_v2, v2_resources_test_data):
"""Test calling the turn off service on a light."""
@ -177,6 +186,26 @@ async def test_light_turn_off_service(hass, mock_bridge_v2, v2_resources_test_da
assert mock_bridge_v2.mock_requests[1]["json"]["on"]["on"] is False
assert mock_bridge_v2.mock_requests[1]["json"]["dynamics"]["duration"] == 200
# test again with sending long flash
await hass.services.async_call(
"light",
"turn_off",
{"entity_id": test_light_id, "flash": "long"},
blocking=True,
)
assert len(mock_bridge_v2.mock_requests) == 3
assert mock_bridge_v2.mock_requests[2]["json"]["alert"]["action"] == "breathe"
# test again with sending short flash
await hass.services.async_call(
"light",
"turn_off",
{"entity_id": test_light_id, "flash": "short"},
blocking=True,
)
assert len(mock_bridge_v2.mock_requests) == 4
assert mock_bridge_v2.mock_requests[3]["json"]["identify"]["action"] == "identify"
async def test_light_added(hass, mock_bridge_v2):
"""Test new light added to bridge."""
@ -386,3 +415,65 @@ async def test_grouped_lights(hass, mock_bridge_v2, v2_resources_test_data):
assert (
mock_bridge_v2.mock_requests[index]["json"]["dynamics"]["duration"] == 200
)
# Test sending short flash effect to a grouped light
mock_bridge_v2.mock_requests.clear()
test_light_id = "light.test_zone"
await hass.services.async_call(
"light",
"turn_on",
{
"entity_id": test_light_id,
"flash": "short",
},
blocking=True,
)
# PUT request should have been sent to ALL group lights with correct params
assert len(mock_bridge_v2.mock_requests) == 3
for index in range(0, 3):
assert (
mock_bridge_v2.mock_requests[index]["json"]["identify"]["action"]
== "identify"
)
# Test sending long flash effect to a grouped light
mock_bridge_v2.mock_requests.clear()
test_light_id = "light.test_zone"
await hass.services.async_call(
"light",
"turn_on",
{
"entity_id": test_light_id,
"flash": "long",
},
blocking=True,
)
# PUT request should have been sent to ALL group lights with correct params
assert len(mock_bridge_v2.mock_requests) == 3
for index in range(0, 3):
assert (
mock_bridge_v2.mock_requests[index]["json"]["alert"]["action"] == "breathe"
)
# Test sending flash effect in turn_off call
mock_bridge_v2.mock_requests.clear()
test_light_id = "light.test_zone"
await hass.services.async_call(
"light",
"turn_off",
{
"entity_id": test_light_id,
"flash": "short",
},
blocking=True,
)
# PUT request should have been sent to ALL group lights with correct params
assert len(mock_bridge_v2.mock_requests) == 3
for index in range(0, 3):
assert (
mock_bridge_v2.mock_requests[index]["json"]["identify"]["action"]
== "identify"
)