mirror of
https://github.com/home-assistant/core.git
synced 2025-04-22 16:27:56 +00:00
Support for GE Zwave fan controller (#7767)
* Support for GE Zwave fan controller * Tests for zwave fan * Add additional fan workarounds
This commit is contained in:
parent
6899c7b6f7
commit
9e9705d6b2
86
homeassistant/components/fan/zwave.py
Normal file
86
homeassistant/components/fan/zwave.py
Normal file
@ -0,0 +1,86 @@
|
||||
"""
|
||||
Z-Wave platform that handles fans.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/fan.zwave/
|
||||
"""
|
||||
import logging
|
||||
import math
|
||||
|
||||
from homeassistant.components.fan import (
|
||||
DOMAIN, FanEntity, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH,
|
||||
SUPPORT_SET_SPEED)
|
||||
from homeassistant.components import zwave
|
||||
from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SPEED_LIST = [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
|
||||
|
||||
SUPPORTED_FEATURES = SUPPORT_SET_SPEED
|
||||
|
||||
# Value will first be divided to an integer
|
||||
VALUE_TO_SPEED = {
|
||||
0: SPEED_OFF,
|
||||
1: SPEED_LOW,
|
||||
2: SPEED_MEDIUM,
|
||||
3: SPEED_HIGH,
|
||||
}
|
||||
|
||||
SPEED_TO_VALUE = {
|
||||
SPEED_OFF: 0,
|
||||
SPEED_LOW: 1,
|
||||
SPEED_MEDIUM: 50,
|
||||
SPEED_HIGH: 99,
|
||||
}
|
||||
|
||||
|
||||
def get_device(values, **kwargs):
|
||||
"""Create zwave entity device."""
|
||||
return ZwaveFan(values)
|
||||
|
||||
|
||||
class ZwaveFan(zwave.ZWaveDeviceEntity, FanEntity):
|
||||
"""Representation of a Z-Wave fan."""
|
||||
|
||||
def __init__(self, values):
|
||||
"""Initialize the Z-Wave fan device."""
|
||||
zwave.ZWaveDeviceEntity.__init__(self, values, DOMAIN)
|
||||
self.update_properties()
|
||||
|
||||
def update_properties(self):
|
||||
"""Handle data changes for node values."""
|
||||
value = math.ceil(self.values.primary.data * 3 / 100)
|
||||
self._state = VALUE_TO_SPEED[value]
|
||||
|
||||
def set_speed(self, speed):
|
||||
"""Set the speed of the fan."""
|
||||
self.node.set_dimmer(
|
||||
self.values.primary.value_id, SPEED_TO_VALUE[speed])
|
||||
|
||||
def turn_on(self, speed=None, **kwargs):
|
||||
"""Turn the device on."""
|
||||
if speed is None:
|
||||
# Value 255 tells device to return to previous value
|
||||
self.node.set_dimmer(self.values.primary.value_id, 255)
|
||||
else:
|
||||
self.set_speed(speed)
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
"""Turn the device off."""
|
||||
self.node.set_dimmer(self.values.primary.value_id, 0)
|
||||
|
||||
@property
|
||||
def speed(self):
|
||||
"""Return the current speed."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def speed_list(self):
|
||||
"""Get the list of available speeds."""
|
||||
return SPEED_LIST
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Flag supported features."""
|
||||
return SUPPORTED_FEATURES
|
@ -3,22 +3,30 @@ from . import const
|
||||
|
||||
# Manufacturers
|
||||
FIBARO = 0x010f
|
||||
GE = 0x0063
|
||||
PHILIO = 0x013c
|
||||
SOMFY = 0x0047
|
||||
WENZHOU = 0x0118
|
||||
SOMFY = 0x47
|
||||
VIZIA = 0x001D
|
||||
|
||||
# Product IDs
|
||||
GE_FAN_CONTROLLER_12730 = 0x3034
|
||||
GE_FAN_CONTROLLER_14287 = 0x3131
|
||||
JASCO_FAN_CONTROLLER_14314 = 0x3138
|
||||
PHILIO_SLIM_SENSOR = 0x0002
|
||||
PHILIO_3_IN_1_SENSOR_GEN_4 = 0x000d
|
||||
PHILIO_PAN07 = 0x0005
|
||||
VIZIA_FAN_CONTROLLER_VRF01 = 0x0334
|
||||
|
||||
# Product Types
|
||||
FGFS101_FLOOD_SENSOR_TYPE = 0x0b00
|
||||
FGRM222_SHUTTER2 = 0x0301
|
||||
FGR222_SHUTTER2 = 0x0302
|
||||
GE_DIMMER = 0x4944
|
||||
PHILIO_SWITCH = 0x0001
|
||||
PHILIO_SENSOR = 0x0002
|
||||
SOMFY_ZRTSI = 0x5a52
|
||||
VIZIA_DIMMER = 0x1001
|
||||
|
||||
# Mapping devices
|
||||
PHILIO_SLIM_SENSOR_MOTION_MTII = (PHILIO, PHILIO_SENSOR, PHILIO_SLIM_SENSOR, 0)
|
||||
@ -53,7 +61,6 @@ DEVICE_MAPPINGS_MT = {
|
||||
SOMFY_ZRTSI_CONTROLLER_MT: WORKAROUND_NO_POSITION,
|
||||
}
|
||||
|
||||
|
||||
# Component mapping devices
|
||||
FIBARO_FGFS101_SENSOR_ALARM = (
|
||||
FIBARO, FGFS101_FLOOD_SENSOR_TYPE, const.COMMAND_CLASS_SENSOR_ALARM)
|
||||
@ -61,6 +68,18 @@ FIBARO_FGRM222_BINARY = (
|
||||
FIBARO, FGRM222_SHUTTER2, const.COMMAND_CLASS_SWITCH_BINARY)
|
||||
FIBARO_FGR222_BINARY = (
|
||||
FIBARO, FGR222_SHUTTER2, const.COMMAND_CLASS_SWITCH_BINARY)
|
||||
GE_FAN_CONTROLLER_12730_MULTILEVEL = (
|
||||
GE, GE_DIMMER, GE_FAN_CONTROLLER_12730,
|
||||
const.COMMAND_CLASS_SWITCH_MULTILEVEL)
|
||||
GE_FAN_CONTROLLER_14287_MULTILEVEL = (
|
||||
GE, GE_DIMMER, GE_FAN_CONTROLLER_14287,
|
||||
const.COMMAND_CLASS_SWITCH_MULTILEVEL)
|
||||
JASCO_FAN_CONTROLLER_14314_MULTILEVEL = (
|
||||
GE, GE_DIMMER, JASCO_FAN_CONTROLLER_14314,
|
||||
const.COMMAND_CLASS_SWITCH_MULTILEVEL)
|
||||
VIZIA_FAN_CONTROLLER_VRF01_MULTILEVEL = (
|
||||
VIZIA, VIZIA_DIMMER, VIZIA_FAN_CONTROLLER_VRF01,
|
||||
const.COMMAND_CLASS_SWITCH_MULTILEVEL)
|
||||
|
||||
# List of component workarounds by
|
||||
# (manufacturer_id, product_type, command_class)
|
||||
@ -70,6 +89,15 @@ DEVICE_COMPONENT_MAPPING = {
|
||||
FIBARO_FGR222_BINARY: WORKAROUND_IGNORE,
|
||||
}
|
||||
|
||||
# List of component workarounds by
|
||||
# (manufacturer_id, product_type, product_id, command_class)
|
||||
DEVICE_COMPONENT_MAPPING_MTI = {
|
||||
GE_FAN_CONTROLLER_12730_MULTILEVEL: 'fan',
|
||||
GE_FAN_CONTROLLER_14287_MULTILEVEL: 'fan',
|
||||
JASCO_FAN_CONTROLLER_14314_MULTILEVEL: 'fan',
|
||||
VIZIA_FAN_CONTROLLER_VRF01_MULTILEVEL: 'fan',
|
||||
}
|
||||
|
||||
|
||||
def get_device_component_mapping(value):
|
||||
"""Get mapping of value to another component."""
|
||||
@ -77,8 +105,16 @@ def get_device_component_mapping(value):
|
||||
value.node.product_type.strip()):
|
||||
manufacturer_id = int(value.node.manufacturer_id, 16)
|
||||
product_type = int(value.node.product_type, 16)
|
||||
return DEVICE_COMPONENT_MAPPING.get(
|
||||
product_id = int(value.node.product_id, 16)
|
||||
result = DEVICE_COMPONENT_MAPPING.get(
|
||||
(manufacturer_id, product_type, value.command_class))
|
||||
if result:
|
||||
return result
|
||||
|
||||
result = DEVICE_COMPONENT_MAPPING_MTI.get(
|
||||
(manufacturer_id, product_type, product_id, value.command_class))
|
||||
if result:
|
||||
return result
|
||||
|
||||
return None
|
||||
|
||||
|
117
tests/components/fan/test_zwave.py
Normal file
117
tests/components/fan/test_zwave.py
Normal file
@ -0,0 +1,117 @@
|
||||
"""Test Z-Wave fans."""
|
||||
from homeassistant.components.fan import (
|
||||
zwave, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH, SUPPORT_SET_SPEED)
|
||||
|
||||
from tests.mock.zwave import (
|
||||
MockNode, MockValue, MockEntityValues, value_changed)
|
||||
|
||||
|
||||
def test_get_device_detects_fan(mock_openzwave):
|
||||
"""Test get_device returns a zwave fan."""
|
||||
node = MockNode()
|
||||
value = MockValue(data=0, node=node)
|
||||
values = MockEntityValues(primary=value)
|
||||
|
||||
device = zwave.get_device(node=node, values=values, node_config={})
|
||||
assert isinstance(device, zwave.ZwaveFan)
|
||||
assert device.supported_features == SUPPORT_SET_SPEED
|
||||
assert device.speed_list == [
|
||||
SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
|
||||
|
||||
|
||||
def test_fan_turn_on(mock_openzwave):
|
||||
"""Test turning on a zwave fan."""
|
||||
node = MockNode()
|
||||
value = MockValue(data=0, node=node)
|
||||
values = MockEntityValues(primary=value)
|
||||
device = zwave.get_device(node=node, values=values, node_config={})
|
||||
|
||||
device.turn_on()
|
||||
|
||||
assert node.set_dimmer.called
|
||||
value_id, brightness = node.set_dimmer.mock_calls[0][1]
|
||||
assert value_id == value.value_id
|
||||
assert brightness == 255
|
||||
|
||||
node.reset_mock()
|
||||
|
||||
device.turn_on(speed=SPEED_OFF)
|
||||
|
||||
assert node.set_dimmer.called
|
||||
value_id, brightness = node.set_dimmer.mock_calls[0][1]
|
||||
|
||||
assert value_id == value.value_id
|
||||
assert brightness == 0
|
||||
|
||||
node.reset_mock()
|
||||
|
||||
device.turn_on(speed=SPEED_LOW)
|
||||
|
||||
assert node.set_dimmer.called
|
||||
value_id, brightness = node.set_dimmer.mock_calls[0][1]
|
||||
|
||||
assert value_id == value.value_id
|
||||
assert brightness == 1
|
||||
|
||||
node.reset_mock()
|
||||
|
||||
device.turn_on(speed=SPEED_MEDIUM)
|
||||
|
||||
assert node.set_dimmer.called
|
||||
value_id, brightness = node.set_dimmer.mock_calls[0][1]
|
||||
|
||||
assert value_id == value.value_id
|
||||
assert brightness == 50
|
||||
|
||||
node.reset_mock()
|
||||
|
||||
device.turn_on(speed=SPEED_HIGH)
|
||||
|
||||
assert node.set_dimmer.called
|
||||
value_id, brightness = node.set_dimmer.mock_calls[0][1]
|
||||
|
||||
assert value_id == value.value_id
|
||||
assert brightness == 99
|
||||
|
||||
|
||||
def test_fan_turn_off(mock_openzwave):
|
||||
"""Test turning off a dimmable zwave fan."""
|
||||
node = MockNode()
|
||||
value = MockValue(data=46, node=node)
|
||||
values = MockEntityValues(primary=value)
|
||||
device = zwave.get_device(node=node, values=values, node_config={})
|
||||
|
||||
device.turn_off()
|
||||
|
||||
assert node.set_dimmer.called
|
||||
value_id, brightness = node.set_dimmer.mock_calls[0][1]
|
||||
assert value_id == value.value_id
|
||||
assert brightness == 0
|
||||
|
||||
|
||||
def test_fan_value_changed(mock_openzwave):
|
||||
"""Test value changed for zwave fan."""
|
||||
node = MockNode()
|
||||
value = MockValue(data=0, node=node)
|
||||
values = MockEntityValues(primary=value)
|
||||
device = zwave.get_device(node=node, values=values, node_config={})
|
||||
|
||||
assert not device.is_on
|
||||
|
||||
value.data = 10
|
||||
value_changed(value)
|
||||
|
||||
assert device.is_on
|
||||
assert device.speed == SPEED_LOW
|
||||
|
||||
value.data = 50
|
||||
value_changed(value)
|
||||
|
||||
assert device.is_on
|
||||
assert device.speed == SPEED_MEDIUM
|
||||
|
||||
value.data = 90
|
||||
value_changed(value)
|
||||
|
||||
assert device.is_on
|
||||
assert device.speed == SPEED_HIGH
|
@ -24,7 +24,7 @@ class MockLightValues(MockEntityValues):
|
||||
|
||||
|
||||
def test_get_device_detects_dimmer(mock_openzwave):
|
||||
"""Test get_device returns a color light."""
|
||||
"""Test get_device returns a normal dimmer."""
|
||||
node = MockNode()
|
||||
value = MockValue(data=0, node=node)
|
||||
values = MockLightValues(primary=value)
|
||||
|
@ -18,6 +18,23 @@ def test_get_device_component_mapping():
|
||||
assert workaround.get_device_component_mapping(value) == 'binary_sensor'
|
||||
|
||||
|
||||
def test_get_device_component_mapping_mti():
|
||||
"""Test that component is returned."""
|
||||
# GE Fan controller
|
||||
node = MockNode(manufacturer_id='0063', product_type='4944',
|
||||
product_id='3034')
|
||||
value = MockValue(data=0, node=node,
|
||||
command_class=const.COMMAND_CLASS_SWITCH_MULTILEVEL)
|
||||
assert workaround.get_device_component_mapping(value) == 'fan'
|
||||
|
||||
# GE Dimmer
|
||||
node = MockNode(manufacturer_id='0063', product_type='4944',
|
||||
product_id='3031')
|
||||
value = MockValue(data=0, node=node,
|
||||
command_class=const.COMMAND_CLASS_SWITCH_MULTILEVEL)
|
||||
assert workaround.get_device_component_mapping(value) is None
|
||||
|
||||
|
||||
def test_get_device_no_mapping():
|
||||
"""Test that no device mapping is returned."""
|
||||
node = MockNode(manufacturer_id=' ')
|
||||
|
Loading…
x
Reference in New Issue
Block a user