mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Fix ESPHome color temperature precision for light entities (#91424)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
91611bbd3b
commit
983ff10541
@ -13,7 +13,7 @@ from aioesphomeapi import (
|
|||||||
|
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
ATTR_BRIGHTNESS,
|
ATTR_BRIGHTNESS,
|
||||||
ATTR_COLOR_TEMP,
|
ATTR_COLOR_TEMP_KELVIN,
|
||||||
ATTR_EFFECT,
|
ATTR_EFFECT,
|
||||||
ATTR_FLASH,
|
ATTR_FLASH,
|
||||||
ATTR_RGB_COLOR,
|
ATTR_RGB_COLOR,
|
||||||
@ -98,6 +98,20 @@ _COLOR_MODE_MAPPING = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _mired_to_kelvin(mired_temperature: float) -> int:
|
||||||
|
"""Convert absolute mired shift to degrees kelvin.
|
||||||
|
|
||||||
|
This function rounds the converted value instead of flooring the value as
|
||||||
|
is done in homeassistant.util.color.color_temperature_mired_to_kelvin().
|
||||||
|
|
||||||
|
If the value of mired_temperature is less than or equal to zero, return
|
||||||
|
the original value to avoid a divide by zero.
|
||||||
|
"""
|
||||||
|
if mired_temperature <= 0:
|
||||||
|
return round(mired_temperature)
|
||||||
|
return round(1000000 / mired_temperature)
|
||||||
|
|
||||||
|
|
||||||
def _color_mode_to_ha(mode: int) -> str:
|
def _color_mode_to_ha(mode: int) -> str:
|
||||||
"""Convert an esphome color mode to a HA color mode constant.
|
"""Convert an esphome color mode to a HA color mode constant.
|
||||||
|
|
||||||
@ -198,8 +212,9 @@ class EsphomeLight(EsphomeEntity[LightInfo, LightState], LightEntity):
|
|||||||
# need to convert cw+ww part to white+color_temp
|
# need to convert cw+ww part to white+color_temp
|
||||||
white = data["white"] = max(cw, ww)
|
white = data["white"] = max(cw, ww)
|
||||||
if white != 0:
|
if white != 0:
|
||||||
min_ct = self.min_mireds
|
static_info = self._static_info
|
||||||
max_ct = self.max_mireds
|
min_ct = static_info.min_mireds
|
||||||
|
max_ct = static_info.max_mireds
|
||||||
ct_ratio = ww / (cw + ww)
|
ct_ratio = ww / (cw + ww)
|
||||||
data["color_temperature"] = min_ct + ct_ratio * (max_ct - min_ct)
|
data["color_temperature"] = min_ct + ct_ratio * (max_ct - min_ct)
|
||||||
color_modes = _filter_color_modes(
|
color_modes = _filter_color_modes(
|
||||||
@ -216,8 +231,9 @@ class EsphomeLight(EsphomeEntity[LightInfo, LightState], LightEntity):
|
|||||||
if (transition := kwargs.get(ATTR_TRANSITION)) is not None:
|
if (transition := kwargs.get(ATTR_TRANSITION)) is not None:
|
||||||
data["transition_length"] = transition
|
data["transition_length"] = transition
|
||||||
|
|
||||||
if (color_temp := kwargs.get(ATTR_COLOR_TEMP)) is not None:
|
if (color_temp_k := kwargs.get(ATTR_COLOR_TEMP_KELVIN)) is not None:
|
||||||
data["color_temperature"] = color_temp
|
# Do not use kelvin_to_mired here to prevent precision loss
|
||||||
|
data["color_temperature"] = 1000000.0 / color_temp_k
|
||||||
if _filter_color_modes(color_modes, LightColorCapability.COLOR_TEMPERATURE):
|
if _filter_color_modes(color_modes, LightColorCapability.COLOR_TEMPERATURE):
|
||||||
color_modes = _filter_color_modes(
|
color_modes = _filter_color_modes(
|
||||||
color_modes, LightColorCapability.COLOR_TEMPERATURE
|
color_modes, LightColorCapability.COLOR_TEMPERATURE
|
||||||
@ -349,6 +365,12 @@ class EsphomeLight(EsphomeEntity[LightInfo, LightState], LightEntity):
|
|||||||
"""Return the CT color value in mireds."""
|
"""Return the CT color value in mireds."""
|
||||||
return round(self._state.color_temperature)
|
return round(self._state.color_temperature)
|
||||||
|
|
||||||
|
@property
|
||||||
|
@esphome_state_property
|
||||||
|
def color_temp_kelvin(self) -> int:
|
||||||
|
"""Return the CT color value in Kelvin."""
|
||||||
|
return _mired_to_kelvin(self._state.color_temperature)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@esphome_state_property
|
@esphome_state_property
|
||||||
def effect(self) -> str | None:
|
def effect(self) -> str | None:
|
||||||
@ -385,3 +407,6 @@ class EsphomeLight(EsphomeEntity[LightInfo, LightState], LightEntity):
|
|||||||
self._attr_effect_list = static_info.effects
|
self._attr_effect_list = static_info.effects
|
||||||
self._attr_min_mireds = round(static_info.min_mireds)
|
self._attr_min_mireds = round(static_info.min_mireds)
|
||||||
self._attr_max_mireds = round(static_info.max_mireds)
|
self._attr_max_mireds = round(static_info.max_mireds)
|
||||||
|
if ColorMode.COLOR_TEMP in supported:
|
||||||
|
self._attr_min_color_temp_kelvin = _mired_to_kelvin(static_info.max_mireds)
|
||||||
|
self._attr_max_color_temp_kelvin = _mired_to_kelvin(static_info.min_mireds)
|
||||||
|
@ -76,8 +76,8 @@ async def test_light_color_temp(
|
|||||||
key=1,
|
key=1,
|
||||||
name="my light",
|
name="my light",
|
||||||
unique_id="my_light",
|
unique_id="my_light",
|
||||||
min_mireds=153,
|
min_mireds=153.846161,
|
||||||
max_mireds=400,
|
max_mireds=370.370361,
|
||||||
supported_color_modes=[
|
supported_color_modes=[
|
||||||
LightColorCapability.COLOR_TEMPERATURE
|
LightColorCapability.COLOR_TEMPERATURE
|
||||||
| LightColorCapability.ON_OFF
|
| LightColorCapability.ON_OFF
|
||||||
@ -90,7 +90,7 @@ async def test_light_color_temp(
|
|||||||
key=1,
|
key=1,
|
||||||
state=True,
|
state=True,
|
||||||
brightness=100,
|
brightness=100,
|
||||||
color_temperature=153,
|
color_temperature=153.846161,
|
||||||
color_mode=LightColorCapability.COLOR_TEMPERATURE,
|
color_mode=LightColorCapability.COLOR_TEMPERATURE,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
@ -106,10 +106,93 @@ async def test_light_color_temp(
|
|||||||
assert state.state == STATE_ON
|
assert state.state == STATE_ON
|
||||||
attributes = state.attributes
|
attributes = state.attributes
|
||||||
|
|
||||||
assert attributes[ATTR_MAX_MIREDS] == 400
|
|
||||||
assert attributes[ATTR_MIN_MIREDS] == 153
|
assert attributes[ATTR_MIN_MIREDS] == 153
|
||||||
assert attributes[ATTR_MIN_COLOR_TEMP_KELVIN] == 2500
|
assert attributes[ATTR_MAX_MIREDS] == 370
|
||||||
assert attributes[ATTR_MAX_COLOR_TEMP_KELVIN] == 6535
|
|
||||||
|
assert attributes[ATTR_MIN_COLOR_TEMP_KELVIN] == 2700
|
||||||
|
assert attributes[ATTR_MAX_COLOR_TEMP_KELVIN] == 6500
|
||||||
|
await hass.services.async_call(
|
||||||
|
LIGHT_DOMAIN,
|
||||||
|
SERVICE_TURN_ON,
|
||||||
|
{ATTR_ENTITY_ID: "light.test_my_light"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
mock_client.light_command.assert_has_calls(
|
||||||
|
[
|
||||||
|
call(
|
||||||
|
key=1,
|
||||||
|
state=True,
|
||||||
|
color_mode=LightColorCapability.COLOR_TEMPERATURE
|
||||||
|
| LightColorCapability.ON_OFF
|
||||||
|
| LightColorCapability.BRIGHTNESS,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
mock_client.light_command.reset_mock()
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
LIGHT_DOMAIN,
|
||||||
|
SERVICE_TURN_OFF,
|
||||||
|
{ATTR_ENTITY_ID: "light.test_my_light"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
mock_client.light_command.assert_has_calls([call(key=1, state=False)])
|
||||||
|
mock_client.light_command.reset_mock()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_light_color_temp_legacy(
|
||||||
|
hass: HomeAssistant, mock_client: APIClient, mock_generic_device_entry
|
||||||
|
) -> None:
|
||||||
|
"""Test a legacy light entity that does supports color temp."""
|
||||||
|
mock_client.api_version = APIVersion(1, 7)
|
||||||
|
entity_info = [
|
||||||
|
LightInfo(
|
||||||
|
object_id="mylight",
|
||||||
|
key=1,
|
||||||
|
name="my light",
|
||||||
|
unique_id="my_light",
|
||||||
|
min_mireds=153.846161,
|
||||||
|
max_mireds=370.370361,
|
||||||
|
supported_color_modes=[
|
||||||
|
LightColorCapability.COLOR_TEMPERATURE
|
||||||
|
| LightColorCapability.ON_OFF
|
||||||
|
| LightColorCapability.BRIGHTNESS
|
||||||
|
],
|
||||||
|
legacy_supports_brightness=True,
|
||||||
|
legacy_supports_color_temperature=True,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
states = [
|
||||||
|
LightState(
|
||||||
|
key=1,
|
||||||
|
state=True,
|
||||||
|
brightness=100,
|
||||||
|
red=1,
|
||||||
|
green=1,
|
||||||
|
blue=1,
|
||||||
|
white=1,
|
||||||
|
cold_white=1,
|
||||||
|
color_temperature=153.846161,
|
||||||
|
color_mode=19,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
user_service = []
|
||||||
|
await mock_generic_device_entry(
|
||||||
|
mock_client=mock_client,
|
||||||
|
entity_info=entity_info,
|
||||||
|
user_service=user_service,
|
||||||
|
states=states,
|
||||||
|
)
|
||||||
|
state = hass.states.get("light.test_my_light")
|
||||||
|
assert state is not None
|
||||||
|
assert state.state == STATE_ON
|
||||||
|
attributes = state.attributes
|
||||||
|
|
||||||
|
assert attributes[ATTR_MIN_MIREDS] == 153
|
||||||
|
assert attributes[ATTR_MAX_MIREDS] == 370
|
||||||
|
|
||||||
|
assert attributes[ATTR_MIN_COLOR_TEMP_KELVIN] == 2700
|
||||||
|
assert attributes[ATTR_MAX_COLOR_TEMP_KELVIN] == 6500
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
LIGHT_DOMAIN,
|
LIGHT_DOMAIN,
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user