Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 61 additions & 34 deletions homeassistant/components/zwave_js/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@
ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP,
ATTR_HS_COLOR,
ATTR_RGBW_COLOR,
ATTR_TRANSITION,
ATTR_WHITE_VALUE,
COLOR_MODE_BRIGHTNESS,
COLOR_MODE_COLOR_TEMP,
COLOR_MODE_HS,
COLOR_MODE_RGBW,
DOMAIN as LIGHT_DOMAIN,
SUPPORT_BRIGHTNESS,
SUPPORT_COLOR,
SUPPORT_COLOR_TEMP,
SUPPORT_TRANSITION,
SUPPORT_WHITE_VALUE,
LightEntity,
)
from homeassistant.config_entries import ConfigEntry
Expand Down Expand Up @@ -85,14 +85,14 @@ def __init__(
"""Initialize the light."""
super().__init__(config_entry, client, info)
self._supports_color = False
self._supports_white_value = False
self._supports_rgbw = False
self._supports_color_temp = False
self._hs_color: tuple[float, float] | None = None
self._white_value: int | None = None
self._rgbw_color: tuple[int, int, int, int] | None = None
self._color_mode: str | None = None
self._color_temp: int | None = None
self._min_mireds = 153 # 6500K as a safe default
self._max_mireds = 370 # 2700K as a safe default
self._supported_features = SUPPORT_BRIGHTNESS
self._warm_white = self.get_zwave_value(
"targetColor",
CommandClass.SWITCH_COLOR,
Expand All @@ -103,19 +103,23 @@ def __init__(
CommandClass.SWITCH_COLOR,
value_property_key=ColorComponent.COLD_WHITE,
)
self._supported_color_modes = set()
Comment thread
emontnemery marked this conversation as resolved.
self._supported_features = 0

# get additional (optional) values and set features
self._target_value = self.get_zwave_value("targetValue")
self._dimming_duration = self.get_zwave_value("duration")
if self._dimming_duration is not None:
self._supported_features |= SUPPORT_TRANSITION
self._calculate_color_values()
if self._supports_color:
self._supported_features |= SUPPORT_COLOR
if self._supports_rgbw:
self._supported_color_modes.add(COLOR_MODE_RGBW)
elif self._supports_color:
self._supported_color_modes.add(COLOR_MODE_HS)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

zwave is using raw RGB internally, we might want to flag COLOR_MODE_RGB instead.
Also, if the light supports both color and color_temp, it's a 5 channel light and we might want to flag COLOR_MODE_RGBWW as well.

if self._supports_color_temp:
self._supported_features |= SUPPORT_COLOR_TEMP
if self._supports_white_value:
self._supported_features |= SUPPORT_WHITE_VALUE
self._supported_color_modes.add(COLOR_MODE_COLOR_TEMP)
if not self._supported_color_modes:
self._supported_color_modes.add(COLOR_MODE_BRIGHTNESS)

@callback
def on_value_update(self) -> None:
Expand All @@ -132,6 +136,11 @@ def brightness(self) -> int:
return round((self.info.primary_value.value / 99) * 255)
return 0

@property
def color_mode(self) -> str | None:
"""Return the color mode of the light."""
return self._color_mode

@property
def is_on(self) -> bool:
"""Return true if device is on (brightness above 0)."""
Expand All @@ -143,9 +152,9 @@ def hs_color(self) -> tuple[float, float] | None:
return self._hs_color

@property
def white_value(self) -> int | None:
"""Return the white value of this light between 0..255."""
return self._white_value
def rgbw_color(self) -> tuple[int, int, int, int] | None:
"""Return the hs color."""
return self._rgbw_color

@property
def color_temp(self) -> int | None:
Expand All @@ -162,6 +171,11 @@ def max_mireds(self) -> int:
"""Return the warmest color_temp that this light supports."""
return self._max_mireds

@property
def supported_color_modes(self) -> set | None:
Comment thread
emontnemery marked this conversation as resolved.
"""Flag supported features."""
return self._supported_color_modes

@property
def supported_features(self) -> int:
"""Flag supported features."""
Expand Down Expand Up @@ -211,20 +225,20 @@ async def async_turn_on(self, **kwargs: Any) -> None:
}
)

