Refactor zwave_js.fan and add tests (#93256)
* Refactor zwave_js.fan and add tests * fix const
This commit is contained in:
parent
03300c24da
commit
18eeeaaf68
3 changed files with 86 additions and 27 deletions
|
@ -240,6 +240,12 @@ SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA = ZWaveValueDiscoverySchema(
|
||||||
type={ValueType.NUMBER},
|
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(
|
SWITCH_BINARY_CURRENT_VALUE_SCHEMA = ZWaveValueDiscoverySchema(
|
||||||
command_class={CommandClass.SWITCH_BINARY}, property={CURRENT_VALUE_PROPERTY}
|
command_class={CommandClass.SWITCH_BINARY}, property={CURRENT_VALUE_PROPERTY}
|
||||||
)
|
)
|
||||||
|
@ -261,6 +267,7 @@ DISCOVERY_SCHEMAS = [
|
||||||
product_id={0x3131},
|
product_id={0x3131},
|
||||||
product_type={0x4944},
|
product_type={0x4944},
|
||||||
primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA,
|
primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA,
|
||||||
|
required_values=[SWITCH_MULTILEVEL_TARGET_VALUE_SCHEMA],
|
||||||
),
|
),
|
||||||
# GE/Jasco - In-Wall Smart Fan Control - 12730 / ZW4002
|
# GE/Jasco - In-Wall Smart Fan Control - 12730 / ZW4002
|
||||||
ZWaveDiscoverySchema(
|
ZWaveDiscoverySchema(
|
||||||
|
@ -842,6 +849,7 @@ DISCOVERY_SCHEMAS = [
|
||||||
device_class_generic={"Multilevel Switch"},
|
device_class_generic={"Multilevel Switch"},
|
||||||
device_class_specific={"Fan Switch"},
|
device_class_specific={"Fan Switch"},
|
||||||
primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA,
|
primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA,
|
||||||
|
required_values=[SWITCH_MULTILEVEL_TARGET_VALUE_SCHEMA],
|
||||||
),
|
),
|
||||||
# number platform
|
# number platform
|
||||||
# valve control for thermostats
|
# valve control for thermostats
|
||||||
|
|
|
@ -25,7 +25,6 @@ from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.util.percentage import (
|
from homeassistant.util.percentage import (
|
||||||
int_states_in_range,
|
|
||||||
percentage_to_ranged_value,
|
percentage_to_ranged_value,
|
||||||
ranged_value_to_percentage,
|
ranged_value_to_percentage,
|
||||||
)
|
)
|
||||||
|
@ -85,7 +84,9 @@ class ZwaveFan(ZWaveBaseEntity, FanEntity):
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the fan."""
|
"""Initialize the fan."""
|
||||||
super().__init__(config_entry, driver, info)
|
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:
|
async def async_set_percentage(self, percentage: int) -> None:
|
||||||
"""Set the speed percentage of the fan."""
|
"""Set the speed percentage of the fan."""
|
||||||
|
@ -96,9 +97,7 @@ class ZwaveFan(ZWaveBaseEntity, FanEntity):
|
||||||
percentage_to_ranged_value(DEFAULT_SPEED_RANGE, percentage)
|
percentage_to_ranged_value(DEFAULT_SPEED_RANGE, percentage)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (target_value := self._target_value) is None:
|
await self.info.node.async_set_value(self._target_value, zwave_speed)
|
||||||
raise HomeAssistantError("Missing target value on device.")
|
|
||||||
await self.info.node.async_set_value(target_value, zwave_speed)
|
|
||||||
|
|
||||||
async def async_turn_on(
|
async def async_turn_on(
|
||||||
self,
|
self,
|
||||||
|
@ -112,16 +111,12 @@ class ZwaveFan(ZWaveBaseEntity, FanEntity):
|
||||||
elif preset_mode is not None:
|
elif preset_mode is not None:
|
||||||
await self.async_set_preset_mode(preset_mode)
|
await self.async_set_preset_mode(preset_mode)
|
||||||
else:
|
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
|
# 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:
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Turn the device off."""
|
"""Turn the device off."""
|
||||||
if (target_value := self._target_value) is None:
|
await self.info.node.async_set_value(self._target_value, 0)
|
||||||
raise HomeAssistantError("Missing target value on device.")
|
|
||||||
await self.info.node.async_set_value(target_value, 0)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool | None:
|
def is_on(self) -> bool | None:
|
||||||
|
@ -146,11 +141,6 @@ class ZwaveFan(ZWaveBaseEntity, FanEntity):
|
||||||
"""Return the step size for percentage."""
|
"""Return the step size for percentage."""
|
||||||
return 1
|
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):
|
class ValueMappingZwaveFan(ZwaveFan):
|
||||||
"""A Zwave fan with a value mapping data (e.g., 1-24 is low)."""
|
"""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:
|
async def async_set_percentage(self, percentage: int) -> None:
|
||||||
"""Set the speed percentage of the fan."""
|
"""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)
|
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:
|
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||||
"""Set new preset mode."""
|
"""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():
|
for zwave_value, mapped_preset_mode in self.fan_value_mapping.presets.items():
|
||||||
if preset_mode == mapped_preset_mode:
|
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
|
return
|
||||||
|
|
||||||
raise NotValidPresetModeError(
|
raise NotValidPresetModeError(
|
||||||
|
@ -277,12 +263,9 @@ class ValueMappingZwaveFan(ZwaveFan):
|
||||||
assert step_percentage
|
assert step_percentage
|
||||||
|
|
||||||
if percentage <= step_percentage:
|
if percentage <= step_percentage:
|
||||||
return max_speed
|
break
|
||||||
|
|
||||||
# This shouldn't actually happen; the last entry in
|
return max_speed
|
||||||
# `self.fan_value_mapping.speeds` should map to 100%.
|
|
||||||
(_, last_max_speed) = self.fan_value_mapping.speeds[-1]
|
|
||||||
return last_max_speed
|
|
||||||
|
|
||||||
def zwave_speed_to_percentage(self, zwave_speed: int) -> int | None:
|
def zwave_speed_to_percentage(self, zwave_speed: int) -> int | None:
|
||||||
"""Convert a Zwave speed to a percentage.
|
"""Convert a Zwave speed to a percentage.
|
||||||
|
|
|
@ -13,6 +13,7 @@ from homeassistant.components.fan import (
|
||||||
ATTR_PRESET_MODE,
|
ATTR_PRESET_MODE,
|
||||||
ATTR_PRESET_MODES,
|
ATTR_PRESET_MODES,
|
||||||
DOMAIN as FAN_DOMAIN,
|
DOMAIN as FAN_DOMAIN,
|
||||||
|
SERVICE_SET_PERCENTAGE,
|
||||||
SERVICE_SET_PRESET_MODE,
|
SERVICE_SET_PRESET_MODE,
|
||||||
FanEntityFeature,
|
FanEntityFeature,
|
||||||
NotValidPresetModeError,
|
NotValidPresetModeError,
|
||||||
|
@ -167,6 +168,50 @@ async def test_generic_fan(
|
||||||
assert state.state == "off"
|
assert state.state == "off"
|
||||||
assert state.attributes[ATTR_PERCENTAGE] == 0
|
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(
|
async def test_configurable_speeds_fan(
|
||||||
hass: HomeAssistant, client, hs_fc200, integration
|
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_PERCENTAGE_STEP] == pytest.approx(33.3333, rel=1e-3)
|
||||||
assert state.attributes[ATTR_PRESET_MODES] == []
|
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(
|
async def test_inovelli_lzw36(
|
||||||
hass: HomeAssistant, client, inovelli_lzw36, integration
|
hass: HomeAssistant, client, inovelli_lzw36, integration
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue