Remove global limit on white light temperature (#7206)

* Remove global limit on white light temperature

Here are the supported temperatures of some popular bulbs:

 Philips Hue: 2000K-6500K (the current 500-154 mired range)
 LIFX Color 1000: 2500K-9000K
 IKEA TRÅDFRI: 2200K, 2700K, 4000K

Obviously, Home Assistant cannot enforce a global limit and work properly
with all of these bulbs. So just remove the limit and leave it up to each
platform to work it out.

This commit updates the existing users and adds a clamp to Hue (where the
limit appears to have originated). It does not attempt to update other
platforms that might need extra handling of the larger range that is now
possible.

* Add min_mireds/max_mireds state attributes to lights

* Support min_mireds/max_mireds with LIFX lights
This commit is contained in:
Anders Melchiorsen 2017-04-30 00:04:20 +02:00 committed by Paulus Schoutsen
parent ae9f44c708
commit 64a7be66b1
8 changed files with 60 additions and 17 deletions

View file

@ -50,6 +50,8 @@ ATTR_TRANSITION = "transition"
ATTR_RGB_COLOR = "rgb_color" ATTR_RGB_COLOR = "rgb_color"
ATTR_XY_COLOR = "xy_color" ATTR_XY_COLOR = "xy_color"
ATTR_COLOR_TEMP = "color_temp" ATTR_COLOR_TEMP = "color_temp"
ATTR_MIN_MIREDS = "min_mireds"
ATTR_MAX_MIREDS = "max_mireds"
ATTR_COLOR_NAME = "color_name" ATTR_COLOR_NAME = "color_name"
ATTR_WHITE_VALUE = "white_value" ATTR_WHITE_VALUE = "white_value"
@ -78,6 +80,8 @@ LIGHT_PROFILES_FILE = "light_profiles.csv"
PROP_TO_ATTR = { PROP_TO_ATTR = {
'brightness': ATTR_BRIGHTNESS, 'brightness': ATTR_BRIGHTNESS,
'color_temp': ATTR_COLOR_TEMP, 'color_temp': ATTR_COLOR_TEMP,
'min_mireds': ATTR_MIN_MIREDS,
'max_mireds': ATTR_MAX_MIREDS,
'rgb_color': ATTR_RGB_COLOR, 'rgb_color': ATTR_RGB_COLOR,
'xy_color': ATTR_XY_COLOR, 'xy_color': ATTR_XY_COLOR,
'white_value': ATTR_WHITE_VALUE, 'white_value': ATTR_WHITE_VALUE,
@ -99,9 +103,7 @@ LIGHT_TURN_ON_SCHEMA = vol.Schema({
vol.Coerce(tuple)), vol.Coerce(tuple)),
ATTR_XY_COLOR: vol.All(vol.ExactSequence((cv.small_float, cv.small_float)), ATTR_XY_COLOR: vol.All(vol.ExactSequence((cv.small_float, cv.small_float)),
vol.Coerce(tuple)), vol.Coerce(tuple)),
ATTR_COLOR_TEMP: vol.All(vol.Coerce(int), ATTR_COLOR_TEMP: vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Range(min=color_util.HASS_COLOR_MIN,
max=color_util.HASS_COLOR_MAX)),
ATTR_WHITE_VALUE: vol.All(vol.Coerce(int), vol.Range(min=0, max=255)), ATTR_WHITE_VALUE: vol.All(vol.Coerce(int), vol.Range(min=0, max=255)),
ATTR_FLASH: vol.In([FLASH_SHORT, FLASH_LONG]), ATTR_FLASH: vol.In([FLASH_SHORT, FLASH_LONG]),
ATTR_EFFECT: cv.string, ATTR_EFFECT: cv.string,
@ -337,6 +339,18 @@ class Light(ToggleEntity):
"""Return the CT color value in mireds.""" """Return the CT color value in mireds."""
return None return None
@property
def min_mireds(self):
"""Return the coldest color_temp that this light supports."""
# Default to the Philips Hue value that HA has always assumed
return 154
@property
def max_mireds(self):
"""Return the warmest color_temp that this light supports."""
# Default to the Philips Hue value that HA has always assumed
return 500
@property @property
def white_value(self): def white_value(self):
"""Return the white value of this light between 0..255.""" """Return the white value of this light between 0..255."""

View file

@ -403,7 +403,8 @@ class HueLight(Light):
command['bri'] = kwargs[ATTR_BRIGHTNESS] command['bri'] = kwargs[ATTR_BRIGHTNESS]
if ATTR_COLOR_TEMP in kwargs: if ATTR_COLOR_TEMP in kwargs:
command['ct'] = kwargs[ATTR_COLOR_TEMP] temp = kwargs[ATTR_COLOR_TEMP]
command['ct'] = max(self.min_mireds, min(temp, self.max_mireds))
flash = kwargs.get(ATTR_FLASH) flash = kwargs.get(ATTR_FLASH)

View file

@ -8,6 +8,7 @@ import colorsys
import logging import logging
import asyncio import asyncio
import sys import sys
import math
from functools import partial from functools import partial
from datetime import timedelta from datetime import timedelta
import async_timeout import async_timeout
@ -96,7 +97,12 @@ class LIFXManager(object):
self.hass.async_add_job(entity.async_update_ha_state()) self.hass.async_add_job(entity.async_update_ha_state())
else: else:
_LOGGER.debug("%s register NEW", device.ip_addr) _LOGGER.debug("%s register NEW", device.ip_addr)
device.get_color(self.ready) device.get_version(self.got_version)
@callback
def got_version(self, device, msg):
"""Request current color setting once we have the product version."""
device.get_color(self.ready)
@callback @callback
def ready(self, device, msg): def ready(self, device, msg):
@ -166,6 +172,7 @@ class LIFXLight(Light):
def __init__(self, device): def __init__(self, device):
"""Initialize the light.""" """Initialize the light."""
self.device = device self.device = device
self.product = device.product
self.blocker = None self.blocker = None
self.effect_data = None self.effect_data = None
self.postponed_update = None self.postponed_update = None
@ -213,6 +220,28 @@ class LIFXLight(Light):
_LOGGER.debug("color_temp: %d", temperature) _LOGGER.debug("color_temp: %d", temperature)
return temperature return temperature
@property
def min_mireds(self):
"""Return the coldest color_temp that this light supports."""
# The 3 LIFX "White" products supported a limited temperature range
# https://lan.developer.lifx.com/docs/lifx-products
if self.product in [10, 11, 18]:
kelvin = 6500
else:
kelvin = 9000
return math.floor(color_temperature_kelvin_to_mired(kelvin))
@property
def max_mireds(self):
"""Return the warmest color_temp that this light supports."""
# The 3 LIFX "White" products supported a limited temperature range
# https://lan.developer.lifx.com/docs/lifx-products
if self.product in [10, 11, 18]:
kelvin = 2700
else:
kelvin = 2500
return math.ceil(color_temperature_kelvin_to_mired(kelvin))
@property @property
def is_on(self): def is_on(self):
"""Return true if device is on.""" """Return true if device is on."""

View file

@ -25,7 +25,7 @@ turn_on:
example: '[0.52, 0.43]' example: '[0.52, 0.43]'
color_temp: color_temp:
description: Color temperature for the light in mireds (154-500) description: Color temperature for the light in mireds
example: '250' example: '250'
white_value: white_value:

View file

@ -15,9 +15,8 @@ from homeassistant.components.light import ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, \
from homeassistant.components import zwave from homeassistant.components import zwave
from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import
from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.util.color import HASS_COLOR_MAX, HASS_COLOR_MIN, \ from homeassistant.util.color import color_temperature_mired_to_kelvin, \
color_temperature_mired_to_kelvin, color_temperature_to_rgb, \ color_temperature_to_rgb, color_rgb_to_rgbw, color_rgbw_to_rgb
color_rgb_to_rgbw, color_rgbw_to_rgb
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -39,9 +38,11 @@ DEVICE_MAPPINGS = {
# Generate midpoint color temperatures for bulbs that have limited # Generate midpoint color temperatures for bulbs that have limited
# support for white light colors # support for white light colors
TEMP_MID_HASS = (HASS_COLOR_MAX - HASS_COLOR_MIN) / 2 + HASS_COLOR_MIN TEMP_COLOR_MAX = 500 # mireds (inverted)
TEMP_WARM_HASS = (HASS_COLOR_MAX - HASS_COLOR_MIN) / 3 * 2 + HASS_COLOR_MIN TEMP_COLOR_MIN = 154
TEMP_COLD_HASS = (HASS_COLOR_MAX - HASS_COLOR_MIN) / 3 + HASS_COLOR_MIN TEMP_MID_HASS = (TEMP_COLOR_MAX - TEMP_COLOR_MIN) / 2 + TEMP_COLOR_MIN
TEMP_WARM_HASS = (TEMP_COLOR_MAX - TEMP_COLOR_MIN) / 3 * 2 + TEMP_COLOR_MIN
TEMP_COLD_HASS = (TEMP_COLOR_MAX - TEMP_COLOR_MIN) / 3 + TEMP_COLOR_MIN
def get_device(node, values, node_config, **kwargs): def get_device(node, values, node_config, **kwargs):

View file

@ -17,7 +17,7 @@ from homeassistant.const import CONF_NAME, CONF_PLATFORM
from homeassistant.helpers.event import track_time_change from homeassistant.helpers.event import track_time_change
from homeassistant.util.color import ( from homeassistant.util.color import (
color_temperature_to_rgb, color_RGB_to_xy, color_temperature_to_rgb, color_RGB_to_xy,
color_temperature_kelvin_to_mired, HASS_COLOR_MIN, HASS_COLOR_MAX) color_temperature_kelvin_to_mired)
from homeassistant.util.dt import now as dt_now from homeassistant.util.dt import now as dt_now
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -208,7 +208,6 @@ class FluxSwitch(SwitchDevice):
else: else:
# Convert to mired and clamp to allowed values # Convert to mired and clamp to allowed values
mired = color_temperature_kelvin_to_mired(temp) mired = color_temperature_kelvin_to_mired(temp)
mired = max(HASS_COLOR_MIN, min(mired, HASS_COLOR_MAX))
set_lights_temp(self.hass, self._lights, mired, brightness) set_lights_temp(self.hass, self._lights, mired, brightness)
_LOGGER.info("Lights updated to mired:%s brightness:%s, %s%%" _LOGGER.info("Lights updated to mired:%s brightness:%s, %s%%"
" of %s cycle complete at %s", mired, brightness, " of %s cycle complete at %s", mired, brightness,

View file

@ -7,9 +7,6 @@ from typing import Tuple
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
HASS_COLOR_MAX = 500 # mireds (inverted)
HASS_COLOR_MIN = 154
# Official CSS3 colors from w3.org: # Official CSS3 colors from w3.org:
# https://www.w3.org/TR/2010/PR-css3-color-20101028/#html4 # https://www.w3.org/TR/2010/PR-css3-color-20101028/#html4
# names do not have spaces in them so that we can compare against # names do not have spaces in them so that we can compare against

View file

@ -53,6 +53,8 @@ class TestDemoLight(unittest.TestCase):
self.hass.block_till_done() self.hass.block_till_done()
state = self.hass.states.get(ENTITY_LIGHT) state = self.hass.states.get(ENTITY_LIGHT)
self.assertEqual(400, state.attributes.get(light.ATTR_COLOR_TEMP)) self.assertEqual(400, state.attributes.get(light.ATTR_COLOR_TEMP))
self.assertEqual(154, state.attributes.get(light.ATTR_MIN_MIREDS))
self.assertEqual(500, state.attributes.get(light.ATTR_MAX_MIREDS))
self.assertEqual('none', state.attributes.get(light.ATTR_EFFECT)) self.assertEqual('none', state.attributes.get(light.ATTR_EFFECT))
def test_turn_off(self): def test_turn_off(self):