Add warning when light entities do not provide kelvin attributes or properties (#132723)
parent
067daad70e
commit
8080ad14bf
|
@ -32,6 +32,7 @@ from homeassistant.helpers.deprecation import (
|
|||
)
|
||||
from homeassistant.helpers.entity import ToggleEntity, ToggleEntityDescription
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.frame import ReportBehavior, report_usage
|
||||
from homeassistant.helpers.typing import ConfigType, VolDictType
|
||||
from homeassistant.loader import bind_hass
|
||||
import homeassistant.util.color as color_util
|
||||
|
@ -41,6 +42,8 @@ from .const import ( # noqa: F401
|
|||
COLOR_MODES_COLOR,
|
||||
DATA_COMPONENT,
|
||||
DATA_PROFILES,
|
||||
DEFAULT_MAX_KELVIN,
|
||||
DEFAULT_MIN_KELVIN,
|
||||
DOMAIN,
|
||||
SCAN_INTERVAL,
|
||||
VALID_COLOR_MODES,
|
||||
|
@ -863,17 +866,15 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||
entity_description: LightEntityDescription
|
||||
_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
|
||||
# Default to the Philips Hue value that HA has always assumed
|
||||
# https://developers.meethue.com/documentation/core-concepts
|
||||
# We cannot set defaults without causing breaking changes until mireds
|
||||
# are fully removed. Until then, developers can explicitly
|
||||
# use DEFAULT_MIN_KELVIN and DEFAULT_MAX_KELVIN
|
||||
_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 # 6500 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
|
||||
|
@ -881,6 +882,11 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||
_attr_supported_features: LightEntityFeature = LightEntityFeature(0)
|
||||
_attr_xy_color: tuple[float, float] | None = None
|
||||
|
||||
# Deprecated, see https://github.com/home-assistant/core/pull/79591
|
||||
_attr_color_temp: Final[int | None] = None
|
||||
_attr_max_mireds: Final[int] = 500 # = 2000 K
|
||||
_attr_min_mireds: Final[int] = 153 # = 6535.94 K (~ 6500 K)
|
||||
|
||||
__color_mode_reported = False
|
||||
|
||||
@cached_property
|
||||
|
@ -956,32 +962,70 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||
"""Return the rgbww color value [int, int, int, int, int]."""
|
||||
return self._attr_rgbww_color
|
||||
|
||||
@final
|
||||
@cached_property
|
||||
def color_temp(self) -> int | None:
|
||||
"""Return the CT color value in mireds."""
|
||||
"""Return the CT color value in mireds.
|
||||
|
||||
Deprecated, see https://github.com/home-assistant/core/pull/79591
|
||||
"""
|
||||
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 (color_temp := self.color_temp):
|
||||
report_usage(
|
||||
"is using mireds for current light color temperature, when "
|
||||
"it should be adjusted to use the kelvin attribute "
|
||||
"`_attr_color_temp_kelvin` or override the kelvin property "
|
||||
"`color_temp_kelvin` (see "
|
||||
"https://github.com/home-assistant/core/pull/79591)",
|
||||
breaks_in_ha_version="2026.1",
|
||||
core_behavior=ReportBehavior.LOG,
|
||||
integration_domain=self.platform.platform_name
|
||||
if self.platform
|
||||
else None,
|
||||
exclude_integrations={DOMAIN},
|
||||
)
|
||||
return color_util.color_temperature_mired_to_kelvin(color_temp)
|
||||
return self._attr_color_temp_kelvin
|
||||
|
||||
@final
|
||||
@cached_property
|
||||
def min_mireds(self) -> int:
|
||||
"""Return the coldest color_temp that this light supports."""
|
||||
"""Return the coldest color_temp that this light supports.
|
||||
|
||||
Deprecated, see https://github.com/home-assistant/core/pull/79591
|
||||
"""
|
||||
return self._attr_min_mireds
|
||||
|
||||
@final
|
||||
@cached_property
|
||||
def max_mireds(self) -> int:
|
||||
"""Return the warmest color_temp that this light supports."""
|
||||
"""Return the warmest color_temp that this light supports.
|
||||
|
||||
Deprecated, see https://github.com/home-assistant/core/pull/79591
|
||||
"""
|
||||
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:
|
||||
report_usage(
|
||||
"is using mireds for warmest light color temperature, when "
|
||||
"it should be adjusted to use the kelvin attribute "
|
||||
"`_attr_min_color_temp_kelvin` or override the kelvin property "
|
||||
"`min_color_temp_kelvin`, possibly with default DEFAULT_MIN_KELVIN "
|
||||
"(see https://github.com/home-assistant/core/pull/79591)",
|
||||
breaks_in_ha_version="2026.1",
|
||||
core_behavior=ReportBehavior.LOG,
|
||||
integration_domain=self.platform.platform_name
|
||||
if self.platform
|
||||
else None,
|
||||
exclude_integrations={DOMAIN},
|
||||
)
|
||||
return color_util.color_temperature_mired_to_kelvin(self.max_mireds)
|
||||
return self._attr_min_color_temp_kelvin
|
||||
|
||||
|
@ -989,6 +1033,19 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||
def max_color_temp_kelvin(self) -> int:
|
||||
"""Return the coldest color_temp_kelvin that this light supports."""
|
||||
if self._attr_max_color_temp_kelvin is None:
|
||||
report_usage(
|
||||
"is using mireds for coldest light color temperature, when "
|
||||
"it should be adjusted to use the kelvin attribute "
|
||||
"`_attr_max_color_temp_kelvin` or override the kelvin property "
|
||||
"`max_color_temp_kelvin`, possibly with default DEFAULT_MAX_KELVIN "
|
||||
"(see https://github.com/home-assistant/core/pull/79591)",
|
||||
breaks_in_ha_version="2026.1",
|
||||
core_behavior=ReportBehavior.LOG,
|
||||
integration_domain=self.platform.platform_name
|
||||
if self.platform
|
||||
else None,
|
||||
exclude_integrations={DOMAIN},
|
||||
)
|
||||
return color_util.color_temperature_mired_to_kelvin(self.min_mireds)
|
||||
return self._attr_max_color_temp_kelvin
|
||||
|
||||
|
|
|
@ -66,3 +66,8 @@ COLOR_MODES_COLOR = {
|
|||
ColorMode.RGBWW,
|
||||
ColorMode.XY,
|
||||
}
|
||||
|
||||
# Default to the Philips Hue value that HA has always assumed
|
||||
# https://developers.meethue.com/documentation/core-concepts
|
||||
DEFAULT_MIN_KELVIN = 2000 # 500 mireds
|
||||
DEFAULT_MAX_KELVIN = 6535 # 153 mireds
|
||||
|
|
|
@ -21,6 +21,8 @@ from homeassistant.components.light import (
|
|||
ATTR_TRANSITION,
|
||||
ATTR_WHITE,
|
||||
ATTR_XY_COLOR,
|
||||
DEFAULT_MAX_KELVIN,
|
||||
DEFAULT_MIN_KELVIN,
|
||||
DOMAIN,
|
||||
ColorMode,
|
||||
LightEntity,
|
||||
|
@ -153,8 +155,8 @@ TURN_ON_ARG_TO_COLOR_MODE = {
|
|||
class MockLight(MockToggleEntity, LightEntity):
|
||||
"""Mock light class."""
|
||||
|
||||
_attr_max_color_temp_kelvin = 6500
|
||||
_attr_min_color_temp_kelvin = 2000
|
||||
_attr_max_color_temp_kelvin = DEFAULT_MAX_KELVIN
|
||||
_attr_min_color_temp_kelvin = DEFAULT_MIN_KELVIN
|
||||
supported_features = LightEntityFeature(0)
|
||||
|
||||
brightness = None
|
||||
|
|
|
@ -20,6 +20,7 @@ from homeassistant.const import (
|
|||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError, Unauthorized
|
||||
from homeassistant.helpers import frame
|
||||
from homeassistant.setup import async_setup_component
|
||||
import homeassistant.util.color as color_util
|
||||
|
||||
|
@ -1209,7 +1210,7 @@ async def test_light_state_off(hass: HomeAssistant) -> None:
|
|||
"hs_color": None,
|
||||
"rgb_color": None,
|
||||
"xy_color": None,
|
||||
"max_color_temp_kelvin": 6500,
|
||||
"max_color_temp_kelvin": 6535,
|
||||
"max_mireds": 500,
|
||||
"min_color_temp_kelvin": 2000,
|
||||
"min_mireds": 153,
|
||||
|
@ -1842,7 +1843,7 @@ async def test_light_service_call_color_temp_conversion(hass: HomeAssistant) ->
|
|||
assert entity1.min_mireds == 153
|
||||
assert entity1.max_mireds == 500
|
||||
assert entity1.min_color_temp_kelvin == 2000
|
||||
assert entity1.max_color_temp_kelvin == 6500
|
||||
assert entity1.max_color_temp_kelvin == 6535
|
||||
|
||||
assert await async_setup_component(hass, "light", {"light": {"platform": "test"}})
|
||||
await hass.async_block_till_done()
|
||||
|
@ -1855,7 +1856,7 @@ async def test_light_service_call_color_temp_conversion(hass: HomeAssistant) ->
|
|||
assert state.attributes["min_mireds"] == 153
|
||||
assert state.attributes["max_mireds"] == 500
|
||||
assert state.attributes["min_color_temp_kelvin"] == 2000
|
||||
assert state.attributes["max_color_temp_kelvin"] == 6500
|
||||
assert state.attributes["max_color_temp_kelvin"] == 6535
|
||||
|
||||
state = hass.states.get(entity1.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [light.ColorMode.RGBWW]
|
||||
|
@ -2547,6 +2548,71 @@ def test_report_invalid_color_modes(
|
|||
assert (expected_warning in caplog.text) is warning_expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("attributes", "expected_warnings", "expected_values"),
|
||||
[
|
||||
(
|
||||
{
|
||||
"_attr_color_temp_kelvin": 4000,
|
||||
"_attr_min_color_temp_kelvin": 3000,
|
||||
"_attr_max_color_temp_kelvin": 5000,
|
||||
},
|
||||
{"current": False, "warmest": False, "coldest": False},
|
||||
# Just highlighting that the attributes match the
|
||||
# converted kelvin values, not the mired properties
|
||||
(3000, 4000, 5000, 200, 250, 333, 153, None, 500),
|
||||
),
|
||||
(
|
||||
{"_attr_color_temp": 350, "_attr_min_mireds": 300, "_attr_max_mireds": 400},
|
||||
{"current": True, "warmest": True, "coldest": True},
|
||||
(2500, 2857, 3333, 300, 350, 400, 300, 350, 400),
|
||||
),
|
||||
(
|
||||
{},
|
||||
{"current": False, "warmest": True, "coldest": True},
|
||||
(2000, None, 6535, 153, None, 500, 153, None, 500),
|
||||
),
|
||||
],
|
||||
ids=["with_kelvin", "with_mired_values", "with_mired_defaults"],
|
||||
)
|
||||
@patch.object(frame, "_REPORTED_INTEGRATIONS", set())
|
||||
def test_missing_kelvin_property_warnings(
|
||||
hass: HomeAssistant,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
attributes: dict[str, int | None],
|
||||
expected_warnings: dict[str, bool],
|
||||
expected_values: tuple[int, int | None, int],
|
||||
) -> None:
|
||||
"""Test missing kelvin properties."""
|
||||
|
||||
class MockLightEntityEntity(light.LightEntity):
|
||||
_attr_color_mode = light.ColorMode.COLOR_TEMP
|
||||
_attr_is_on = True
|
||||
_attr_supported_features = light.LightEntityFeature.EFFECT
|
||||
_attr_supported_color_modes = {light.ColorMode.COLOR_TEMP}
|
||||
platform = MockEntityPlatform(hass, platform_name="test")
|
||||
|
||||
entity = MockLightEntityEntity()
|
||||
for k, v in attributes.items():
|
||||
setattr(entity, k, v)
|
||||
|
||||
state = entity._async_calculate_state()
|
||||
for warning, expected in expected_warnings.items():
|
||||
assert (
|
||||
f"is using mireds for {warning} light color temperature" in caplog.text
|
||||
) is expected, f"Expected {expected} for '{warning}'"
|
||||
|
||||
assert state.attributes[light.ATTR_MIN_COLOR_TEMP_KELVIN] == expected_values[0]
|
||||
assert state.attributes[light.ATTR_COLOR_TEMP_KELVIN] == expected_values[1]
|
||||
assert state.attributes[light.ATTR_MAX_COLOR_TEMP_KELVIN] == expected_values[2]
|
||||
assert state.attributes[light.ATTR_MIN_MIREDS] == expected_values[3]
|
||||
assert state.attributes[light.ATTR_COLOR_TEMP] == expected_values[4]
|
||||
assert state.attributes[light.ATTR_MAX_MIREDS] == expected_values[5]
|
||||
assert entity.min_mireds == expected_values[6]
|
||||
assert entity.color_temp == expected_values[7]
|
||||
assert entity.max_mireds == expected_values[8]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"module",
|
||||
[light],
|
||||
|
|
Loading…
Reference in New Issue