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
J. Nick Koston 2022-01-23 20:53:17 -10:00 committed by GitHub
parent 0cd3302ebc
commit 3b6bc6654f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 105 additions and 27 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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