diff --git a/homeassistant/components/homekit/type_lights.py b/homeassistant/components/homekit/type_lights.py index 4b433a5dc3a..7ccf3dcc38f 100644 --- a/homeassistant/components/homekit/type_lights.py +++ b/homeassistant/components/homekit/type_lights.py @@ -193,7 +193,10 @@ class Light(HomeAccessory): params[ATTR_COLOR_TEMP] = temp elif self.rgbww_supported: params[ATTR_RGBWW_COLOR] = color_temperature_to_rgbww( - temp, bright_val, self.min_mireds, self.max_mireds + color_temperature_mired_to_kelvin(temp), + bright_val, + color_temperature_mired_to_kelvin(self.max_mireds), + color_temperature_mired_to_kelvin(self.min_mireds), ) elif self.rgbw_supported: params[ATTR_RGBW_COLOR] = (*(0,) * 3, bright_val) diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 7d34c607b1f..866c3338a75 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -196,10 +196,13 @@ ATTR_RGBW_COLOR = "rgbw_color" ATTR_RGBWW_COLOR = "rgbww_color" ATTR_XY_COLOR = "xy_color" ATTR_HS_COLOR = "hs_color" -ATTR_COLOR_TEMP = "color_temp" -ATTR_KELVIN = "kelvin" -ATTR_MIN_MIREDS = "min_mireds" -ATTR_MAX_MIREDS = "max_mireds" +ATTR_COLOR_TEMP = "color_temp" # Deprecated in HA Core 2022.11 +ATTR_KELVIN = "kelvin" # Deprecated in HA Core 2022.11 +ATTR_MIN_MIREDS = "min_mireds" # Deprecated in HA Core 2022.11 +ATTR_MAX_MIREDS = "max_mireds" # Deprecated in HA Core 2022.11 +ATTR_COLOR_TEMP_KELVIN = "color_temp_kelvin" +ATTR_MIN_COLOR_TEMP_KELVIN = "min_color_temp_kelvin" +ATTR_MAX_COLOR_TEMP_KELVIN = "max_color_temp_kelvin" ATTR_COLOR_NAME = "color_name" ATTR_WHITE = "white" @@ -249,6 +252,7 @@ LIGHT_TURN_ON_SCHEMA = { vol.Exclusive(ATTR_COLOR_TEMP, COLOR_GROUP): vol.All( vol.Coerce(int), vol.Range(min=1) ), + vol.Exclusive(ATTR_COLOR_TEMP_KELVIN, COLOR_GROUP): cv.positive_int, vol.Exclusive(ATTR_KELVIN, COLOR_GROUP): cv.positive_int, vol.Exclusive(ATTR_HS_COLOR, COLOR_GROUP): vol.All( vol.Coerce(tuple), @@ -309,9 +313,20 @@ def preprocess_turn_on_alternatives( _LOGGER.warning("Got unknown color %s, falling back to white", color_name) params[ATTR_RGB_COLOR] = (255, 255, 255) + if (mired := params.pop(ATTR_COLOR_TEMP, None)) is not None: + kelvin = color_util.color_temperature_mired_to_kelvin(mired) + params[ATTR_COLOR_TEMP] = int(mired) + params[ATTR_COLOR_TEMP_KELVIN] = int(kelvin) + if (kelvin := params.pop(ATTR_KELVIN, None)) is not None: mired = color_util.color_temperature_kelvin_to_mired(kelvin) params[ATTR_COLOR_TEMP] = int(mired) + params[ATTR_COLOR_TEMP_KELVIN] = int(kelvin) + + if (kelvin := params.pop(ATTR_COLOR_TEMP_KELVIN, None)) is not None: + mired = color_util.color_temperature_kelvin_to_mired(kelvin) + params[ATTR_COLOR_TEMP] = int(mired) + params[ATTR_COLOR_TEMP_KELVIN] = int(kelvin) brightness_pct = params.pop(ATTR_BRIGHTNESS_PCT, None) if brightness_pct is not None: @@ -350,6 +365,7 @@ def filter_turn_on_params(light: LightEntity, params: dict[str, Any]) -> dict[st params.pop(ATTR_BRIGHTNESS, None) if ColorMode.COLOR_TEMP not in supported_color_modes: params.pop(ATTR_COLOR_TEMP, None) + params.pop(ATTR_COLOR_TEMP_KELVIN, None) if ColorMode.HS not in supported_color_modes: params.pop(ATTR_HS_COLOR, None) if ColorMode.RGB not in supported_color_modes: @@ -424,22 +440,28 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: supported_color_modes = light.supported_color_modes # If a color temperature is specified, emulate it if not supported by the light - if ATTR_COLOR_TEMP in params: + if ATTR_COLOR_TEMP_KELVIN in params: if ( supported_color_modes and ColorMode.COLOR_TEMP not in supported_color_modes and ColorMode.RGBWW in supported_color_modes ): - color_temp = params.pop(ATTR_COLOR_TEMP) + params.pop(ATTR_COLOR_TEMP) + color_temp = params.pop(ATTR_COLOR_TEMP_KELVIN) brightness = params.get(ATTR_BRIGHTNESS, light.brightness) params[ATTR_RGBWW_COLOR] = color_util.color_temperature_to_rgbww( - color_temp, brightness, light.min_mireds, light.max_mireds + color_temp, + brightness, + light.min_color_temp_kelvin, + light.max_color_temp_kelvin, ) elif ColorMode.COLOR_TEMP not in legacy_supported_color_modes: - color_temp = params.pop(ATTR_COLOR_TEMP) + params.pop(ATTR_COLOR_TEMP) + color_temp = params.pop(ATTR_COLOR_TEMP_KELVIN) if color_supported(legacy_supported_color_modes): - temp_k = color_util.color_temperature_mired_to_kelvin(color_temp) - params[ATTR_HS_COLOR] = color_util.color_temperature_to_hs(temp_k) + params[ATTR_HS_COLOR] = color_util.color_temperature_to_hs( + color_temp + ) # If a color is specified, convert to the color space supported by the light # Backwards compatibility: Fall back to hs color if light.supported_color_modes @@ -457,7 +479,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: elif (rgbww_color := params.pop(ATTR_RGBWW_COLOR, None)) is not None: # https://github.com/python/mypy/issues/13673 rgb_color = color_util.color_rgbww_to_rgb( # type: ignore[call-arg] - *rgbww_color, light.min_mireds, light.max_mireds + *rgbww_color, + light.min_color_temp_kelvin, + light.max_color_temp_kelvin, ) params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color) elif ATTR_HS_COLOR in params and ColorMode.HS not in supported_color_modes: @@ -470,7 +494,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: elif ColorMode.RGBWW in supported_color_modes: rgb_color = color_util.color_hs_to_RGB(*hs_color) params[ATTR_RGBWW_COLOR] = color_util.color_rgb_to_rgbww( - *rgb_color, light.min_mireds, light.max_mireds + *rgb_color, light.min_color_temp_kelvin, light.max_color_temp_kelvin ) elif ColorMode.XY in supported_color_modes: params[ATTR_XY_COLOR] = color_util.color_hs_to_xy(*hs_color) @@ -481,7 +505,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: elif ColorMode.RGBWW in supported_color_modes: # https://github.com/python/mypy/issues/13673 params[ATTR_RGBWW_COLOR] = color_util.color_rgb_to_rgbww( # type: ignore[call-arg] - *rgb_color, light.min_mireds, light.max_mireds + *rgb_color, light.min_color_temp_kelvin, light.max_color_temp_kelvin ) elif ColorMode.HS in supported_color_modes: params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color) @@ -499,7 +523,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: elif ColorMode.RGBWW in supported_color_modes: rgb_color = color_util.color_xy_to_RGB(*xy_color) params[ATTR_RGBWW_COLOR] = color_util.color_rgb_to_rgbww( - *rgb_color, light.min_mireds, light.max_mireds + *rgb_color, light.min_color_temp_kelvin, light.max_color_temp_kelvin ) elif ATTR_RGBW_COLOR in params and ColorMode.RGBW not in supported_color_modes: rgbw_color = params.pop(ATTR_RGBW_COLOR) @@ -508,7 +532,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: params[ATTR_RGB_COLOR] = rgb_color elif ColorMode.RGBWW in supported_color_modes: params[ATTR_RGBWW_COLOR] = color_util.color_rgb_to_rgbww( - *rgb_color, light.min_mireds, light.max_mireds + *rgb_color, light.min_color_temp_kelvin, light.max_color_temp_kelvin ) elif ColorMode.HS in supported_color_modes: params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color) @@ -520,7 +544,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: assert (rgbww_color := params.pop(ATTR_RGBWW_COLOR)) is not None # https://github.com/python/mypy/issues/13673 rgb_color = color_util.color_rgbww_to_rgb( # type: ignore[call-arg] - *rgbww_color, light.min_mireds, light.max_mireds + *rgbww_color, light.min_color_temp_kelvin, light.max_color_temp_kelvin ) if ColorMode.RGB in supported_color_modes: params[ATTR_RGB_COLOR] = rgb_color @@ -755,11 +779,16 @@ class LightEntity(ToggleEntity): _attr_brightness: int | None = None _attr_color_mode: ColorMode | str | None = None _attr_color_temp: int | None = None + _attr_color_temp_kelvin: int | None = None _attr_effect_list: list[str] | None = None _attr_effect: str | None = None _attr_hs_color: tuple[float, float] | None = None - _attr_max_mireds: int = 500 - _attr_min_mireds: int = 153 + # Default to the Philips Hue value that HA has always assumed + # https://developers.meethue.com/documentation/core-concepts + _attr_max_color_temp_kelvin: int | None = None + _attr_min_color_temp_kelvin: int | None = None + _attr_max_mireds: int = 500 # 2000 K + _attr_min_mireds: int = 153 # 6535 K _attr_rgb_color: tuple[int, int, int] | None = None _attr_rgbw_color: tuple[int, int, int, int] | None = None _attr_rgbww_color: tuple[int, int, int, int, int] | None = None @@ -787,7 +816,7 @@ class LightEntity(ToggleEntity): if ColorMode.HS in supported and self.hs_color is not None: return ColorMode.HS - if ColorMode.COLOR_TEMP in supported and self.color_temp is not None: + if ColorMode.COLOR_TEMP in supported and self.color_temp_kelvin is not None: return ColorMode.COLOR_TEMP if ColorMode.BRIGHTNESS in supported and self.brightness is not None: return ColorMode.BRIGHTNESS @@ -833,20 +862,37 @@ class LightEntity(ToggleEntity): """Return the CT color value in mireds.""" return self._attr_color_temp + @property + def color_temp_kelvin(self) -> int | None: + """Return the CT color value in Kelvin.""" + if self._attr_color_temp_kelvin is None and self.color_temp: + return color_util.color_temperature_mired_to_kelvin(self.color_temp) + return self._attr_color_temp_kelvin + @property def min_mireds(self) -> int: """Return the coldest color_temp that this light supports.""" - # Default to the Philips Hue value that HA has always assumed - # https://developers.meethue.com/documentation/core-concepts return self._attr_min_mireds @property def max_mireds(self) -> int: """Return the warmest color_temp that this light supports.""" - # Default to the Philips Hue value that HA has always assumed - # https://developers.meethue.com/documentation/core-concepts return self._attr_max_mireds + @property + def min_color_temp_kelvin(self) -> int: + """Return the warmest color_temp_kelvin that this light supports.""" + if self._attr_min_color_temp_kelvin is None: + return color_util.color_temperature_mired_to_kelvin(self.max_mireds) + return self._attr_min_color_temp_kelvin + + @property + def max_color_temp_kelvin(self) -> int: + """Return the coldest color_temp_kelvin that this light supports.""" + if self._attr_min_color_temp_kelvin is None: + return color_util.color_temperature_mired_to_kelvin(self.min_mireds) + return self._attr_min_color_temp_kelvin + @property def effect_list(self) -> list[str] | None: """Return the list of supported effects.""" @@ -867,6 +913,8 @@ class LightEntity(ToggleEntity): if ColorMode.COLOR_TEMP in supported_color_modes: data[ATTR_MIN_MIREDS] = self.min_mireds data[ATTR_MAX_MIREDS] = self.max_mireds + data[ATTR_MIN_COLOR_TEMP_KELVIN] = self.min_color_temp_kelvin + data[ATTR_MAX_COLOR_TEMP_KELVIN] = self.max_color_temp_kelvin if supported_features & LightEntityFeature.EFFECT: data[ATTR_EFFECT_LIST] = self.effect_list @@ -904,16 +952,14 @@ class LightEntity(ToggleEntity): elif color_mode == ColorMode.RGBWW and self.rgbww_color: rgbww_color = self.rgbww_color rgb_color = color_util.color_rgbww_to_rgb( - *rgbww_color, self.min_mireds, self.max_mireds + *rgbww_color, self.min_color_temp_kelvin, self.max_color_temp_kelvin ) data[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color) data[ATTR_RGB_COLOR] = tuple(int(x) for x in rgb_color[0:3]) data[ATTR_RGBWW_COLOR] = tuple(int(x) for x in rgbww_color[0:5]) data[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color) - elif color_mode == ColorMode.COLOR_TEMP and self.color_temp: - hs_color = color_util.color_temperature_to_hs( - color_util.color_temperature_mired_to_kelvin(self.color_temp) - ) + elif color_mode == ColorMode.COLOR_TEMP and self.color_temp_kelvin: + hs_color = color_util.color_temperature_to_hs(self.color_temp_kelvin) data[ATTR_HS_COLOR] = (round(hs_color[0], 3), round(hs_color[1], 3)) data[ATTR_RGB_COLOR] = color_util.color_hs_to_RGB(*hs_color) data[ATTR_XY_COLOR] = color_util.color_hs_to_xy(*hs_color) @@ -949,7 +995,13 @@ class LightEntity(ToggleEntity): data[ATTR_BRIGHTNESS] = self.brightness if color_mode == ColorMode.COLOR_TEMP: - data[ATTR_COLOR_TEMP] = self.color_temp + data[ATTR_COLOR_TEMP_KELVIN] = self.color_temp_kelvin + if not self.color_temp_kelvin: + data[ATTR_COLOR_TEMP] = None + else: + data[ATTR_COLOR_TEMP] = color_util.color_temperature_kelvin_to_mired( + self.color_temp_kelvin + ) if color_mode in COLOR_MODES_COLOR or color_mode == ColorMode.COLOR_TEMP: data.update(self._light_internal_convert_color(color_mode)) @@ -957,7 +1009,13 @@ class LightEntity(ToggleEntity): if supported_features & SUPPORT_COLOR_TEMP and not self.supported_color_modes: # Backwards compatibility # Add warning in 2021.6, remove in 2021.10 - data[ATTR_COLOR_TEMP] = self.color_temp + data[ATTR_COLOR_TEMP_KELVIN] = self.color_temp_kelvin + if not self.color_temp_kelvin: + data[ATTR_COLOR_TEMP] = None + else: + data[ATTR_COLOR_TEMP] = color_util.color_temperature_kelvin_to_mired( + self.color_temp_kelvin + ) if supported_features & LightEntityFeature.EFFECT: data[ATTR_EFFECT] = self.effect diff --git a/homeassistant/util/color.py b/homeassistant/util/color.py index 494ee04546c..3823c0e45bd 100644 --- a/homeassistant/util/color.py +++ b/homeassistant/util/color.py @@ -436,10 +436,12 @@ def color_rgbw_to_rgb(r: int, g: int, b: int, w: int) -> tuple[int, int, int]: def color_rgb_to_rgbww( - r: int, g: int, b: int, min_mireds: int, max_mireds: int + r: int, g: int, b: int, min_kelvin: int, max_kelvin: int ) -> tuple[int, int, int, int, int]: """Convert an rgb color to an rgbww representation.""" # Find the color temperature when both white channels have equal brightness + max_mireds = color_temperature_kelvin_to_mired(min_kelvin) + min_mireds = color_temperature_kelvin_to_mired(max_kelvin) mired_range = max_mireds - min_mireds mired_midpoint = min_mireds + mired_range / 2 color_temp_kelvin = color_temperature_mired_to_kelvin(mired_midpoint) @@ -460,10 +462,12 @@ def color_rgb_to_rgbww( def color_rgbww_to_rgb( - r: int, g: int, b: int, cw: int, ww: int, min_mireds: int, max_mireds: int + r: int, g: int, b: int, cw: int, ww: int, min_kelvin: int, max_kelvin: int ) -> tuple[int, int, int]: """Convert an rgbww color to an rgb representation.""" # Calculate color temperature of the white channels + max_mireds = color_temperature_kelvin_to_mired(min_kelvin) + min_mireds = color_temperature_kelvin_to_mired(max_kelvin) mired_range = max_mireds - min_mireds try: ct_ratio = ww / (cw + ww) @@ -530,9 +534,15 @@ def color_temperature_to_rgb( def color_temperature_to_rgbww( - temperature: int, brightness: int, min_mireds: int, max_mireds: int + temperature: int, brightness: int, min_kelvin: int, max_kelvin: int ) -> tuple[int, int, int, int, int]: - """Convert color temperature in mireds to rgbcw.""" + """Convert color temperature in kelvin to rgbcw. + + Returns a (r, g, b, cw, ww) tuple. + """ + max_mireds = color_temperature_kelvin_to_mired(min_kelvin) + min_mireds = color_temperature_kelvin_to_mired(max_kelvin) + temperature = color_temperature_kelvin_to_mired(temperature) mired_range = max_mireds - min_mireds cold = ((max_mireds - temperature) / mired_range) * brightness warm = brightness - cold @@ -540,22 +550,33 @@ def color_temperature_to_rgbww( def rgbww_to_color_temperature( - rgbww: tuple[int, int, int, int, int], min_mireds: int, max_mireds: int + rgbww: tuple[int, int, int, int, int], min_kelvin: int, max_kelvin: int ) -> tuple[int, int]: - """Convert rgbcw to color temperature in mireds.""" + """Convert rgbcw to color temperature in kelvin. + + Returns a tuple (color_temperature, brightness). + """ _, _, _, cold, warm = rgbww - return while_levels_to_color_temperature(cold, warm, min_mireds, max_mireds) + return _white_levels_to_color_temperature(cold, warm, min_kelvin, max_kelvin) -def while_levels_to_color_temperature( - cold: int, warm: int, min_mireds: int, max_mireds: int +def _white_levels_to_color_temperature( + cold: int, warm: int, min_kelvin: int, max_kelvin: int ) -> tuple[int, int]: - """Convert whites to color temperature in mireds.""" + """Convert whites to color temperature in kelvin. + + Returns a tuple (color_temperature, brightness). + """ + max_mireds = color_temperature_kelvin_to_mired(min_kelvin) + min_mireds = color_temperature_kelvin_to_mired(max_kelvin) brightness = warm / 255 + cold / 255 if brightness == 0: - return (max_mireds, 0) + # Return the warmest color if brightness is 0 + return (min_kelvin, 0) return round( - ((cold / 255 / brightness) * (min_mireds - max_mireds)) + max_mireds + color_temperature_mired_to_kelvin( + ((cold / 255 / brightness) * (min_mireds - max_mireds)) + max_mireds + ) ), min(255, round(brightness * 255)) diff --git a/tests/components/homekit_controller/specific_devices/test_nanoleaf_strip_nl55.py b/tests/components/homekit_controller/specific_devices/test_nanoleaf_strip_nl55.py index c66ea0d76a9..61d872ccd2a 100644 --- a/tests/components/homekit_controller/specific_devices/test_nanoleaf_strip_nl55.py +++ b/tests/components/homekit_controller/specific_devices/test_nanoleaf_strip_nl55.py @@ -37,6 +37,8 @@ async def test_nanoleaf_nl55_setup(hass): unique_id="homekit-AAAA011111111111-19", supported_features=0, capabilities={ + "max_color_temp_kelvin": 6535, + "min_color_temp_kelvin": 2127, "max_mireds": 470, "min_mireds": 153, "supported_color_modes": ["color_temp", "hs"], diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index 1f21981340f..3a7f9cfccb8 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -629,11 +629,33 @@ async def test_default_profiles_group( }, { light.ATTR_COLOR_TEMP: 600, + light.ATTR_COLOR_TEMP_KELVIN: 1666, light.ATTR_BRIGHTNESS: 11, light.ATTR_TRANSITION: 1, }, { light.ATTR_COLOR_TEMP: 600, + light.ATTR_COLOR_TEMP_KELVIN: 1666, + light.ATTR_BRIGHTNESS: 11, + light.ATTR_TRANSITION: 1, + }, + ), + ( + # Color temp in turn on params, color from profile ignored + { + light.ATTR_COLOR_TEMP_KELVIN: 6500, + light.ATTR_BRIGHTNESS: 11, + light.ATTR_TRANSITION: 1, + }, + { + light.ATTR_COLOR_TEMP: 153, + light.ATTR_COLOR_TEMP_KELVIN: 6500, + light.ATTR_BRIGHTNESS: 11, + light.ATTR_TRANSITION: 1, + }, + { + light.ATTR_COLOR_TEMP: 153, + light.ATTR_COLOR_TEMP_KELVIN: 6500, light.ATTR_BRIGHTNESS: 11, light.ATTR_TRANSITION: 1, }, @@ -1440,7 +1462,7 @@ async def test_light_service_call_color_conversion(hass, enable_custom_integrati _, data = entity5.last_call("turn_on") assert data == {"brightness": 255, "rgbw_color": (0, 0, 0, 255)} _, data = entity6.last_call("turn_on") - # The midpoint the the white channels is warm, compensated by adding green + blue + # The midpoint of the the white channels is warm, compensated by adding green + blue assert data == {"brightness": 255, "rgbww_color": (0, 76, 141, 255, 255)} await hass.services.async_call( @@ -1843,7 +1865,7 @@ async def test_light_service_call_color_temp_emulation( blocking=True, ) _, data = entity0.last_call("turn_on") - assert data == {"brightness": 255, "color_temp": 200} + assert data == {"brightness": 255, "color_temp": 200, "color_temp_kelvin": 5000} _, data = entity1.last_call("turn_on") assert data == {"brightness": 255, "hs_color": (27.001, 19.243)} _, data = entity2.last_call("turn_on") @@ -1868,6 +1890,10 @@ async def test_light_service_call_color_temp_conversion( entity1 = platform.ENTITIES[1] entity1.supported_color_modes = {light.ColorMode.RGBWW} + assert entity1.min_mireds == 153 + assert entity1.max_mireds == 500 + assert entity1.min_color_temp_kelvin == 2000 + assert entity1.max_color_temp_kelvin == 6535 assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) await hass.async_block_till_done() @@ -1895,7 +1921,7 @@ async def test_light_service_call_color_temp_conversion( blocking=True, ) _, data = entity0.last_call("turn_on") - assert data == {"brightness": 255, "color_temp": 153} + assert data == {"brightness": 255, "color_temp": 153, "color_temp_kelvin": 6535} _, data = entity1.last_call("turn_on") # Home Assistant uses RGBCW so a mireds of 153 should be maximum cold at 100% brightness so 255 assert data == {"brightness": 255, "rgbww_color": (0, 0, 0, 255, 0)} @@ -1914,7 +1940,7 @@ async def test_light_service_call_color_temp_conversion( blocking=True, ) _, data = entity0.last_call("turn_on") - assert data == {"brightness": 128, "color_temp": 500} + assert data == {"brightness": 128, "color_temp": 500, "color_temp_kelvin": 2000} _, data = entity1.last_call("turn_on") # Home Assistant uses RGBCW so a mireds of 500 should be maximum warm at 50% brightness so 128 assert data == {"brightness": 128, "rgbww_color": (0, 0, 0, 0, 128)} @@ -1933,7 +1959,7 @@ async def test_light_service_call_color_temp_conversion( blocking=True, ) _, data = entity0.last_call("turn_on") - assert data == {"brightness": 255, "color_temp": 327} + assert data == {"brightness": 255, "color_temp": 327, "color_temp_kelvin": 3058} _, data = entity1.last_call("turn_on") # Home Assistant uses RGBCW so a mireds of 328 should be the midway point at 100% brightness so 127 (rounding), 128 assert data == {"brightness": 255, "rgbww_color": (0, 0, 0, 127, 128)} @@ -1952,7 +1978,7 @@ async def test_light_service_call_color_temp_conversion( blocking=True, ) _, data = entity0.last_call("turn_on") - assert data == {"brightness": 255, "color_temp": 240} + assert data == {"brightness": 255, "color_temp": 240, "color_temp_kelvin": 4166} _, data = entity1.last_call("turn_on") assert data == {"brightness": 255, "rgbww_color": (0, 0, 0, 191, 64)} @@ -1970,7 +1996,7 @@ async def test_light_service_call_color_temp_conversion( blocking=True, ) _, data = entity0.last_call("turn_on") - assert data == {"brightness": 255, "color_temp": 410} + assert data == {"brightness": 255, "color_temp": 410, "color_temp_kelvin": 2439} _, data = entity1.last_call("turn_on") assert data == {"brightness": 255, "rgbww_color": (0, 0, 0, 66, 189)} diff --git a/tests/components/yeelight/test_light.py b/tests/components/yeelight/test_light.py index fad39a052d5..f38705c6e9a 100644 --- a/tests/components/yeelight/test_light.py +++ b/tests/components/yeelight/test_light.py @@ -861,14 +861,16 @@ async def test_device_types(hass: HomeAssistant, caplog): await hass.async_block_till_done() bright = round(255 * int(PROPERTIES["bright"]) / 100) - ct = color_temperature_kelvin_to_mired(int(PROPERTIES["ct"])) + ct = int(PROPERTIES["ct"]) + ct_mired = color_temperature_kelvin_to_mired(int(PROPERTIES["ct"])) hue = int(PROPERTIES["hue"]) sat = int(PROPERTIES["sat"]) rgb = int(PROPERTIES["rgb"]) rgb_color = ((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF) hs_color = (hue, sat) bg_bright = round(255 * int(PROPERTIES["bg_bright"]) / 100) - bg_ct = color_temperature_kelvin_to_mired(int(PROPERTIES["bg_ct"])) + bg_ct = int(PROPERTIES["bg_ct"]) + bg_ct_kelvin = color_temperature_kelvin_to_mired(int(PROPERTIES["bg_ct"])) bg_hue = int(PROPERTIES["bg_hue"]) bg_sat = int(PROPERTIES["bg_sat"]) bg_rgb = int(PROPERTIES["bg_rgb"]) @@ -911,6 +913,10 @@ async def test_device_types(hass: HomeAssistant, caplog): { "effect_list": YEELIGHT_COLOR_EFFECT_LIST, "supported_features": SUPPORT_YEELIGHT, + "min_color_temp_kelvin": model_specs["color_temp"]["min"], + "max_color_temp_kelvin": color_temperature_mired_to_kelvin( + color_temperature_kelvin_to_mired(model_specs["color_temp"]["max"]) + ), "min_mireds": color_temperature_kelvin_to_mired( model_specs["color_temp"]["max"] ), @@ -918,7 +924,8 @@ async def test_device_types(hass: HomeAssistant, caplog): model_specs["color_temp"]["min"] ), "brightness": bright, - "color_temp": ct, + "color_temp_kelvin": ct, + "color_temp": ct_mired, "color_mode": "color_temp", "supported_color_modes": ["color_temp", "hs", "rgb"], "hs_color": (26.812, 34.87), @@ -936,6 +943,10 @@ async def test_device_types(hass: HomeAssistant, caplog): "hs_color": (28.401, 100.0), "rgb_color": (255, 120, 0), "xy_color": (0.621, 0.367), + "min_color_temp_kelvin": model_specs["color_temp"]["min"], + "max_color_temp_kelvin": color_temperature_mired_to_kelvin( + color_temperature_kelvin_to_mired(model_specs["color_temp"]["max"]) + ), "min_mireds": color_temperature_kelvin_to_mired( model_specs["color_temp"]["max"] ), @@ -945,6 +956,7 @@ async def test_device_types(hass: HomeAssistant, caplog): "brightness": nl_br, "color_mode": "color_temp", "supported_color_modes": ["color_temp", "hs", "rgb"], + "color_temp_kelvin": model_specs["color_temp"]["min"], "color_temp": color_temperature_kelvin_to_mired( model_specs["color_temp"]["min"] ), @@ -960,6 +972,10 @@ async def test_device_types(hass: HomeAssistant, caplog): { "effect_list": YEELIGHT_COLOR_EFFECT_LIST, "supported_features": SUPPORT_YEELIGHT, + "min_color_temp_kelvin": model_specs["color_temp"]["min"], + "max_color_temp_kelvin": color_temperature_mired_to_kelvin( + color_temperature_kelvin_to_mired(model_specs["color_temp"]["max"]) + ), "min_mireds": color_temperature_kelvin_to_mired( model_specs["color_temp"]["max"] ), @@ -989,6 +1005,10 @@ async def test_device_types(hass: HomeAssistant, caplog): { "effect_list": YEELIGHT_COLOR_EFFECT_LIST, "supported_features": SUPPORT_YEELIGHT, + "min_color_temp_kelvin": model_specs["color_temp"]["min"], + "max_color_temp_kelvin": color_temperature_mired_to_kelvin( + color_temperature_kelvin_to_mired(model_specs["color_temp"]["max"]) + ), "min_mireds": color_temperature_kelvin_to_mired( model_specs["color_temp"]["max"] ), @@ -1019,6 +1039,10 @@ async def test_device_types(hass: HomeAssistant, caplog): { "effect_list": YEELIGHT_COLOR_EFFECT_LIST, "supported_features": SUPPORT_YEELIGHT, + "min_color_temp_kelvin": model_specs["color_temp"]["min"], + "max_color_temp_kelvin": color_temperature_mired_to_kelvin( + color_temperature_kelvin_to_mired(model_specs["color_temp"]["max"]) + ), "min_mireds": color_temperature_kelvin_to_mired( model_specs["color_temp"]["max"] ), @@ -1046,6 +1070,10 @@ async def test_device_types(hass: HomeAssistant, caplog): { "effect_list": YEELIGHT_COLOR_EFFECT_LIST, "supported_features": SUPPORT_YEELIGHT, + "min_color_temp_kelvin": model_specs["color_temp"]["min"], + "max_color_temp_kelvin": color_temperature_mired_to_kelvin( + color_temperature_kelvin_to_mired(model_specs["color_temp"]["max"]) + ), "min_mireds": color_temperature_kelvin_to_mired( model_specs["color_temp"]["max"] ), @@ -1072,6 +1100,10 @@ async def test_device_types(hass: HomeAssistant, caplog): { "effect_list": YEELIGHT_COLOR_EFFECT_LIST, "supported_features": SUPPORT_YEELIGHT, + "min_color_temp_kelvin": model_specs["color_temp"]["min"], + "max_color_temp_kelvin": color_temperature_mired_to_kelvin( + color_temperature_kelvin_to_mired(model_specs["color_temp"]["max"]) + ), "min_mireds": color_temperature_kelvin_to_mired( model_specs["color_temp"]["max"] ), @@ -1097,6 +1129,12 @@ async def test_device_types(hass: HomeAssistant, caplog): { "effect_list": YEELIGHT_TEMP_ONLY_EFFECT_LIST, "supported_features": SUPPORT_YEELIGHT, + "min_color_temp_kelvin": color_temperature_mired_to_kelvin( + color_temperature_kelvin_to_mired(model_specs["color_temp"]["min"]) + ), + "max_color_temp_kelvin": color_temperature_mired_to_kelvin( + color_temperature_kelvin_to_mired(model_specs["color_temp"]["max"]) + ), "min_mireds": color_temperature_kelvin_to_mired( model_specs["color_temp"]["max"] ), @@ -1104,7 +1142,8 @@ async def test_device_types(hass: HomeAssistant, caplog): model_specs["color_temp"]["min"] ), "brightness": bright, - "color_temp": ct, + "color_temp_kelvin": ct, + "color_temp": ct_mired, "color_mode": "color_temp", "supported_color_modes": ["color_temp"], "hs_color": (26.812, 34.87), @@ -1120,6 +1159,12 @@ async def test_device_types(hass: HomeAssistant, caplog): nightlight_mode_properties={ "effect_list": YEELIGHT_TEMP_ONLY_EFFECT_LIST, "supported_features": SUPPORT_YEELIGHT, + "min_color_temp_kelvin": color_temperature_mired_to_kelvin( + color_temperature_kelvin_to_mired(model_specs["color_temp"]["min"]) + ), + "max_color_temp_kelvin": color_temperature_mired_to_kelvin( + color_temperature_kelvin_to_mired(model_specs["color_temp"]["max"]) + ), "min_mireds": color_temperature_kelvin_to_mired( model_specs["color_temp"]["max"] ), @@ -1127,6 +1172,9 @@ async def test_device_types(hass: HomeAssistant, caplog): model_specs["color_temp"]["min"] ), "brightness": nl_br, + "color_temp_kelvin": color_temperature_mired_to_kelvin( + color_temperature_kelvin_to_mired(model_specs["color_temp"]["min"]) + ), "color_temp": color_temperature_kelvin_to_mired( model_specs["color_temp"]["min"] ), @@ -1151,6 +1199,12 @@ async def test_device_types(hass: HomeAssistant, caplog): "flowing": False, "night_light": True, "supported_features": SUPPORT_YEELIGHT, + "min_color_temp_kelvin": color_temperature_mired_to_kelvin( + color_temperature_kelvin_to_mired(model_specs["color_temp"]["min"]) + ), + "max_color_temp_kelvin": color_temperature_mired_to_kelvin( + color_temperature_kelvin_to_mired(model_specs["color_temp"]["max"]) + ), "min_mireds": color_temperature_kelvin_to_mired( model_specs["color_temp"]["max"] ), @@ -1158,7 +1212,8 @@ async def test_device_types(hass: HomeAssistant, caplog): model_specs["color_temp"]["min"] ), "brightness": bright, - "color_temp": ct, + "color_temp_kelvin": ct, + "color_temp": ct_mired, "color_mode": "color_temp", "supported_color_modes": ["color_temp"], "hs_color": (26.812, 34.87), @@ -1177,6 +1232,12 @@ async def test_device_types(hass: HomeAssistant, caplog): "flowing": False, "night_light": True, "supported_features": SUPPORT_YEELIGHT, + "min_color_temp_kelvin": color_temperature_mired_to_kelvin( + color_temperature_kelvin_to_mired(model_specs["color_temp"]["min"]) + ), + "max_color_temp_kelvin": color_temperature_mired_to_kelvin( + color_temperature_kelvin_to_mired(model_specs["color_temp"]["max"]) + ), "min_mireds": color_temperature_kelvin_to_mired( model_specs["color_temp"]["max"] ), @@ -1184,6 +1245,9 @@ async def test_device_types(hass: HomeAssistant, caplog): model_specs["color_temp"]["min"] ), "brightness": nl_br, + "color_temp_kelvin": color_temperature_mired_to_kelvin( + color_temperature_kelvin_to_mired(model_specs["color_temp"]["min"]) + ), "color_temp": color_temperature_kelvin_to_mired( model_specs["color_temp"]["min"] ), @@ -1202,10 +1266,15 @@ async def test_device_types(hass: HomeAssistant, caplog): { "effect_list": YEELIGHT_COLOR_EFFECT_LIST, "supported_features": SUPPORT_YEELIGHT, + "min_color_temp_kelvin": 1700, + "max_color_temp_kelvin": color_temperature_mired_to_kelvin( + color_temperature_kelvin_to_mired(6500) + ), "min_mireds": color_temperature_kelvin_to_mired(6500), "max_mireds": color_temperature_kelvin_to_mired(1700), "brightness": bg_bright, - "color_temp": bg_ct, + "color_temp_kelvin": bg_ct, + "color_temp": bg_ct_kelvin, "color_mode": "color_temp", "supported_color_modes": ["color_temp", "hs", "rgb"], "hs_color": (27.001, 19.243), @@ -1224,6 +1293,10 @@ async def test_device_types(hass: HomeAssistant, caplog): { "effect_list": YEELIGHT_COLOR_EFFECT_LIST, "supported_features": SUPPORT_YEELIGHT, + "min_color_temp_kelvin": 1700, + "max_color_temp_kelvin": color_temperature_mired_to_kelvin( + color_temperature_kelvin_to_mired(6500) + ), "min_mireds": color_temperature_kelvin_to_mired(6500), "max_mireds": color_temperature_kelvin_to_mired(1700), "brightness": bg_bright, @@ -1245,6 +1318,10 @@ async def test_device_types(hass: HomeAssistant, caplog): { "effect_list": YEELIGHT_COLOR_EFFECT_LIST, "supported_features": SUPPORT_YEELIGHT, + "min_color_temp_kelvin": 1700, + "max_color_temp_kelvin": color_temperature_mired_to_kelvin( + color_temperature_kelvin_to_mired(6500) + ), "min_mireds": color_temperature_kelvin_to_mired(6500), "max_mireds": color_temperature_kelvin_to_mired(1700), "brightness": bg_bright, diff --git a/tests/util/test_color.py b/tests/util/test_color.py index b77540acc2b..95d2ffc0fd7 100644 --- a/tests/util/test_color.py +++ b/tests/util/test_color.py @@ -370,82 +370,84 @@ def test_get_color_in_voluptuous(): def test_color_rgb_to_rgbww(): """Test color_rgb_to_rgbww conversions.""" - assert color_util.color_rgb_to_rgbww(255, 255, 255, 154, 370) == ( + # Light with mid point at ~4600K (warm white) -> output compensated by adding blue + assert color_util.color_rgb_to_rgbww(255, 255, 255, 2702, 6493) == ( 0, 54, 98, 255, 255, ) - assert color_util.color_rgb_to_rgbww(255, 255, 255, 100, 1000) == ( + # Light with mid point at ~5500K (less warm white) -> output compensated by adding less blue + assert color_util.color_rgb_to_rgbww(255, 255, 255, 1000, 10000) == ( 255, 255, 255, 0, 0, ) - assert color_util.color_rgb_to_rgbww(255, 255, 255, 1, 1000) == ( + # Light with mid point at ~1MK (unrealistically cold white) -> output compensated by adding red + assert color_util.color_rgb_to_rgbww(255, 255, 255, 1000, 1000000) == ( 0, 118, 241, 255, 255, ) - assert color_util.color_rgb_to_rgbww(128, 128, 128, 154, 370) == ( + assert color_util.color_rgb_to_rgbww(128, 128, 128, 2702, 6493) == ( 0, 27, 49, 128, 128, ) - assert color_util.color_rgb_to_rgbww(64, 64, 64, 154, 370) == (0, 14, 25, 64, 64) - assert color_util.color_rgb_to_rgbww(32, 64, 16, 154, 370) == (9, 64, 0, 38, 38) - assert color_util.color_rgb_to_rgbww(0, 0, 0, 154, 370) == (0, 0, 0, 0, 0) - assert color_util.color_rgb_to_rgbww(0, 0, 0, 0, 100) == (0, 0, 0, 0, 0) - assert color_util.color_rgb_to_rgbww(255, 255, 255, 1, 5) == (103, 69, 0, 255, 255) + assert color_util.color_rgb_to_rgbww(64, 64, 64, 2702, 6493) == (0, 14, 25, 64, 64) + assert color_util.color_rgb_to_rgbww(32, 64, 16, 2702, 6493) == (9, 64, 0, 38, 38) + assert color_util.color_rgb_to_rgbww(0, 0, 0, 2702, 6493) == (0, 0, 0, 0, 0) + assert color_util.color_rgb_to_rgbww(0, 0, 0, 10000, 1000000) == (0, 0, 0, 0, 0) + assert color_util.color_rgb_to_rgbww(255, 255, 255, 200000, 1000000) == ( + 103, + 69, + 0, + 255, + 255, + ) def test_color_rgbww_to_rgb(): """Test color_rgbww_to_rgb conversions.""" - assert color_util.color_rgbww_to_rgb(0, 54, 98, 255, 255, 154, 370) == ( + assert color_util.color_rgbww_to_rgb(0, 54, 98, 255, 255, 2702, 6493) == ( 255, 255, 255, ) - assert color_util.color_rgbww_to_rgb(255, 255, 255, 0, 0, 154, 370) == ( + # rgb fully on, + both white channels turned off -> rgb fully on + assert color_util.color_rgbww_to_rgb(255, 255, 255, 0, 0, 2702, 6493) == ( 255, 255, 255, ) - assert color_util.color_rgbww_to_rgb(0, 118, 241, 255, 255, 154, 370) == ( + # r < g < b + both white channels fully enabled -> r < g < b capped at 255 + assert color_util.color_rgbww_to_rgb(0, 118, 241, 255, 255, 2702, 6493) == ( 163, 204, 255, ) - assert color_util.color_rgbww_to_rgb(0, 27, 49, 128, 128, 154, 370) == ( + # r < g < b + both white channels 50% enabled -> r < g < b capped at 128 + assert color_util.color_rgbww_to_rgb(0, 27, 49, 128, 128, 2702, 6493) == ( 128, 128, 128, ) - assert color_util.color_rgbww_to_rgb(0, 14, 25, 64, 64, 154, 370) == (64, 64, 64) - assert color_util.color_rgbww_to_rgb(9, 64, 0, 38, 38, 154, 370) == (32, 64, 16) - assert color_util.color_rgbww_to_rgb(0, 0, 0, 0, 0, 154, 370) == (0, 0, 0) - assert color_util.color_rgbww_to_rgb(103, 69, 0, 255, 255, 153, 370) == ( + # r < g < b + both white channels 25% enabled -> r < g < b capped at 64 + assert color_util.color_rgbww_to_rgb(0, 14, 25, 64, 64, 2702, 6493) == (64, 64, 64) + assert color_util.color_rgbww_to_rgb(9, 64, 0, 38, 38, 2702, 6493) == (32, 64, 16) + assert color_util.color_rgbww_to_rgb(0, 0, 0, 0, 0, 2702, 6493) == (0, 0, 0) + assert color_util.color_rgbww_to_rgb(103, 69, 0, 255, 255, 2702, 6535) == ( 255, 193, 112, ) - assert color_util.color_rgbww_to_rgb(255, 255, 255, 0, 0, 0, 0) == (255, 255, 255) - assert color_util.color_rgbww_to_rgb(255, 255, 255, 255, 255, 0, 0) == ( - 255, - 161, - 128, - ) - assert color_util.color_rgbww_to_rgb(255, 255, 255, 255, 255, 0, 370) == ( - 255, - 245, - 237, - ) def test_color_temperature_to_rgbww(): @@ -454,42 +456,45 @@ def test_color_temperature_to_rgbww(): Temperature values must be in mireds Home Assistant uses rgbcw for rgbww """ - assert color_util.color_temperature_to_rgbww(153, 255, 153, 500) == ( + # Coldest color temperature -> only cold channel enabled + assert color_util.color_temperature_to_rgbww(6535, 255, 2000, 6535) == ( 0, 0, 0, 255, 0, ) - assert color_util.color_temperature_to_rgbww(153, 128, 153, 500) == ( + assert color_util.color_temperature_to_rgbww(6535, 128, 2000, 6535) == ( 0, 0, 0, 128, 0, ) - assert color_util.color_temperature_to_rgbww(500, 255, 153, 500) == ( + # Warmest color temperature -> only cold channel enabled + assert color_util.color_temperature_to_rgbww(2000, 255, 2000, 6535) == ( 0, 0, 0, 0, 255, ) - assert color_util.color_temperature_to_rgbww(500, 128, 153, 500) == ( + assert color_util.color_temperature_to_rgbww(2000, 128, 2000, 6535) == ( 0, 0, 0, 0, 128, ) - assert color_util.color_temperature_to_rgbww(347, 255, 153, 500) == ( + # Warmer than mid point color temperature -> More warm than cold channel enabled + assert color_util.color_temperature_to_rgbww(2881, 255, 2000, 6535) == ( 0, 0, 0, 112, 143, ) - assert color_util.color_temperature_to_rgbww(347, 128, 153, 500) == ( + assert color_util.color_temperature_to_rgbww(2881, 128, 2000, 6535) == ( 0, 0, 0, @@ -504,39 +509,36 @@ def test_rgbww_to_color_temperature(): Temperature values must be in mireds Home Assistant uses rgbcw for rgbww """ - assert color_util.rgbww_to_color_temperature( - ( - 0, - 0, - 0, - 255, - 0, - ), - 153, - 500, - ) == (153, 255) - assert color_util.rgbww_to_color_temperature((0, 0, 0, 128, 0), 153, 500) == ( - 153, - 128, - ) - assert color_util.rgbww_to_color_temperature((0, 0, 0, 0, 255), 153, 500) == ( - 500, + # Only cold channel enabled -> coldest color temperature + assert color_util.rgbww_to_color_temperature((0, 0, 0, 255, 0), 2000, 6535) == ( + 6535, 255, ) - assert color_util.rgbww_to_color_temperature((0, 0, 0, 0, 128), 153, 500) == ( - 500, + assert color_util.rgbww_to_color_temperature((0, 0, 0, 128, 0), 2000, 6535) == ( + 6535, 128, ) - assert color_util.rgbww_to_color_temperature((0, 0, 0, 112, 143), 153, 500) == ( - 348, + # Only warm channel enabled -> warmest color temperature + assert color_util.rgbww_to_color_temperature((0, 0, 0, 0, 255), 2000, 6535) == ( + 2000, 255, ) - assert color_util.rgbww_to_color_temperature((0, 0, 0, 56, 72), 153, 500) == ( - 348, + assert color_util.rgbww_to_color_temperature((0, 0, 0, 0, 128), 2000, 6535) == ( + 2000, 128, ) - assert color_util.rgbww_to_color_temperature((0, 0, 0, 0, 0), 153, 500) == ( - 500, + # More warm than cold channel enabled -> warmer than mid point + assert color_util.rgbww_to_color_temperature((0, 0, 0, 112, 143), 2000, 6535) == ( + 2876, + 255, + ) + assert color_util.rgbww_to_color_temperature((0, 0, 0, 56, 72), 2000, 6535) == ( + 2872, + 128, + ) + # Both channels turned off -> warmest color temperature + assert color_util.rgbww_to_color_temperature((0, 0, 0, 0, 0), 2000, 6535) == ( + 2000, 0, ) @@ -547,33 +549,34 @@ def test_white_levels_to_color_temperature(): Temperature values must be in mireds Home Assistant uses rgbcw for rgbww """ - assert color_util.while_levels_to_color_temperature( - 255, - 0, - 153, - 500, - ) == (153, 255) - assert color_util.while_levels_to_color_temperature(128, 0, 153, 500) == ( - 153, - 128, - ) - assert color_util.while_levels_to_color_temperature(0, 255, 153, 500) == ( - 500, + # Only cold channel enabled -> coldest color temperature + assert color_util._white_levels_to_color_temperature(255, 0, 2000, 6535) == ( + 6535, 255, ) - assert color_util.while_levels_to_color_temperature(0, 128, 153, 500) == ( - 500, + assert color_util._white_levels_to_color_temperature(128, 0, 2000, 6535) == ( + 6535, 128, ) - assert color_util.while_levels_to_color_temperature(112, 143, 153, 500) == ( - 348, + # Only warm channel enabled -> warmest color temperature + assert color_util._white_levels_to_color_temperature(0, 255, 2000, 6535) == ( + 2000, 255, ) - assert color_util.while_levels_to_color_temperature(56, 72, 153, 500) == ( - 348, + assert color_util._white_levels_to_color_temperature(0, 128, 2000, 6535) == ( + 2000, 128, ) - assert color_util.while_levels_to_color_temperature(0, 0, 153, 500) == ( - 500, + assert color_util._white_levels_to_color_temperature(112, 143, 2000, 6535) == ( + 2876, + 255, + ) + assert color_util._white_levels_to_color_temperature(56, 72, 2000, 6535) == ( + 2872, + 128, + ) + # Both channels turned off -> warmest color temperature + assert color_util._white_levels_to_color_temperature(0, 0, 2000, 6535) == ( + 2000, 0, )