# White value
white_value = kwargs.get(ATTR_WHITE_VALUE)
if white_value is not None and self._supports_white_value:
# white led brightness is controlled by white level
# rgb leds (if any) can be on at the same time
white_channel = {}

# RGBW
rgbw = kwargs.get(ATTR_RGBW_COLOR)
if rgbw is not None and self._supports_rgbw:
rgbw_channels = {
ColorComponent.RED: rgbw[0],
ColorComponent.GREEN: rgbw[1],
ColorComponent.BLUE: rgbw[2],
}
if self._warm_white:
white_channel[ColorComponent.WARM_WHITE] = white_value
rgbw_channels[ColorComponent.WARM_WHITE] = rgbw[3]

if self._cold_white:
white_channel[ColorComponent.COLD_WHITE] = white_value

await self._async_set_colors(white_channel)
rgbw_channels[ColorComponent.COLD_WHITE] = rgbw[3]
await self._async_set_colors(rgbw_channels)

# set brightness
await self._async_set_brightness(
Expand Down Expand Up @@ -361,6 +375,9 @@ def _calculate_color_values(self) -> None:
else:
multi_color = {}

# Default: Brightness (no color)
self._color_mode = COLOR_MODE_BRIGHTNESS

# RGB support
if red_val and green_val and blue_val:
# prefer values from the multicolor property
Expand All @@ -370,6 +387,8 @@ def _calculate_color_values(self) -> None:
self._supports_color = True
# convert to HS
self._hs_color = color_util.color_RGB_to_hs(red, green, blue)
# Light supports color, set color mode to hs
self._color_mode = COLOR_MODE_HS
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

zwave is using raw RGB internally, we might want to flag COLOR_MODE_RGB instead.


# color temperature support
if ww_val and cw_val:
Expand All @@ -382,13 +401,21 @@ def _calculate_color_values(self) -> None:
self._max_mireds
- ((cold_white / 255) * (self._max_mireds - self._min_mireds))
)
# White channels turned on, set color mode to color_temp
self._color_mode = COLOR_MODE_COLOR_TEMP
Copy link
Copy Markdown
Contributor Author

@emontnemery emontnemery Apr 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

zwave allows all 5 channels in a 5 channel light to be on simultaneously. Unless blocked by zwave_js, we might want to check which channels are on:
if r, g, b are all zero and at least one white channel is non zero -> COLOR_MODE_COLOR_TEMP
if at least one of r, g, b is non zero and at least one white channel is non zero -> COLOR_MODE_RGBWW

else:
self._color_temp = None
# only one white channel (warm white) = white_level support
elif ww_val:
self._supports_white_value = True
self._white_value = multi_color.get("warmWhite", ww_val.value)
# only one white channel (cool white) = white_level support
# only one white channel (warm white) = rgbw support
elif red_val and green_val and blue_val and ww_val:
self._supports_rgbw = True
white = multi_color.get("warmWhite", ww_val.value)
self._rgbw_color = (red, green, blue, white)
# Light supports rgbw, set color mode to rgbw
self._color_mode = COLOR_MODE_RGBW
# only one white channel (cool white) = rgbw support
elif cw_val:
self._supports_white_value = True
self._white_value = multi_color.get("coldWhite", cw_val.value)
self._supports_rgbw = True
white = multi_color.get("coldWhite", cw_val.value)
self._rgbw_color = (red, green, blue, white)
# Light supports rgbw, set color mode to rgbw
self._color_mode = COLOR_MODE_RGBW
23 changes: 16 additions & 7 deletions tests/components/zwave_js/test_light.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@

