mirror of
https://github.com/home-assistant/core.git
synced 2025-07-27 15:17:35 +00:00
Add warning when light entities do not provide kelvin attributes or properties (#132723)
This commit is contained in:
parent
067daad70e
commit
8080ad14bf
@ -32,6 +32,7 @@ from homeassistant.helpers.deprecation import (
|
|||||||
)
|
)
|
||||||
from homeassistant.helpers.entity import ToggleEntity, ToggleEntityDescription
|
from homeassistant.helpers.entity import ToggleEntity, ToggleEntityDescription
|
||||||
from homeassistant.helpers.entity_component import EntityComponent
|
from homeassistant.helpers.entity_component import EntityComponent
|
||||||
|
from homeassistant.helpers.frame import ReportBehavior, report_usage
|
||||||
from homeassistant.helpers.typing import ConfigType, VolDictType
|
from homeassistant.helpers.typing import ConfigType, VolDictType
|
||||||
from homeassistant.loader import bind_hass
|
from homeassistant.loader import bind_hass
|
||||||
import homeassistant.util.color as color_util
|
import homeassistant.util.color as color_util
|
||||||
@ -41,6 +42,8 @@ from .const import ( # noqa: F401
|
|||||||
COLOR_MODES_COLOR,
|
COLOR_MODES_COLOR,
|
||||||
DATA_COMPONENT,
|
DATA_COMPONENT,
|
||||||
DATA_PROFILES,
|
DATA_PROFILES,
|
||||||
|
DEFAULT_MAX_KELVIN,
|
||||||
|
DEFAULT_MIN_KELVIN,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SCAN_INTERVAL,
|
SCAN_INTERVAL,
|
||||||
VALID_COLOR_MODES,
|
VALID_COLOR_MODES,
|
||||||
@ -863,17 +866,15 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||||||
entity_description: LightEntityDescription
|
entity_description: LightEntityDescription
|
||||||
_attr_brightness: int | None = None
|
_attr_brightness: int | None = None
|
||||||
_attr_color_mode: ColorMode | str | None = None
|
_attr_color_mode: ColorMode | str | None = None
|
||||||
_attr_color_temp: int | None = None
|
|
||||||
_attr_color_temp_kelvin: int | None = None
|
_attr_color_temp_kelvin: int | None = None
|
||||||
_attr_effect_list: list[str] | None = None
|
_attr_effect_list: list[str] | None = None
|
||||||
_attr_effect: str | None = None
|
_attr_effect: str | None = None
|
||||||
_attr_hs_color: tuple[float, float] | None = None
|
_attr_hs_color: tuple[float, float] | None = None
|
||||||
# Default to the Philips Hue value that HA has always assumed
|
# We cannot set defaults without causing breaking changes until mireds
|
||||||
# https://developers.meethue.com/documentation/core-concepts
|
# 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_max_color_temp_kelvin: int | None = None
|
||||||
_attr_min_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_rgb_color: tuple[int, int, int] | None = None
|
||||||
_attr_rgbw_color: tuple[int, 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
|
_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_supported_features: LightEntityFeature = LightEntityFeature(0)
|
||||||
_attr_xy_color: tuple[float, float] | None = None
|
_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
|
__color_mode_reported = False
|
||||||
|
|
||||||
@cached_property
|
@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 the rgbww color value [int, int, int, int, int]."""
|
||||||
return self._attr_rgbww_color
|
return self._attr_rgbww_color
|
||||||
|
|
||||||
|
@final
|
||||||
@cached_property
|
@cached_property
|
||||||
def color_temp(self) -> int | None:
|
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
|
return self._attr_color_temp
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def color_temp_kelvin(self) -> int | None:
|
def color_temp_kelvin(self) -> int | None:
|
||||||
"""Return the CT color value in Kelvin."""
|
"""Return the CT color value in Kelvin."""
|
||||||
if self._attr_color_temp_kelvin is None and (color_temp := self.color_temp):
|
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 color_util.color_temperature_mired_to_kelvin(color_temp)
|
||||||
return self._attr_color_temp_kelvin
|
return self._attr_color_temp_kelvin
|
||||||
|
|
||||||
|
@final
|
||||||
@cached_property
|
@cached_property
|
||||||
def min_mireds(self) -> int:
|
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
|
return self._attr_min_mireds
|
||||||
|
|
||||||
|
@final
|
||||||
@cached_property
|
@cached_property
|
||||||
def max_mireds(self) -> int:
|
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
|
return self._attr_max_mireds
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def min_color_temp_kelvin(self) -> int:
|
def min_color_temp_kelvin(self) -> int:
|
||||||
"""Return the warmest color_temp_kelvin that this light supports."""
|
"""Return the warmest color_temp_kelvin that this light supports."""
|
||||||
if self._attr_min_color_temp_kelvin is None:
|
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 color_util.color_temperature_mired_to_kelvin(self.max_mireds)
|
||||||
return self._attr_min_color_temp_kelvin
|
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:
|
def max_color_temp_kelvin(self) -> int:
|
||||||
"""Return the coldest color_temp_kelvin that this light supports."""
|
"""Return the coldest color_temp_kelvin that this light supports."""
|
||||||
if self._attr_max_color_temp_kelvin is None:
|
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 color_util.color_temperature_mired_to_kelvin(self.min_mireds)
|
||||||
return self._attr_max_color_temp_kelvin
|
return self._attr_max_color_temp_kelvin
|
||||||
|
|
||||||
|
@ -66,3 +66,8 @@ COLOR_MODES_COLOR = {
|
|||||||
ColorMode.RGBWW,
|
ColorMode.RGBWW,
|
||||||
ColorMode.XY,
|
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_TRANSITION,
|
||||||
ATTR_WHITE,
|
ATTR_WHITE,
|
||||||
ATTR_XY_COLOR,
|
ATTR_XY_COLOR,
|
||||||
|
DEFAULT_MAX_KELVIN,
|
||||||
|
DEFAULT_MIN_KELVIN,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
ColorMode,
|
ColorMode,
|
||||||
LightEntity,
|
LightEntity,
|
||||||
@ -153,8 +155,8 @@ TURN_ON_ARG_TO_COLOR_MODE = {
|
|||||||
class MockLight(MockToggleEntity, LightEntity):
|
class MockLight(MockToggleEntity, LightEntity):
|
||||||
"""Mock light class."""
|
"""Mock light class."""
|
||||||
|
|
||||||
_attr_max_color_temp_kelvin = 6500
|
_attr_max_color_temp_kelvin = DEFAULT_MAX_KELVIN
|
||||||
_attr_min_color_temp_kelvin = 2000
|
_attr_min_color_temp_kelvin = DEFAULT_MIN_KELVIN
|
||||||
supported_features = LightEntityFeature(0)
|
supported_features = LightEntityFeature(0)
|
||||||
|
|
||||||
brightness = None
|
brightness = None
|
||||||
|
@ -20,6 +20,7 @@ from homeassistant.const import (
|
|||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import HomeAssistantError, Unauthorized
|
from homeassistant.exceptions import HomeAssistantError, Unauthorized
|
||||||
|
from homeassistant.helpers import frame
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
import homeassistant.util.color as color_util
|
import homeassistant.util.color as color_util
|
||||||
|
|
||||||
@ -1209,7 +1210,7 @@ async def test_light_state_off(hass: HomeAssistant) -> None:
|
|||||||
"hs_color": None,
|
"hs_color": None,
|
||||||
"rgb_color": None,
|
"rgb_color": None,
|
||||||
"xy_color": None,
|
"xy_color": None,
|
||||||
"max_color_temp_kelvin": 6500,
|
"max_color_temp_kelvin": 6535,
|
||||||
"max_mireds": 500,
|
"max_mireds": 500,
|
||||||
"min_color_temp_kelvin": 2000,
|
"min_color_temp_kelvin": 2000,
|
||||||
"min_mireds": 153,
|
"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.min_mireds == 153
|
||||||
assert entity1.max_mireds == 500
|
assert entity1.max_mireds == 500
|
||||||
assert entity1.min_color_temp_kelvin == 2000
|
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"}})
|
assert await async_setup_component(hass, "light", {"light": {"platform": "test"}})
|
||||||
await hass.async_block_till_done()
|
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["min_mireds"] == 153
|
||||||
assert state.attributes["max_mireds"] == 500
|
assert state.attributes["max_mireds"] == 500
|
||||||
assert state.attributes["min_color_temp_kelvin"] == 2000
|
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)
|
state = hass.states.get(entity1.entity_id)
|
||||||
assert state.attributes["supported_color_modes"] == [light.ColorMode.RGBWW]
|
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
|
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(
|
@pytest.mark.parametrize(
|
||||||
"module",
|
"module",
|
||||||
[light],
|
[light],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user