Fix unexpected color mode switch in flux_led when brightness is near 0 (#64812)
Co-authored-by: Chris Talkington <chris@talkingtontech.com>pull/64821/head
parent
0cd3302ebc
commit
3b6bc6654f
|
@ -20,6 +20,8 @@ from homeassistant.components.light import (
|
|||
|
||||
DOMAIN: Final = "flux_led"
|
||||
|
||||
MIN_RGB_BRIGHTNESS: Final = 1
|
||||
MIN_CCT_BRIGHTNESS: Final = 2
|
||||
|
||||
FLUX_COLOR_MODE_TO_HASS: Final = {
|
||||
FLUX_COLOR_MODE_RGB: COLOR_MODE_RGB,
|
||||
|
|
|
@ -44,6 +44,8 @@ from .const import (
|
|||
CONF_TRANSITION,
|
||||
DEFAULT_EFFECT_SPEED,
|
||||
DOMAIN,
|
||||
MIN_CCT_BRIGHTNESS,
|
||||
MIN_RGB_BRIGHTNESS,
|
||||
MULTI_BRIGHTNESS_COLOR_MODES,
|
||||
TRANSITION_GRADUAL,
|
||||
TRANSITION_JUMP,
|
||||
|
@ -290,7 +292,7 @@ class FluxLight(FluxOnOffEntity, CoordinatorEntity, LightEntity):
|
|||
# We previously had a problem with the brightness
|
||||
# sometimes reporting as 0 when an effect was in progress,
|
||||
# however this has since been resolved in the upstream library
|
||||
return max(1, brightness)
|
||||
return max(MIN_RGB_BRIGHTNESS, brightness)
|
||||
|
||||
async def _async_set_mode(self, **kwargs: Any) -> None:
|
||||
"""Set an effect or color mode."""
|
||||
|
@ -308,7 +310,7 @@ class FluxLight(FluxOnOffEntity, CoordinatorEntity, LightEntity):
|
|||
):
|
||||
# When switching to color temp from RGBWW or RGB&W mode,
|
||||
# we do not want the overall brightness of the RGB channels
|
||||
brightness = max(self._device.rgb)
|
||||
brightness = max(MIN_CCT_BRIGHTNESS, *self._device.rgb)
|
||||
await self._device.async_set_white_temp(color_temp_kelvin, brightness)
|
||||
return
|
||||
# Handle switch to RGB Color Mode
|
||||
|
@ -322,8 +324,7 @@ class FluxLight(FluxOnOffEntity, CoordinatorEntity, LightEntity):
|
|||
if rgbw := kwargs.get(ATTR_RGBW_COLOR):
|
||||
if ATTR_BRIGHTNESS in kwargs:
|
||||
rgbw = rgbw_brightness(rgbw, brightness)
|
||||
if not self._device.requires_turn_on:
|
||||
rgbw = _min_rgbw_brightness(rgbw)
|
||||
rgbw = _min_rgbw_brightness(rgbw, self._device.rgbw)
|
||||
await self._device.async_set_levels(*rgbw)
|
||||
return
|
||||
# Handle switch to RGBWW Color Mode
|
||||
|
@ -331,8 +332,7 @@ class FluxLight(FluxOnOffEntity, CoordinatorEntity, LightEntity):
|
|||
if ATTR_BRIGHTNESS in kwargs:
|
||||
rgbcw = rgbcw_brightness(kwargs[ATTR_RGBWW_COLOR], brightness)
|
||||
rgbwc = rgbcw_to_rgbwc(rgbcw)
|
||||
if not self._device.requires_turn_on:
|
||||
rgbwc = _min_rgbwc_brightness(rgbwc)
|
||||
rgbwc = _min_rgbwc_brightness(rgbwc, self._device.rgbww)
|
||||
await self._device.async_set_levels(*rgbwc)
|
||||
return
|
||||
if (white := kwargs.get(ATTR_WHITE)) is not None:
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"config_flow": true,
|
||||
"dependencies": ["network"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/flux_led",
|
||||
"requirements": ["flux_led==0.28.8"],
|
||||
"requirements": ["flux_led==0.28.10"],
|
||||
"quality_scale": "platinum",
|
||||
"codeowners": ["@icemanch", "@bdraco"],
|
||||
"iot_class": "local_push",
|
||||
|
|
|
@ -9,8 +9,9 @@ from homeassistant.components.light import (
|
|||
COLOR_MODE_ONOFF,
|
||||
COLOR_MODE_WHITE,
|
||||
)
|
||||
from homeassistant.util.color import color_hsv_to_RGB, color_RGB_to_hsv
|
||||
|
||||
from .const import FLUX_COLOR_MODE_TO_HASS
|
||||
from .const import FLUX_COLOR_MODE_TO_HASS, MIN_RGB_BRIGHTNESS
|
||||
|
||||
|
||||
def _hass_color_modes(device: AIOWifiLedBulb) -> set[str]:
|
||||
|
@ -54,24 +55,50 @@ def _str_to_multi_color_effect(effect_str: str) -> MultiColorEffects:
|
|||
assert False # pragma: no cover
|
||||
|
||||
|
||||
def _is_zero_rgb_brightness(rgb: tuple[int, int, int]) -> bool:
|
||||
"""RGB brightness is zero."""
|
||||
return all(byte == 0 for byte in rgb)
|
||||
|
||||
|
||||
def _min_rgb_brightness(rgb: tuple[int, int, int]) -> tuple[int, int, int]:
|
||||
"""Ensure the RGB value will not turn off the device from a turn on command."""
|
||||
if all(byte == 0 for byte in rgb):
|
||||
return (1, 1, 1)
|
||||
if _is_zero_rgb_brightness(rgb):
|
||||
return (MIN_RGB_BRIGHTNESS, MIN_RGB_BRIGHTNESS, MIN_RGB_BRIGHTNESS)
|
||||
return rgb
|
||||
|
||||
|
||||
def _min_rgbw_brightness(rgbw: tuple[int, int, int, int]) -> tuple[int, int, int, int]:
|
||||
"""Ensure the RGBW value will not turn off the device from a turn on command."""
|
||||
if all(byte == 0 for byte in rgbw):
|
||||
return (1, 1, 1, 0)
|
||||
return rgbw
|
||||
def _min_scaled_rgb_brightness(rgb: tuple[int, int, int]) -> tuple[int, int, int]:
|
||||
"""Scale an RGB tuple to minimum brightness."""
|
||||
return color_hsv_to_RGB(*color_RGB_to_hsv(*rgb)[:2], 1)
|
||||
|
||||
|
||||
def _min_rgbw_brightness(
|
||||
rgbw: tuple[int, int, int, int], current_rgbw: tuple[int, int, int, int]
|
||||
) -> tuple[int, int, int, int]:
|
||||
"""Ensure the RGBW value will not turn off the device from a turn on command.
|
||||
|
||||
For RGBW, we also need to ensure that there is at least one
|
||||
value in the RGB fields or the device will switch to CCT mode unexpectedly.
|
||||
|
||||
If the new value being set is all zeros, scale the current
|
||||
color to brightness of 1 so we do not unexpected switch to white
|
||||
"""
|
||||
if _is_zero_rgb_brightness(rgbw[:3]):
|
||||
return (*_min_scaled_rgb_brightness(current_rgbw[:3]), rgbw[3])
|
||||
return (*_min_rgb_brightness(rgbw[:3]), rgbw[3])
|
||||
|
||||
|
||||
def _min_rgbwc_brightness(
|
||||
rgbwc: tuple[int, int, int, int, int]
|
||||
rgbwc: tuple[int, int, int, int, int], current_rgbwc: tuple[int, int, int, int, int]
|
||||
) -> tuple[int, int, int, int, int]:
|
||||
"""Ensure the RGBWC value will not turn off the device from a turn on command."""
|
||||
if all(byte == 0 for byte in rgbwc):
|
||||
return (1, 1, 1, 0, 0)
|
||||
return rgbwc
|
||||
"""Ensure the RGBWC value will not turn off the device from a turn on command.
|
||||
|
||||
For RGBWC, we also need to ensure that there is at least one
|
||||
value in the RGB fields or the device will switch to CCT mode unexpectedly
|
||||
|
||||
If the new value being set is all zeros, scale the current
|
||||
color to brightness of 1 so we do not unexpected switch to white
|
||||
"""
|
||||
if _is_zero_rgb_brightness(rgbwc[:3]):
|
||||
return (*_min_scaled_rgb_brightness(current_rgbwc[:3]), rgbwc[3], rgbwc[4])
|
||||
return (*_min_rgb_brightness(rgbwc[:3]), rgbwc[3], rgbwc[4])
|
||||
|
|
|
@ -681,7 +681,7 @@ fjaraskupan==1.0.2
|
|||
flipr-api==1.4.1
|
||||
|
||||
# homeassistant.components.flux_led
|
||||
flux_led==0.28.8
|
||||
flux_led==0.28.10
|
||||
|
||||
# homeassistant.components.homekit
|
||||
fnvhash==0.1.0
|
||||
|
|
|
@ -427,7 +427,7 @@ fjaraskupan==1.0.2
|
|||
flipr-api==1.4.1
|
||||
|
||||
# homeassistant.components.flux_led
|
||||
flux_led==0.28.8
|
||||
flux_led==0.28.10
|
||||
|
||||
# homeassistant.components.homekit
|
||||
fnvhash==0.1.0
|
||||
|
|
|
@ -28,6 +28,8 @@ from homeassistant.components.flux_led.const import (
|
|||
CONF_TRANSITION,
|
||||
CONF_WHITE_CHANNEL_TYPE,
|
||||
DOMAIN,
|
||||
MIN_CCT_BRIGHTNESS,
|
||||
MIN_RGB_BRIGHTNESS,
|
||||
TRANSITION_JUMP,
|
||||
)
|
||||
from homeassistant.components.flux_led.light import (
|
||||
|
@ -352,7 +354,12 @@ async def test_rgb_light_auto_on(hass: HomeAssistant) -> None:
|
|||
# If the bulb is off and we are using existing brightness
|
||||
# it has to be at least 1 or the bulb won't turn on
|
||||
bulb.async_turn_on.assert_not_called()
|
||||
bulb.async_set_levels.assert_called_with(1, 1, 1, brightness=1)
|
||||
bulb.async_set_levels.assert_called_with(
|
||||
MIN_RGB_BRIGHTNESS,
|
||||
MIN_RGB_BRIGHTNESS,
|
||||
MIN_RGB_BRIGHTNESS,
|
||||
brightness=MIN_RGB_BRIGHTNESS,
|
||||
)
|
||||
bulb.async_set_levels.reset_mock()
|
||||
bulb.async_turn_on.reset_mock()
|
||||
|
||||
|
@ -498,10 +505,30 @@ async def test_rgbw_light_auto_on(hass: HomeAssistant) -> None:
|
|||
)
|
||||
# If the bulb is on and we are using existing brightness
|
||||
# and brightness was 0 we need to set it to at least 1
|
||||
# or the device may not turn on
|
||||
# or the device may not turn on. In this case we scale
|
||||
# the current color to brightness of 1 to ensure the device
|
||||
# does not switch to white since otherwise we do not have
|
||||
# enough resolution to determine which color to display
|
||||
bulb.async_turn_on.assert_not_called()
|
||||
bulb.async_set_brightness.assert_not_called()
|
||||
bulb.async_set_levels.assert_called_with(1, 1, 1, 0)
|
||||
bulb.async_set_levels.assert_called_with(2, 0, 0, 0)
|
||||
bulb.async_set_levels.reset_mock()
|
||||
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
"turn_on",
|
||||
{ATTR_ENTITY_ID: entity_id, ATTR_RGBW_COLOR: (0, 0, 0, 56)},
|
||||
blocking=True,
|
||||
)
|
||||
# If the bulb is on and we are using existing brightness
|
||||
# and brightness was 0 we need to set it to at least 1
|
||||
# or the device may not turn on. In this case we scale
|
||||
# the current color to brightness of 1 to ensure the device
|
||||
# does not switch to white since otherwise we do not have
|
||||
# enough resolution to determine which color to display
|
||||
bulb.async_turn_on.assert_not_called()
|
||||
bulb.async_set_brightness.assert_not_called()
|
||||
bulb.async_set_levels.assert_called_with(2, 0, 0, 56)
|
||||
bulb.async_set_levels.reset_mock()
|
||||
|
||||
bulb.brightness = 128
|
||||
|
@ -613,10 +640,13 @@ async def test_rgbww_light_auto_on(hass: HomeAssistant) -> None:
|
|||
)
|
||||
# If the bulb is on and we are using existing brightness
|
||||
# and brightness was 0 we need to set it to at least 1
|
||||
# or the device may not turn on
|
||||
# or the device may not turn on. In this case we scale
|
||||
# the current color so we do not unexpectedly switch to white
|
||||
# since other we do not have enough resolution to determine
|
||||
# which color to display
|
||||
bulb.async_turn_on.assert_not_called()
|
||||
bulb.async_set_brightness.assert_not_called()
|
||||
bulb.async_set_levels.assert_called_with(1, 1, 1, 0, 0)
|
||||
bulb.async_set_levels.assert_called_with(2, 0, 0, 0, 0)
|
||||
bulb.async_set_levels.reset_mock()
|
||||
|
||||
bulb.brightness = 128
|
||||
|
@ -1267,6 +1297,25 @@ async def test_rgbcw_light(hass: HomeAssistant) -> None:
|
|||
bulb.async_set_brightness.assert_called_with(255)
|
||||
bulb.async_set_brightness.reset_mock()
|
||||
|
||||
await async_mock_device_turn_off(hass, bulb)
|
||||
bulb.color_mode = FLUX_COLOR_MODE_RGBWW
|
||||
bulb.brightness = MIN_RGB_BRIGHTNESS
|
||||
bulb.rgb = (MIN_RGB_BRIGHTNESS, MIN_RGB_BRIGHTNESS, MIN_RGB_BRIGHTNESS)
|
||||
await async_mock_device_turn_on(hass, bulb)
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[ATTR_COLOR_MODE] == COLOR_MODE_RGBWW
|
||||
assert state.attributes[ATTR_BRIGHTNESS] == 1
|
||||
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
"turn_on",
|
||||
{ATTR_ENTITY_ID: entity_id, ATTR_COLOR_TEMP: 170},
|
||||
blocking=True,
|
||||
)
|
||||
bulb.async_set_white_temp.assert_called_with(5882, MIN_CCT_BRIGHTNESS)
|
||||
bulb.async_set_white_temp.reset_mock()
|
||||
|
||||
|
||||
async def test_white_light(hass: HomeAssistant) -> None:
|
||||
"""Test a white light."""
|
||||
|
|
Loading…
Reference in New Issue