Add color mode support to zwave light (#55264)

* Add color mode support to zwave light

* Fix typo
This commit is contained in:
Erik Montnemery 2021-12-13 14:38:49 +01:00 committed by GitHub
parent e48f6d548f
commit c157f1a787
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 99 additions and 85 deletions

View File

@ -5,21 +5,20 @@ from threading import Timer
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP,
ATTR_HS_COLOR,
ATTR_RGB_COLOR,
ATTR_RGBW_COLOR,
ATTR_TRANSITION,
ATTR_WHITE_VALUE,
COLOR_MODE_BRIGHTNESS,
COLOR_MODE_COLOR_TEMP,
COLOR_MODE_RGB,
COLOR_MODE_RGBW,
DOMAIN,
SUPPORT_BRIGHTNESS,
SUPPORT_COLOR,
SUPPORT_COLOR_TEMP,
SUPPORT_TRANSITION,
SUPPORT_WHITE_VALUE,
LightEntity,
)
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
import homeassistant.util.color as color_util
from . import CONF_REFRESH_DELAY, CONF_REFRESH_VALUE, ZWaveDeviceEntity, const
@ -108,16 +107,6 @@ def byte_to_zwave_brightness(value):
return 0
def ct_to_hs(temp):
"""Convert color temperature (mireds) to hs."""
colorlist = list(
color_util.color_temperature_to_hs(
color_util.color_temperature_mired_to_kelvin(temp)
)
)
return [int(val) for val in colorlist]
class ZwaveDimmer(ZWaveDeviceEntity, LightEntity):
"""Representation of a Z-Wave dimmer."""
@ -126,6 +115,8 @@ class ZwaveDimmer(ZWaveDeviceEntity, LightEntity):
ZWaveDeviceEntity.__init__(self, values, DOMAIN)
self._brightness = None
self._state = None
self._color_mode = None
self._supported_color_modes = set()
self._supported_features = None
self._delay = delay
self._refresh_value = refresh
@ -161,9 +152,10 @@ class ZwaveDimmer(ZWaveDeviceEntity, LightEntity):
def value_added(self):
"""Call when a new value is added to this entity."""
self._supported_features = SUPPORT_BRIGHTNESS
self._supported_color_modes = {COLOR_MODE_BRIGHTNESS}
self._color_mode = COLOR_MODE_BRIGHTNESS
if self.values.dimming_duration is not None:
self._supported_features |= SUPPORT_TRANSITION
self._supported_features = SUPPORT_TRANSITION
def value_changed(self):
"""Call when a value for this entity's node has changed."""
@ -195,6 +187,16 @@ class ZwaveDimmer(ZWaveDeviceEntity, LightEntity):
"""Return true if device is on."""
return self._state == STATE_ON
@property
def color_mode(self):
"""Return the current color mode."""
return self._color_mode
@property
def supported_color_modes(self):
"""Flag supported color modes."""
return self._supported_color_modes
@property
def supported_features(self):
"""Flag supported features."""
@ -260,7 +262,7 @@ class ZwaveColorLight(ZwaveDimmer):
def __init__(self, values, refresh, delay):
"""Initialize the light."""
self._color_channels = None
self._hs = None
self._rgb = None
self._ct = None
self._white = None
@ -268,15 +270,18 @@ class ZwaveColorLight(ZwaveDimmer):
def value_added(self):
"""Call when a new value is added to this entity."""
super().value_added()
if self.values.dimming_duration is not None:
self._supported_features = SUPPORT_TRANSITION
self._supported_features |= SUPPORT_COLOR
self._supported_color_modes = {COLOR_MODE_RGB}
self._color_mode = COLOR_MODE_RGB
if self._zw098:
self._supported_features |= SUPPORT_COLOR_TEMP
self._supported_color_modes.add(COLOR_MODE_COLOR_TEMP)
elif self._color_channels is not None and self._color_channels & (
COLOR_CHANNEL_WARM_WHITE | COLOR_CHANNEL_COLD_WHITE
):
self._supported_features |= SUPPORT_WHITE_VALUE
self._supported_color_modes = {COLOR_MODE_RGBW}
self._color_mode = COLOR_MODE_RGBW
def update_properties(self):
"""Update internal properties based on zwave values."""
@ -294,8 +299,7 @@ class ZwaveColorLight(ZwaveDimmer):
data = self.values.color.data
# RGB is always present in the openzwave color data string.
rgb = [int(data[1:3], 16), int(data[3:5], 16), int(data[5:7], 16)]
self._hs = color_util.color_RGB_to_hs(*rgb)
self._rgb = (int(data[1:3], 16), int(data[3:5], 16), int(data[5:7], 16))
# Parse remaining color channels. Openzwave appends white channels
# that are present.
@ -321,13 +325,12 @@ class ZwaveColorLight(ZwaveDimmer):
if self._zw098:
if warm_white > 0:
self._ct = TEMP_WARM_HASS
self._hs = ct_to_hs(self._ct)
self._color_mode = COLOR_MODE_COLOR_TEMP
elif cold_white > 0:
self._ct = TEMP_COLD_HASS
self._hs = ct_to_hs(self._ct)
self._color_mode = COLOR_MODE_COLOR_TEMP
else:
# RGB color is being used. Just report midpoint.
self._ct = TEMP_MID_HASS
self._color_mode = COLOR_MODE_RGB
elif self._color_channels & COLOR_CHANNEL_WARM_WHITE:
self._white = warm_white
@ -341,17 +344,19 @@ class ZwaveColorLight(ZwaveDimmer):
or self._color_channels & COLOR_CHANNEL_GREEN
or self._color_channels & COLOR_CHANNEL_BLUE
):
self._hs = None
self._rgb = None
@property
def hs_color(self):
"""Return the hs color."""
return self._hs
def rgb_color(self):
"""Return the rgb color."""
return self._rgb
@property
def white_value(self):
"""Return the white value of this light between 0..255."""
return self._white
def rgbw_color(self):
"""Return the rgbw color."""
if self._rgb is None:
return None
return (*self._rgb, self._white)
@property
def color_temp(self):
@ -362,31 +367,28 @@ class ZwaveColorLight(ZwaveDimmer):
"""Turn the device on."""
rgbw = None
if ATTR_WHITE_VALUE in kwargs:
self._white = kwargs[ATTR_WHITE_VALUE]
if ATTR_COLOR_TEMP in kwargs:
# Color temperature. With the AEOTEC ZW098 bulb, only two color
# temperatures are supported. The warm and cold channel values
# indicate brightness for warm/cold color temperature.
if self._zw098:
self._color_mode = COLOR_MODE_COLOR_TEMP
if kwargs[ATTR_COLOR_TEMP] > TEMP_MID_HASS:
self._ct = TEMP_WARM_HASS
rgbw = "#000000ff00"
else:
self._ct = TEMP_COLD_HASS
rgbw = "#00000000ff"
elif ATTR_HS_COLOR in kwargs:
self._hs = kwargs[ATTR_HS_COLOR]
if ATTR_WHITE_VALUE not in kwargs:
# white LED must be off in order for color to work
self._white = 0
elif ATTR_RGB_COLOR in kwargs:
self._rgb = kwargs[ATTR_RGB_COLOR]
self._white = 0
elif ATTR_RGBW_COLOR in kwargs:
self._rgb = kwargs[ATTR_RGBW_COLOR][0:3]
self._white = kwargs[ATTR_RGBW_COLOR][3]
if (
ATTR_WHITE_VALUE in kwargs or ATTR_HS_COLOR in kwargs
) and self._hs is not None:
if ATTR_RGB_COLOR in kwargs or ATTR_RGBW_COLOR in kwargs:
rgbw = "#"
for colorval in color_util.color_hs_to_RGB(*self._hs):
for colorval in self._rgb:
rgbw += format(colorval, "02x")
if self._white is not None:
rgbw += format(self._white, "02x") + "00"

