From 443463e19d74dc1362b58c26d03bdfced08454ed Mon Sep 17 00:00:00 2001
From: Erik Montnemery <erik@montnemery.com>
Date: Wed, 9 Jun 2021 11:23:01 +0200
Subject: [PATCH] Emulate color_temp for lights which support color or white
 (#51654)

* Emulate color_temp for lights which support color or white

* Support legacy lights

* Tidy up group.light code

* Improve color_temp to white conversion

* Remove color_temp to white conversion

* Add test

* Tweak deconz test
---
 homeassistant/components/group/light.py    | 61 +++-------------------
 homeassistant/components/light/__init__.py | 16 ++++--
 tests/components/deconz/test_light.py      |  1 -
 tests/components/light/test_init.py        | 60 +++++++++++++++++++++
 4 files changed, 79 insertions(+), 59 deletions(-)

diff --git a/homeassistant/components/group/light.py b/homeassistant/components/group/light.py
index 9567735c9eb..0abe842af2c 100644
--- a/homeassistant/components/group/light.py
+++ b/homeassistant/components/group/light.py
@@ -1,7 +1,6 @@
 """This platform allows several lights to be grouped into one light."""
 from __future__ import annotations
 
-import asyncio
 from collections import Counter
 from collections.abc import Iterator
 import itertools
@@ -34,8 +33,6 @@ from homeassistant.components.light import (
     SUPPORT_FLASH,
     SUPPORT_TRANSITION,
     SUPPORT_WHITE_VALUE,
-    color_supported,
-    color_temp_supported,
 )
 from homeassistant.const import (
     ATTR_ENTITY_ID,
@@ -49,7 +46,6 @@ from homeassistant.core import CoreState, HomeAssistant, State
 import homeassistant.helpers.config_validation as cv
 from homeassistant.helpers.event import async_track_state_change_event
 from homeassistant.helpers.typing import ConfigType
-from homeassistant.util import color as color_util
 
 from . import GroupEntity
 
@@ -233,7 +229,6 @@ class LightGroup(GroupEntity, light.LightEntity):
     async def async_turn_on(self, **kwargs):
         """Forward the turn_on command to all lights in the light group."""
         data = {ATTR_ENTITY_ID: self._entity_ids}
-        emulate_color_temp_entity_ids = []
 
         if ATTR_BRIGHTNESS in kwargs:
             data[ATTR_BRIGHTNESS] = kwargs[ATTR_BRIGHTNESS]
@@ -256,21 +251,6 @@ class LightGroup(GroupEntity, light.LightEntity):
         if ATTR_COLOR_TEMP in kwargs:
             data[ATTR_COLOR_TEMP] = kwargs[ATTR_COLOR_TEMP]
 
-            # Create a new entity list to mutate
-            updated_entities = list(self._entity_ids)
-
-            # Walk through initial entity ids, split entity lists by support
-            for entity_id in self._entity_ids:
-                state = self.hass.states.get(entity_id)
-                if not state:
-                    continue
-                support = state.attributes.get(ATTR_SUPPORTED_COLOR_MODES)
-                # Only pass color temperature to supported entity_ids
-                if color_supported(support) and not color_temp_supported(support):
-                    emulate_color_temp_entity_ids.append(entity_id)
-                    updated_entities.remove(entity_id)
-                    data[ATTR_ENTITY_ID] = updated_entities
-
         if ATTR_WHITE_VALUE in kwargs:
             data[ATTR_WHITE_VALUE] = kwargs[ATTR_WHITE_VALUE]
 
@@ -283,41 +263,12 @@ class LightGroup(GroupEntity, light.LightEntity):
         if ATTR_FLASH in kwargs:
             data[ATTR_FLASH] = kwargs[ATTR_FLASH]
 
-        if not emulate_color_temp_entity_ids:
-            await self.hass.services.async_call(
-                light.DOMAIN,
-                light.SERVICE_TURN_ON,
-                data,
-                blocking=True,
-                context=self._context,
-            )
-            return
-
-        emulate_color_temp_data = data.copy()
-        temp_k = color_util.color_temperature_mired_to_kelvin(
-            emulate_color_temp_data[ATTR_COLOR_TEMP]
-        )
-        hs_color = color_util.color_temperature_to_hs(temp_k)
-        emulate_color_temp_data[ATTR_HS_COLOR] = hs_color
-        del emulate_color_temp_data[ATTR_COLOR_TEMP]
-
-        emulate_color_temp_data[ATTR_ENTITY_ID] = emulate_color_temp_entity_ids
-
-        await asyncio.gather(
-            self.hass.services.async_call(
-                light.DOMAIN,
-                light.SERVICE_TURN_ON,
-                data,
-                blocking=True,
-                context=self._context,
-            ),
-            self.hass.services.async_call(
-                light.DOMAIN,
-                light.SERVICE_TURN_ON,
-                emulate_color_temp_data,
-                blocking=True,
-                context=self._context,
-            ),
+        await self.hass.services.async_call(
+            light.DOMAIN,
+            light.SERVICE_TURN_ON,
+            data,
+            blocking=True,
+            context=self._context,
         )
 
     async def async_turn_off(self, **kwargs):
diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py
index 32494592697..a4693857178 100644
--- a/homeassistant/components/light/__init__.py
+++ b/homeassistant/components/light/__init__.py
@@ -370,13 +370,13 @@ async def async_setup(hass, config):  # noqa: C901
         ):
             profiles.apply_default(light.entity_id, light.is_on, params)
 
+        legacy_supported_color_modes = (
+            light._light_internal_supported_color_modes  # pylint: disable=protected-access
+        )
         supported_color_modes = light.supported_color_modes
         # Backwards compatibility: if an RGBWW color is specified, convert to RGB + W
         # for legacy lights
         if ATTR_RGBW_COLOR in params:
-            legacy_supported_color_modes = (
-                light._light_internal_supported_color_modes  # pylint: disable=protected-access
-            )
             if (
                 COLOR_MODE_RGBW in legacy_supported_color_modes
                 and not supported_color_modes
@@ -385,6 +385,16 @@ async def async_setup(hass, config):  # noqa: C901
                 params[ATTR_RGB_COLOR] = rgbw_color[0:3]
                 params[ATTR_WHITE_VALUE] = rgbw_color[3]
 
+        # If a color temperature is specified, emulate it if not supported by the light
+        if (
+            ATTR_COLOR_TEMP in params
+            and COLOR_MODE_COLOR_TEMP not in legacy_supported_color_modes
+        ):
+            color_temp = params.pop(ATTR_COLOR_TEMP)
+            if color_supported(legacy_supported_color_modes):
+                temp_k = color_util.color_temperature_mired_to_kelvin(color_temp)
+                params[ATTR_HS_COLOR] = color_util.color_temperature_to_hs(temp_k)
+
         # If a color is specified, convert to the color space supported by the light
         # Backwards compatibility: Fall back to hs color if light.supported_color_modes
         # is not implemented
diff --git a/tests/components/deconz/test_light.py b/tests/components/deconz/test_light.py
index a5e27709ebf..6ec73085322 100644
--- a/tests/components/deconz/test_light.py
+++ b/tests/components/deconz/test_light.py
@@ -170,7 +170,6 @@ async def test_lights_and_groups(hass, aioclient_mock, mock_deconz_websocket):
         SERVICE_TURN_ON,
         {
             ATTR_ENTITY_ID: "light.rgb_light",
-            ATTR_COLOR_TEMP: 2500,
             ATTR_BRIGHTNESS: 200,
             ATTR_TRANSITION: 5,
             ATTR_FLASH: FLASH_SHORT,
diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py
index 368a5b0dab4..b3682cbdd2c 100644
--- a/tests/components/light/test_init.py
+++ b/tests/components/light/test_init.py
@@ -1586,6 +1586,66 @@ async def test_light_service_call_color_conversion(hass, enable_custom_integrati
     assert data == {"brightness": 128, "rgbww_color": (0, 75, 140, 255, 255)}
 
 
+async def test_light_service_call_color_temp_emulation(
+    hass, enable_custom_integrations
+):
+    """Test color conversion in service calls."""
+    platform = getattr(hass.components, "test.light")
+    platform.init(empty=True)
+
+    platform.ENTITIES.append(platform.MockLight("Test_hs_ct", STATE_ON))
+    platform.ENTITIES.append(platform.MockLight("Test_hs", STATE_ON))
+    platform.ENTITIES.append(platform.MockLight("Test_hs_white", STATE_ON))
+
+    entity0 = platform.ENTITIES[0]
+    entity0.supported_color_modes = {light.COLOR_MODE_COLOR_TEMP, light.COLOR_MODE_HS}
+
+    entity1 = platform.ENTITIES[1]
+    entity1.supported_color_modes = {light.COLOR_MODE_HS}
+
+    entity2 = platform.ENTITIES[2]
+    entity2.supported_color_modes = {light.COLOR_MODE_HS, light.COLOR_MODE_WHITE}
+
+    assert await async_setup_component(hass, "light", {"light": {"platform": "test"}})
+    await hass.async_block_till_done()
+
+    state = hass.states.get(entity0.entity_id)
+    assert state.attributes["supported_color_modes"] == [
+        light.COLOR_MODE_COLOR_TEMP,
+        light.COLOR_MODE_HS,
+    ]
+
+    state = hass.states.get(entity1.entity_id)
+    assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_HS]
+
+    state = hass.states.get(entity2.entity_id)
+    assert state.attributes["supported_color_modes"] == [
+        light.COLOR_MODE_HS,
+        light.COLOR_MODE_WHITE,
+    ]
+
+    await hass.services.async_call(
+        "light",
+        "turn_on",
+        {
+            "entity_id": [
+                entity0.entity_id,
+                entity1.entity_id,
+                entity2.entity_id,
+            ],
+            "brightness_pct": 100,
+            "color_temp": 200,
+        },
+        blocking=True,
+    )
+    _, data = entity0.last_call("turn_on")
+    assert data == {"brightness": 255, "color_temp": 200}
+    _, data = entity1.last_call("turn_on")
+    assert data == {"brightness": 255, "hs_color": (27.001, 19.243)}
+    _, data = entity2.last_call("turn_on")
+    assert data == {"brightness": 255, "hs_color": (27.001, 19.243)}
+
+
 async def test_light_service_call_white_mode(hass, enable_custom_integrations):
     """Test color_mode white in service calls."""
     platform = getattr(hass.components, "test.light")