Add zwave_js speed configurations for GE/Jasco 12730 and 14287 fans (#60517)

This commit is contained in:
Michael Kowalchuk 2021-11-28 23:27:32 -08:00 committed by GitHub
parent 70b8decfb5
commit 15bf4dae9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 176 additions and 37 deletions

View File

@ -47,6 +47,7 @@ from .discovery_data_template import (
ConfigurableFanSpeedDataTemplate,
CoverTiltDataTemplate,
DynamicCurrentTempClimateDataTemplate,
FixedFanSpeedDataTemplate,
NumericSensorDataTemplate,
ZwaveValueID,
)
@ -230,11 +231,35 @@ DISCOVERY_SCHEMAS = [
product_type={0x4944},
primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA,
),
# GE/Jasco fan controllers using switch multilevel CC
# GE/Jasco - In-Wall Smart Fan Control - 12730 / ZW4002
ZWaveDiscoverySchema(
platform="fan",
hint="configured_fan_speed",
manufacturer_id={0x0063},
product_id={0x3034},
product_type={0x4944},
primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA,
data_template=FixedFanSpeedDataTemplate(
speeds=[33, 67, 99],
),
),
# GE/Jasco - In-Wall Smart Fan Control - 14287 / ZW4002
ZWaveDiscoverySchema(
platform="fan",
hint="configured_fan_speed",
manufacturer_id={0x0063},
product_id={0x3131},
product_type={0x4944},
primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA,
data_template=FixedFanSpeedDataTemplate(
speeds=[32, 66, 99],
),
),
# GE/Jasco - In-Wall Smart Fan Control - 14314 / ZW4002
ZWaveDiscoverySchema(
platform="fan",
manufacturer_id={0x0063},
product_id={0x3034, 0x3131, 0x3138},
product_id={0x3138},
product_type={0x4944},
primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA,
),

View File

@ -523,3 +523,48 @@ class ConfigurableFanSpeedDataTemplate(
return None
return speed_config
@dataclass
class FixedFanSpeedValueMix:
"""Mixin data class for defining supported fan speeds."""
speeds: list[int]
def __post_init__(self) -> None:
"""
Validate inputs.
These inputs are hardcoded in `discovery.py`, so these checks should
only fail due to developer error.
"""
assert len(self.speeds) > 0
assert sorted(self.speeds) == self.speeds
@dataclass
class FixedFanSpeedDataTemplate(
BaseDiscoverySchemaDataTemplate, FanSpeedDataTemplate, FixedFanSpeedValueMix
):
"""
Specifies a fixed set of fan speeds.
Example:
ZWaveDiscoverySchema(
platform="fan",
hint="configured_fan_speed",
...
data_template=FixedFanSpeedDataTemplate(
speeds=[32,65,99]
),
),
`speeds` indicates the maximum setting on the underlying fan controller
for each actual speed.
"""
def get_speed_config(
self, resolved_data: dict[str, ZwaveConfigurationValue]
) -> list[int]:
"""Get the fan speed configuration for this device."""
return self.speeds

View File

@ -326,10 +326,10 @@ def window_cover_state_fixture():
return json.loads(load_fixture("zwave_js/chain_actuator_zws12_state.json"))
@pytest.fixture(name="in_wall_smart_fan_control_state", scope="session")
def in_wall_smart_fan_control_state_fixture():
@pytest.fixture(name="fan_generic_state", scope="session")
def fan_generic_state_fixture():
"""Load the fan node state fixture data."""
return json.loads(load_fixture("zwave_js/in_wall_smart_fan_control_state.json"))
return json.loads(load_fixture("zwave_js/fan_generic_state.json"))
@pytest.fixture(name="hs_fc200_state", scope="session")
@ -695,10 +695,10 @@ def window_cover_fixture(client, chain_actuator_zws12_state):
return node
@pytest.fixture(name="in_wall_smart_fan_control")
def in_wall_smart_fan_control_fixture(client, in_wall_smart_fan_control_state):
@pytest.fixture(name="fan_generic")
def fan_generic_fixture(client, fan_generic_state):
"""Mock a fan node."""
node = Node(client, copy.deepcopy(in_wall_smart_fan_control_state))
node = Node(client, copy.deepcopy(fan_generic_state))
client.driver.controller.nodes[node.node_id] = node
return node

View File

@ -19,22 +19,22 @@
"isSecure": false,
"version": 4,
"isBeaming": true,
"manufacturerId": 99,
"productId": 12593,
"productType": 18756,
"manufacturerId": 4919,
"productId": 4919,
"productType": 4919,
"firmwareVersion": "5.22",
"zwavePlusVersion": 1,
"nodeType": 0,
"roleType": 5,
"deviceConfig": {
"manufacturerId": 99,
"manufacturer": "GE/Jasco",
"manufacturerId": 4919,
"manufacturer": "Unknown",
"label": "ZW4002",
"description": "In-Wall Smart Fan Control",
"description": "Generic Fan Controller",
"devices": [
{
"productType": "0x4944",
"productId": "0x3131"
"productType": "0x1337",
"productId": "0x1337"
}
],
"firmwareVersion": {
@ -349,4 +349,4 @@
}
}
]
}
}

