Fix KNX rgb(w) color (#51060)

* calculate brightness from color; scale color

* fix merge

* fix sending color only for brightness independent rgb color

* fix tests for rgb and rgbw color

* use public match_max_scale
This commit is contained in:
Matthias Alphart 2021-11-15 20:42:59 +01:00 committed by GitHub
parent 4f7e405a2c
commit ca3e672b1d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 179 additions and 82 deletions

View file

@ -268,19 +268,28 @@ class KNXLight(KnxEntity, LightEntity):
if self._device.current_xyy_color is not None:
_, brightness = self._device.current_xyy_color
return brightness
if (rgb := self.rgb_color) is not None:
return max(rgb)
if self._device.supports_color or self._device.supports_rgbw:
rgb, white = self._device.current_color
if rgb is None:
return white
if white is None:
return max(rgb)
return max(*rgb, white)
return None
@property
def rgb_color(self) -> tuple[int, int, int] | None:
"""Return the rgb color value [int, int, int]."""
if (rgbw := self.rgbw_color) is not None:
# used in brightness calculation when no address is given
return color_util.color_rgbw_to_rgb(*rgbw)
if self._device.supports_color:
rgb, _ = self._device.current_color
return rgb
if rgb is not None:
if not self._device.supports_brightness:
# brightness will be calculated from color so color must not hold brightness again
# pylint: disable=protected-access
return cast(
Tuple[int, int, int], color_util.match_max_scale((255,), rgb)
)
return rgb
return None
@property
@ -289,6 +298,13 @@ class KNXLight(KnxEntity, LightEntity):
if self._device.supports_rgbw:
rgb, white = self._device.current_color
if rgb is not None and white is not None:
if not self._device.supports_brightness:
# brightness will be calculated from color so color must not hold brightness again
# pylint: disable=protected-access
return cast(
Tuple[int, int, int, int],
color_util.match_max_scale((255,), (*rgb, white)),
)
return (*rgb, white)
return None
@ -376,16 +392,21 @@ class KNXLight(KnxEntity, LightEntity):
rgb: tuple[int, int, int], white: int | None, brightness: int | None
) -> None:
"""Set color of light. Normalize colors for brightness when not writable."""
if brightness:
if self._device.brightness.writable:
await self._device.set_color(rgb, white)
if self._device.brightness.writable:
# let the KNX light controller handle brightness
await self._device.set_color(rgb, white)
if brightness:
await self._device.set_brightness(brightness)
return
rgb = cast(
Tuple[int, int, int],
tuple(color * brightness // 255 for color in rgb),
)
white = white * brightness // 255 if white is not None else None
return
if brightness is None:
# normalize for brightness if brightness is derived from color
brightness = self.brightness or 255
rgb = cast(
Tuple[int, int, int],
tuple(color * brightness // 255 for color in rgb),
)
white = white * brightness // 255 if white is not None else None
await self._device.set_color(rgb, white)
# return after RGB(W) color has changed as it implicitly sets the brightness
@ -433,18 +454,16 @@ class KNXLight(KnxEntity, LightEntity):
return
# default to white if color not known for RGB(W)
if self.color_mode == COLOR_MODE_RGBW:
rgbw = self.rgbw_color
if not rgbw or not any(rgbw):
await self._device.set_color((0, 0, 0), brightness)
return
await set_color(rgbw[:3], rgbw[3], brightness)
_rgbw = self.rgbw_color
if not _rgbw or not any(_rgbw):
_rgbw = (0, 0, 0, 255)
await set_color(_rgbw[:3], _rgbw[3], brightness)
return
if self.color_mode == COLOR_MODE_RGB:
rgb = self.rgb_color
if not rgb or not any(rgb):
await self._device.set_color((brightness, brightness, brightness))
return
await set_color(rgb, None, brightness)
_rgb = self.rgb_color
if not _rgb or not any(_rgb):
_rgb = (255, 255, 255)
await set_color(_rgb, None, brightness)
return
async def async_turn_off(self, **kwargs: Any) -> None:

View file

@ -8,6 +8,7 @@ from homeassistant.components.light import (
ATTR_COLOR_NAME,
ATTR_COLOR_TEMP,
ATTR_HS_COLOR,
ATTR_RGBW_COLOR,
COLOR_MODE_BRIGHTNESS,
COLOR_MODE_COLOR_TEMP,
COLOR_MODE_HS,
@ -539,20 +540,31 @@ async def test_light_rgb_individual(hass: HomeAssistant, knx: KNXTestKit):
await knx.assert_write(test_red, (200,))
await knx.assert_write(test_green, (0,))
await knx.assert_write(test_blue, (0,))
knx.assert_state("light.test", STATE_ON, brightness=200, rgb_color=(200, 0, 0))
knx.assert_state("light.test", STATE_ON, brightness=200, rgb_color=(255, 0, 0))
# change color and brightness from HA
# change only color, keep brightness from HA
await hass.services.async_call(
"light",
"turn_on",
{"entity_id": "light.test", ATTR_COLOR_NAME: "hotpink"},
blocking=True,
)
#
await knx.assert_write(test_red, (255,))
await knx.assert_write(test_green, (105,))
await knx.assert_write(test_blue, (180,))
knx.assert_state("light.test", STATE_ON, brightness=255, rgb_color=(255, 105, 180))
await knx.assert_write(test_red, (200,))
await knx.assert_write(test_green, (82,))
await knx.assert_write(test_blue, (141,))
knx.assert_state("light.test", STATE_ON, brightness=200, rgb_color=(255, 105, 180))
# change color and brightness from HA
await hass.services.async_call(
"light",
"turn_on",
{"entity_id": "light.test", ATTR_BRIGHTNESS: 100, ATTR_COLOR_NAME: "yellow"},
blocking=True,
)
await knx.assert_write(test_red, (100,))
await knx.assert_write(test_green, (100,))
await knx.assert_write(test_blue, (0,))
knx.assert_state("light.test", STATE_ON, brightness=100, rgb_color=(255, 255, 0))
# turn OFF from KNX
await knx.receive_write(test_red, (0,))
@ -563,7 +575,7 @@ async def test_light_rgb_individual(hass: HomeAssistant, knx: KNXTestKit):
await knx.receive_write(test_red, (0,))
await knx.receive_write(test_green, (180,))
await knx.receive_write(test_blue, (0,))
knx.assert_state("light.test", STATE_ON, brightness=180, rgb_color=(0, 180, 0))
knx.assert_state("light.test", STATE_ON, brightness=180, rgb_color=(0, 255, 0))
# turn OFF from HA
await hass.services.async_call(
@ -684,40 +696,52 @@ async def test_light_rgbw_individual(hass: HomeAssistant, knx: KNXTestKit):
await knx.assert_write(test_green, (0,))
await knx.assert_write(test_blue, (0,))
await knx.assert_write(test_white, (0,))
knx.assert_state("light.test", STATE_ON, brightness=200, rgbw_color=(200, 0, 0, 0))
knx.assert_state("light.test", STATE_ON, brightness=200, rgbw_color=(255, 0, 0, 0))
# change color and brightness from HA
# change only color, keep brightness from HA
await hass.services.async_call(
"light",
"turn_on",
{"entity_id": "light.test", ATTR_COLOR_NAME: "hotpink"},
blocking=True,
)
await knx.assert_write(test_red, (255,))
await knx.assert_write(test_red, (200,))
await knx.assert_write(test_green, (0,))
await knx.assert_write(test_blue, (128,))
await knx.assert_write(test_white, (178,))
await knx.assert_write(test_blue, (100,))
await knx.assert_write(test_white, (139,))
knx.assert_state(
"light.test",
STATE_ON,
brightness=255,
rgb_color=(255, 105, 180),
rgbw_color=(255, 0, 128, 178),
brightness=200,
rgb_color=(255, 104, 179), # minor rounding error - expected (255, 105, 180)
rgbw_color=(255, 0, 127, 177), # expected (255, 0, 128, 178)
)
# change color and brightness from HA
await hass.services.async_call(
"light",
"turn_on",
{"entity_id": "light.test", ATTR_BRIGHTNESS: 100, ATTR_COLOR_NAME: "yellow"},
blocking=True,
)
await knx.assert_write(test_red, (100,))
await knx.assert_write(test_green, (100,))
await knx.assert_write(test_blue, (0,))
await knx.assert_write(test_white, (0,))
knx.assert_state(
"light.test", STATE_ON, brightness=100, rgbw_color=(255, 255, 0, 0)
)
# turn OFF from KNX
await knx.receive_write(test_red, (0,))
await knx.receive_write(test_green, (0,))
await knx.receive_write(test_blue, (0,))
knx.assert_state("light.test", STATE_ON)
await knx.receive_write(test_white, (0,))
knx.assert_state("light.test", STATE_OFF)
# turn ON from KNX
await knx.receive_write(test_red, (0,))
await knx.receive_write(test_green, (180,))
await knx.receive_write(test_blue, (0,))
await knx.receive_write(test_white, (0,))
knx.assert_state("light.test", STATE_ON, brightness=180, rgbw_color=(0, 180, 0, 0))
knx.assert_state("light.test", STATE_ON, brightness=180, rgbw_color=(0, 255, 0, 0))
# turn OFF from HA
await hass.services.async_call(
@ -815,18 +839,31 @@ async def test_light_rgb(hass: HomeAssistant, knx: KNXTestKit):
blocking=True,
)
await knx.assert_write(test_rgb, (200, 0, 0))
knx.assert_state("light.test", STATE_ON, brightness=200, rgb_color=(200, 0, 0))
knx.assert_state("light.test", STATE_ON, brightness=200, rgb_color=(255, 0, 0))
# change color, keep brightness from HA
await hass.services.async_call(
"light",
"turn_on",
{"entity_id": "light.test", ATTR_COLOR_NAME: "hotpink"},
blocking=True,
)
await knx.assert_write(test_rgb, (200, 82, 141))
knx.assert_state(
"light.test",
STATE_ON,
brightness=200,
rgb_color=(255, 105, 180),
)
# change color and brightness from HA
await hass.services.async_call(
"light",
"turn_on",
{"entity_id": "light.test", ATTR_BRIGHTNESS: 128, ATTR_COLOR_NAME: "hotpink"},
{"entity_id": "light.test", ATTR_BRIGHTNESS: 100, ATTR_COLOR_NAME: "yellow"},
blocking=True,
)
#
await knx.assert_write(test_rgb, (128, 52, 90))
knx.assert_state("light.test", STATE_ON, brightness=128, rgb_color=(128, 52, 90))
await knx.assert_write(test_rgb, (100, 100, 0))
knx.assert_state("light.test", STATE_ON, brightness=100, rgb_color=(255, 255, 0))
# turn OFF from KNX
await knx.receive_write(test_address_state, False)
@ -836,7 +873,7 @@ async def test_light_rgb(hass: HomeAssistant, knx: KNXTestKit):
knx.assert_state("light.test", STATE_OFF)
# turn ON from KNX - include color update
await knx.receive_write(test_address_state, True)
knx.assert_state("light.test", STATE_ON, brightness=180, rgb_color=(0, 180, 0))
knx.assert_state("light.test", STATE_ON, brightness=180, rgb_color=(0, 255, 0))
# turn OFF from HA
await hass.services.async_call(
@ -857,7 +894,7 @@ async def test_light_rgb(hass: HomeAssistant, knx: KNXTestKit):
)
# color will be restored in no other state was received
await knx.assert_write(test_address, True)
knx.assert_state("light.test", STATE_ON, brightness=180, rgb_color=(0, 180, 0))
knx.assert_state("light.test", STATE_ON, brightness=180, rgb_color=(0, 255, 0))
async def test_light_rgbw(hass: HomeAssistant, knx: KNXTestKit):
@ -883,14 +920,14 @@ async def test_light_rgbw(hass: HomeAssistant, knx: KNXTestKit):
await knx.assert_read(test_address_state)
await knx.assert_read(test_rgbw_state)
await knx.receive_response(test_address_state, True)
await knx.receive_response(test_rgbw_state, (0x64, 0x65, 0x66, 0x67, 0x00, 0x0F))
await knx.receive_response(test_rgbw_state, (0xFF, 0x65, 0x66, 0x67, 0x00, 0x0F))
knx.assert_state(
"light.test",
STATE_ON,
brightness=103,
brightness=255,
color_mode=COLOR_MODE_RGBW,
rgbw_color=(100, 101, 102, 103),
rgbw_color=(255, 101, 102, 103),
)
# change color from HA
await hass.services.async_call(
@ -910,22 +947,33 @@ async def test_light_rgbw(hass: HomeAssistant, knx: KNXTestKit):
blocking=True,
)
await knx.assert_write(test_rgbw, (0xC8, 0x00, 0x00, 0x00, 0x00, 0x0F))
knx.assert_state("light.test", STATE_ON, brightness=200, rgbw_color=(200, 0, 0, 0))
knx.assert_state("light.test", STATE_ON, brightness=200, rgbw_color=(255, 0, 0, 0))
# change color, keep brightness from HA
await hass.services.async_call(
"light",
"turn_on",
{"entity_id": "light.test", ATTR_COLOR_NAME: "hotpink"},
blocking=True,
)
await knx.assert_write(test_rgbw, (200, 0, 100, 139, 0x00, 0x0F))
knx.assert_state(
"light.test",
STATE_ON,
brightness=200,
rgb_color=(255, 104, 179), # minor rounding error - expected (255, 105, 180)
rgbw_color=(255, 0, 127, 177), # expected (255, 0, 128, 178)
)
# change color and brightness from HA
await hass.services.async_call(
"light",
"turn_on",
{"entity_id": "light.test", ATTR_BRIGHTNESS: 128, ATTR_COLOR_NAME: "hotpink"},
{"entity_id": "light.test", ATTR_BRIGHTNESS: 100, ATTR_COLOR_NAME: "yellow"},
blocking=True,
)
await knx.assert_write(test_rgbw, (128, 0, 64, 89, 0x00, 0x0F))
await knx.assert_write(test_rgbw, (100, 100, 0, 0, 0x00, 0x0F))
knx.assert_state(
"light.test",
STATE_ON,
brightness=128,
rgb_color=(128, 52, 90),
rgbw_color=(128, 0, 64, 89),
"light.test", STATE_ON, brightness=100, rgbw_color=(255, 255, 0, 0)
)
# turn OFF from KNX
@ -936,7 +984,7 @@ async def test_light_rgbw(hass: HomeAssistant, knx: KNXTestKit):
knx.assert_state("light.test", STATE_OFF)
# turn ON from KNX - include color update
await knx.receive_write(test_address_state, True)
knx.assert_state("light.test", STATE_ON, brightness=180, rgbw_color=(0, 180, 0, 0))
knx.assert_state("light.test", STATE_ON, brightness=180, rgbw_color=(0, 255, 0, 0))
# turn OFF from HA
await hass.services.async_call(
@ -955,9 +1003,9 @@ async def test_light_rgbw(hass: HomeAssistant, knx: KNXTestKit):
{"entity_id": "light.test"},
blocking=True,
)
# color will be restored in no other state was received
# color will be restored if no other state was received
await knx.assert_write(test_address, True)
knx.assert_state("light.test", STATE_ON, brightness=180, rgbw_color=(0, 180, 0, 0))
knx.assert_state("light.test", STATE_ON, brightness=180, rgbw_color=(0, 255, 0, 0))
async def test_light_rgbw_brightness(hass: HomeAssistant, knx: KNXTestKit):
@ -987,16 +1035,16 @@ async def test_light_rgbw_brightness(hass: HomeAssistant, knx: KNXTestKit):
await knx.assert_read(test_address_state)
await knx.assert_read(test_brightness_state)
await knx.receive_response(test_address_state, True)
await knx.receive_response(test_brightness_state, (0x67,))
await knx.receive_response(test_brightness_state, (0xFF,))
await knx.assert_read(test_rgbw_state)
await knx.receive_response(test_rgbw_state, (0x64, 0x65, 0x66, 0x67, 0x00, 0x0F))
await knx.receive_response(test_rgbw_state, (0xFF, 0x65, 0x66, 0x67, 0x00, 0x0F))
knx.assert_state(
"light.test",
STATE_ON,
brightness=103,
brightness=255,
color_mode=COLOR_MODE_RGBW,
rgbw_color=(100, 101, 102, 103),
rgbw_color=(255, 101, 102, 103),
)
# change color from HA
await hass.services.async_call(
@ -1006,25 +1054,22 @@ async def test_light_rgbw_brightness(hass: HomeAssistant, knx: KNXTestKit):
blocking=True,
)
await knx.assert_write(test_rgbw, (0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F))
knx.assert_state("light.test", STATE_ON, brightness=103, rgbw_color=(255, 0, 0, 0))
# # relies on dedicated brightness state
await knx.receive_write(test_brightness_state, (0xFF,))
knx.assert_state("light.test", STATE_ON, brightness=255, rgbw_color=(255, 0, 0, 0))
# # update from dedicated brightness state
await knx.receive_write(test_brightness_state, (0xF0,))
knx.assert_state("light.test", STATE_ON, brightness=240, rgbw_color=(255, 0, 0, 0))
# change brightness from HA
# single encoded brightness - at least one primary color = 255
# # change brightness from HA
await hass.services.async_call(
"light",
"turn_on",
{"entity_id": "light.test", ATTR_BRIGHTNESS: 200},
{"entity_id": "light.test", ATTR_BRIGHTNESS: 128},
blocking=True,
)
await knx.assert_write(test_brightness, (200,))
knx.assert_state("light.test", STATE_ON, brightness=200, rgbw_color=(255, 0, 0, 0))
# # relies on dedicated rgbw state
await knx.receive_write(test_rgbw_state, (0xC8, 0x00, 0x00, 0x00, 0x00, 0x0F))
knx.assert_state("light.test", STATE_ON, brightness=200, rgbw_color=(200, 0, 0, 0))
# change color and brightness from HA
await knx.assert_write(test_brightness, (128,))
knx.assert_state("light.test", STATE_ON, brightness=128, rgbw_color=(255, 0, 0, 0))
# # change color and brightness from HA
await hass.services.async_call(
"light",
"turn_on",
@ -1040,3 +1085,36 @@ async def test_light_rgbw_brightness(hass: HomeAssistant, knx: KNXTestKit):
rgb_color=(255, 105, 180),
rgbw_color=(255, 0, 128, 178),
)
# doubly encoded brightness
# brightness is handled by dedicated brightness address only
# # from dedicated rgbw state
await knx.receive_write(test_rgbw_state, (0xC8, 0x00, 0x00, 0x00, 0x00, 0x0F))
knx.assert_state("light.test", STATE_ON, brightness=128, rgbw_color=(200, 0, 0, 0))
# # from HA - only color
await hass.services.async_call(
"light",
"turn_on",
{"entity_id": "light.test", ATTR_RGBW_COLOR: (20, 30, 40, 50)},
blocking=True,
)
await knx.assert_write(test_rgbw, (20, 30, 40, 50, 0x00, 0x0F))
knx.assert_state(
"light.test", STATE_ON, brightness=128, rgbw_color=(20, 30, 40, 50)
)
# # from HA - brightness and color
await hass.services.async_call(
"light",
"turn_on",
{
"entity_id": "light.test",
ATTR_BRIGHTNESS: 50,
ATTR_RGBW_COLOR: (100, 200, 55, 12),
},
blocking=True,
)
await knx.assert_write(test_rgbw, (100, 200, 55, 12, 0x00, 0x0F))
await knx.assert_write(test_brightness, (50,))
knx.assert_state(
"light.test", STATE_ON, brightness=50, rgbw_color=(100, 200, 55, 12)
)