Support non-dimmable color lights in Z-Wave JS (#127808)
* Z-Wave JS: support non-dimmable color lights * remove black_is_off light, support on/off/color * fix: tests for on/off light * fix: typo * remove commented out old test code * add test for off and on * support colored lights without separate brightness control * add test for color-only light * refactor: extract color only light * fix: preserve color when changing brightness * extend tests * refactor again * refactor scale check * refactor: remove impossible check * review feedback * review feedback * fix discovery to handle all 3 switch CCs, limit search to same endpoint * Update homeassistant/components/zwave_js/discovery.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/zwave_js/discovery.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * add test for Smart Switch 7 state * Add type annotations --------- Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
67f67a02f8
commit
f446e42317
6 changed files with 2676 additions and 364 deletions
|
@ -238,6 +238,12 @@ SWITCH_BINARY_CURRENT_VALUE_SCHEMA = ZWaveValueDiscoverySchema(
|
|||
command_class={CommandClass.SWITCH_BINARY}, property={CURRENT_VALUE_PROPERTY}
|
||||
)
|
||||
|
||||
COLOR_SWITCH_CURRENT_VALUE_SCHEMA = ZWaveValueDiscoverySchema(
|
||||
command_class={CommandClass.SWITCH_COLOR},
|
||||
property={CURRENT_COLOR_PROPERTY},
|
||||
property_key={None},
|
||||
)
|
||||
|
||||
SIREN_TONE_SCHEMA = ZWaveValueDiscoverySchema(
|
||||
command_class={CommandClass.SOUND_SWITCH},
|
||||
property={TONE_ID_PROPERTY},
|
||||
|
@ -762,33 +768,6 @@ DISCOVERY_SCHEMAS = [
|
|||
},
|
||||
),
|
||||
),
|
||||
# HomeSeer HSM-200 v1
|
||||
ZWaveDiscoverySchema(
|
||||
platform=Platform.LIGHT,
|
||||
hint="black_is_off",
|
||||
manufacturer_id={0x001E},
|
||||
product_id={0x0001},
|
||||
product_type={0x0004},
|
||||
primary_value=ZWaveValueDiscoverySchema(
|
||||
command_class={CommandClass.SWITCH_COLOR},
|
||||
property={CURRENT_COLOR_PROPERTY},
|
||||
property_key={None},
|
||||
),
|
||||
absent_values=[SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA],
|
||||
),
|
||||
# Logic Group ZDB5100
|
||||
ZWaveDiscoverySchema(
|
||||
platform=Platform.LIGHT,
|
||||
hint="black_is_off",
|
||||
manufacturer_id={0x0234},
|
||||
product_id={0x0121},
|
||||
product_type={0x0003},
|
||||
primary_value=ZWaveValueDiscoverySchema(
|
||||
command_class={CommandClass.SWITCH_COLOR},
|
||||
property={CURRENT_COLOR_PROPERTY},
|
||||
property_key={None},
|
||||
),
|
||||
),
|
||||
# ====== START OF GENERIC MAPPING SCHEMAS =======
|
||||
# locks
|
||||
# Door Lock CC
|
||||
|
@ -990,11 +969,6 @@ DISCOVERY_SCHEMAS = [
|
|||
),
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
),
|
||||
# binary switches
|
||||
ZWaveDiscoverySchema(
|
||||
platform=Platform.SWITCH,
|
||||
primary_value=SWITCH_BINARY_CURRENT_VALUE_SCHEMA,
|
||||
),
|
||||
# switch for Indicator CC
|
||||
ZWaveDiscoverySchema(
|
||||
platform=Platform.SWITCH,
|
||||
|
@ -1082,15 +1056,51 @@ DISCOVERY_SCHEMAS = [
|
|||
device_class_generic={"Thermostat"},
|
||||
primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA,
|
||||
),
|
||||
# lights
|
||||
# primary value is the currentValue (brightness)
|
||||
# catch any device with multilevel CC as light
|
||||
# NOTE: keep this at the bottom of the discovery scheme,
|
||||
# to handle all others that need the multilevel CC first
|
||||
# Handle the different combinations of Binary Switch, Multilevel Switch and Color Switch
|
||||
# to create switches and/or (colored) lights. The goal is to:
|
||||
# - couple Color Switch CC with Multilevel Switch CC if possible
|
||||
# - couple Color Switch CC with Binary Switch CC as the first fallback
|
||||
# - use Color Switch CC standalone as the last fallback
|
||||
#
|
||||
# Multilevel Switch CC (+ Color Switch CC) -> Dimmable light with or without color support.
|
||||
ZWaveDiscoverySchema(
|
||||
platform=Platform.LIGHT,
|
||||
primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA,
|
||||
),
|
||||
# Binary Switch CC when Multilevel Switch and Color Switch CC exist ->
|
||||
# On/Off switch, assign color to light entity instead
|
||||
ZWaveDiscoverySchema(
|
||||
platform=Platform.SWITCH,
|
||||
primary_value=SWITCH_BINARY_CURRENT_VALUE_SCHEMA,
|
||||
required_values=[
|
||||
SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA,
|
||||
COLOR_SWITCH_CURRENT_VALUE_SCHEMA,
|
||||
],
|
||||
),
|
||||
# Binary Switch CC and Color Switch CC ->
|
||||
# Colored light that uses Binary Switch CC for turning on/off.
|
||||
ZWaveDiscoverySchema(
|
||||
platform=Platform.LIGHT,
|
||||
hint="color_onoff",
|
||||
primary_value=SWITCH_BINARY_CURRENT_VALUE_SCHEMA,
|
||||
required_values=[COLOR_SWITCH_CURRENT_VALUE_SCHEMA],
|
||||
),
|
||||
# Binary Switch CC without Color Switch CC -> On/Off switch
|
||||
ZWaveDiscoverySchema(
|
||||
platform=Platform.SWITCH,
|
||||
primary_value=SWITCH_BINARY_CURRENT_VALUE_SCHEMA,
|
||||
absent_values=[COLOR_SWITCH_CURRENT_VALUE_SCHEMA],
|
||||
),
|
||||
# Colored light (legacy device) that can only be controlled through Color Switch CC.
|
||||
ZWaveDiscoverySchema(
|
||||
platform=Platform.LIGHT,
|
||||
hint="color_onoff",
|
||||
primary_value=COLOR_SWITCH_CURRENT_VALUE_SCHEMA,
|
||||
absent_values=[
|
||||
SWITCH_BINARY_CURRENT_VALUE_SCHEMA,
|
||||
SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA,
|
||||
],
|
||||
),
|
||||
# light for Basic CC with target
|
||||
ZWaveDiscoverySchema(
|
||||
platform=Platform.LIGHT,
|
||||
|
@ -1315,14 +1325,20 @@ def async_discover_single_value(
|
|||
|
||||
# check additional required values
|
||||
if schema.required_values is not None and not all(
|
||||
any(check_value(val, val_scheme) for val in value.node.values.values())
|
||||
any(
|
||||
check_value(val, val_scheme, primary_value=value)
|
||||
for val in value.node.values.values()
|
||||
)
|
||||
for val_scheme in schema.required_values
|
||||
):
|
||||
continue
|
||||
|
||||
# check for values that may not be present
|
||||
if schema.absent_values is not None and any(
|
||||
any(check_value(val, val_scheme) for val in value.node.values.values())
|
||||
any(
|
||||
check_value(val, val_scheme, primary_value=value)
|
||||
for val in value.node.values.values()
|
||||
)
|
||||
for val_scheme in schema.absent_values
|
||||
):
|
||||
continue
|
||||
|
@ -1441,7 +1457,11 @@ def async_discover_single_configuration_value(
|
|||
|
||||
|
||||
@callback
|
||||
def check_value(value: ZwaveValue, schema: ZWaveValueDiscoverySchema) -> bool:
|
||||
def check_value(
|
||||
value: ZwaveValue,
|
||||
schema: ZWaveValueDiscoverySchema,
|
||||
primary_value: ZwaveValue | None = None,
|
||||
) -> bool:
|
||||
"""Check if value matches scheme."""
|
||||
# check command_class
|
||||
if (
|
||||
|
@ -1452,6 +1472,14 @@ def check_value(value: ZwaveValue, schema: ZWaveValueDiscoverySchema) -> bool:
|
|||
# check endpoint
|
||||
if schema.endpoint is not None and value.endpoint not in schema.endpoint:
|
||||
return False
|
||||
# If the schema does not require an endpoint, make sure the value is on the
|
||||
# same endpoint as the primary value
|
||||
if (
|
||||
schema.endpoint is None
|
||||
and primary_value is not None
|
||||
and value.endpoint != primary_value.endpoint
|
||||
):
|
||||
return False
|
||||
# check property
|
||||
if schema.property is not None and value.property_ not in schema.property:
|
||||
return False
|
||||
|
|
|
@ -76,8 +76,8 @@ async def async_setup_entry(
|
|||
driver = client.driver
|
||||
assert driver is not None # Driver is ready before platforms are loaded.
|
||||
|
||||
if info.platform_hint == "black_is_off":
|
||||
async_add_entities([ZwaveBlackIsOffLight(config_entry, driver, info)])
|
||||
if info.platform_hint == "color_onoff":
|
||||
async_add_entities([ZwaveColorOnOffLight(config_entry, driver, info)])
|
||||
else:
|
||||
async_add_entities([ZwaveLight(config_entry, driver, info)])
|
||||
|
||||
|
@ -111,9 +111,10 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity):
|
|||
self._supports_color = False
|
||||
self._supports_rgbw = False
|
||||
self._supports_color_temp = False
|
||||
self._supports_dimming = False
|
||||
self._color_mode: str | None = None
|
||||
self._hs_color: tuple[float, float] | 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
|
||||
|
@ -129,15 +130,28 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity):
|
|||
)
|
||||
self._supported_color_modes: set[ColorMode] = set()
|
||||
|
||||
self._target_brightness: Value | None = None
|
||||
|
||||
# get additional (optional) values and set features
|
||||
# If the command class is Basic, we must geenerate a name that includes
|
||||
# the command class name to avoid ambiguity
|
||||
self._target_brightness = self.get_zwave_value(
|
||||
TARGET_VALUE_PROPERTY,
|
||||
CommandClass.SWITCH_MULTILEVEL,
|
||||
add_to_watched_value_ids=False,
|
||||
)
|
||||
if self.info.primary_value.command_class == CommandClass.BASIC:
|
||||
if self.info.primary_value.command_class == CommandClass.SWITCH_BINARY:
|
||||
# This light can not be dimmed separately from the color channels
|
||||
self._target_brightness = self.get_zwave_value(
|
||||
TARGET_VALUE_PROPERTY,
|
||||
CommandClass.SWITCH_BINARY,
|
||||
add_to_watched_value_ids=False,
|
||||
)
|
||||
self._supports_dimming = False
|
||||
elif self.info.primary_value.command_class == CommandClass.SWITCH_MULTILEVEL:
|
||||
# This light can be dimmed separately from the color channels
|
||||
self._target_brightness = self.get_zwave_value(
|
||||
TARGET_VALUE_PROPERTY,
|
||||
CommandClass.SWITCH_MULTILEVEL,
|
||||
add_to_watched_value_ids=False,
|
||||
)
|
||||
self._supports_dimming = True
|
||||
elif self.info.primary_value.command_class == CommandClass.BASIC:
|
||||
# If the command class is Basic, we must generate a name that includes
|
||||
# the command class name to avoid ambiguity
|
||||
self._attr_name = self.generate_name(
|
||||
include_value_name=True, alternate_value_name="Basic"
|
||||
)
|
||||
|
@ -146,6 +160,13 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity):
|
|||
CommandClass.BASIC,
|
||||
add_to_watched_value_ids=False,
|
||||
)
|
||||
self._supports_dimming = True
|
||||
|
||||
self._current_color = self.get_zwave_value(
|
||||
CURRENT_COLOR_PROPERTY,
|
||||
CommandClass.SWITCH_COLOR,
|
||||
value_property_key=None,
|
||||
)
|
||||
self._target_color = self.get_zwave_value(
|
||||
TARGET_COLOR_PROPERTY,
|
||||
CommandClass.SWITCH_COLOR,
|
||||
|
@ -216,7 +237,7 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity):
|
|||
|
||||
@property
|
||||
def rgbw_color(self) -> tuple[int, int, int, int] | None:
|
||||
"""Return the hs color."""
|
||||
"""Return the RGBW color."""
|
||||
return self._rgbw_color
|
||||
|
||||
@property
|
||||
|
@ -243,11 +264,39 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity):
|
|||
"""Turn the device on."""
|
||||
|
||||
transition = kwargs.get(ATTR_TRANSITION)
|
||||
brightness = kwargs.get(ATTR_BRIGHTNESS)
|
||||
|
||||
hs_color = kwargs.get(ATTR_HS_COLOR)
|
||||
color_temp = kwargs.get(ATTR_COLOR_TEMP)
|
||||
rgbw = kwargs.get(ATTR_RGBW_COLOR)
|
||||
|
||||
new_colors = self._get_new_colors(hs_color, color_temp, rgbw)
|
||||
if new_colors is not None:
|
||||
await self._async_set_colors(new_colors, transition)
|
||||
|
||||
# set brightness (or turn on if dimming is not supported)
|
||||
await self._async_set_brightness(brightness, transition)
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the light off."""
|
||||
await self._async_set_brightness(0, kwargs.get(ATTR_TRANSITION))
|
||||
|
||||
def _get_new_colors(
|
||||
self,
|
||||
hs_color: tuple[float, float] | None,
|
||||
color_temp: int | None,
|
||||
rgbw: tuple[int, int, int, int] | None,
|
||||
brightness_scale: float | None = None,
|
||||
) -> dict[ColorComponent, int] | None:
|
||||
"""Determine the new color dict to set."""
|
||||
|
||||
# RGB/HS color
|
||||
hs_color = kwargs.get(ATTR_HS_COLOR)
|
||||
if hs_color is not None and self._supports_color:
|
||||
red, green, blue = color_util.color_hs_to_RGB(*hs_color)
|
||||
if brightness_scale is not None:
|
||||
red = round(red * brightness_scale)
|
||||
green = round(green * brightness_scale)
|
||||
blue = round(blue * brightness_scale)
|
||||
colors = {
|
||||
ColorComponent.RED: red,
|
||||
ColorComponent.GREEN: green,
|
||||
|
@ -257,10 +306,9 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity):
|
|||
# turn of white leds when setting rgb
|
||||
colors[ColorComponent.WARM_WHITE] = 0
|
||||
colors[ColorComponent.COLD_WHITE] = 0
|
||||
await self._async_set_colors(colors, transition)
|
||||
return colors
|
||||
|
||||
# Color temperature
|
||||
color_temp = kwargs.get(ATTR_COLOR_TEMP)
|
||||
if color_temp is not None and self._supports_color_temp:
|
||||
# Limit color temp to min/max values
|
||||
cold = max(
|
||||
|
@ -275,20 +323,18 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity):
|
|||
),
|
||||
)
|
||||
warm = 255 - 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,
|
||||
},
|
||||
transition,
|
||||
)
|
||||
colors = {
|
||||
ColorComponent.WARM_WHITE: warm,
|
||||
ColorComponent.COLD_WHITE: cold,
|
||||
}
|
||||
if self._supports_color:
|
||||
# turn off color leds when setting color temperature
|
||||
colors[ColorComponent.RED] = 0
|
||||
colors[ColorComponent.GREEN] = 0
|
||||
colors[ColorComponent.BLUE] = 0
|
||||
return colors
|
||||
|
||||
# RGBW
|
||||
rgbw = kwargs.get(ATTR_RGBW_COLOR)
|
||||
if rgbw is not None and self._supports_rgbw:
|
||||
rgbw_channels = {
|
||||
ColorComponent.RED: rgbw[0],
|
||||
|
@ -300,17 +346,15 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity):
|
|||
|
||||
if self._cold_white:
|
||||
rgbw_channels[ColorComponent.COLD_WHITE] = rgbw[3]
|
||||
await self._async_set_colors(rgbw_channels, transition)
|
||||
|
||||
# set brightness
|
||||
await self._async_set_brightness(kwargs.get(ATTR_BRIGHTNESS), transition)
|
||||
return rgbw_channels
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the light off."""
|
||||
await self._async_set_brightness(0, kwargs.get(ATTR_TRANSITION))
|
||||
return None
|
||||
|
||||
async def _async_set_colors(
|
||||
self, colors: dict[ColorComponent, int], transition: float | None = None
|
||||
self,
|
||||
colors: dict[ColorComponent, int],
|
||||
transition: float | None = None,
|
||||
) -> None:
|
||||
"""Set (multiple) defined colors to given value(s)."""
|
||||
# prefer the (new) combined color property
|
||||
|
@ -361,9 +405,14 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity):
|
|||
zwave_transition = {TRANSITION_DURATION_OPTION: "default"}
|
||||
|
||||
# setting a value requires setting targetValue
|
||||
await self._async_set_value(
|
||||
self._target_brightness, zwave_brightness, zwave_transition
|
||||
)
|
||||
if self._supports_dimming:
|
||||
await self._async_set_value(
|
||||
self._target_brightness, zwave_brightness, zwave_transition
|
||||
)
|
||||
else:
|
||||
await self._async_set_value(
|
||||
self._target_brightness, zwave_brightness > 0, zwave_transition
|
||||
)
|
||||
# We do an optimistic state update when setting to a previous value
|
||||
# to avoid waiting for the value to be updated from the device which is
|
||||
# typically delayed and causes a confusing UX.
|
||||
|
@ -427,15 +476,8 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity):
|
|||
"""Calculate light colors."""
|
||||
(red_val, green_val, blue_val, ww_val, cw_val) = self._get_color_values()
|
||||
|
||||
# prefer the (new) combined color property
|
||||
# https://github.com/zwave-js/node-zwave-js/pull/1782
|
||||
combined_color_val = self.get_zwave_value(
|
||||
CURRENT_COLOR_PROPERTY,
|
||||
CommandClass.SWITCH_COLOR,
|
||||
value_property_key=None,
|
||||
)
|
||||
if combined_color_val and isinstance(combined_color_val.value, dict):
|
||||
multi_color = combined_color_val.value
|
||||
if self._current_color and isinstance(self._current_color.value, dict):
|
||||
multi_color = self._current_color.value
|
||||
else:
|
||||
multi_color = {}
|
||||
|
||||
|
@ -486,11 +528,10 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity):
|
|||
self._color_mode = ColorMode.RGBW
|
||||
|
||||
|
||||
class ZwaveBlackIsOffLight(ZwaveLight):
|
||||
"""Representation of a Z-Wave light where setting the color to black turns it off.
|
||||
class ZwaveColorOnOffLight(ZwaveLight):
|
||||
"""Representation of a colored Z-Wave light with an optional binary switch to turn on/off.
|
||||
|
||||
Currently only supports lights with RGB, no color temperature, and no white
|
||||
channels.
|
||||
Dimming for RGB lights is realized by scaling the color channels.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
|
@ -499,61 +540,137 @@ class ZwaveBlackIsOffLight(ZwaveLight):
|
|||
"""Initialize the light."""
|
||||
super().__init__(config_entry, driver, info)
|
||||
|
||||
self._last_color: dict[str, int] | None = None
|
||||
self._supported_color_modes.discard(ColorMode.BRIGHTNESS)
|
||||
self._last_on_color: dict[ColorComponent, int] | None = None
|
||||
self._last_brightness: int | None = None
|
||||
|
||||
@property
|
||||
def brightness(self) -> int:
|
||||
"""Return the brightness of this light between 0..255."""
|
||||
return 255
|
||||
def brightness(self) -> int | None:
|
||||
"""Return the brightness of this light between 0..255.
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool | None:
|
||||
"""Return true if device is on (brightness above 0)."""
|
||||
Z-Wave multilevel switches use a range of [0, 99] to control brightness.
|
||||
"""
|
||||
if self.info.primary_value.value is None:
|
||||
return None
|
||||
return any(value != 0 for value in self.info.primary_value.value.values())
|
||||
if self._target_brightness and self.info.primary_value.value is False:
|
||||
# Binary switch exists and is turned off
|
||||
return 0
|
||||
|
||||
# Brightness is encoded in the color channels by scaling them lower than 255
|
||||
color_values = [
|
||||
v.value
|
||||
for v in self._get_color_values()
|
||||
if v is not None and v.value is not None
|
||||
]
|
||||
return max(color_values) if color_values else 0
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the device on."""
|
||||
|
||||
if (
|
||||
kwargs.get(ATTR_RGBW_COLOR) is not None
|
||||
or kwargs.get(ATTR_COLOR_TEMP) is not None
|
||||
or kwargs.get(ATTR_HS_COLOR) is not None
|
||||
):
|
||||
# RGBW and color temp are not supported in this mode,
|
||||
# delegate to the parent class
|
||||
await super().async_turn_on(**kwargs)
|
||||
return
|
||||
|
||||
transition = kwargs.get(ATTR_TRANSITION)
|
||||
# turn on light to last color if known, otherwise set to white
|
||||
if self._last_color is not None:
|
||||
await self._async_set_colors(
|
||||
{
|
||||
ColorComponent.RED: self._last_color["red"],
|
||||
ColorComponent.GREEN: self._last_color["green"],
|
||||
ColorComponent.BLUE: self._last_color["blue"],
|
||||
},
|
||||
transition,
|
||||
)
|
||||
else:
|
||||
await self._async_set_colors(
|
||||
{
|
||||
brightness = kwargs.get(ATTR_BRIGHTNESS)
|
||||
hs_color = kwargs.get(ATTR_HS_COLOR)
|
||||
new_colors: dict[ColorComponent, int] | None = None
|
||||
scale: float | None = None
|
||||
|
||||
if brightness is None and hs_color is None:
|
||||
# Turned on without specifying brightness or color
|
||||
if self._last_on_color is not None:
|
||||
if self._target_brightness:
|
||||
# Color is already set, use the binary switch to turn on
|
||||
await self._async_set_brightness(None, transition)
|
||||
return
|
||||
|
||||
# Preserve the previous color
|
||||
new_colors = self._last_on_color
|
||||
elif self._supports_color:
|
||||
# Turned on for the first time. Make it white
|
||||
new_colors = {
|
||||
ColorComponent.RED: 255,
|
||||
ColorComponent.GREEN: 255,
|
||||
ColorComponent.BLUE: 255,
|
||||
},
|
||||
transition,
|
||||
}
|
||||
elif brightness is not None:
|
||||
# If brightness gets set, preserve the color and mix it with the new brightness
|
||||
if self.color_mode == ColorMode.HS:
|
||||
scale = brightness / 255
|
||||
if (
|
||||
self._last_on_color is not None
|
||||
and None not in self._last_on_color.values()
|
||||
):
|
||||
# Changed brightness from 0 to >0
|
||||
old_brightness = max(self._last_on_color.values())
|
||||
new_scale = brightness / old_brightness
|
||||
scale = new_scale
|
||||
new_colors = {}
|
||||
for color, value in self._last_on_color.items():
|
||||
new_colors[color] = round(value * new_scale)
|
||||
elif hs_color is None and self._color_mode == ColorMode.HS:
|
||||
hs_color = self._hs_color
|
||||
elif hs_color is not None and brightness is None:
|
||||
# Turned on by using the color controls
|
||||
current_brightness = self.brightness
|
||||
if current_brightness == 0 and self._last_brightness is not None:
|
||||
# Use the last brightness value if the light is currently off
|
||||
scale = self._last_brightness / 255
|
||||
elif current_brightness is not None:
|
||||
scale = current_brightness / 255
|
||||
|
||||
# Reset last color until turning off again
|
||||
self._last_on_color = None
|
||||
|
||||
if new_colors is None:
|
||||
new_colors = self._get_new_colors(
|
||||
hs_color=hs_color, color_temp=None, rgbw=None, brightness_scale=scale
|
||||
)
|
||||
|
||||
if new_colors is not None:
|
||||
await self._async_set_colors(new_colors, transition)
|
||||
|
||||
# Turn the binary switch on if there is one
|
||||
await self._async_set_brightness(brightness, transition)
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the light off."""
|
||||
self._last_color = self.info.primary_value.value
|
||||
await self._async_set_colors(
|
||||
{
|
||||
|
||||
# Remember last color and brightness to restore it when turning on
|
||||
self._last_brightness = self.brightness
|
||||
if self._current_color and isinstance(self._current_color.value, dict):
|
||||
red = self._current_color.value.get(COLOR_SWITCH_COMBINED_RED)
|
||||
green = self._current_color.value.get(COLOR_SWITCH_COMBINED_GREEN)
|
||||
blue = self._current_color.value.get(COLOR_SWITCH_COMBINED_BLUE)
|
||||
|
||||
last_color: dict[ColorComponent, int] = {}
|
||||
if red is not None:
|
||||
last_color[ColorComponent.RED] = red
|
||||
if green is not None:
|
||||
last_color[ColorComponent.GREEN] = green
|
||||
if blue is not None:
|
||||
last_color[ColorComponent.BLUE] = blue
|
||||
|
||||
if last_color:
|
||||
self._last_on_color = last_color
|
||||
|
||||
if self._target_brightness:
|
||||
# Turn off the binary switch only
|
||||
await self._async_set_brightness(0, kwargs.get(ATTR_TRANSITION))
|
||||
else:
|
||||
# turn off all color channels
|
||||
colors = {
|
||||
ColorComponent.RED: 0,
|
||||
ColorComponent.GREEN: 0,
|
||||
ColorComponent.BLUE: 0,
|
||||
},
|
||||
kwargs.get(ATTR_TRANSITION),
|
||||
)
|
||||
await self._async_set_brightness(0, kwargs.get(ATTR_TRANSITION))
|
||||
}
|
||||
|
||||
await self._async_set_colors(
|
||||
colors,
|
||||
kwargs.get(ATTR_TRANSITION),
|
||||
)
|
||||
|
|
|
@ -498,6 +498,15 @@ def siren_neo_coolcam_state_state_fixture() -> NodeDataType:
|
|||
)
|
||||
|
||||
|
||||
@pytest.fixture(name="aeotec_smart_switch_7_state")
|
||||
def aeotec_smart_switch_7_state_fixture() -> NodeDataType:
|
||||
"""Load node with fixture data for Aeotec Smart Switch 7."""
|
||||
return cast(
|
||||
NodeDataType,
|
||||
load_json_object_fixture("aeotec_smart_switch_7_state.json", DOMAIN),
|
||||
)
|
||||
|
||||
|
||||
# model fixtures
|
||||
|
||||
|
||||
|
@ -1212,3 +1221,13 @@ def siren_neo_coolcam_fixture(
|
|||
node = Node(client, siren_neo_coolcam_state)
|
||||
client.driver.controller.nodes[node.node_id] = node
|
||||
return node
|
||||
|
||||
|
||||
@pytest.fixture(name="aeotec_smart_switch_7")
|
||||
def aeotec_smart_switch_7_fixture(
|
||||
client: MagicMock, aeotec_smart_switch_7_state: NodeDataType
|
||||
) -> Node:
|
||||
"""Load node for Aeotec Smart Switch 7."""
|
||||
node = Node(client, aeotec_smart_switch_7_state)
|
||||
client.driver.controller.nodes[node.node_id] = node
|
||||
return node
|
||||
|
|
1863
tests/components/zwave_js/fixtures/aeotec_smart_switch_7_state.json
Normal file
1863
tests/components/zwave_js/fixtures/aeotec_smart_switch_7_state.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -6,6 +6,7 @@ from zwave_js_server.model.node import Node
|
|||
|
||||
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
|
||||
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS
|
||||
from homeassistant.components.light import ATTR_SUPPORTED_COLOR_MODES, ColorMode
|
||||
from homeassistant.components.number import (
|
||||
ATTR_VALUE,
|
||||
DOMAIN as NUMBER_DOMAIN,
|
||||
|
@ -426,3 +427,19 @@ async def test_rediscovery(
|
|||
assert state
|
||||
assert state.state == "Beep Beep"
|
||||
assert "Platform zwave_js does not generate unique IDs" not in caplog.text
|
||||
|
||||
|
||||
async def test_aeotec_smart_switch_7(
|
||||
hass: HomeAssistant,
|
||||
aeotec_smart_switch_7: Node,
|
||||
integration: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test that Smart Switch 7 has a light and a switch entity."""
|
||||
state = hass.states.get("light.smart_switch_7")
|
||||
assert state
|
||||
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == [
|
||||
ColorMode.HS,
|
||||
]
|
||||
|
||||
state = hass.states.get("switch.smart_switch_7")
|
||||
assert state
|
||||
|
|
|
@ -8,6 +8,7 @@ from homeassistant.components.light import (
|
|||
ATTR_BRIGHTNESS,
|
||||
ATTR_COLOR_MODE,
|
||||
ATTR_COLOR_TEMP,
|
||||
ATTR_HS_COLOR,
|
||||
ATTR_MAX_MIREDS,
|
||||
ATTR_MIN_MIREDS,
|
||||
ATTR_RGB_COLOR,
|
||||
|
@ -37,8 +38,8 @@ from .common import (
|
|||
ZEN_31_ENTITY,
|
||||
)
|
||||
|
||||
HSM200_V1_ENTITY = "light.hsm200"
|
||||
ZDB5100_ENTITY = "light.matrix_office"
|
||||
HSM200_V1_ENTITY = "light.hsm200"
|
||||
|
||||
|
||||
async def test_light(
|
||||
|
@ -510,14 +511,388 @@ async def test_light_none_color_value(
|
|||
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["hs"]
|
||||
|
||||
|
||||
async def test_black_is_off(
|
||||
async def test_light_on_off_color(
|
||||
hass: HomeAssistant, client, logic_group_zdb5100, integration
|
||||
) -> None:
|
||||
"""Test the light entity for RGB lights without dimming support."""
|
||||
node = logic_group_zdb5100
|
||||
state = hass.states.get(ZDB5100_ENTITY)
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
async def update_color(red: int, green: int, blue: int) -> None:
|
||||
event = Event(
|
||||
type="value updated",
|
||||
data={
|
||||
"source": "node",
|
||||
"event": "value updated",
|
||||
"nodeId": node.node_id,
|
||||
"args": {
|
||||
"commandClassName": "Color Switch",
|
||||
"commandClass": 51,
|
||||
"endpoint": 1,
|
||||
"property": "currentColor",
|
||||
"propertyKey": 2, # red
|
||||
"newValue": red,
|
||||
"prevValue": None,
|
||||
"propertyName": "currentColor",
|
||||
"propertyKeyName": "red",
|
||||
},
|
||||
},
|
||||
)
|
||||
node.receive_event(event)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
event = Event(
|
||||
type="value updated",
|
||||
data={
|
||||
"source": "node",
|
||||
"event": "value updated",
|
||||
"nodeId": node.node_id,
|
||||
"args": {
|
||||
"commandClassName": "Color Switch",
|
||||
"commandClass": 51,
|
||||
"endpoint": 1,
|
||||
"property": "currentColor",
|
||||
"propertyKey": 3, # green
|
||||
"newValue": green,
|
||||
"prevValue": None,
|
||||
"propertyName": "currentColor",
|
||||
"propertyKeyName": "green",
|
||||
},
|
||||
},
|
||||
)
|
||||
node.receive_event(event)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
event = Event(
|
||||
type="value updated",
|
||||
data={
|
||||
"source": "node",
|
||||
"event": "value updated",
|
||||
"nodeId": node.node_id,
|
||||
"args": {
|
||||
"commandClassName": "Color Switch",
|
||||
"commandClass": 51,
|
||||
"endpoint": 1,
|
||||
"property": "currentColor",
|
||||
"propertyKey": 4, # blue
|
||||
"newValue": blue,
|
||||
"prevValue": None,
|
||||
"propertyName": "currentColor",
|
||||
"propertyKeyName": "blue",
|
||||
},
|
||||
},
|
||||
)
|
||||
node.receive_event(event)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
event = Event(
|
||||
type="value updated",
|
||||
data={
|
||||
"source": "node",
|
||||
"event": "value updated",
|
||||
"nodeId": node.node_id,
|
||||
"args": {
|
||||
"commandClassName": "Color Switch",
|
||||
"commandClass": 51,
|
||||
"endpoint": 1,
|
||||
"property": "currentColor",
|
||||
"newValue": {
|
||||
"red": red,
|
||||
"green": green,
|
||||
"blue": blue,
|
||||
},
|
||||
"prevValue": None,
|
||||
"propertyName": "currentColor",
|
||||
},
|
||||
},
|
||||
)
|
||||
node.receive_event(event)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
async def update_switch_state(state: bool) -> None:
|
||||
event = Event(
|
||||
type="value updated",
|
||||
data={
|
||||
"source": "node",
|
||||
"event": "value updated",
|
||||
"nodeId": node.node_id,
|
||||
"args": {
|
||||
"commandClassName": "Binary Switch",
|
||||
"commandClass": 37,
|
||||
"endpoint": 1,
|
||||
"property": "currentValue",
|
||||
"newValue": state,
|
||||
"prevValue": None,
|
||||
"propertyName": "currentValue",
|
||||
},
|
||||
},
|
||||
)
|
||||
node.receive_event(event)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Turn on the light. Since this is the first call, the light should default to white
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: ZDB5100_ENTITY},
|
||||
blocking=True,
|
||||
)
|
||||
assert len(client.async_send_command.call_args_list) == 2
|
||||
args = client.async_send_command.call_args_list[0][0][0]
|
||||
assert args["command"] == "node.set_value"
|
||||
assert args["nodeId"] == node.node_id
|
||||
assert args["valueId"] == {
|
||||
"commandClass": 51,
|
||||
"endpoint": 1,
|
||||
"property": "targetColor",
|
||||
}
|
||||
assert args["value"] == {
|
||||
"red": 255,
|
||||
"green": 255,
|
||||
"blue": 255,
|
||||
}
|
||||
|
||||
args = client.async_send_command.call_args_list[1][0][0]
|
||||
assert args["command"] == "node.set_value"
|
||||
assert args["nodeId"] == node.node_id
|
||||
assert args["valueId"] == {
|
||||
"commandClass": 37,
|
||||
"endpoint": 1,
|
||||
"property": "targetValue",
|
||||
}
|
||||
assert args["value"] is True
|
||||
|
||||
# Force the light to turn off
|
||||
await update_switch_state(False)
|
||||
|
||||
state = hass.states.get(ZDB5100_ENTITY)
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
# Force the light to turn on (green)
|
||||
await update_color(0, 255, 0)
|
||||
await update_switch_state(True)
|
||||
|
||||
state = hass.states.get(ZDB5100_ENTITY)
|
||||
assert state.state == STATE_ON
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
# Set the brightness to 128. This should be encoded in the color value
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: ZDB5100_ENTITY, ATTR_BRIGHTNESS: 128},
|
||||
blocking=True,
|
||||
)
|
||||
assert len(client.async_send_command.call_args_list) == 2
|
||||
args = client.async_send_command.call_args_list[0][0][0]
|
||||
assert args["command"] == "node.set_value"
|
||||
assert args["nodeId"] == node.node_id
|
||||
assert args["valueId"] == {
|
||||
"commandClass": 51,
|
||||
"endpoint": 1,
|
||||
"property": "targetColor",
|
||||
}
|
||||
assert args["value"] == {
|
||||
"red": 0,
|
||||
"green": 128,
|
||||
"blue": 0,
|
||||
}
|
||||
|
||||
args = client.async_send_command.call_args_list[1][0][0]
|
||||
assert args["command"] == "node.set_value"
|
||||
assert args["nodeId"] == node.node_id
|
||||
assert args["valueId"] == {
|
||||
"commandClass": 37,
|
||||
"endpoint": 1,
|
||||
"property": "targetValue",
|
||||
}
|
||||
assert args["value"] is True
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
# Force the light to turn on (green, 50%)
|
||||
await update_color(0, 128, 0)
|
||||
|
||||
# Set the color to red. This should preserve the previous brightness value
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: ZDB5100_ENTITY, ATTR_HS_COLOR: (0, 100)},
|
||||
blocking=True,
|
||||
)
|
||||
assert len(client.async_send_command.call_args_list) == 2
|
||||
args = client.async_send_command.call_args_list[0][0][0]
|
||||
assert args["command"] == "node.set_value"
|
||||
assert args["nodeId"] == node.node_id
|
||||
assert args["valueId"] == {
|
||||
"commandClass": 51,
|
||||
"endpoint": 1,
|
||||
"property": "targetColor",
|
||||
}
|
||||
assert args["value"] == {
|
||||
"red": 128,
|
||||
"green": 0,
|
||||
"blue": 0,
|
||||
}
|
||||
|
||||
args = client.async_send_command.call_args_list[1][0][0]
|
||||
assert args["command"] == "node.set_value"
|
||||
assert args["nodeId"] == node.node_id
|
||||
assert args["valueId"] == {
|
||||
"commandClass": 37,
|
||||
"endpoint": 1,
|
||||
"property": "targetValue",
|
||||
}
|
||||
assert args["value"] is True
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
# Force the light to turn on (red, 50%)
|
||||
await update_color(128, 0, 0)
|
||||
|
||||
# Turn the device off. This should only affect the binary switch, not the color
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
{ATTR_ENTITY_ID: ZDB5100_ENTITY},
|
||||
blocking=True,
|
||||
)
|
||||
assert len(client.async_send_command.call_args_list) == 1
|
||||
args = client.async_send_command.call_args_list[0][0][0]
|
||||
assert args["command"] == "node.set_value"
|
||||
assert args["nodeId"] == node.node_id
|
||||
assert args["valueId"] == {
|
||||
"commandClass": 37,
|
||||
"endpoint": 1,
|
||||
"property": "targetValue",
|
||||
}
|
||||
assert args["value"] is False
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
# Force the light to turn off
|
||||
await update_switch_state(False)
|
||||
|
||||
# Turn the device on again. This should only affect the binary switch, not the color
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: ZDB5100_ENTITY},
|
||||
blocking=True,
|
||||
)
|
||||
assert len(client.async_send_command.call_args_list) == 1
|
||||
args = client.async_send_command.call_args_list[0][0][0]
|
||||
assert args["command"] == "node.set_value"
|
||||
assert args["nodeId"] == node.node_id
|
||||
assert args["valueId"] == {
|
||||
"commandClass": 37,
|
||||
"endpoint": 1,
|
||||
"property": "targetValue",
|
||||
}
|
||||
assert args["value"] is True
|
||||
|
||||
|
||||
async def test_light_color_only(
|
||||
hass: HomeAssistant, client, express_controls_ezmultipli, integration
|
||||
) -> None:
|
||||
"""Test the black is off light entity."""
|
||||
"""Test the light entity for RGB lights with Color Switch CC only."""
|
||||
node = express_controls_ezmultipli
|
||||
state = hass.states.get(HSM200_V1_ENTITY)
|
||||
assert state.state == STATE_ON
|
||||
|
||||
async def update_color(red: int, green: int, blue: int) -> None:
|
||||
event = Event(
|
||||
type="value updated",
|
||||
data={
|
||||
"source": "node",
|
||||
"event": "value updated",
|
||||
"nodeId": node.node_id,
|
||||
"args": {
|
||||
"commandClassName": "Color Switch",
|
||||
"commandClass": 51,
|
||||
"endpoint": 0,
|
||||
"property": "currentColor",
|
||||
"propertyKey": 2, # red
|
||||
"newValue": red,
|
||||
"prevValue": None,
|
||||
"propertyName": "currentColor",
|
||||
"propertyKeyName": "red",
|
||||
},
|
||||
},
|
||||
)
|
||||
node.receive_event(event)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
event = Event(
|
||||
type="value updated",
|
||||
data={
|
||||
"source": "node",
|
||||
"event": "value updated",
|
||||
"nodeId": node.node_id,
|
||||
"args": {
|
||||
"commandClassName": "Color Switch",
|
||||
"commandClass": 51,
|
||||
"endpoint": 0,
|
||||
"property": "currentColor",
|
||||
"propertyKey": 3, # green
|
||||
"newValue": green,
|
||||
"prevValue": None,
|
||||
"propertyName": "currentColor",
|
||||
"propertyKeyName": "green",
|
||||
},
|
||||
},
|
||||
)
|
||||
node.receive_event(event)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
event = Event(
|
||||
type="value updated",
|
||||
data={
|
||||
"source": "node",
|
||||
"event": "value updated",
|
||||
"nodeId": node.node_id,
|
||||
"args": {
|
||||
"commandClassName": "Color Switch",
|
||||
"commandClass": 51,
|
||||
"endpoint": 0,
|
||||
"property": "currentColor",
|
||||
"propertyKey": 4, # blue
|
||||
"newValue": blue,
|
||||
"prevValue": None,
|
||||
"propertyName": "currentColor",
|
||||
"propertyKeyName": "blue",
|
||||
},
|
||||
},
|
||||
)
|
||||
node.receive_event(event)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
event = Event(
|
||||
type="value updated",
|
||||
data={
|
||||
"source": "node",
|
||||
"event": "value updated",
|
||||
"nodeId": node.node_id,
|
||||
"args": {
|
||||
"commandClassName": "Color Switch",
|
||||
"commandClass": 51,
|
||||
"endpoint": 0,
|
||||
"property": "currentColor",
|
||||
"newValue": {
|
||||
"red": red,
|
||||
"green": green,
|
||||
"blue": blue,
|
||||
},
|
||||
"prevValue": None,
|
||||
"propertyName": "currentColor",
|
||||
},
|
||||
},
|
||||
)
|
||||
node.receive_event(event)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Attempt to turn on the light and ensure it defaults to white
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
|
@ -539,64 +914,14 @@ async def test_black_is_off(
|
|||
client.async_send_command.reset_mock()
|
||||
|
||||
# Force the light to turn off
|
||||
event = Event(
|
||||
type="value updated",
|
||||
data={
|
||||
"source": "node",
|
||||
"event": "value updated",
|
||||
"nodeId": node.node_id,
|
||||
"args": {
|
||||
"commandClassName": "Color Switch",
|
||||
"commandClass": 51,
|
||||
"endpoint": 0,
|
||||
"property": "currentColor",
|
||||
"newValue": {
|
||||
"red": 0,
|
||||
"green": 0,
|
||||
"blue": 0,
|
||||
},
|
||||
"prevValue": {
|
||||
"red": 0,
|
||||
"green": 255,
|
||||
"blue": 0,
|
||||
},
|
||||
"propertyName": "currentColor",
|
||||
},
|
||||
},
|
||||
)
|
||||
node.receive_event(event)
|
||||
await hass.async_block_till_done()
|
||||
await update_color(0, 0, 0)
|
||||
|
||||
state = hass.states.get(HSM200_V1_ENTITY)
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
# Force the light to turn on
|
||||
event = Event(
|
||||
type="value updated",
|
||||
data={
|
||||
"source": "node",
|
||||
"event": "value updated",
|
||||
"nodeId": node.node_id,
|
||||
"args": {
|
||||
"commandClassName": "Color Switch",
|
||||
"commandClass": 51,
|
||||
"endpoint": 0,
|
||||
"property": "currentColor",
|
||||
"newValue": {
|
||||
"red": 0,
|
||||
"green": 255,
|
||||
"blue": 0,
|
||||
},
|
||||
"prevValue": {
|
||||
"red": 0,
|
||||
"green": 0,
|
||||
"blue": 0,
|
||||
},
|
||||
"propertyName": "currentColor",
|
||||
},
|
||||
},
|
||||
)
|
||||
node.receive_event(event)
|
||||
await hass.async_block_till_done()
|
||||
# Force the light to turn on (50% green)
|
||||
await update_color(0, 128, 0)
|
||||
|
||||
state = hass.states.get(HSM200_V1_ENTITY)
|
||||
assert state.state == STATE_ON
|
||||
|
||||
|
@ -619,6 +944,9 @@ async def test_black_is_off(
|
|||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
# Force the light to turn off
|
||||
await update_color(0, 0, 0)
|
||||
|
||||
# Assert that the last color is restored
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
|
@ -635,11 +963,131 @@ async def test_black_is_off(
|
|||
"endpoint": 0,
|
||||
"property": "targetColor",
|
||||
}
|
||||
assert args["value"] == {"red": 0, "green": 255, "blue": 0}
|
||||
assert args["value"] == {"red": 0, "green": 128, "blue": 0}
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
# Force the light to turn on
|
||||
# Force the light to turn on (50% green)
|
||||
await update_color(0, 128, 0)
|
||||
|
||||
state = hass.states.get(HSM200_V1_ENTITY)
|
||||
assert state.state == STATE_ON
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
# Assert that the brightness is preserved when changing colors
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: HSM200_V1_ENTITY, ATTR_RGB_COLOR: (255, 0, 0)},
|
||||
blocking=True,
|
||||
)
|
||||
assert len(client.async_send_command.call_args_list) == 1
|
||||
args = client.async_send_command.call_args_list[0][0][0]
|
||||
assert args["command"] == "node.set_value"
|
||||
assert args["nodeId"] == node.node_id
|
||||
assert args["valueId"] == {
|
||||
"commandClass": 51,
|
||||
"endpoint": 0,
|
||||
"property": "targetColor",
|
||||
}
|
||||
assert args["value"] == {"red": 128, "green": 0, "blue": 0}
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
# Force the light to turn on (50% red)
|
||||
await update_color(128, 0, 0)
|
||||
|
||||
state = hass.states.get(HSM200_V1_ENTITY)
|
||||
assert state.state == STATE_ON
|
||||
|
||||
# Assert that the color is preserved when changing brightness
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: HSM200_V1_ENTITY, ATTR_BRIGHTNESS: 69},
|
||||
blocking=True,
|
||||
)
|
||||
assert len(client.async_send_command.call_args_list) == 1
|
||||
args = client.async_send_command.call_args_list[0][0][0]
|
||||
assert args["command"] == "node.set_value"
|
||||
assert args["nodeId"] == node.node_id
|
||||
assert args["valueId"] == {
|
||||
"commandClass": 51,
|
||||
"endpoint": 0,
|
||||
"property": "targetColor",
|
||||
}
|
||||
assert args["value"] == {"red": 69, "green": 0, "blue": 0}
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
await update_color(69, 0, 0)
|
||||
|
||||
# Turn off again
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
{ATTR_ENTITY_ID: HSM200_V1_ENTITY},
|
||||
blocking=True,
|
||||
)
|
||||
await update_color(0, 0, 0)
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
# Assert that the color is preserved when turning on with brightness
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: HSM200_V1_ENTITY, ATTR_BRIGHTNESS: 123},
|
||||
blocking=True,
|
||||
)
|
||||
assert len(client.async_send_command.call_args_list) == 1
|
||||
args = client.async_send_command.call_args_list[0][0][0]
|
||||
assert args["command"] == "node.set_value"
|
||||
assert args["nodeId"] == node.node_id
|
||||
assert args["valueId"] == {
|
||||
"commandClass": 51,
|
||||
"endpoint": 0,
|
||||
"property": "targetColor",
|
||||
}
|
||||
assert args["value"] == {"red": 123, "green": 0, "blue": 0}
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
await update_color(123, 0, 0)
|
||||
|
||||
# Turn off again
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
{ATTR_ENTITY_ID: HSM200_V1_ENTITY},
|
||||
blocking=True,
|
||||
)
|
||||
await update_color(0, 0, 0)
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
# Assert that the brightness is preserved when turning on with color
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: HSM200_V1_ENTITY, ATTR_HS_COLOR: (240, 100)},
|
||||
blocking=True,
|
||||
)
|
||||
assert len(client.async_send_command.call_args_list) == 1
|
||||
args = client.async_send_command.call_args_list[0][0][0]
|
||||
assert args["command"] == "node.set_value"
|
||||
assert args["nodeId"] == node.node_id
|
||||
assert args["valueId"] == {
|
||||
"commandClass": 51,
|
||||
"endpoint": 0,
|
||||
"property": "targetColor",
|
||||
}
|
||||
assert args["value"] == {"red": 0, "green": 0, "blue": 123}
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
# Clear the color value to trigger an unknown state
|
||||
event = Event(
|
||||
type="value updated",
|
||||
data={
|
||||
|
@ -652,17 +1100,14 @@ async def test_black_is_off(
|
|||
"endpoint": 0,
|
||||
"property": "currentColor",
|
||||
"newValue": None,
|
||||
"prevValue": {
|
||||
"red": 0,
|
||||
"green": 255,
|
||||
"blue": 0,
|
||||
},
|
||||
"prevValue": None,
|
||||
"propertyName": "currentColor",
|
||||
},
|
||||
},
|
||||
)
|
||||
node.receive_event(event)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(HSM200_V1_ENTITY)
|
||||
assert state.state == STATE_UNKNOWN
|
||||
|
||||
|
@ -687,183 +1132,6 @@ async def test_black_is_off(
|
|||
assert args["value"] == {"red": 255, "green": 76, "blue": 255}
|
||||
|
||||
|
||||
async def test_black_is_off_zdb5100(
|
||||
hass: HomeAssistant, client, logic_group_zdb5100, integration
|
||||
) -> None:
|
||||
"""Test the black is off light entity."""
|
||||
node = logic_group_zdb5100
|
||||
state = hass.states.get(ZDB5100_ENTITY)
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
# Attempt to turn on the light and ensure it defaults to white
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: ZDB5100_ENTITY},
|
||||
blocking=True,
|
||||
)
|
||||
assert len(client.async_send_command.call_args_list) == 1
|
||||
args = client.async_send_command.call_args_list[0][0][0]
|
||||
assert args["command"] == "node.set_value"
|
||||
assert args["nodeId"] == node.node_id
|
||||
assert args["valueId"] == {
|
||||
"commandClass": 51,
|
||||
"endpoint": 1,
|
||||
"property": "targetColor",
|
||||
}
|
||||
assert args["value"] == {"red": 255, "green": 255, "blue": 255}
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
# Force the light to turn off
|
||||
event = Event(
|
||||
type="value updated",
|
||||
data={
|
||||
"source": "node",
|
||||
"event": "value updated",
|
||||
"nodeId": node.node_id,
|
||||
"args": {
|
||||
"commandClassName": "Color Switch",
|
||||
"commandClass": 51,
|
||||
"endpoint": 1,
|
||||
"property": "currentColor",
|
||||
"newValue": {
|
||||
"red": 0,
|
||||
"green": 0,
|
||||
"blue": 0,
|
||||
},
|
||||
"prevValue": {
|
||||
"red": 0,
|
||||
"green": 255,
|
||||
"blue": 0,
|
||||
},
|
||||
"propertyName": "currentColor",
|
||||
},
|
||||
},
|
||||
)
|
||||
node.receive_event(event)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get(ZDB5100_ENTITY)
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
# Force the light to turn on
|
||||
event = Event(
|
||||
type="value updated",
|
||||
data={
|
||||
"source": "node",
|
||||
"event": "value updated",
|
||||
"nodeId": node.node_id,
|
||||
"args": {
|
||||
"commandClassName": "Color Switch",
|
||||
"commandClass": 51,
|
||||
"endpoint": 1,
|
||||
"property": "currentColor",
|
||||
"newValue": {
|
||||
"red": 0,
|
||||
"green": 255,
|
||||
"blue": 0,
|
||||
},
|
||||
"prevValue": {
|
||||
"red": 0,
|
||||
"green": 0,
|
||||
"blue": 0,
|
||||
},
|
||||
"propertyName": "currentColor",
|
||||
},
|
||||
},
|
||||
)
|
||||
node.receive_event(event)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get(ZDB5100_ENTITY)
|
||||
assert state.state == STATE_ON
|
||||
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
{ATTR_ENTITY_ID: ZDB5100_ENTITY},
|
||||
blocking=True,
|
||||
)
|
||||
assert len(client.async_send_command.call_args_list) == 1
|
||||
args = client.async_send_command.call_args_list[0][0][0]
|
||||
assert args["command"] == "node.set_value"
|
||||
assert args["nodeId"] == node.node_id
|
||||
assert args["valueId"] == {
|
||||
"commandClass": 51,
|
||||
"endpoint": 1,
|
||||
"property": "targetColor",
|
||||
}
|
||||
assert args["value"] == {"red": 0, "green": 0, "blue": 0}
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
# Assert that the last color is restored
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: ZDB5100_ENTITY},
|
||||
blocking=True,
|
||||
)
|
||||
assert len(client.async_send_command.call_args_list) == 1
|
||||
args = client.async_send_command.call_args_list[0][0][0]
|
||||
assert args["command"] == "node.set_value"
|
||||
assert args["nodeId"] == node.node_id
|
||||
assert args["valueId"] == {
|
||||
"commandClass": 51,
|
||||
"endpoint": 1,
|
||||
"property": "targetColor",
|
||||
}
|
||||
assert args["value"] == {"red": 0, "green": 255, "blue": 0}
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
# Force the light to turn on
|
||||
event = Event(
|
||||
type="value updated",
|
||||
data={
|
||||
"source": "node",
|
||||
"event": "value updated",
|
||||
"nodeId": node.node_id,
|
||||
"args": {
|
||||
"commandClassName": "Color Switch",
|
||||
"commandClass": 51,
|
||||
"endpoint": 1,
|
||||
"property": "currentColor",
|
||||
"newValue": None,
|
||||
"prevValue": {
|
||||
"red": 0,
|
||||
"green": 255,
|
||||
"blue": 0,
|
||||
},
|
||||
"propertyName": "currentColor",
|
||||
},
|
||||
},
|
||||
)
|
||||
node.receive_event(event)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get(ZDB5100_ENTITY)
|
||||
assert state.state == STATE_UNKNOWN
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
# Assert that call fails if attribute is added to service call
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: ZDB5100_ENTITY, ATTR_RGBW_COLOR: (255, 76, 255, 0)},
|
||||
blocking=True,
|
||||
)
|
||||
assert len(client.async_send_command.call_args_list) == 1
|
||||
args = client.async_send_command.call_args_list[0][0][0]
|
||||
assert args["command"] == "node.set_value"
|
||||
assert args["nodeId"] == node.node_id
|
||||
assert args["valueId"] == {
|
||||
"commandClass": 51,
|
||||
"endpoint": 1,
|
||||
"property": "targetColor",
|
||||
}
|
||||
assert args["value"] == {"red": 255, "green": 76, "blue": 255}
|
||||
|
||||
|
||||
async def test_basic_cc_light(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
|
|
Loading…
Add table
Reference in a new issue