From 18eeeaaf68cbc2a1080476e9c2eca20e52fe893b Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 22 May 2023 12:08:13 -0400 Subject: [PATCH] Refactor zwave_js.fan and add tests (#93256) * Refactor zwave_js.fan and add tests * fix const --- .../components/zwave_js/discovery.py | 8 +++ homeassistant/components/zwave_js/fan.py | 37 +++------- tests/components/zwave_js/test_fan.py | 68 +++++++++++++++++++ 3 files changed, 86 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index bea2836fead..79a37c8d9fa 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -240,6 +240,12 @@ SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA = ZWaveValueDiscoverySchema( type={ValueType.NUMBER}, ) +SWITCH_MULTILEVEL_TARGET_VALUE_SCHEMA = ZWaveValueDiscoverySchema( + command_class={CommandClass.SWITCH_MULTILEVEL}, + property={TARGET_VALUE_PROPERTY}, + type={ValueType.NUMBER}, +) + SWITCH_BINARY_CURRENT_VALUE_SCHEMA = ZWaveValueDiscoverySchema( command_class={CommandClass.SWITCH_BINARY}, property={CURRENT_VALUE_PROPERTY} ) @@ -261,6 +267,7 @@ DISCOVERY_SCHEMAS = [ product_id={0x3131}, product_type={0x4944}, primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA, + required_values=[SWITCH_MULTILEVEL_TARGET_VALUE_SCHEMA], ), # GE/Jasco - In-Wall Smart Fan Control - 12730 / ZW4002 ZWaveDiscoverySchema( @@ -842,6 +849,7 @@ DISCOVERY_SCHEMAS = [ device_class_generic={"Multilevel Switch"}, device_class_specific={"Fan Switch"}, primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA, + required_values=[SWITCH_MULTILEVEL_TARGET_VALUE_SCHEMA], ), # number platform # valve control for thermostats diff --git a/homeassistant/components/zwave_js/fan.py b/homeassistant/components/zwave_js/fan.py index 224921b1b78..bb634328a06 100644 --- a/homeassistant/components/zwave_js/fan.py +++ b/homeassistant/components/zwave_js/fan.py @@ -25,7 +25,6 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util.percentage import ( - int_states_in_range, percentage_to_ranged_value, ranged_value_to_percentage, ) @@ -85,7 +84,9 @@ class ZwaveFan(ZWaveBaseEntity, FanEntity): ) -> None: """Initialize the fan.""" super().__init__(config_entry, driver, info) - self._target_value = self.get_zwave_value(TARGET_VALUE_PROPERTY) + target_value = self.get_zwave_value(TARGET_VALUE_PROPERTY) + assert target_value + self._target_value = target_value async def async_set_percentage(self, percentage: int) -> None: """Set the speed percentage of the fan.""" @@ -96,9 +97,7 @@ class ZwaveFan(ZWaveBaseEntity, FanEntity): percentage_to_ranged_value(DEFAULT_SPEED_RANGE, percentage) ) - if (target_value := self._target_value) is None: - raise HomeAssistantError("Missing target value on device.") - await self.info.node.async_set_value(target_value, zwave_speed) + await self.info.node.async_set_value(self._target_value, zwave_speed) async def async_turn_on( self, @@ -112,16 +111,12 @@ class ZwaveFan(ZWaveBaseEntity, FanEntity): elif preset_mode is not None: await self.async_set_preset_mode(preset_mode) else: - if (target_value := self._target_value) is None: - raise HomeAssistantError("Missing target value on device.") # Value 255 tells device to return to previous value - await self.info.node.async_set_value(target_value, 255) + await self.info.node.async_set_value(self._target_value, 255) async def async_turn_off(self, **kwargs: Any) -> None: """Turn the device off.""" - if (target_value := self._target_value) is None: - raise HomeAssistantError("Missing target value on device.") - await self.info.node.async_set_value(target_value, 0) + await self.info.node.async_set_value(self._target_value, 0) @property def is_on(self) -> bool | None: @@ -146,11 +141,6 @@ class ZwaveFan(ZWaveBaseEntity, FanEntity): """Return the step size for percentage.""" return 1 - @property - def speed_count(self) -> int: - """Return the number of speeds the fan supports.""" - return int_states_in_range(DEFAULT_SPEED_RANGE) - class ValueMappingZwaveFan(ZwaveFan): """A Zwave fan with a value mapping data (e.g., 1-24 is low).""" @@ -166,18 +156,14 @@ class ValueMappingZwaveFan(ZwaveFan): async def async_set_percentage(self, percentage: int) -> None: """Set the speed percentage of the fan.""" - if (target_value := self._target_value) is None: - raise HomeAssistantError("Missing target value on device.") zwave_speed = self.percentage_to_zwave_speed(percentage) - await self.info.node.async_set_value(target_value, zwave_speed) + await self.info.node.async_set_value(self._target_value, zwave_speed) async def async_set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" - if (target_value := self._target_value) is None: - raise HomeAssistantError("Missing target value on device.") for zwave_value, mapped_preset_mode in self.fan_value_mapping.presets.items(): if preset_mode == mapped_preset_mode: - await self.info.node.async_set_value(target_value, zwave_value) + await self.info.node.async_set_value(self._target_value, zwave_value) return raise NotValidPresetModeError( @@ -277,12 +263,9 @@ class ValueMappingZwaveFan(ZwaveFan): assert step_percentage if percentage <= step_percentage: - return max_speed + break - # This shouldn't actually happen; the last entry in - # `self.fan_value_mapping.speeds` should map to 100%. - (_, last_max_speed) = self.fan_value_mapping.speeds[-1] - return last_max_speed + return max_speed def zwave_speed_to_percentage(self, zwave_speed: int) -> int | None: """Convert a Zwave speed to a percentage. diff --git a/tests/components/zwave_js/test_fan.py b/tests/components/zwave_js/test_fan.py index d9de2379ce4..471246c59aa 100644 --- a/tests/components/zwave_js/test_fan.py +++ b/tests/components/zwave_js/test_fan.py @@ -13,6 +13,7 @@ from homeassistant.components.fan import ( ATTR_PRESET_MODE, ATTR_PRESET_MODES, DOMAIN as FAN_DOMAIN, + SERVICE_SET_PERCENTAGE, SERVICE_SET_PRESET_MODE, FanEntityFeature, NotValidPresetModeError, @@ -167,6 +168,50 @@ async def test_generic_fan( assert state.state == "off" assert state.attributes[ATTR_PERCENTAGE] == 0 + client.async_send_command.reset_mock() + + # Test setting percentage to 0 + await hass.services.async_call( + "fan", + SERVICE_SET_PERCENTAGE, + {"entity_id": entity_id, "percentage": 0}, + 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"] == 17 + assert args["valueId"] == { + "commandClass": 38, + "endpoint": 0, + "property": "targetValue", + } + assert args["value"] == 0 + + # Test value is None + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": 17, + "args": { + "commandClassName": "Multilevel Switch", + "commandClass": 38, + "endpoint": 0, + "property": "currentValue", + "newValue": None, + "prevValue": 0, + "propertyName": "currentValue", + }, + }, + ) + node.receive_event(event) + + state = hass.states.get(entity_id) + assert state.state == STATE_UNKNOWN + async def test_configurable_speeds_fan( hass: HomeAssistant, client, hs_fc200, integration @@ -361,6 +406,29 @@ async def test_ge_12730_fan(hass: HomeAssistant, client, ge_12730, integration) assert state.attributes[ATTR_PERCENTAGE_STEP] == pytest.approx(33.3333, rel=1e-3) assert state.attributes[ATTR_PRESET_MODES] == [] + # Test value is None + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": node_id, + "args": { + "commandClassName": "Multilevel Switch", + "commandClass": 38, + "endpoint": 0, + "property": "currentValue", + "newValue": None, + "prevValue": 0, + "propertyName": "currentValue", + }, + }, + ) + node.receive_event(event) + + state = hass.states.get(entity_id) + assert state.state == STATE_UNKNOWN + async def test_inovelli_lzw36( hass: HomeAssistant, client, inovelli_lzw36, integration