Fix short flash effect in Hue integration (#62988)

This commit is contained in:
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_COLOR_TEMP,
COLOR_MODE_ONOFF, COLOR_MODE_ONOFF,
COLOR_MODE_XY, COLOR_MODE_XY,
FLASH_SHORT,
SUPPORT_FLASH, SUPPORT_FLASH,
SUPPORT_TRANSITION, SUPPORT_TRANSITION,
LightEntity, LightEntity,
@ -37,7 +38,6 @@ ALLOWED_ERRORS = [
'device (groupedLight) is "soft off", command (on) may not have effect', 'device (groupedLight) is "soft off", command (on) may not have effect',
"device (light) has communication issues, 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', '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)) brightness = normalize_hue_brightness(kwargs.get(ATTR_BRIGHTNESS))
flash = kwargs.get(ATTR_FLASH) 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 # NOTE: a grouped_light can only handle turn on/off
# To set other features, you'll have to control the attached lights # To set other features, you'll have to control the attached lights
if ( if (
@ -194,6 +199,12 @@ class GroupedHueLight(HueBaseEntity, LightEntity):
async def async_turn_off(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the light off.""" """Turn the light off."""
transition = normalize_hue_transition(kwargs.get(ATTR_TRANSITION)) 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 # NOTE: a grouped_light can only handle turn on/off
# To set other features, you'll have to control the attached lights # 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 @callback
def on_update(self) -> None: def on_update(self) -> None:
"""Call on update event.""" """Call on update event."""

View File

@ -6,7 +6,6 @@ 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 AlertEffectType
from aiohue.v2.models.light import Light from aiohue.v2.models.light import Light
from homeassistant.components.light import ( from homeassistant.components.light import (
@ -19,6 +18,7 @@ from homeassistant.components.light import (
COLOR_MODE_COLOR_TEMP, COLOR_MODE_COLOR_TEMP,
COLOR_MODE_ONOFF, COLOR_MODE_ONOFF,
COLOR_MODE_XY, COLOR_MODE_XY,
FLASH_SHORT,
SUPPORT_FLASH, SUPPORT_FLASH,
SUPPORT_TRANSITION, SUPPORT_TRANSITION,
LightEntity, LightEntity,
@ -35,7 +35,6 @@ from .helpers import normalize_hue_brightness, normalize_hue_transition
ALLOWED_ERRORS = [ ALLOWED_ERRORS = [
"device (light) has communication issues, 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', 'device (light) is "soft off", command (on) may not have effect',
"attribute (supportedAlertActions) cannot be written",
] ]
@ -73,6 +72,7 @@ class HueLight(HueBaseEntity, LightEntity):
) -> None: ) -> None:
"""Initialize the light.""" """Initialize the light."""
super().__init__(bridge, controller, resource) super().__init__(bridge, controller, resource)
if self.resource.alert and self.resource.alert.action_values:
self._attr_supported_features |= SUPPORT_FLASH self._attr_supported_features |= SUPPORT_FLASH
self.resource = resource self.resource = resource
self.controller = controller self.controller = controller
@ -162,6 +162,14 @@ class HueLight(HueBaseEntity, LightEntity):
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)
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( await self.bridge.async_request_call(
self.controller.set_state, self.controller.set_state,
id=self.resource.id, id=self.resource.id,
@ -170,7 +178,6 @@ 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,
alert=AlertEffectType.BREATHE if flash is not None else None,
allowed_errors=ALLOWED_ERRORS, allowed_errors=ALLOWED_ERRORS,
) )
@ -179,11 +186,25 @@ class HueLight(HueBaseEntity, LightEntity):
transition = normalize_hue_transition(kwargs.get(ATTR_TRANSITION)) transition = normalize_hue_transition(kwargs.get(ATTR_TRANSITION))
flash = kwargs.get(ATTR_FLASH) 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( await self.bridge.async_request_call(
self.controller.set_state, self.controller.set_state,
id=self.resource.id, id=self.resource.id,
on=False, on=False,
transition_time=transition, transition_time=transition,
alert=AlertEffectType.BREATHE if flash is not None else None,
allowed_errors=ALLOWED_ERRORS, 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"]["on"]["on"] is True
assert mock_bridge_v2.mock_requests[1]["json"]["dynamics"]["duration"] == 200 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( await hass.services.async_call(
"light", "light",
"turn_on", "turn_on",
@ -129,9 +129,18 @@ async def test_light_turn_on_service(hass, mock_bridge_v2, v2_resources_test_dat
blocking=True, blocking=True,
) )
assert len(mock_bridge_v2.mock_requests) == 3 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" 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): 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."""
@ -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"]["on"]["on"] is False
assert mock_bridge_v2.mock_requests[1]["json"]["dynamics"]["duration"] == 200 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): async def test_light_added(hass, mock_bridge_v2):
"""Test new light added to bridge.""" """Test new light added to bridge."""
@ -386,3 +415,65 @@ async def test_grouped_lights(hass, mock_bridge_v2, v2_resources_test_data):
assert ( assert (
mock_bridge_v2.mock_requests[index]["json"]["dynamics"]["duration"] == 200 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"
)