from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_MODE,
ATTR_COLOR_TEMP,
ATTR_MAX_MIREDS,
ATTR_MIN_MIREDS,
ATTR_RGB_COLOR,
ATTR_WHITE_VALUE,
ATTR_RGBW_COLOR,
ATTR_SUPPORTED_COLOR_MODES,
SUPPORT_TRANSITION,
)
from homeassistant.const import ATTR_SUPPORTED_FEATURES, STATE_OFF, STATE_ON

Expand All @@ -30,7 +33,8 @@ async def test_light(hass, client, bulb_6_multi_color, integration):
assert state.state == STATE_OFF
assert state.attributes[ATTR_MIN_MIREDS] == 153
assert state.attributes[ATTR_MAX_MIREDS] == 370
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 51
assert state.attributes[ATTR_SUPPORTED_FEATURES] == SUPPORT_TRANSITION
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["color_temp", "hs"]

# Test turning on
await hass.services.async_call(
Expand Down Expand Up @@ -86,8 +90,10 @@ async def test_light(hass, client, bulb_6_multi_color, integration):

state = hass.states.get(BULB_6_MULTI_COLOR_LIGHT_ENTITY)
assert state.state == STATE_ON
assert state.attributes[ATTR_COLOR_MODE] == "color_temp"
assert state.attributes[ATTR_BRIGHTNESS] == 255
assert state.attributes[ATTR_COLOR_TEMP] == 370
assert ATTR_RGB_COLOR not in state.attributes

# Test turning on with same brightness
await hass.services.async_call(
Expand Down Expand Up @@ -231,8 +237,10 @@ async def test_light(hass, client, bulb_6_multi_color, integration):

state = hass.states.get(BULB_6_MULTI_COLOR_LIGHT_ENTITY)
assert state.state == STATE_ON
assert state.attributes[ATTR_COLOR_MODE] == "hs"
assert state.attributes[ATTR_BRIGHTNESS] == 255
assert state.attributes[ATTR_RGB_COLOR] == (255, 76, 255)
assert ATTR_COLOR_TEMP not in state.attributes

client.async_send_command.reset_mock()

Expand Down Expand Up @@ -352,9 +360,10 @@ async def test_light(hass, client, bulb_6_multi_color, integration):

state = hass.states.get(BULB_6_MULTI_COLOR_LIGHT_ENTITY)
assert state.state == STATE_ON
assert state.attributes[ATTR_COLOR_MODE] == "color_temp"
assert state.attributes[ATTR_BRIGHTNESS] == 255
assert state.attributes[ATTR_COLOR_TEMP] == 170
assert state.attributes[ATTR_RGB_COLOR] == (255, 255, 255)
assert ATTR_RGB_COLOR not in state.attributes

# Test turning on with same color temp
await hass.services.async_call(
Expand Down Expand Up @@ -415,20 +424,20 @@ async def test_optional_light(hass, client, aeon_smart_switch_6, integration):
assert state.state == STATE_ON


async def test_white_value_light(hass, client, zen_31, integration):
async def test_rgbw_light(hass, client, zen_31, integration):
"""Test the light entity."""
zen_31
state = hass.states.get(ZEN_31_ENTITY)

assert state
assert state.state == STATE_ON
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 177
assert state.attributes[ATTR_SUPPORTED_FEATURES] == SUPPORT_TRANSITION

# Test turning on
await hass.services.async_call(
"light",
"turn_on",
{"entity_id": ZEN_31_ENTITY, ATTR_WHITE_VALUE: 128},
{"entity_id": ZEN_31_ENTITY, ATTR_RGBW_COLOR: (0, 0, 0, 128)},
blocking=True,
)

Expand All @@ -451,7 +460,7 @@ async def test_white_value_light(hass, client, zen_31, integration):
},
"value": {"blue": 70, "green": 159, "red": 255, "warmWhite": 141},
}
assert args["value"] == {"warmWhite": 128}
assert args["value"] == {"blue": 0, "green": 0, "red": 0, "warmWhite": 128}

args = client.async_send_command.call_args_list[1][0][0]
assert args["command"] == "node.set_value"
Expand Down