mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 18:57:06 +00:00
Small cleanups to ESPHome light platform (#107003)
- Remove unreachable code - Cache filtering when possible - Add missing coverage
This commit is contained in:
parent
d535409349
commit
8d2ddb6a04
@ -1,7 +1,8 @@
|
||||
"""Support for ESPHome lights."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, cast
|
||||
from functools import lru_cache
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
|
||||
from aioesphomeapi import (
|
||||
APIVersion,
|
||||
@ -111,6 +112,7 @@ def _mired_to_kelvin(mired_temperature: float) -> int:
|
||||
return round(1000000 / mired_temperature)
|
||||
|
||||
|
||||
@lru_cache
|
||||
def _color_mode_to_ha(mode: int) -> str:
|
||||
"""Convert an esphome color mode to a HA color mode constant.
|
||||
|
||||
@ -134,20 +136,34 @@ def _color_mode_to_ha(mode: int) -> str:
|
||||
return candidates[-1][0]
|
||||
|
||||
|
||||
@lru_cache
|
||||
def _filter_color_modes(
|
||||
supported: list[int], features: LightColorCapability
|
||||
) -> list[int]:
|
||||
) -> tuple[int, ...]:
|
||||
"""Filter the given supported color modes.
|
||||
|
||||
Excluding all values that don't have the requested features.
|
||||
"""
|
||||
return [mode for mode in supported if (mode & features) == features]
|
||||
features_value = features.value
|
||||
return tuple(
|
||||
mode for mode in supported if (mode & features_value) == features_value
|
||||
)
|
||||
|
||||
|
||||
@lru_cache
|
||||
def _least_complex_color_mode(color_modes: tuple[int, ...]) -> int:
|
||||
"""Return the color mode with the least complexity."""
|
||||
# popcount with bin() function because it appears
|
||||
# to be the best way: https://stackoverflow.com/a/9831671
|
||||
color_modes_list = list(color_modes)
|
||||
color_modes_list.sort(key=lambda mode: bin(mode).count("1"))
|
||||
return color_modes_list[0]
|
||||
|
||||
|
||||
class EsphomeLight(EsphomeEntity[LightInfo, LightState], LightEntity):
|
||||
"""A light implementation for ESPHome."""
|
||||
|
||||
_native_supported_color_modes: list[int]
|
||||
_native_supported_color_modes: tuple[int, ...]
|
||||
_supports_color_mode = False
|
||||
|
||||
@property
|
||||
@ -231,10 +247,10 @@ class EsphomeLight(EsphomeEntity[LightInfo, LightState], LightEntity):
|
||||
if (color_temp_k := kwargs.get(ATTR_COLOR_TEMP_KELVIN)) is not None:
|
||||
# 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):
|
||||
color_modes = _filter_color_modes(
|
||||
color_modes, LightColorCapability.COLOR_TEMPERATURE
|
||||
)
|
||||
if color_temp_modes := _filter_color_modes(
|
||||
color_modes, LightColorCapability.COLOR_TEMPERATURE
|
||||
):
|
||||
color_modes = color_temp_modes
|
||||
else:
|
||||
color_modes = _filter_color_modes(
|
||||
color_modes, LightColorCapability.COLD_WARM_WHITE
|
||||
@ -267,10 +283,7 @@ class EsphomeLight(EsphomeEntity[LightInfo, LightState], LightEntity):
|
||||
else:
|
||||
# otherwise try the color mode with the least complexity
|
||||
# (fewest capabilities set)
|
||||
# popcount with bin() function because it appears
|
||||
# to be the best way: https://stackoverflow.com/a/9831671
|
||||
color_modes.sort(key=lambda mode: bin(mode).count("1"))
|
||||
data["color_mode"] = color_modes[0]
|
||||
data["color_mode"] = _least_complex_color_mode(color_modes)
|
||||
|
||||
await self._client.light_command(**data)
|
||||
|
||||
@ -294,9 +307,10 @@ class EsphomeLight(EsphomeEntity[LightInfo, LightState], LightEntity):
|
||||
def color_mode(self) -> str | None:
|
||||
"""Return the color mode of the light."""
|
||||
if not self._supports_color_mode:
|
||||
if not (supported := self.supported_color_modes):
|
||||
return None
|
||||
return next(iter(supported))
|
||||
supported_color_modes = self.supported_color_modes
|
||||
if TYPE_CHECKING:
|
||||
assert supported_color_modes is not None
|
||||
return next(iter(supported_color_modes))
|
||||
|
||||
return _color_mode_to_ha(self._state.color_mode)
|
||||
|
||||
@ -374,8 +388,8 @@ class EsphomeLight(EsphomeEntity[LightInfo, LightState], LightEntity):
|
||||
super()._on_static_info_update(static_info)
|
||||
static_info = self._static_info
|
||||
self._supports_color_mode = self._api_version >= APIVersion(1, 6)
|
||||
self._native_supported_color_modes = static_info.supported_color_modes_compat(
|
||||
self._api_version
|
||||
self._native_supported_color_modes = tuple(
|
||||
static_info.supported_color_modes_compat(self._api_version)
|
||||
)
|
||||
flags = LightEntityFeature.FLASH
|
||||
|
||||
|
@ -29,6 +29,7 @@ from homeassistant.components.light import (
|
||||
ATTR_RGBWW_COLOR,
|
||||
ATTR_SUPPORTED_COLOR_MODES,
|
||||
ATTR_TRANSITION,
|
||||
ATTR_WHITE,
|
||||
DOMAIN as LIGHT_DOMAIN,
|
||||
FLASH_LONG,
|
||||
FLASH_SHORT,
|
||||
@ -317,6 +318,68 @@ async def test_light_legacy_white_converted_to_brightness(
|
||||
mock_client.light_command.reset_mock()
|
||||
|
||||
|
||||
async def test_light_legacy_white_with_rgb(
|
||||
hass: HomeAssistant, mock_client: APIClient, mock_generic_device_entry
|
||||
) -> None:
|
||||
"""Test a generic light entity with rgb and white."""
|
||||
mock_client.api_version = APIVersion(1, 7)
|
||||
color_mode = (
|
||||
LightColorCapability.ON_OFF
|
||||
| LightColorCapability.BRIGHTNESS
|
||||
| LightColorCapability.WHITE
|
||||
)
|
||||
color_mode_2 = (
|
||||
LightColorCapability.ON_OFF
|
||||
| LightColorCapability.BRIGHTNESS
|
||||
| LightColorCapability.RGB
|
||||
)
|
||||
entity_info = [
|
||||
LightInfo(
|
||||
object_id="mylight",
|
||||
key=1,
|
||||
name="my light",
|
||||
unique_id="my_light",
|
||||
min_mireds=153,
|
||||
max_mireds=400,
|
||||
supported_color_modes=[color_mode, color_mode_2],
|
||||
)
|
||||
]
|
||||
states = [LightState(key=1, state=True, brightness=100)]
|
||||
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_mylight")
|
||||
assert state is not None
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == [
|
||||
ColorMode.RGB,
|
||||
ColorMode.WHITE,
|
||||
]
|
||||
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: "light.test_mylight", ATTR_WHITE: 60},
|
||||
blocking=True,
|
||||
)
|
||||
mock_client.light_command.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
key=1,
|
||||
state=True,
|
||||
brightness=pytest.approx(0.23529411764705882),
|
||||
white=1.0,
|
||||
color_mode=color_mode,
|
||||
)
|
||||
]
|
||||
)
|
||||
mock_client.light_command.reset_mock()
|
||||
|
||||
|
||||
async def test_light_brightness_on_off_with_unknown_color_mode(
|
||||
hass: HomeAssistant, mock_client: APIClient, mock_generic_device_entry
|
||||
) -> None:
|
||||
@ -1676,3 +1739,139 @@ async def test_light_effects(
|
||||
]
|
||||
)
|
||||
mock_client.light_command.reset_mock()
|
||||
|
||||
|
||||
async def test_only_cold_warm_white_support(
|
||||
hass: HomeAssistant, mock_client: APIClient, mock_generic_device_entry
|
||||
) -> None:
|
||||
"""Test a generic light entity with only cold warm white support."""
|
||||
mock_client.api_version = APIVersion(1, 7)
|
||||
color_modes = (
|
||||
LightColorCapability.COLD_WARM_WHITE
|
||||
| LightColorCapability.ON_OFF
|
||||
| LightColorCapability.BRIGHTNESS
|
||||
)
|
||||
entity_info = [
|
||||
LightInfo(
|
||||
object_id="mylight",
|
||||
key=1,
|
||||
name="my light",
|
||||
unique_id="my_light",
|
||||
min_mireds=153,
|
||||
max_mireds=400,
|
||||
supported_color_modes=[color_modes],
|
||||
)
|
||||
]
|
||||
states = [
|
||||
LightState(
|
||||
key=1,
|
||||
state=True,
|
||||
color_brightness=1,
|
||||
brightness=100,
|
||||
red=1,
|
||||
green=1,
|
||||
blue=1,
|
||||
warm_white=1,
|
||||
cold_white=1,
|
||||
color_mode=color_modes,
|
||||
)
|
||||
]
|
||||
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_mylight")
|
||||
assert state is not None
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == [ColorMode.COLOR_TEMP]
|
||||
assert state.attributes[ATTR_COLOR_MODE] == ColorMode.COLOR_TEMP
|
||||
assert state.attributes[ATTR_COLOR_TEMP_KELVIN] == 0
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: "light.test_mylight"},
|
||||
blocking=True,
|
||||
)
|
||||
mock_client.light_command.assert_has_calls(
|
||||
[call(key=1, state=True, color_mode=color_modes)]
|
||||
)
|
||||
mock_client.light_command.reset_mock()
|
||||
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: "light.test_mylight", ATTR_BRIGHTNESS: 127},
|
||||
blocking=True,
|
||||
)
|
||||
mock_client.light_command.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
key=1,
|
||||
state=True,
|
||||
color_mode=color_modes,
|
||||
brightness=pytest.approx(0.4980392156862745),
|
||||
)
|
||||
]
|
||||
)
|
||||
mock_client.light_command.reset_mock()
|
||||
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: "light.test_mylight", ATTR_COLOR_TEMP_KELVIN: 2500},
|
||||
blocking=True,
|
||||
)
|
||||
mock_client.light_command.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
key=1,
|
||||
state=True,
|
||||
color_mode=color_modes,
|
||||
color_temperature=400.0,
|
||||
)
|
||||
]
|
||||
)
|
||||
mock_client.light_command.reset_mock()
|
||||
|
||||
|
||||
async def test_light_no_color_modes(
|
||||
hass: HomeAssistant, mock_client: APIClient, mock_generic_device_entry
|
||||
) -> None:
|
||||
"""Test a generic light entity with no color modes."""
|
||||
mock_client.api_version = APIVersion(1, 7)
|
||||
color_mode = 0
|
||||
entity_info = [
|
||||
LightInfo(
|
||||
object_id="mylight",
|
||||
key=1,
|
||||
name="my light",
|
||||
unique_id="my_light",
|
||||
min_mireds=153,
|
||||
max_mireds=400,
|
||||
supported_color_modes=[color_mode],
|
||||
)
|
||||
]
|
||||
states = [LightState(key=1, state=True, brightness=100)]
|
||||
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_mylight")
|
||||
assert state is not None
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == [ColorMode.UNKNOWN]
|
||||
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: "light.test_mylight"},
|
||||
blocking=True,
|
||||
)
|
||||
mock_client.light_command.assert_has_calls([call(key=1, state=True, color_mode=0)])
|
||||
mock_client.light_command.reset_mock()
|
||||
|
Loading…
x
Reference in New Issue
Block a user