View File

@ -5,14 +5,14 @@ from homeassistant.components import zwave
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP,
ATTR_HS_COLOR,
ATTR_RGB_COLOR,
ATTR_RGBW_COLOR,
ATTR_TRANSITION,
ATTR_WHITE_VALUE,
SUPPORT_BRIGHTNESS,
SUPPORT_COLOR,
SUPPORT_COLOR_TEMP,
COLOR_MODE_BRIGHTNESS,
COLOR_MODE_COLOR_TEMP,
COLOR_MODE_RGB,
COLOR_MODE_RGBW,
SUPPORT_TRANSITION,
SUPPORT_WHITE_VALUE,
)
from homeassistant.components.zwave import const, light
@ -38,7 +38,9 @@ def test_get_device_detects_dimmer(mock_openzwave):
device = light.get_device(node=node, values=values, node_config={})
assert isinstance(device, light.ZwaveDimmer)
assert device.supported_features == SUPPORT_BRIGHTNESS
assert device.color_mode == COLOR_MODE_BRIGHTNESS
assert device.supported_features is None
assert device.supported_color_modes == {COLOR_MODE_BRIGHTNESS}
def test_get_device_detects_colorlight(mock_openzwave):
@ -49,7 +51,9 @@ def test_get_device_detects_colorlight(mock_openzwave):
device = light.get_device(node=node, values=values, node_config={})
assert isinstance(device, light.ZwaveColorLight)
assert device.supported_features == SUPPORT_BRIGHTNESS | SUPPORT_COLOR
assert device.color_mode == COLOR_MODE_RGB
assert device.supported_features is None
assert device.supported_color_modes == {COLOR_MODE_RGB}
def test_get_device_detects_zw098(mock_openzwave):
@ -63,9 +67,9 @@ def test_get_device_detects_zw098(mock_openzwave):
values = MockLightValues(primary=value)
device = light.get_device(node=node, values=values, node_config={})
assert isinstance(device, light.ZwaveColorLight)
assert device.supported_features == (
SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_COLOR_TEMP
)
assert device.color_mode == COLOR_MODE_RGB
assert device.supported_features is None
assert device.supported_color_modes == {COLOR_MODE_COLOR_TEMP, COLOR_MODE_RGB}
def test_get_device_detects_rgbw_light(mock_openzwave):
@ -79,9 +83,9 @@ def test_get_device_detects_rgbw_light(mock_openzwave):
device = light.get_device(node=node, values=values, node_config={})
device.value_added()
assert isinstance(device, light.ZwaveColorLight)
assert device.supported_features == (
SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_WHITE_VALUE
)
assert device.color_mode == COLOR_MODE_RGBW
assert device.supported_features is None
assert device.supported_color_modes == {COLOR_MODE_RGBW}
def test_dimmer_turn_on(mock_openzwave):
@ -153,7 +157,9 @@ def test_dimmer_transitions(mock_openzwave):
duration = MockValue(data=0, node=node)
values = MockLightValues(primary=value, dimming_duration=duration)
device = light.get_device(node=node, values=values, node_config={})
assert device.supported_features == SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION
assert device.color_mode == COLOR_MODE_BRIGHTNESS
assert device.supported_features == SUPPORT_TRANSITION
assert device.supported_color_modes == {COLOR_MODE_BRIGHTNESS}
# Test turn_on
# Factory Default
@ -261,7 +267,7 @@ def test_dimmer_refresh_value(mock_openzwave):
assert device.brightness == 118
def test_set_hs_color(mock_openzwave):
def test_set_rgb_color(mock_openzwave):
"""Test setting zwave light color."""
node = MockNode(command_classes=[const.COMMAND_CLASS_SWITCH_COLOR])
value = MockValue(data=0, node=node)
@ -273,7 +279,7 @@ def test_set_hs_color(mock_openzwave):
assert color.data == "#0000000000"
device.turn_on(**{ATTR_HS_COLOR: (30, 50)})
device.turn_on(**{ATTR_RGB_COLOR: (0xFF, 0xBF, 0x7F)})
assert color.data == "#ffbf7f0000"
@ -290,14 +296,14 @@ def test_set_white_value(mock_openzwave):
assert color.data == "#0000000000"
device.turn_on(**{ATTR_WHITE_VALUE: 200})
device.turn_on(**{ATTR_RGBW_COLOR: (0xFF, 0xFF, 0xFF, 0xC8)})
assert color.data == "#ffffffc800"
def test_disable_white_if_set_color(mock_openzwave):
"""
Test that _white is set to 0 if turn_on with ATTR_HS_COLOR.
Test that _white is set to 0 if turn_on with ATTR_RGB_COLOR.
See Issue #13930 - many RGBW ZWave bulbs will only activate the RGB LED to
produce color if _white is set to zero.
@ -312,12 +318,12 @@ def test_disable_white_if_set_color(mock_openzwave):
device._white = 234
assert color.data == "#0000000000"
assert device.white_value == 234
assert device.rgbw_color == (0, 0, 0, 234)
device.turn_on(**{ATTR_HS_COLOR: (30, 50)})
device.turn_on(**{ATTR_RGB_COLOR: (0xFF, 0xBF, 0x7F)})
assert device.white_value == 0
assert color.data == "#ffbf7f0000"
assert device.rgbw_color == (0xFF, 0xBF, 0x7F, 0x00)
def test_zw098_set_color_temp(mock_openzwave):
@ -355,7 +361,8 @@ def test_rgb_not_supported(mock_openzwave):
values = MockLightValues(primary=value, color=color, color_channels=color_channels)
device = light.get_device(node=node, values=values, node_config={})
assert device.hs_color is None
assert device.rgb_color is None
assert device.rgbw_color is None
def test_no_color_value(mock_openzwave):
@ -365,7 +372,8 @@ def test_no_color_value(mock_openzwave):
values = MockLightValues(primary=value)
device = light.get_device(node=node, values=values, node_config={})
assert device.hs_color is None
assert device.rgb_color is None
assert device.rgbw_color is None
def test_no_color_channels_value(mock_openzwave):
@ -376,7 +384,8 @@ def test_no_color_channels_value(mock_openzwave):
values = MockLightValues(primary=value, color=color)
device = light.get_device(node=node, values=values, node_config={})
assert device.hs_color is None
assert device.rgb_color is None
assert device.rgbw_color is None
def test_rgb_value_changed(mock_openzwave):
@ -389,12 +398,12 @@ def test_rgb_value_changed(mock_openzwave):
values = MockLightValues(primary=value, color=color, color_channels=color_channels)
device = light.get_device(node=node, values=values, node_config={})
assert device.hs_color == (0, 0)
assert device.rgb_color == (0, 0, 0)
color.data = "#ffbf800000"
value_changed(color)
assert device.hs_color == (29.764, 49.804)
assert device.rgb_color == (0xFF, 0xBF, 0x80)
def test_rgbww_value_changed(mock_openzwave):
@ -407,14 +416,12 @@ def test_rgbww_value_changed(mock_openzwave):
values = MockLightValues(primary=value, color=color, color_channels=color_channels)
device = light.get_device(node=node, values=values, node_config={})
assert device.hs_color == (0, 0)
assert device.white_value == 0
assert device.rgbw_color == (0, 0, 0, 0)
color.data = "#c86400c800"
value_changed(color)
assert device.hs_color == (30, 100)
assert device.white_value == 200
assert device.rgbw_color == (0xC8, 0x64, 0x00, 0xC8)
def test_rgbcw_value_changed(mock_openzwave):
@ -427,14 +434,12 @@ def test_rgbcw_value_changed(mock_openzwave):
values = MockLightValues(primary=value, color=color, color_channels=color_channels)
device = light.get_device(node=node, values=values, node_config={})
assert device.hs_color == (0, 0)
assert device.white_value == 0
assert device.rgbw_color == (0, 0, 0, 0)
color.data = "#c86400c800"
value_changed(color)
assert device.hs_color == (30, 100)
assert device.white_value == 200
assert device.rgbw_color == (0xC8, 0x64, 0x00, 0xC8)
def test_ct_value_changed(mock_openzwave):
@ -451,14 +456,21 @@ def test_ct_value_changed(mock_openzwave):
values = MockLightValues(primary=value, color=color, color_channels=color_channels)
device = light.get_device(node=node, values=values, node_config={})
assert device.color_temp == light.TEMP_MID_HASS
assert device.color_mode == COLOR_MODE_RGB
assert device.color_temp is None
color.data = "#000000ff00"
value_changed(color)
assert device.color_mode == COLOR_MODE_COLOR_TEMP
assert device.color_temp == light.TEMP_WARM_HASS
color.data = "#00000000ff"
value_changed(color)
assert device.color_mode == COLOR_MODE_COLOR_TEMP
assert device.color_temp == light.TEMP_COLD_HASS
color.data = "#ff00000000"
value_changed(color)
assert device.color_mode == COLOR_MODE_RGB