Skip to content

Commit 9a27805

Browse files
authored
Correctly calculate average color for light groups in HS Color Mode (home-assistant#154678)
1 parent 477cdbb commit 9a27805

File tree

3 files changed

+47
-2
lines changed

3 files changed

+47
-2
lines changed

homeassistant/components/group/light.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
5656

5757
from .entity import GroupEntity
58-
from .util import find_state_attributes, mean_tuple, reduce_attribute
58+
from .util import find_state_attributes, mean_circle, mean_tuple, reduce_attribute
5959

6060
DEFAULT_NAME = "Light Group"
6161
CONF_ALL = "all"
@@ -229,7 +229,7 @@ def async_update_group_state(self) -> None:
229229
self._attr_brightness = reduce_attribute(on_states, ATTR_BRIGHTNESS)
230230

231231
self._attr_hs_color = reduce_attribute(
232-
on_states, ATTR_HS_COLOR, reduce=mean_tuple
232+
on_states, ATTR_HS_COLOR, reduce=mean_circle
233233
)
234234
self._attr_rgb_color = reduce_attribute(
235235
on_states, ATTR_RGB_COLOR, reduce=mean_tuple

homeassistant/components/group/util.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from collections.abc import Callable, Iterator
66
from itertools import groupby
7+
from math import atan2, cos, degrees, radians, sin
78
from typing import Any
89

910
from homeassistant.core import State
@@ -32,6 +33,23 @@ def mean_tuple(*args: Any) -> tuple[float | Any, ...]:
3233
return tuple(sum(x) / len(x) for x in zip(*args, strict=False))
3334

3435

36+
def mean_circle(*args: Any) -> tuple[float | Any, ...]:
37+
"""Return the circular mean of hue values and arithmetic mean of saturation values from HS color tuples."""
38+
if not args:
39+
return ()
40+
41+
hues, saturations = zip(*args, strict=False)
42+
43+
sum_x = sum(cos(radians(h)) for h in hues)
44+
sum_y = sum(sin(radians(h)) for h in hues)
45+
46+
mean_angle = degrees(atan2(sum_y, sum_x)) % 360
47+
48+
saturation = sum(saturations) / len(saturations)
49+
50+
return (mean_angle, saturation)
51+
52+
3553
def attribute_equal(states: list[State], key: str) -> bool:
3654
"""Return True if all attributes found matching key from states are equal.
3755

tests/components/group/test_light.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,33 @@ async def test_color_hs(hass: HomeAssistant) -> None:
404404
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["hs"]
405405
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
406406

407+
await hass.services.async_call(
408+
"light",
409+
"turn_on",
410+
{"entity_id": [entity0.entity_id], ATTR_HS_COLOR: (355, 100)},
411+
blocking=True,
412+
)
413+
414+
await hass.async_block_till_done()
415+
state = hass.states.get("light.light_group")
416+
assert state.attributes[ATTR_COLOR_MODE] == "hs"
417+
assert state.attributes[ATTR_HS_COLOR] == (357.5, 75)
418+
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["hs"]
419+
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
420+
421+
await hass.services.async_call(
422+
"light",
423+
"turn_on",
424+
{"entity_id": [entity1.entity_id], ATTR_HS_COLOR: (5, 90)},
425+
blocking=True,
426+
)
427+
await hass.async_block_till_done()
428+
state = hass.states.get("light.light_group")
429+
assert state.attributes[ATTR_COLOR_MODE] == "hs"
430+
assert state.attributes[ATTR_HS_COLOR] == (360, 95)
431+
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["hs"]
432+
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
433+
407434

408435
async def test_color_rgb(hass: HomeAssistant) -> None:
409436
"""Test rgbw color reporting."""

0 commit comments

Comments
 (0)