Add support for fan direction in bond integration (#37789)

* Add support for fan direction in bond integration

* Add support for fan direction (PR feedback)
This commit is contained in:
Eugene Prystupa 2020-07-12 16:30:24 -04:00 committed by GitHub
parent 53844488d8
commit e9440c49d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 98 additions and 9 deletions

View File

@ -1,13 +1,16 @@
"""Support for Bond fans.""" """Support for Bond fans."""
from typing import Any, Callable, List, Optional from typing import Any, Callable, List, Optional
from bond import DeviceTypes from bond import DeviceTypes, Directions
from homeassistant.components.fan import ( from homeassistant.components.fan import (
DIRECTION_FORWARD,
DIRECTION_REVERSE,
SPEED_HIGH, SPEED_HIGH,
SPEED_LOW, SPEED_LOW,
SPEED_MEDIUM, SPEED_MEDIUM,
SPEED_OFF, SPEED_OFF,
SUPPORT_DIRECTION,
SUPPORT_SET_SPEED, SUPPORT_SET_SPEED,
FanEntity, FanEntity,
) )
@ -48,13 +51,17 @@ class BondFan(BondEntity, FanEntity):
self._power: Optional[bool] = None self._power: Optional[bool] = None
self._speed: Optional[int] = None self._speed: Optional[int] = None
self._direction: Optional[int] = None
@property @property
def supported_features(self) -> int: def supported_features(self) -> int:
"""Flag supported features.""" """Flag supported features."""
features = 0 features = 0
if self._device.supports_command("SetSpeed"): if self._device.supports_speed():
features |= SUPPORT_SET_SPEED features |= SUPPORT_SET_SPEED
if self._device.supports_direction():
features |= SUPPORT_DIRECTION
return features return features
@property @property
@ -72,11 +79,23 @@ class BondFan(BondEntity, FanEntity):
"""Get the list of available speeds.""" """Get the list of available speeds."""
return [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] return [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
@property
def current_direction(self) -> Optional[str]:
"""Return fan rotation direction."""
direction = None
if self._direction == Directions.FORWARD:
direction = DIRECTION_FORWARD
elif self._direction == Directions.REVERSE:
direction = DIRECTION_REVERSE
return direction
def update(self): def update(self):
"""Fetch assumed state of the fan from the hub using API.""" """Fetch assumed state of the fan from the hub using API."""
state: dict = self._hub.bond.getDeviceState(self._device.device_id) state: dict = self._hub.bond.getDeviceState(self._device.device_id)
self._power = state.get("power") self._power = state.get("power")
self._speed = state.get("speed") self._speed = state.get("speed")
self._direction = state.get("direction")
def set_speed(self, speed: str) -> None: def set_speed(self, speed: str) -> None:
"""Set the desired speed for the fan.""" """Set the desired speed for the fan."""
@ -92,3 +111,10 @@ class BondFan(BondEntity, FanEntity):
def turn_off(self, **kwargs: Any) -> None: def turn_off(self, **kwargs: Any) -> None:
"""Turn the fan off.""" """Turn the fan off."""
self._hub.bond.turnOff(self._device.device_id) self._hub.bond.turnOff(self._device.device_id)
def set_direction(self, direction: str) -> None:
"""Set fan rotation direction."""
bond_direction = (
Directions.REVERSE if direction == DIRECTION_REVERSE else Directions.FORWARD
)
self._hub.bond.setDirection(self._device.device_id, bond_direction)

View File

@ -2,7 +2,7 @@
from typing import List, Optional from typing import List, Optional
from bond import Bond from bond import Actions, Bond
class BondDevice: class BondDevice:
@ -23,10 +23,24 @@ class BondDevice:
"""Get the type of this device.""" """Get the type of this device."""
return self._attrs["type"] return self._attrs["type"]
def supports_command(self, command: str) -> bool: def supports_speed(self) -> bool:
"""Return True if this device supports specified command.""" """Return True if this device supports any of the speed related commands."""
actions: List[str] = self._attrs["actions"] actions: List[str] = self._attrs["actions"]
return command in actions return len([action for action in actions if action in [Actions.SET_SPEED]]) > 0
def supports_direction(self) -> bool:
"""Return True if this device supports any of the direction related commands."""
actions: List[str] = self._attrs["actions"]
return (
len(
[
action
for action in actions
if action in [Actions.SET_DIRECTION, Actions.TOGGLE_DIRECTION]
]
)
> 0
)
class BondHub: class BondHub:

View File

@ -1,11 +1,17 @@
"""Tests for the Bond fan device.""" """Tests for the Bond fan device."""
from datetime import timedelta from datetime import timedelta
from bond import DeviceTypes from bond import DeviceTypes, Directions
from homeassistant import core from homeassistant import core
from homeassistant.components import fan from homeassistant.components import fan
from homeassistant.components.fan import DOMAIN as FAN_DOMAIN from homeassistant.components.fan import (
ATTR_DIRECTION,
DIRECTION_FORWARD,
DIRECTION_REVERSE,
DOMAIN as FAN_DOMAIN,
SERVICE_SET_DIRECTION,
)
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON
from homeassistant.helpers.entity_registry import EntityRegistry from homeassistant.helpers.entity_registry import EntityRegistry
from homeassistant.util import utcnow from homeassistant.util import utcnow
@ -21,7 +27,7 @@ def ceiling_fan(name: str):
return { return {
"name": name, "name": name,
"type": DeviceTypes.CEILING_FAN, "type": DeviceTypes.CEILING_FAN,
"actions": ["SetSpeed"], "actions": ["SetSpeed", "SetDirection"],
} }
@ -90,3 +96,46 @@ async def test_update_reports_fan_off(hass: core.HomeAssistant):
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get("fan.name_1").state == "off" assert hass.states.get("fan.name_1").state == "off"
async def test_update_reports_direction_forward(hass: core.HomeAssistant):
"""Tests that update command sets correct direction when Bond API reports fan direction is forward."""
await setup_platform(hass, FAN_DOMAIN, ceiling_fan("name-1"))
with patch(
"homeassistant.components.bond.Bond.getDeviceState",
return_value={"direction": Directions.FORWARD},
):
async_fire_time_changed(hass, utcnow() + timedelta(seconds=30))
await hass.async_block_till_done()
assert hass.states.get("fan.name_1").attributes[ATTR_DIRECTION] == DIRECTION_FORWARD
async def test_update_reports_direction_reverse(hass: core.HomeAssistant):
"""Tests that update command sets correct direction when Bond API reports fan direction is reverse."""
await setup_platform(hass, FAN_DOMAIN, ceiling_fan("name-1"))
with patch(
"homeassistant.components.bond.Bond.getDeviceState",
return_value={"direction": Directions.REVERSE},
):
async_fire_time_changed(hass, utcnow() + timedelta(seconds=30))
await hass.async_block_till_done()
assert hass.states.get("fan.name_1").attributes[ATTR_DIRECTION] == DIRECTION_REVERSE
async def test_set_fan_direction(hass: core.HomeAssistant):
"""Tests that set direction command delegates to API."""
await setup_platform(hass, FAN_DOMAIN, ceiling_fan("name-1"))
with patch("homeassistant.components.bond.Bond.setDirection") as mock_set_direction:
await hass.services.async_call(
FAN_DOMAIN,
SERVICE_SET_DIRECTION,
{ATTR_ENTITY_ID: "fan.name_1", ATTR_DIRECTION: DIRECTION_FORWARD},
blocking=True,
)
await hass.async_block_till_done()
mock_set_direction.assert_called_once()