View File

@ -11,14 +11,12 @@ from homeassistant.components.fan import (
SPEED_MEDIUM,
)
STANDARD_FAN_ENTITY = "fan.in_wall_smart_fan_control"
HS_FAN_ENTITY = "fan.scene_capable_fan_control_switch"
async def test_standard_fan(hass, client, in_wall_smart_fan_control, integration):
"""Test the fan entity."""
node = in_wall_smart_fan_control
state = hass.states.get(STANDARD_FAN_ENTITY)
async def test_generic_fan(hass, client, fan_generic, integration):
"""Test the fan entity for a generic fan that lacks specific speed configuration."""
node = fan_generic
entity_id = "fan.generic_fan_controller"
state = hass.states.get(entity_id)
assert state
assert state.state == "off"
@ -27,7 +25,7 @@ async def test_standard_fan(hass, client, in_wall_smart_fan_control, integration
await hass.services.async_call(
"fan",
"turn_on",
{"entity_id": STANDARD_FAN_ENTITY, "speed": SPEED_MEDIUM},
{"entity_id": entity_id, "speed": SPEED_MEDIUM},
blocking=True,
)
@ -60,7 +58,7 @@ async def test_standard_fan(hass, client, in_wall_smart_fan_control, integration
await hass.services.async_call(
"fan",
"set_speed",
{"entity_id": STANDARD_FAN_ENTITY, "speed": 99},
{"entity_id": entity_id, "speed": 99},
blocking=True,
)
@ -70,7 +68,7 @@ async def test_standard_fan(hass, client, in_wall_smart_fan_control, integration
await hass.services.async_call(
"fan",
"turn_on",
{"entity_id": STANDARD_FAN_ENTITY},
{"entity_id": entity_id},
blocking=True,
)
@ -102,7 +100,7 @@ async def test_standard_fan(hass, client, in_wall_smart_fan_control, integration
await hass.services.async_call(
"fan",
"turn_off",
{"entity_id": STANDARD_FAN_ENTITY},
{"entity_id": entity_id},
blocking=True,
)
@ -150,7 +148,7 @@ async def test_standard_fan(hass, client, in_wall_smart_fan_control, integration
)
node.receive_event(event)
state = hass.states.get(STANDARD_FAN_ENTITY)
state = hass.states.get(entity_id)
assert state.state == "on"
assert state.attributes[ATTR_SPEED] == "high"
@ -175,13 +173,16 @@ async def test_standard_fan(hass, client, in_wall_smart_fan_control, integration
)
node.receive_event(event)
state = hass.states.get(STANDARD_FAN_ENTITY)
state = hass.states.get(entity_id)
assert state.state == "off"
assert state.attributes[ATTR_SPEED] == "off"
async def test_hs_fan(hass, client, hs_fc200, integration):
async def test_configurable_speeds_fan(hass, client, hs_fc200, integration):
"""Test a fan entity with configurable speeds."""
node = hs_fc200
node_id = 39
entity_id = "fan.scene_capable_fan_control_switch"
async def get_zwave_speed_from_percentage(percentage):
"""Set the fan to a particular percentage and get the resulting Zwave speed."""
@ -189,14 +190,14 @@ async def test_hs_fan(hass, client, hs_fc200, integration):
await hass.services.async_call(
"fan",
"turn_on",
{"entity_id": HS_FAN_ENTITY, "percentage": percentage},
{"entity_id": entity_id, "percentage": percentage},
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"] == 39
assert args["nodeId"] == node_id
return args["value"]
async def get_percentage_from_zwave_speed(zwave_speed):
@ -206,7 +207,7 @@ async def test_hs_fan(hass, client, hs_fc200, integration):
data={
"source": "node",
"event": "value updated",
"nodeId": 39,
"nodeId": node_id,
"args": {
"commandClassName": "Multilevel Switch",
"commandClass": 38,
@ -218,10 +219,12 @@ async def test_hs_fan(hass, client, hs_fc200, integration):
},
},
)
hs_fc200.receive_event(event)
state = hass.states.get(HS_FAN_ENTITY)
node.receive_event(event)
state = hass.states.get(entity_id)
return state.attributes[ATTR_PERCENTAGE]
# In 3-speed mode, the speeds are:
# low = 1-33, med=34-66, high=67-99
percentages_to_zwave_speeds = [
[[0], [0]],
[range(1, 34), range(1, 34)],
@ -237,5 +240,71 @@ async def test_hs_fan(hass, client, hs_fc200, integration):
actual_percentage = await get_percentage_from_zwave_speed(zwave_speed)
assert actual_percentage in percentages
state = hass.states.get(HS_FAN_ENTITY)
state = hass.states.get(entity_id)
assert math.isclose(state.attributes[ATTR_PERCENTAGE_STEP], 33.3333, rel_tol=1e-3)
async def test_fixed_speeds_fan(hass, client, ge_12730, integration):
"""Test a fan entity with fixed speeds."""
node = ge_12730
node_id = 24
entity_id = "fan.in_wall_smart_fan_control"
async def get_zwave_speed_from_percentage(percentage):
"""Set the fan to a particular percentage and get the resulting Zwave speed."""
client.async_send_command.reset_mock()
await hass.services.async_call(
"fan",
"turn_on",
{"entity_id": entity_id, "percentage": percentage},
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"] == node_id
return args["value"]
async def get_percentage_from_zwave_speed(zwave_speed):
"""Set the underlying device speed and get the resulting percentage."""
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": zwave_speed,
"prevValue": 0,
"propertyName": "currentValue",
},
},
)
node.receive_event(event)
state = hass.states.get(entity_id)
return state.attributes[ATTR_PERCENTAGE]
# This device has the speeds:
# low = 1-33, med = 34-67, high = 68-99
percentages_to_zwave_speeds = [
[[0], [0]],
[range(1, 34), range(1, 34)],
[range(34, 68), range(34, 68)],
[range(68, 101), range(68, 100)],
]
for percentages, zwave_speeds in percentages_to_zwave_speeds:
for percentage in percentages:
actual_zwave_speed = await get_zwave_speed_from_percentage(percentage)
assert actual_zwave_speed in zwave_speeds
for zwave_speed in zwave_speeds:
actual_percentage = await get_percentage_from_zwave_speed(zwave_speed)
assert actual_percentage in percentages
state = hass.states.get(entity_id)
assert math.isclose(state.attributes[ATTR_PERCENTAGE_STEP], 33.3333, rel_tol=1e-3)