Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support knx tunable white and color temperature lights #19699

Merged
merged 13 commits into from Feb 8, 2019
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion homeassistant/components/knx/__init__.py
Expand Up @@ -17,7 +17,7 @@
from homeassistant.helpers.event import async_track_state_change
from homeassistant.helpers.script import Script

REQUIREMENTS = ['xknx==0.9.3']
REQUIREMENTS = ['xknx==0.9.4']

DOMAIN = "knx"
DATA_KNX = "data_knx"
Expand Down
162 changes: 142 additions & 20 deletions homeassistant/components/knx/light.py
Expand Up @@ -4,28 +4,50 @@
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.knx/
"""
from enum import Enum

import voluptuous as vol

from homeassistant.components.knx import ATTR_DISCOVER_DEVICES, DATA_KNX
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_HS_COLOR, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS,
SUPPORT_COLOR, Light)
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, PLATFORM_SCHEMA,
SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP,
Light)
from homeassistant.const import CONF_NAME
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
import homeassistant.util.color as color_util

from homeassistant.components.knx import ATTR_DISCOVER_DEVICES, DATA_KNX


CONF_ADDRESS = 'address'
CONF_STATE_ADDRESS = 'state_address'
CONF_BRIGHTNESS_ADDRESS = 'brightness_address'
CONF_BRIGHTNESS_STATE_ADDRESS = 'brightness_state_address'
CONF_COLOR_ADDRESS = 'color_address'
CONF_COLOR_STATE_ADDRESS = 'color_state_address'
CONF_COLOR_TEMP_ADDRESS = 'color_temperature_address'
CONF_COLOR_TEMP_STATE_ADDRESS = 'color_temperature_state_address'
CONF_COLOR_TEMP_MODE = 'color_temperature_mode'
CONF_MIN_KELVIN = 'min_kelvin'
CONF_MAX_KELVIN = 'max_kelvin'

DEFAULT_NAME = 'KNX Light'
DEFAULT_COLOR = [255, 255, 255]
DEFAULT_BRIGHTNESS = 255
DEFAULT_COLOR_TEMP_MODE = 'absolute'
DEFAULT_MIN_KELVIN = 2700 # 370 mireds
DEFAULT_MAX_KELVIN = 6000 # 166 mireds
DEPENDENCIES = ['knx']


class ColorTempModes(Enum):
"""Color temperature modes for config validation."""

absolute = "DPT-7.600"
relative = "DPT-5.001"


PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ADDRESS): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
Expand All @@ -34,6 +56,14 @@
vol.Optional(CONF_BRIGHTNESS_STATE_ADDRESS): cv.string,
vol.Optional(CONF_COLOR_ADDRESS): cv.string,
vol.Optional(CONF_COLOR_STATE_ADDRESS): cv.string,
vol.Optional(CONF_COLOR_TEMP_ADDRESS): cv.string,
vol.Optional(CONF_COLOR_TEMP_STATE_ADDRESS): cv.string,
vol.Optional(CONF_COLOR_TEMP_MODE, default=DEFAULT_COLOR_TEMP_MODE):
cv.enum(ColorTempModes),
vol.Optional(CONF_MIN_KELVIN, default=DEFAULT_MIN_KELVIN):
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Optional(CONF_MAX_KELVIN, default=DEFAULT_MAX_KELVIN):
vol.All(vol.Coerce(int), vol.Range(min=1)),
})


Expand All @@ -60,6 +90,20 @@ def async_add_entities_discovery(hass, discovery_info, async_add_entities):
def async_add_entities_config(hass, config, async_add_entities):
"""Set up light for KNX platform configured within platform."""
import xknx

group_address_tunable_white = None
group_address_tunable_white_state = None
group_address_color_temp = None
group_address_color_temp_state = None
if config.get(CONF_COLOR_TEMP_MODE) == ColorTempModes.absolute:
marvin-w marked this conversation as resolved.
Show resolved Hide resolved
group_address_color_temp = config.get(CONF_COLOR_TEMP_ADDRESS)
group_address_color_temp_state = \
config.get(CONF_COLOR_TEMP_STATE_ADDRESS)
elif config.get(CONF_COLOR_TEMP_MODE) == ColorTempModes.relative:
group_address_tunable_white = config.get(CONF_COLOR_TEMP_ADDRESS)
group_address_tunable_white_state = \
config.get(CONF_COLOR_TEMP_STATE_ADDRESS)

light = xknx.devices.Light(
hass.data[DATA_KNX].xknx,
name=config.get(CONF_NAME),
Expand All @@ -69,7 +113,13 @@ def async_add_entities_config(hass, config, async_add_entities):
group_address_brightness_state=config.get(
CONF_BRIGHTNESS_STATE_ADDRESS),
group_address_color=config.get(CONF_COLOR_ADDRESS),
group_address_color_state=config.get(CONF_COLOR_STATE_ADDRESS))
group_address_color_state=config.get(CONF_COLOR_STATE_ADDRESS),
group_address_tunable_white=group_address_tunable_white,
group_address_tunable_white_state=group_address_tunable_white_state,
group_address_color_temperature=group_address_color_temp,
group_address_color_temperature_state=group_address_color_temp_state,
min_kelvin=config.get(CONF_MIN_KELVIN),
max_kelvin=config.get(CONF_MAX_KELVIN))
hass.data[DATA_KNX].xknx.devices.add(light)
async_add_entities([KNXLight(light)])

Expand All @@ -81,6 +131,13 @@ def __init__(self, device):
"""Initialize of KNX light."""
self.device = device

self._min_kelvin = device.min_kelvin
self._max_kelvin = device.max_kelvin
self._min_mireds = \
color_util.color_temperature_kelvin_to_mired(self._max_kelvin)
self._max_mireds = \
color_util.color_temperature_kelvin_to_mired(self._min_kelvin)

@callback
def async_register_callbacks(self):
"""Register callbacks to update hass after device was changed."""
Expand Down Expand Up @@ -111,26 +168,51 @@ def should_poll(self):
@property
def brightness(self):
"""Return the brightness of this light between 0..255."""
return self.device.current_brightness \
if self.device.supports_brightness else \
None
if self.device.supports_color:
if self.device.current_color is None:
return None
return max(self.device.current_color)
if self.device.supports_brightness:
return self.device.current_brightness
return None

@property
def hs_color(self):
"""Return the HS color value."""
if self.device.supports_color:
return color_util.color_RGB_to_hs(*self.device.current_color)
rgb = self.device.current_color
if rgb is None:
return None
return color_util.color_RGB_to_hs(*rgb)
return None

@property
def color_temp(self):
"""Return the CT color temperature."""
"""Return the color temperature in mireds."""
if self.device.supports_color_temperature:
kelvin = self.device.current_color_temperature
if kelvin is not None:
return color_util.color_temperature_kelvin_to_mired(kelvin)
if self.device.supports_tunable_white:
relative_ct = self.device.current_tunable_white
if relative_ct is not None:
# as KNX devices typically use Kelvin we use it as base for
# calculating ct from percent
return color_util.color_temperature_kelvin_to_mired(
self._min_kelvin + (
(relative_ct / 255) *
(self._max_kelvin - self._min_kelvin)))
return None

@property
def white_value(self):
"""Return the white value of this light between 0..255."""
return None
def min_mireds(self):
"""Return the coldest color temp this light supports in mireds."""
return self._min_mireds

@property
def max_mireds(self):
"""Return the warmest color temp this light supports in mireds."""
return self._max_mireds

@property
def effect_list(self):
Expand All @@ -154,19 +236,59 @@ def supported_features(self):
if self.device.supports_brightness:
flags |= SUPPORT_BRIGHTNESS
if self.device.supports_color:
flags |= SUPPORT_COLOR
flags |= SUPPORT_COLOR | SUPPORT_BRIGHTNESS
if self.device.supports_color_temperature or \
self.device.supports_tunable_white:
flags |= SUPPORT_COLOR_TEMP
return flags

async def async_turn_on(self, **kwargs):
"""Turn the light on."""
if ATTR_BRIGHTNESS in kwargs:
if self.device.supports_brightness:
await self.device.set_brightness(int(kwargs[ATTR_BRIGHTNESS]))
elif ATTR_HS_COLOR in kwargs:
if self.device.supports_color:
await self.device.set_color(color_util.color_hs_to_RGB(
*kwargs[ATTR_HS_COLOR]))
brightness = kwargs.get(ATTR_BRIGHTNESS, self.brightness)
hs_color = kwargs.get(ATTR_HS_COLOR, self.hs_color)
mireds = kwargs.get(ATTR_COLOR_TEMP, self.color_temp)

update_brightness = ATTR_BRIGHTNESS in kwargs
update_color = ATTR_HS_COLOR in kwargs
update_color_temp = ATTR_COLOR_TEMP in kwargs

# always only go one path for turning on (avoid conflicting changes
# and weird effects)
if self.device.supports_brightness and \
(update_brightness and not update_color):
# if we don't need to update the color, try updating brightness
# directly if supported; don't do it if color also has to be
# changed, as RGB color implicitly sets the brightness as well
await self.device.set_brightness(brightness)
elif self.device.supports_color and \
(update_brightness or update_color):
# change RGB color (includes brightness)
# if brightness or hs_color was not yet set use the default value
# to claculate RGB from as a fallback
marvin-w marked this conversation as resolved.
Show resolved Hide resolved
if brightness is None:
brightness = DEFAULT_BRIGHTNESS
if hs_color is None:
hs_color = DEFAULT_COLOR
await self.device.set_color(
color_util.color_hsv_to_RGB(*hs_color, brightness * 100 / 255))
elif self.device.supports_color_temperature and \
update_color_temp:
# change color temperature without ON telegram
kelvin = int(color_util.color_temperature_mired_to_kelvin(mireds))
if kelvin > self._max_kelvin:
kelvin = self._max_kelvin
elif kelvin < self._min_kelvin:
kelvin = self._min_kelvin
await self.device.set_color_temperature(kelvin)
elif self.device.supports_tunable_white and \
update_color_temp:
# calculate relative_ct from Kelvin to fit typical KNX devices
kelvin = int(color_util.color_temperature_mired_to_kelvin(mireds))
relative_ct = int(255 * (kelvin - self._min_kelvin) /
(self._max_kelvin - self._min_kelvin))
marvin-w marked this conversation as resolved.
Show resolved Hide resolved
await self.device.set_tunable_white(relative_ct)
else:
# no color/brightness change requested, so just turn it on
await self.device.set_on()

async def async_turn_off(self, **kwargs):
Expand Down
2 changes: 1 addition & 1 deletion requirements_all.txt
Expand Up @@ -1742,7 +1742,7 @@ xbee-helper==0.0.7
xboxapi==0.1.1

# homeassistant.components.knx
xknx==0.9.3
xknx==0.9.4

# homeassistant.components.media_player.bluesound
# homeassistant.components.sensor.startca
Expand Down