mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Fix short flash effect in Hue integration (#62988)
This commit is contained in:
parent
4d8c9fc1ab
commit
c5bdf858a8
@ -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."""
|
||||||
|
@ -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,7 +72,8 @@ class HueLight(HueBaseEntity, LightEntity):
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the light."""
|
"""Initialize the light."""
|
||||||
super().__init__(bridge, controller, resource)
|
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.resource = resource
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self._supported_color_modes = set()
|
self._supported_color_modes = set()
|
||||||
@ -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,
|
||||||
|
)
|
||||||
|
@ -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"
|
||||||
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user