Revert "Support Z-Wave JS dimming lights using color intensity (#122639)" (#127256)

This reverts commit c7cfd56b720be8212af2686ecfa5b8cad6ee299b.
This commit is contained in:
Martin Hjelmare 2024-10-02 06:48:47 +02:00 committed by GitHub
parent 40dbfab671
commit e3e68dad36
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 352 additions and 736 deletions

View File

@ -238,12 +238,6 @@ 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},
@ -768,6 +762,33 @@ 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
@ -969,11 +990,10 @@ DISCOVERY_SCHEMAS = [
),
entity_category=EntityCategory.CONFIG,
),
# binary switches without color support
# binary switches
ZWaveDiscoverySchema(
platform=Platform.SWITCH,
primary_value=SWITCH_BINARY_CURRENT_VALUE_SCHEMA,
absent_values=[COLOR_SWITCH_CURRENT_VALUE_SCHEMA],
),
# switch for Indicator CC
ZWaveDiscoverySchema(
@ -1067,25 +1087,6 @@ DISCOVERY_SCHEMAS = [
# 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
#
# 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,
],
),
# Colored light that can be turned on or off with the Binary Switch CC.
ZWaveDiscoverySchema(
platform=Platform.LIGHT,
hint="color_onoff",
primary_value=SWITCH_BINARY_CURRENT_VALUE_SCHEMA,
required_values=[COLOR_SWITCH_CURRENT_VALUE_SCHEMA],
),
# Dimmable light with or without color support.
ZWaveDiscoverySchema(
platform=Platform.LIGHT,
primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA,

View File

@ -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 == "color_onoff":
async_add_entities([ZwaveColorOnOffLight(config_entry, driver, info)])
if info.platform_hint == "black_is_off":
async_add_entities([ZwaveBlackIsOffLight(config_entry, driver, info)])
else:
async_add_entities([ZwaveLight(config_entry, driver, info)])
@ -111,10 +111,9 @@ 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
@ -130,28 +129,15 @@ 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 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
# 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:
self._attr_name = self.generate_name(
include_value_name=True, alternate_value_name="Basic"
)
@ -160,13 +146,6 @@ 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,
@ -237,7 +216,7 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity):
@property
def rgbw_color(self) -> tuple[int, int, int, int] | None:
"""Return the RGBW color."""
"""Return the hs color."""
return self._rgbw_color
@property
@ -264,39 +243,11 @@ 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,
@ -306,9 +257,10 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity):
# turn of white leds when setting rgb
colors[ColorComponent.WARM_WHITE] = 0
colors[ColorComponent.COLD_WHITE] = 0
return colors
await self._async_set_colors(colors, transition)
# 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(
@ -323,18 +275,20 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity):
),
)
warm = 255 - cold
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
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,
)
# RGBW
rgbw = kwargs.get(ATTR_RGBW_COLOR)
if rgbw is not None and self._supports_rgbw:
rgbw_channels = {
ColorComponent.RED: rgbw[0],
@ -346,15 +300,17 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity):
if self._cold_white:
rgbw_channels[ColorComponent.COLD_WHITE] = rgbw[3]
await self._async_set_colors(rgbw_channels, transition)
return rgbw_channels
# set brightness
await self._async_set_brightness(kwargs.get(ATTR_BRIGHTNESS), transition)
return None
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the light off."""
await self._async_set_brightness(0, kwargs.get(ATTR_TRANSITION))
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
@ -405,14 +361,9 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity):
zwave_transition = {TRANSITION_DURATION_OPTION: "default"}
# setting a value requires setting targetValue
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
)
await self._async_set_value(
self._target_brightness, zwave_brightness, 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.
@ -476,8 +427,15 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity):
"""Calculate light colors."""
(red_val, green_val, blue_val, ww_val, cw_val) = self._get_color_values()
if self._current_color and isinstance(self._current_color.value, dict):
multi_color = self._current_color.value
# 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
else:
multi_color = {}
@ -528,10 +486,11 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity):
self._color_mode = ColorMode.RGBW
class ZwaveColorOnOffLight(ZwaveLight):
"""Representation of a colored Z-Wave light with an optional binary switch to turn on/off.
class ZwaveBlackIsOffLight(ZwaveLight):
"""Representation of a Z-Wave light where setting the color to black turns it off.
Dimming for RGB lights is realized by scaling the color channels.
Currently only supports lights with RGB, no color temperature, and no white
channels.
"""
def __init__(
@ -540,137 +499,61 @@ class ZwaveColorOnOffLight(ZwaveLight):
"""Initialize the light."""
super().__init__(config_entry, driver, info)
self._last_on_color: dict[ColorComponent, int] | None = None
self._last_brightness: int | None = None
self._last_color: dict[str, int] | None = None
self._supported_color_modes.discard(ColorMode.BRIGHTNESS)
@property
def brightness(self) -> int | None:
"""Return the brightness of this light between 0..255.
def brightness(self) -> int:
"""Return the brightness of this light between 0..255."""
return 255
Z-Wave multilevel switches use a range of [0, 99] to control brightness.
"""
@property
def is_on(self) -> bool | None:
"""Return true if device is on (brightness above 0)."""
if self.info.primary_value.value is None:
return None
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
return any(value != 0 for value in self.info.primary_value.value.values())
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)
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 = {
# 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(
{
ColorComponent.RED: 255,
ColorComponent.GREEN: 255,
ColorComponent.BLUE: 255,
}
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
},
transition,
)
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."""
# 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 = {
self._last_color = self.info.primary_value.value
await self._async_set_colors(
{
ColorComponent.RED: 0,
ColorComponent.GREEN: 0,
ColorComponent.BLUE: 0,
}
await self._async_set_colors(
colors,
kwargs.get(ATTR_TRANSITION),
)
},
kwargs.get(ATTR_TRANSITION),
)
await self._async_set_brightness(0, kwargs.get(ATTR_TRANSITION))

View File

@ -8,7 +8,6 @@ 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,
@ -38,8 +37,8 @@ from .common import (
ZEN_31_ENTITY,
)
ZDB5100_ENTITY = "light.matrix_office"
HSM200_V1_ENTITY = "light.hsm200"
ZDB5100_ENTITY = "light.matrix_office"
async def test_light(
@ -511,388 +510,14 @@ async def test_light_none_color_value(
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["hs"]
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(
async def test_black_is_off(
hass: HomeAssistant, client, express_controls_ezmultipli, integration
) -> None:
"""Test the light entity for RGB lights with Color Switch CC only."""
"""Test the black is off light entity."""
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,
@ -914,14 +539,64 @@ async def test_light_color_only(
client.async_send_command.reset_mock()
# Force the light to turn off
await update_color(0, 0, 0)
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()
state = hass.states.get(HSM200_V1_ENTITY)
assert state.state == STATE_OFF
# Force the light to turn on (50% green)
await update_color(0, 128, 0)
# 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()
state = hass.states.get(HSM200_V1_ENTITY)
assert state.state == STATE_ON
@ -944,9 +619,6 @@ async def test_light_color_only(
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,
@ -963,131 +635,11 @@ async def test_light_color_only(
"endpoint": 0,
"property": "targetColor",
}
assert args["value"] == {"red": 0, "green": 128, "blue": 0}
assert args["value"] == {"red": 0, "green": 255, "blue": 0}
client.async_send_command.reset_mock()
# 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
# Force the light to turn on
event = Event(
type="value updated",
data={
@ -1100,14 +652,17 @@ async def test_light_color_only(
"endpoint": 0,
"property": "currentColor",
"newValue": None,
"prevValue": None,
"prevValue": {
"red": 0,
"green": 255,
"blue": 0,
},
"propertyName": "currentColor",
},
},
)
node.receive_event(event)
await hass.async_block_till_done()
state = hass.states.get(HSM200_V1_ENTITY)
assert state.state == STATE_UNKNOWN
@ -1132,6 +687,183 @@ async def test_light_color_only(
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,