diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index b67b4d2ad24..efb7e0b1496 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -33,9 +33,11 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}' ATTR_SUPPORTED_FEATURES = 'supported_features' SUPPORT_SET_SPEED = 1 SUPPORT_OSCILLATE = 2 +SUPPORT_DIRECTION = 4 SERVICE_SET_SPEED = 'set_speed' SERVICE_OSCILLATE = 'oscillate' +SERVICE_SET_DIRECTION = 'set_direction' SPEED_OFF = 'off' SPEED_LOW = 'low' @@ -43,15 +45,20 @@ SPEED_MED = 'med' SPEED_MEDIUM = 'medium' SPEED_HIGH = 'high' +DIRECTION_FORWARD = 'forward' +DIRECTION_REVERSE = 'reverse' + ATTR_SPEED = 'speed' ATTR_SPEED_LIST = 'speed_list' ATTR_OSCILLATING = 'oscillating' +ATTR_DIRECTION = 'direction' PROP_TO_ATTR = { 'speed': ATTR_SPEED, 'speed_list': ATTR_SPEED_LIST, 'oscillating': ATTR_OSCILLATING, 'supported_features': ATTR_SUPPORTED_FEATURES, + 'direction': ATTR_DIRECTION, } # type: dict FAN_SET_SPEED_SCHEMA = vol.Schema({ @@ -77,6 +84,11 @@ FAN_TOGGLE_SCHEMA = vol.Schema({ vol.Required(ATTR_ENTITY_ID): cv.entity_ids }) +FAN_SET_DIRECTION_SCHEMA = vol.Schema({ + vol.Required(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_DIRECTION): cv.string +}) # type: dict + _LOGGER = logging.getLogger(__name__) @@ -141,6 +153,18 @@ def set_speed(hass, entity_id: str=None, speed: str=None) -> None: hass.services.call(DOMAIN, SERVICE_SET_SPEED, data) +def set_direction(hass, entity_id: str=None, direction: str=None) -> None: + """Set direction for all or specified fan.""" + data = { + key: value for key, value in [ + (ATTR_ENTITY_ID, entity_id), + (ATTR_DIRECTION, direction), + ] if value is not None + } + + hass.services.call(DOMAIN, SERVICE_SET_DIRECTION, data) + + def setup(hass, config: dict) -> None: """Expose fan control via statemachine and services.""" component = EntityComponent( @@ -158,7 +182,8 @@ def setup(hass, config: dict) -> None: service_fun = None for service_def in [SERVICE_TURN_ON, SERVICE_TURN_OFF, - SERVICE_SET_SPEED, SERVICE_OSCILLATE]: + SERVICE_SET_SPEED, SERVICE_OSCILLATE, + SERVICE_SET_DIRECTION]: if service_def == service.service: service_fun = service_def break @@ -191,6 +216,10 @@ def setup(hass, config: dict) -> None: descriptions.get(SERVICE_OSCILLATE), schema=FAN_OSCILLATE_SCHEMA) + hass.services.register(DOMAIN, SERVICE_SET_DIRECTION, handle_fan_service, + descriptions.get(SERVICE_SET_DIRECTION), + schema=FAN_SET_DIRECTION_SCHEMA) + return True @@ -201,7 +230,11 @@ class FanEntity(ToggleEntity): def set_speed(self: ToggleEntity, speed: str) -> None: """Set the speed of the fan.""" - pass + raise NotImplementedError() + + def set_direction(self: ToggleEntity, direction: str) -> None: + """Set the direction of the fan.""" + raise NotImplementedError() def turn_on(self: ToggleEntity, speed: str=None, **kwargs) -> None: """Turn on the fan.""" @@ -218,14 +251,23 @@ class FanEntity(ToggleEntity): @property def is_on(self): """Return true if the entity is on.""" - return self.state_attributes.get(ATTR_SPEED, STATE_UNKNOWN) \ - not in [SPEED_OFF, STATE_UNKNOWN] + return self.speed not in [SPEED_OFF, STATE_UNKNOWN] + + @property + def speed(self) -> str: + """Return the current speed.""" + return None @property def speed_list(self: ToggleEntity) -> list: """Get the list of available speeds.""" return [] + @property + def current_direction(self) -> str: + """Return the current direction of the fan.""" + return None + @property def state_attributes(self: ToggleEntity) -> dict: """Return optional state attributes.""" diff --git a/homeassistant/components/fan/demo.py b/homeassistant/components/fan/demo.py index ba2deb83125..7ba6b4d67fb 100644 --- a/homeassistant/components/fan/demo.py +++ b/homeassistant/components/fan/demo.py @@ -7,14 +7,14 @@ https://home-assistant.io/components/demo/ from homeassistant.components.fan import (SPEED_LOW, SPEED_MED, SPEED_HIGH, FanEntity, SUPPORT_SET_SPEED, - SUPPORT_OSCILLATE) + SUPPORT_OSCILLATE, SUPPORT_DIRECTION) from homeassistant.const import STATE_OFF FAN_NAME = 'Living Room Fan' FAN_ENTITY_ID = 'fan.living_room_fan' -DEMO_SUPPORT = SUPPORT_SET_SPEED | SUPPORT_OSCILLATE +DEMO_SUPPORT = SUPPORT_SET_SPEED | SUPPORT_OSCILLATE | SUPPORT_DIRECTION # pylint: disable=unused-argument @@ -31,8 +31,9 @@ class DemoFan(FanEntity): def __init__(self, hass, name: str, initial_state: str) -> None: """Initialize the entity.""" self.hass = hass - self.speed = initial_state + self._speed = initial_state self.oscillating = False + self.direction = "forward" self._name = name @property @@ -45,6 +46,11 @@ class DemoFan(FanEntity): """No polling needed for a demo fan.""" return False + @property + def speed(self) -> str: + """Return the current speed.""" + return self._speed + @property def speed_list(self) -> list: """Get the list of available speeds.""" @@ -61,7 +67,12 @@ class DemoFan(FanEntity): def set_speed(self, speed: str) -> None: """Set the speed of the fan.""" - self.speed = speed + self._speed = speed + self.update_ha_state() + + def set_direction(self, direction: str) -> None: + """Set the direction of the fan.""" + self.direction = direction self.update_ha_state() def oscillate(self, oscillating: bool) -> None: @@ -69,6 +80,11 @@ class DemoFan(FanEntity): self.oscillating = oscillating self.update_ha_state() + @property + def current_direction(self) -> str: + """Fan direction.""" + return self.direction + @property def supported_features(self) -> int: """Flag supported features.""" diff --git a/homeassistant/components/fan/isy994.py b/homeassistant/components/fan/isy994.py index 2deb938d337..fd0690f4253 100644 --- a/homeassistant/components/fan/isy994.py +++ b/homeassistant/components/fan/isy994.py @@ -64,7 +64,11 @@ class ISYFanDevice(isy.ISYDevice, FanEntity): def __init__(self, node) -> None: """Initialize the ISY994 fan device.""" isy.ISYDevice.__init__(self, node) - self.speed = self.state + + @property + def speed(self) -> str: + """Return the current speed.""" + return self.state @property def state(self) -> str: diff --git a/homeassistant/components/fan/wink.py b/homeassistant/components/fan/wink.py new file mode 100644 index 00000000000..066dbfcb561 --- /dev/null +++ b/homeassistant/components/fan/wink.py @@ -0,0 +1,92 @@ +""" +Support for Wink fans. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/fan.wink/ +""" +import logging + +from homeassistant.components.fan import (FanEntity, SPEED_HIGH, + SPEED_LOW, SPEED_MEDIUM, + STATE_UNKNOWN) +from homeassistant.helpers.entity import ToggleEntity +from homeassistant.components.wink import WinkDevice + +_LOGGER = logging.getLogger(__name__) + +SPEED_LOWEST = "lowest" +SPEED_AUTO = "auto" + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the Wink platform.""" + import pywink + + add_devices(WinkFanDevice(fan, hass) for fan in pywink.get_fans()) + + +class WinkFanDevice(WinkDevice, FanEntity): + """Representation of a Wink fan.""" + + def __init__(self, wink, hass): + """Initialize the fan.""" + WinkDevice.__init__(self, wink, hass) + + def set_drection(self: ToggleEntity, direction: str) -> None: + """Set the direction of the fan.""" + self.wink.set_fan_direction(direction) + + def set_speed(self: ToggleEntity, speed: str) -> None: + """Set the speed of the fan.""" + self.wink.set_fan_speed(speed) + + def turn_on(self: ToggleEntity, speed: str=None, **kwargs) -> None: + """Turn on the fan.""" + self.wink.set_state(True) + + def turn_off(self: ToggleEntity, **kwargs) -> None: + """Turn off the fan.""" + self.wink.set_state(False) + + @property + def is_on(self): + """Return true if the entity is on.""" + return self.wink.state() + + @property + def speed(self) -> str: + """Return the current speed.""" + current_wink_speed = self.wink.current_fan_speed() + if SPEED_AUTO == current_wink_speed: + return SPEED_AUTO + if SPEED_LOWEST == current_wink_speed: + return SPEED_LOWEST + if SPEED_LOW == current_wink_speed: + return SPEED_LOW + if SPEED_MEDIUM == current_wink_speed: + return SPEED_MEDIUM + if SPEED_HIGH == current_wink_speed: + return SPEED_HIGH + return STATE_UNKNOWN + + @property + def current_direction(self): + """Return direction of the fan [forward, reverse].""" + return self.wink.current_fan_direction() + + @property + def speed_list(self: ToggleEntity) -> list: + """Get the list of available speeds.""" + wink_supported_speeds = self.wink.fan_speeds() + supported_speeds = [] + if SPEED_AUTO in wink_supported_speeds: + supported_speeds.append(SPEED_AUTO) + if SPEED_LOWEST in wink_supported_speeds: + supported_speeds.append(SPEED_LOWEST) + if SPEED_LOW in wink_supported_speeds: + supported_speeds.append(SPEED_LOW) + if SPEED_MEDIUM in wink_supported_speeds: + supported_speeds.append(SPEED_MEDIUM) + if SPEED_HIGH in wink_supported_speeds: + supported_speeds.append(SPEED_HIGH) + return supported_speeds diff --git a/homeassistant/components/wink.py b/homeassistant/components/wink.py index affeb376f5c..39c4c21aaa5 100644 --- a/homeassistant/components/wink.py +++ b/homeassistant/components/wink.py @@ -15,7 +15,7 @@ from homeassistant.const import ( from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['python-wink==0.11.0', 'pubnubsub-handler==0.0.5'] +REQUIREMENTS = ['python-wink==0.12.0', 'pubnubsub-handler==0.0.7'] _LOGGER = logging.getLogger(__name__) @@ -50,7 +50,8 @@ CONFIG_SCHEMA = vol.Schema({ }, extra=vol.ALLOW_EXTRA) WINK_COMPONENTS = [ - 'binary_sensor', 'sensor', 'light', 'switch', 'lock', 'cover', 'climate' + 'binary_sensor', 'sensor', 'light', 'switch', 'lock', 'cover', 'climate', + 'fan' ] diff --git a/requirements_all.txt b/requirements_all.txt index a9cdd888643..1b13985963a 100755 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -362,7 +362,7 @@ proliphix==0.4.1 psutil==5.0.1 # homeassistant.components.wink -pubnubsub-handler==0.0.5 +pubnubsub-handler==0.0.7 # homeassistant.components.notify.pushbullet pushbullet.py==0.10.0 @@ -512,7 +512,7 @@ python-twitch==1.3.0 python-vlc==1.1.2 # homeassistant.components.wink -python-wink==0.11.0 +python-wink==0.12.0 # homeassistant.components.device_tracker.trackr pytrackr==0.0.5 diff --git a/tests/components/fan/test_demo.py b/tests/components/fan/test_demo.py index 81e03c13705..2a0de549b99 100644 --- a/tests/components/fan/test_demo.py +++ b/tests/components/fan/test_demo.py @@ -55,6 +55,15 @@ class TestDemoFan(unittest.TestCase): self.hass.block_till_done() self.assertEqual(STATE_OFF, self.get_entity().state) + def test_set_direction(self): + """Test setting the direction of the device.""" + self.assertEqual(STATE_OFF, self.get_entity().state) + + fan.set_direction(self.hass, FAN_ENTITY_ID, fan.DIRECTION_REVERSE) + self.hass.block_till_done() + self.assertEqual(fan.DIRECTION_REVERSE, + self.get_entity().attributes.get('direction')) + def test_set_speed(self): """Test setting the speed of the device.""" self.assertEqual(STATE_OFF, self.get_entity().state)