From 7d8ea404b334aed01ddbc2147b650fb2d08b5b4f Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 13 Oct 2023 11:11:44 -0400 Subject: [PATCH] Make Basic CC Z-Wave values a light (#101438) --- .../components/zwave_js/discovery.py | 38 +++-- homeassistant/components/zwave_js/light.py | 14 +- tests/components/zwave_js/common.py | 2 +- tests/components/zwave_js/test_light.py | 143 ++++++++++++++++++ tests/components/zwave_js/test_number.py | 14 -- 5 files changed, 175 insertions(+), 36 deletions(-) diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index 0a3f61fd824..46975631523 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -853,26 +853,6 @@ DISCOVERY_SCHEMAS = [ allow_multi=True, entity_registry_enabled_default=False, ), - # number for Basic CC - ZWaveDiscoverySchema( - platform=Platform.NUMBER, - hint="Basic", - primary_value=ZWaveValueDiscoverySchema( - command_class={CommandClass.BASIC}, - type={ValueType.NUMBER}, - property={CURRENT_VALUE_PROPERTY}, - ), - required_values=[ - ZWaveValueDiscoverySchema( - command_class={ - CommandClass.BASIC, - }, - type={ValueType.NUMBER}, - property={TARGET_VALUE_PROPERTY}, - ) - ], - entity_registry_enabled_default=False, - ), # number for Indicator CC (exclude property keys 3-5) ZWaveDiscoverySchema( platform=Platform.NUMBER, @@ -997,6 +977,24 @@ DISCOVERY_SCHEMAS = [ platform=Platform.LIGHT, primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA, ), + # light for Basic CC + ZWaveDiscoverySchema( + platform=Platform.LIGHT, + primary_value=ZWaveValueDiscoverySchema( + command_class={CommandClass.BASIC}, + type={ValueType.NUMBER}, + property={CURRENT_VALUE_PROPERTY}, + ), + required_values=[ + ZWaveValueDiscoverySchema( + command_class={ + CommandClass.BASIC, + }, + type={ValueType.NUMBER}, + property={TARGET_VALUE_PROPERTY}, + ) + ], + ), # sirens ZWaveDiscoverySchema( platform=Platform.SIREN, diff --git a/homeassistant/components/zwave_js/light.py b/homeassistant/components/zwave_js/light.py index 1a9abb9b0f8..8ba50c15e02 100644 --- a/homeassistant/components/zwave_js/light.py +++ b/homeassistant/components/zwave_js/light.py @@ -129,11 +129,22 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): self._supported_color_modes: set[ColorMode] = set() # 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: + self._attr_name = self.generate_name( + include_value_name=True, alternate_value_name="Basic" + ) + self._target_brightness = self.get_zwave_value( + TARGET_VALUE_PROPERTY, + CommandClass.BASIC, + add_to_watched_value_ids=False, + ) self._target_color = self.get_zwave_value( TARGET_COLOR_PROPERTY, CommandClass.SWITCH_COLOR, @@ -356,7 +367,8 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): # typically delayed and causes a confusing UX. if ( zwave_brightness == SET_TO_PREVIOUS_VALUE - and self.info.primary_value.command_class == CommandClass.SWITCH_MULTILEVEL + and self.info.primary_value.command_class + in (CommandClass.BASIC, CommandClass.SWITCH_MULTILEVEL) ): self._set_optimistic_state = True self.async_write_ha_state() diff --git a/tests/components/zwave_js/common.py b/tests/components/zwave_js/common.py index 606dda30b24..f4d7ea0a754 100644 --- a/tests/components/zwave_js/common.py +++ b/tests/components/zwave_js/common.py @@ -26,7 +26,7 @@ DISABLED_LEGACY_BINARY_SENSOR = "binary_sensor.multisensor_6_any" NOTIFICATION_MOTION_BINARY_SENSOR = "binary_sensor.multisensor_6_motion_detection" NOTIFICATION_MOTION_SENSOR = "sensor.multisensor_6_home_security_motion_sensor_status" INDICATOR_SENSOR = "sensor.z_wave_thermostat_indicator_value" -BASIC_NUMBER_ENTITY = "number.livingroomlight_basic" +BASIC_LIGHT_ENTITY = "light.livingroomlight_basic" PROPERTY_DOOR_STATUS_BINARY_SENSOR = ( "binary_sensor.august_smart_lock_pro_3rd_gen_the_current_status_of_the_door" ) diff --git a/tests/components/zwave_js/test_light.py b/tests/components/zwave_js/test_light.py index 4b0345b00ea..dff9790634e 100644 --- a/tests/components/zwave_js/test_light.py +++ b/tests/components/zwave_js/test_light.py @@ -26,9 +26,11 @@ from homeassistant.const import ( STATE_UNKNOWN, ) from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from .common import ( AEON_SMART_SWITCH_LIGHT_ENTITY, + BASIC_LIGHT_ENTITY, BULB_6_MULTI_COLOR_LIGHT_ENTITY, EATON_RF9640_ENTITY, ZEN_31_ENTITY, @@ -859,3 +861,144 @@ async def test_black_is_off_zdb5100( "property": "targetColor", } assert args["value"] == {"red": 255, "green": 76, "blue": 255} + + +async def test_basic_cc_light( + hass: HomeAssistant, client, ge_in_wall_dimmer_switch, integration +) -> None: + """Test light is created from Basic CC.""" + node = ge_in_wall_dimmer_switch + + ent_reg = er.async_get(hass) + entity_entry = ent_reg.async_get(BASIC_LIGHT_ENTITY) + + assert entity_entry + assert not entity_entry.disabled + + state = hass.states.get(BASIC_LIGHT_ENTITY) + assert state + assert state.state == STATE_UNKNOWN + assert state.attributes["supported_features"] == 0 + + # Send value to 0 + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 2, + "args": { + "commandClassName": "Basic", + "commandClass": 32, + "endpoint": 0, + "property": "currentValue", + "newValue": 0, + "prevValue": None, + "propertyName": "currentValue", + }, + }, + ) + node.receive_event(event) + + state = hass.states.get(BASIC_LIGHT_ENTITY) + assert state + assert state.state == STATE_OFF + + # Turn on light + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": BASIC_LIGHT_ENTITY}, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 2 + assert args["valueId"] == { + "commandClass": 32, + "endpoint": 0, + "property": "targetValue", + } + assert args["value"] == 255 + + # Due to optimistic updates, the state should be on even though the Z-Wave state + # hasn't been updated yet + state = hass.states.get(BASIC_LIGHT_ENTITY) + + assert state + assert state.state == STATE_ON + + client.async_send_command.reset_mock() + + # Send value to 0 + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 2, + "args": { + "commandClassName": "Basic", + "commandClass": 32, + "endpoint": 0, + "property": "currentValue", + "newValue": 0, + "prevValue": None, + "propertyName": "currentValue", + }, + }, + ) + node.receive_event(event) + + state = hass.states.get(BASIC_LIGHT_ENTITY) + assert state + assert state.state == STATE_OFF + + # Turn on light with brightness + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": BASIC_LIGHT_ENTITY, ATTR_BRIGHTNESS: 128}, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 2 + assert args["valueId"] == { + "commandClass": 32, + "endpoint": 0, + "property": "targetValue", + } + assert args["value"] == 50 + + # Since we specified a brightness, there is no optimistic update so the state + # should be off + state = hass.states.get(BASIC_LIGHT_ENTITY) + + assert state + assert state.state == STATE_OFF + + client.async_send_command.reset_mock() + + # Turn off light + await hass.services.async_call( + "light", + "turn_off", + {"entity_id": BASIC_LIGHT_ENTITY}, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] + assert args["command"] == "node.set_value" + assert args["nodeId"] == 2 + assert args["valueId"] == { + "commandClass": 32, + "endpoint": 0, + "property": "targetValue", + } + assert args["value"] == 0 diff --git a/tests/components/zwave_js/test_number.py b/tests/components/zwave_js/test_number.py index 7a3ffbda589..b05d9e46f73 100644 --- a/tests/components/zwave_js/test_number.py +++ b/tests/components/zwave_js/test_number.py @@ -9,8 +9,6 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er -from .common import BASIC_NUMBER_ENTITY - from tests.common import MockConfigEntry NUMBER_ENTITY = "number.thermostat_hvac_valve_control" @@ -219,18 +217,6 @@ async def test_volume_number( assert state.state == STATE_UNKNOWN -async def test_disabled_basic_number( - hass: HomeAssistant, ge_in_wall_dimmer_switch, integration -) -> None: - """Test number is created from Basic CC and is disabled.""" - ent_reg = er.async_get(hass) - entity_entry = ent_reg.async_get(BASIC_NUMBER_ENTITY) - - assert entity_entry - assert entity_entry.disabled - assert entity_entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION - - async def test_config_parameter_number( hass: HomeAssistant, climate_adc_t3000, integration ) -> None: