mirror of
https://github.com/home-assistant/core.git
synced 2025-07-14 08:47:10 +00:00
Update color logic for zwave_js light platform (#47110)
Co-authored-by: Raman Gupta <7243222+raman325@users.noreply.github.com>
This commit is contained in:
parent
7bc2328802
commit
853d9ac4a9
@ -1,6 +1,6 @@
|
||||
"""Support for Z-Wave lights."""
|
||||
import logging
|
||||
from typing import Any, Callable, Optional, Tuple
|
||||
from typing import Any, Callable, Dict, Optional, Tuple
|
||||
|
||||
from zwave_js_server.client import Client as ZwaveClient
|
||||
from zwave_js_server.const import ColorComponent, CommandClass
|
||||
@ -30,6 +30,17 @@ from .entity import ZWaveBaseEntity
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
MULTI_COLOR_MAP = {
|
||||
ColorComponent.WARM_WHITE: "warmWhite",
|
||||
ColorComponent.COLD_WHITE: "coldWhite",
|
||||
ColorComponent.RED: "red",
|
||||
ColorComponent.GREEN: "green",
|
||||
ColorComponent.BLUE: "blue",
|
||||
ColorComponent.AMBER: "amber",
|
||||
ColorComponent.CYAN: "cyan",
|
||||
ColorComponent.PURPLE: "purple",
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: Callable
|
||||
@ -149,21 +160,21 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity):
|
||||
# RGB/HS color
|
||||
hs_color = kwargs.get(ATTR_HS_COLOR)
|
||||
if hs_color is not None and self._supports_color:
|
||||
# set white levels to 0 when setting rgb
|
||||
await self._async_set_color("Warm White", 0)
|
||||
await self._async_set_color("Cold White", 0)
|
||||
red, green, blue = color_util.color_hs_to_RGB(*hs_color)
|
||||
await self._async_set_color("Red", red)
|
||||
await self._async_set_color("Green", green)
|
||||
await self._async_set_color("Blue", blue)
|
||||
colors = {
|
||||
ColorComponent.RED: red,
|
||||
ColorComponent.GREEN: green,
|
||||
ColorComponent.BLUE: blue,
|
||||
}
|
||||
if self._supports_color_temp:
|
||||
# turn of white leds when setting rgb
|
||||
colors[ColorComponent.WARM_WHITE] = 0
|
||||
colors[ColorComponent.COLD_WHITE] = 0
|
||||
await self._async_set_colors(colors)
|
||||
|
||||
# Color temperature
|
||||
color_temp = kwargs.get(ATTR_COLOR_TEMP)
|
||||
if color_temp is not None and self._supports_color_temp:
|
||||
# turn off rgb when setting white values
|
||||
await self._async_set_color("Red", 0)
|
||||
await self._async_set_color("Green", 0)
|
||||
await self._async_set_color("Blue", 0)
|
||||
# Limit color temp to min/max values
|
||||
cold = max(
|
||||
0,
|
||||
@ -177,17 +188,28 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity):
|
||||
),
|
||||
)
|
||||
warm = 255 - cold
|
||||
await self._async_set_color("Warm White", warm)
|
||||
await self._async_set_color("Cold White", cold)
|
||||
await self._async_set_colors(
|
||||
{
|
||||
# turn off color leds when setting color temperature
|
||||
ColorComponent.RED: 0,
|
||||
ColorComponent.GREEN: 0,
|
||||
ColorComponent.BLUE: 0,
|
||||
ColorComponent.WARM_WHITE: warm,
|
||||
ColorComponent.COLD_WHITE: cold,
|
||||
}
|
||||
)
|
||||
|
||||
# White value
|
||||
white_value = kwargs.get(ATTR_WHITE_VALUE)
|
||||
if white_value is not None and self._supports_white_value:
|
||||
# turn off rgb when setting white values
|
||||
await self._async_set_color("Red", 0)
|
||||
await self._async_set_color("Green", 0)
|
||||
await self._async_set_color("Blue", 0)
|
||||
await self._async_set_color("Warm White", white_value)
|
||||
# white led brightness is controlled by white level
|
||||
# rgb leds (if any) can be on at the same time
|
||||
await self._async_set_colors(
|
||||
{
|
||||
ColorComponent.WARM_WHITE: white_value,
|
||||
ColorComponent.COLD_WHITE: white_value,
|
||||
}
|
||||
)
|
||||
|
||||
# set brightness
|
||||
await self._async_set_brightness(
|
||||
@ -198,24 +220,33 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity):
|
||||
"""Turn the light off."""
|
||||
await self._async_set_brightness(0, kwargs.get(ATTR_TRANSITION))
|
||||
|
||||
async def _async_set_color(self, color_name: str, new_value: int) -> None:
|
||||
"""Set defined color to given value."""
|
||||
try:
|
||||
property_key = ColorComponent[color_name.upper().replace(" ", "_")].value
|
||||
except KeyError:
|
||||
raise ValueError(
|
||||
"Illegal color name specified, color must be one of "
|
||||
f"{','.join([color.name for color in ColorComponent])}"
|
||||
) from None
|
||||
cur_zwave_value = self.get_zwave_value(
|
||||
"currentColor",
|
||||
async def _async_set_colors(self, colors: Dict[ColorComponent, int]) -> None:
|
||||
"""Set (multiple) defined colors to given value(s)."""
|
||||
# prefer the (new) combined color property
|
||||
# https://github.com/zwave-js/node-zwave-js/pull/1782
|
||||
combined_color_val = self.get_zwave_value(
|
||||
"targetColor",
|
||||
CommandClass.SWITCH_COLOR,
|
||||
value_property_key=property_key.key,
|
||||
value_property_key_name=property_key.name,
|
||||
value_property_key=None,
|
||||
value_property_key_name=None,
|
||||
)
|
||||
# guard for unsupported command
|
||||
if cur_zwave_value is None:
|
||||
if combined_color_val and isinstance(combined_color_val.value, dict):
|
||||
colors_dict = {}
|
||||
for color, value in colors.items():
|
||||
color_name = MULTI_COLOR_MAP[color]
|
||||
colors_dict[color_name] = value
|
||||
# set updated color object
|
||||
await self.info.node.async_set_value(combined_color_val, colors_dict)
|
||||
return
|
||||
|
||||
# fallback to setting the color(s) one by one if multicolor fails
|
||||
# not sure this is needed at all, but just in case
|
||||
for color, value in colors.items():
|
||||
await self._async_set_color(color, value)
|
||||
|
||||
async def _async_set_color(self, color: ColorComponent, new_value: int) -> None:
|
||||
"""Set defined color to given value."""
|
||||
property_key = color.value
|
||||
# actually set the new color value
|
||||
target_zwave_value = self.get_zwave_value(
|
||||
"targetColor",
|
||||
@ -224,6 +255,7 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity):
|
||||
value_property_key_name=property_key.name,
|
||||
)
|
||||
if target_zwave_value is None:
|
||||
# guard for unsupported color
|
||||
return
|
||||
await self.info.node.async_set_value(target_zwave_value, new_value)
|
||||
|
||||
@ -231,9 +263,6 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity):
|
||||
self, brightness: Optional[int], transition: Optional[int] = None
|
||||
) -> None:
|
||||
"""Set new brightness to light."""
|
||||
if brightness is None and self.info.primary_value.value:
|
||||
# there is no point in setting default brightness when light is already on
|
||||
return
|
||||
if brightness is None:
|
||||
# Level 255 means to set it to previous value.
|
||||
zwave_brightness = 255
|
||||
@ -282,8 +311,9 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity):
|
||||
@callback
|
||||
def _calculate_color_values(self) -> None:
|
||||
"""Calculate light colors."""
|
||||
|
||||
# RGB support
|
||||
# NOTE: We lookup all values here (instead of relying on the multicolor one)
|
||||
# to find out what colors are supported
|
||||
# as this is a simple lookup by key, this not heavy
|
||||
red_val = self.get_zwave_value(
|
||||
"currentColor",
|
||||
CommandClass.SWITCH_COLOR,
|
||||
@ -302,19 +332,6 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity):
|
||||
value_property_key=ColorComponent.BLUE.value.key,
|
||||
value_property_key_name=ColorComponent.BLUE.value.name,
|
||||
)
|
||||
if red_val and green_val and blue_val:
|
||||
self._supports_color = True
|
||||
# convert to HS
|
||||
if (
|
||||
red_val.value is not None
|
||||
and green_val.value is not None
|
||||
and blue_val.value is not None
|
||||
):
|
||||
self._hs_color = color_util.color_RGB_to_hs(
|
||||
red_val.value, green_val.value, blue_val.value
|
||||
)
|
||||
|
||||
# White colors
|
||||
ww_val = self.get_zwave_value(
|
||||
"currentColor",
|
||||
CommandClass.SWITCH_COLOR,
|
||||
@ -327,23 +344,47 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity):
|
||||
value_property_key=ColorComponent.COLD_WHITE.value.key,
|
||||
value_property_key_name=ColorComponent.COLD_WHITE.value.name,
|
||||
)
|
||||
# prefer the (new) combined color property
|
||||
# https://github.com/zwave-js/node-zwave-js/pull/1782
|
||||
combined_color_val = self.get_zwave_value(
|
||||
"currentColor",
|
||||
CommandClass.SWITCH_COLOR,
|
||||
value_property_key=None,
|
||||
value_property_key_name=None,
|
||||
)
|
||||
if combined_color_val and isinstance(combined_color_val.value, dict):
|
||||
multi_color = combined_color_val.value
|
||||
else:
|
||||
multi_color = {}
|
||||
|
||||
# RGB support
|
||||
if red_val and green_val and blue_val:
|
||||
# prefer values from the multicolor property
|
||||
red = multi_color.get("red", red_val.value)
|
||||
green = multi_color.get("green", green_val.value)
|
||||
blue = multi_color.get("blue", blue_val.value)
|
||||
self._supports_color = True
|
||||
# convert to HS
|
||||
self._hs_color = color_util.color_RGB_to_hs(red, green, blue)
|
||||
|
||||
# color temperature support
|
||||
if ww_val and cw_val:
|
||||
# Color temperature (CW + WW) Support
|
||||
self._supports_color_temp = True
|
||||
warm_white = multi_color.get("warmWhite", ww_val.value)
|
||||
cold_white = multi_color.get("coldWhite", cw_val.value)
|
||||
# Calculate color temps based on whites
|
||||
cold_level = cw_val.value or 0
|
||||
if cold_level or ww_val.value is not None:
|
||||
if cold_white or warm_white:
|
||||
self._color_temp = round(
|
||||
self._max_mireds
|
||||
- ((cold_level / 255) * (self._max_mireds - self._min_mireds))
|
||||
- ((cold_white / 255) * (self._max_mireds - self._min_mireds))
|
||||
)
|
||||
else:
|
||||
self._color_temp = None
|
||||
# only one white channel (warm white) = white_level support
|
||||
elif ww_val:
|
||||
# only one white channel (warm white)
|
||||
self._supports_white_value = True
|
||||
self._white_value = ww_val.value
|
||||
self._white_value = multi_color.get("warmWhite", ww_val.value)
|
||||
# only one white channel (cool white) = white_level support
|
||||
elif cw_val:
|
||||
# only one white channel (cool white)
|
||||
self._supports_white_value = True
|
||||
self._white_value = cw_val.value
|
||||
self._white_value = multi_color.get("coldWhite", cw_val.value)
|
||||
|
@ -139,62 +139,62 @@ async def test_light(hass, client, bulb_6_multi_color, integration):
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert len(client.async_send_command_no_wait.call_args_list) == 5
|
||||
warm_args = client.async_send_command_no_wait.call_args_list[0][0][
|
||||
0
|
||||
] # warm white 0
|
||||
assert len(client.async_send_command_no_wait.call_args_list) == 6
|
||||
warm_args = client.async_send_command_no_wait.call_args_list[0][0][0] # red 255
|
||||
assert warm_args["command"] == "node.set_value"
|
||||
assert warm_args["nodeId"] == 39
|
||||
assert warm_args["valueId"]["commandClassName"] == "Color Switch"
|
||||
assert warm_args["valueId"]["commandClass"] == 51
|
||||
assert warm_args["valueId"]["endpoint"] == 0
|
||||
assert warm_args["valueId"]["metadata"]["label"] == "Target value (Warm White)"
|
||||
assert warm_args["valueId"]["metadata"]["label"] == "Target value (Red)"
|
||||
assert warm_args["valueId"]["property"] == "targetColor"
|
||||
assert warm_args["valueId"]["propertyName"] == "targetColor"
|
||||
assert warm_args["value"] == 0
|
||||
assert warm_args["value"] == 255
|
||||
|
||||
cold_args = client.async_send_command_no_wait.call_args_list[1][0][
|
||||
0
|
||||
] # cold white 0
|
||||
cold_args = client.async_send_command_no_wait.call_args_list[1][0][0] # green 76
|
||||
assert cold_args["command"] == "node.set_value"
|
||||
assert cold_args["nodeId"] == 39
|
||||
assert cold_args["valueId"]["commandClassName"] == "Color Switch"
|
||||
assert cold_args["valueId"]["commandClass"] == 51
|
||||
assert cold_args["valueId"]["endpoint"] == 0
|
||||
assert cold_args["valueId"]["metadata"]["label"] == "Target value (Cold White)"
|
||||
assert cold_args["valueId"]["metadata"]["label"] == "Target value (Green)"
|
||||
assert cold_args["valueId"]["property"] == "targetColor"
|
||||
assert cold_args["valueId"]["propertyName"] == "targetColor"
|
||||
assert cold_args["value"] == 0
|
||||
red_args = client.async_send_command_no_wait.call_args_list[2][0][0] # red 255
|
||||
assert cold_args["value"] == 76
|
||||
red_args = client.async_send_command_no_wait.call_args_list[2][0][0] # blue 255
|
||||
assert red_args["command"] == "node.set_value"
|
||||
assert red_args["nodeId"] == 39
|
||||
assert red_args["valueId"]["commandClassName"] == "Color Switch"
|
||||
assert red_args["valueId"]["commandClass"] == 51
|
||||
assert red_args["valueId"]["endpoint"] == 0
|
||||
assert red_args["valueId"]["metadata"]["label"] == "Target value (Red)"
|
||||
assert red_args["valueId"]["metadata"]["label"] == "Target value (Blue)"
|
||||
assert red_args["valueId"]["property"] == "targetColor"
|
||||
assert red_args["valueId"]["propertyName"] == "targetColor"
|
||||
assert red_args["value"] == 255
|
||||
green_args = client.async_send_command_no_wait.call_args_list[3][0][0] # green 76
|
||||
green_args = client.async_send_command_no_wait.call_args_list[3][0][
|
||||
0
|
||||
] # warm white 0
|
||||
assert green_args["command"] == "node.set_value"
|
||||
assert green_args["nodeId"] == 39
|
||||
assert green_args["valueId"]["commandClassName"] == "Color Switch"
|
||||
assert green_args["valueId"]["commandClass"] == 51
|
||||
assert green_args["valueId"]["endpoint"] == 0
|
||||
assert green_args["valueId"]["metadata"]["label"] == "Target value (Green)"
|
||||
assert green_args["valueId"]["metadata"]["label"] == "Target value (Warm White)"
|
||||
assert green_args["valueId"]["property"] == "targetColor"
|
||||
assert green_args["valueId"]["propertyName"] == "targetColor"
|
||||
assert green_args["value"] == 76
|
||||
blue_args = client.async_send_command_no_wait.call_args_list[4][0][0] # blue 255
|
||||
assert green_args["value"] == 0
|
||||
blue_args = client.async_send_command_no_wait.call_args_list[4][0][
|
||||
0
|
||||
] # cold white 0
|
||||
assert blue_args["command"] == "node.set_value"
|
||||
assert blue_args["nodeId"] == 39
|
||||
assert blue_args["valueId"]["commandClassName"] == "Color Switch"
|
||||
assert blue_args["valueId"]["commandClass"] == 51
|
||||
assert blue_args["valueId"]["endpoint"] == 0
|
||||
assert blue_args["valueId"]["metadata"]["label"] == "Target value (Blue)"
|
||||
assert blue_args["valueId"]["metadata"]["label"] == "Target value (Cold White)"
|
||||
assert blue_args["valueId"]["property"] == "targetColor"
|
||||
assert blue_args["valueId"]["propertyName"] == "targetColor"
|
||||
assert blue_args["value"] == 255
|
||||
assert blue_args["value"] == 0
|
||||
|
||||
# Test rgb color update from value updated event
|
||||
red_event = Event(
|
||||
@ -234,7 +234,6 @@ 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_BRIGHTNESS] == 255
|
||||
assert state.attributes[ATTR_COLOR_TEMP] == 370
|
||||
assert state.attributes[ATTR_RGB_COLOR] == (255, 76, 255)
|
||||
|
||||
client.async_send_command_no_wait.reset_mock()
|
||||
@ -247,7 +246,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration):
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert len(client.async_send_command_no_wait.call_args_list) == 5
|
||||
assert len(client.async_send_command_no_wait.call_args_list) == 6
|
||||
|
||||
client.async_send_command_no_wait.reset_mock()
|
||||
|
||||
@ -259,7 +258,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration):
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert len(client.async_send_command_no_wait.call_args_list) == 5
|
||||
assert len(client.async_send_command_no_wait.call_args_list) == 6
|
||||
red_args = client.async_send_command_no_wait.call_args_list[0][0][0] # red 0
|
||||
assert red_args["command"] == "node.set_value"
|
||||
assert red_args["nodeId"] == 39
|
||||
@ -369,7 +368,7 @@ async def test_light(hass, client, bulb_6_multi_color, integration):
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert len(client.async_send_command_no_wait.call_args_list) == 5
|
||||
assert len(client.async_send_command_no_wait.call_args_list) == 6
|
||||
|
||||
client.async_send_command_no_wait.reset_mock()
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user