diff --git a/homeassistant/components/bond/fan.py b/homeassistant/components/bond/fan.py index a08b46679d0..f6792e2c1bb 100644 --- a/homeassistant/components/bond/fan.py +++ b/homeassistant/components/bond/fan.py @@ -1,13 +1,16 @@ """Support for Bond fans.""" from typing import Any, Callable, List, Optional -from bond import DeviceTypes +from bond import DeviceTypes, Directions from homeassistant.components.fan import ( + DIRECTION_FORWARD, + DIRECTION_REVERSE, SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, SPEED_OFF, + SUPPORT_DIRECTION, SUPPORT_SET_SPEED, FanEntity, ) @@ -48,13 +51,17 @@ class BondFan(BondEntity, FanEntity): self._power: Optional[bool] = None self._speed: Optional[int] = None + self._direction: Optional[int] = None @property def supported_features(self) -> int: """Flag supported features.""" features = 0 - if self._device.supports_command("SetSpeed"): + if self._device.supports_speed(): features |= SUPPORT_SET_SPEED + if self._device.supports_direction(): + features |= SUPPORT_DIRECTION + return features @property @@ -72,11 +79,23 @@ class BondFan(BondEntity, FanEntity): """Get the list of available speeds.""" 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): """Fetch assumed state of the fan from the hub using API.""" state: dict = self._hub.bond.getDeviceState(self._device.device_id) self._power = state.get("power") self._speed = state.get("speed") + self._direction = state.get("direction") def set_speed(self, speed: str) -> None: """Set the desired speed for the fan.""" @@ -92,3 +111,10 @@ class BondFan(BondEntity, FanEntity): def turn_off(self, **kwargs: Any) -> None: """Turn the fan off.""" 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) diff --git a/homeassistant/components/bond/utils.py b/homeassistant/components/bond/utils.py index 58284be5dc7..c019b2cc160 100644 --- a/homeassistant/components/bond/utils.py +++ b/homeassistant/components/bond/utils.py @@ -2,7 +2,7 @@ from typing import List, Optional -from bond import Bond +from bond import Actions, Bond class BondDevice: @@ -23,10 +23,24 @@ class BondDevice: """Get the type of this device.""" return self._attrs["type"] - def supports_command(self, command: str) -> bool: - """Return True if this device supports specified command.""" + def supports_speed(self) -> bool: + """Return True if this device supports any of the speed related commands.""" 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: diff --git a/tests/components/bond/test_fan.py b/tests/components/bond/test_fan.py index 17818042d4b..4eb11521bfb 100644 --- a/tests/components/bond/test_fan.py +++ b/tests/components/bond/test_fan.py @@ -1,11 +1,17 @@ """Tests for the Bond fan device.""" from datetime import timedelta -from bond import DeviceTypes +from bond import DeviceTypes, Directions from homeassistant import core 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.helpers.entity_registry import EntityRegistry from homeassistant.util import utcnow @@ -21,7 +27,7 @@ def ceiling_fan(name: str): return { "name": name, "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() 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()