From 27d42e0cd839169571a3774e4c0be25064e0f4e3 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Tue, 27 Jul 2021 19:36:46 +0200 Subject: [PATCH] KNX: Support for HS-color lights (#53538) --- homeassistant/components/knx/light.py | 25 ++++++++++++ homeassistant/components/knx/schema.py | 55 +++++++++++++++++++++----- 2 files changed, 70 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/knx/light.py b/homeassistant/components/knx/light.py index 56068b5deae..b807ad1335d 100644 --- a/homeassistant/components/knx/light.py +++ b/homeassistant/components/knx/light.py @@ -10,11 +10,13 @@ from xknx.telegram.address import parse_device_group_address from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, + ATTR_HS_COLOR, ATTR_RGB_COLOR, ATTR_RGBW_COLOR, ATTR_XY_COLOR, COLOR_MODE_BRIGHTNESS, COLOR_MODE_COLOR_TEMP, + COLOR_MODE_HS, COLOR_MODE_ONOFF, COLOR_MODE_RGB, COLOR_MODE_RGBW, @@ -158,6 +160,12 @@ def _create_light(xknx: XKNX, config: ConfigType) -> XknxLight: group_address_color_state=config.get(LightSchema.CONF_COLOR_STATE_ADDRESS), group_address_rgbw=config.get(LightSchema.CONF_RGBW_ADDRESS), group_address_rgbw_state=config.get(LightSchema.CONF_RGBW_STATE_ADDRESS), + group_address_hue=config.get(LightSchema.CONF_HUE_ADDRESS), + group_address_hue_state=config.get(LightSchema.CONF_HUE_STATE_ADDRESS), + group_address_saturation=config.get(LightSchema.CONF_SATURATION_ADDRESS), + group_address_saturation_state=config.get( + LightSchema.CONF_SATURATION_STATE_ADDRESS + ), group_address_xyy_color=config.get(LightSchema.CONF_XYY_ADDRESS), group_address_xyy_color_state=config.get(LightSchema.CONF_XYY_STATE_ADDRESS), group_address_tunable_white=group_address_tunable_white, @@ -283,6 +291,13 @@ class KNXLight(KnxEntity, LightEntity): return (*rgb, white) return None + @property + def hs_color(self) -> tuple[float, float] | None: + """Return the hue and saturation color value [float, float].""" + # Hue is scaled 0..360 int encoded in 1 byte in KNX (-> only 256 possible values) + # Saturation is scaled 0..100 int + return self._device.current_hs_color + @property def xy_color(self) -> tuple[float, float] | None: """Return the xy color value [float, float].""" @@ -315,6 +330,8 @@ class KNXLight(KnxEntity, LightEntity): """Return the color mode of the light.""" if self._device.supports_xyy_color: return COLOR_MODE_XY + if self._device.supports_hs_color: + return COLOR_MODE_HS if self._device.supports_rgbw: return COLOR_MODE_RGBW if self._device.supports_color: @@ -339,6 +356,7 @@ class KNXLight(KnxEntity, LightEntity): mireds = kwargs.get(ATTR_COLOR_TEMP) rgb = kwargs.get(ATTR_RGB_COLOR) rgbw = kwargs.get(ATTR_RGBW_COLOR) + hs_color = kwargs.get(ATTR_HS_COLOR) xy_color = kwargs.get(ATTR_XY_COLOR) if ( @@ -347,6 +365,7 @@ class KNXLight(KnxEntity, LightEntity): and mireds is None and rgb is None and rgbw is None + and hs_color is None and xy_color is None ): await self._device.set_on() @@ -396,6 +415,12 @@ class KNXLight(KnxEntity, LightEntity): ) return + if hs_color is not None: + # round so only one telegram will be sent if the other matches state + hue = round(hs_color[0]) + sat = round(hs_color[1]) + await self._device.set_hs_color((hue, sat)) + if brightness is not None: # brightness: 1..255; 0 brightness will call async_turn_off() if self._device.brightness.writable: diff --git a/homeassistant/components/knx/schema.py b/homeassistant/components/knx/schema.py index 079dc7363bf..11b2504d129 100644 --- a/homeassistant/components/knx/schema.py +++ b/homeassistant/components/knx/schema.py @@ -496,8 +496,12 @@ class LightSchema(KNXPlatformSchema): CONF_COLOR_TEMP_ADDRESS = "color_temperature_address" CONF_COLOR_TEMP_STATE_ADDRESS = "color_temperature_state_address" CONF_COLOR_TEMP_MODE = "color_temperature_mode" + CONF_HUE_ADDRESS = "hue_address" + CONF_HUE_STATE_ADDRESS = "hue_state_address" CONF_RGBW_ADDRESS = "rgbw_address" CONF_RGBW_STATE_ADDRESS = "rgbw_state_address" + CONF_SATURATION_ADDRESS = "saturation_address" + CONF_SATURATION_STATE_ADDRESS = "saturation_state_address" CONF_XYY_ADDRESS = "xyy_address" CONF_XYY_STATE_ADDRESS = "xyy_state_address" CONF_MIN_KELVIN = "min_kelvin" @@ -514,7 +518,18 @@ class LightSchema(KNXPlatformSchema): CONF_BLUE = "blue" CONF_WHITE = "white" - COLOR_SCHEMA = vol.Schema( + _hs_color_inclusion_msg = ( + "'hue_address', 'saturation_address' and 'brightness_address'" + " are required for hs_color configuration" + ) + HS_COLOR_SCHEMA = { + vol.Optional(CONF_HUE_ADDRESS): ga_list_validator, + vol.Optional(CONF_HUE_STATE_ADDRESS): ga_list_validator, + vol.Optional(CONF_SATURATION_ADDRESS): ga_list_validator, + vol.Optional(CONF_SATURATION_STATE_ADDRESS): ga_list_validator, + } + + INDIVIDUAL_COLOR_SCHEMA = vol.Schema( { vol.Optional(KNX_ADDRESS): ga_list_validator, vol.Optional(CONF_STATE_ADDRESS): ga_list_validator, @@ -536,18 +551,18 @@ class LightSchema(KNXPlatformSchema): CONF_RED, "individual_colors", msg="'red', 'green' and 'blue' are required for individual colors configuration", - ): COLOR_SCHEMA, + ): INDIVIDUAL_COLOR_SCHEMA, vol.Inclusive( CONF_GREEN, "individual_colors", msg="'red', 'green' and 'blue' are required for individual colors configuration", - ): COLOR_SCHEMA, + ): INDIVIDUAL_COLOR_SCHEMA, vol.Inclusive( CONF_BLUE, "individual_colors", msg="'red', 'green' and 'blue' are required for individual colors configuration", - ): COLOR_SCHEMA, - vol.Optional(CONF_WHITE): COLOR_SCHEMA, + ): INDIVIDUAL_COLOR_SCHEMA, + vol.Optional(CONF_WHITE): INDIVIDUAL_COLOR_SCHEMA, }, vol.Exclusive(CONF_COLOR_ADDRESS, "color"): ga_list_validator, vol.Optional(CONF_COLOR_STATE_ADDRESS): ga_list_validator, @@ -556,6 +571,7 @@ class LightSchema(KNXPlatformSchema): vol.Optional( CONF_COLOR_TEMP_MODE, default=DEFAULT_COLOR_TEMP_MODE ): vol.All(vol.Upper, cv.enum(ColorTempModes)), + **HS_COLOR_SCHEMA, vol.Exclusive(CONF_RGBW_ADDRESS, "color"): ga_list_validator, vol.Optional(CONF_RGBW_STATE_ADDRESS): ga_list_validator, vol.Exclusive(CONF_XYY_ADDRESS, "color"): ga_list_validator, @@ -569,20 +585,39 @@ class LightSchema(KNXPlatformSchema): } ), vol.Any( - # either global "address" or "individual_colors" is required vol.Schema( + {vol.Required(KNX_ADDRESS): object}, + extra=vol.ALLOW_EXTRA, + ), + vol.Schema( # brightness addresses are required in INDIVIDUAL_COLOR_SCHEMA + {vol.Required(CONF_INDIVIDUAL_COLORS): object}, + extra=vol.ALLOW_EXTRA, + ), + msg="either 'address' or 'individual_colors' is required", + ), + vol.Any( + vol.Schema( # 'brightness' is non-optional for hs-color { - # brightness addresses are required in COLOR_SCHEMA - vol.Required(CONF_INDIVIDUAL_COLORS): object, + vol.Inclusive( + CONF_BRIGHTNESS_ADDRESS, "hs_color", msg=_hs_color_inclusion_msg + ): object, + vol.Inclusive( + CONF_HUE_ADDRESS, "hs_color", msg=_hs_color_inclusion_msg + ): object, + vol.Inclusive( + CONF_SATURATION_ADDRESS, "hs_color", msg=_hs_color_inclusion_msg + ): object, }, extra=vol.ALLOW_EXTRA, ), - vol.Schema( + vol.Schema( # hs-colors not used { - vol.Required(KNX_ADDRESS): object, + vol.Optional(CONF_HUE_ADDRESS): None, + vol.Optional(CONF_SATURATION_ADDRESS): None, }, extra=vol.ALLOW_EXTRA, ), + msg=_hs_color_inclusion_msg, ), )