Map bond fan speeds to standard HA speeds (#37808)

This commit is contained in:
Eugene Prystupa 2020-07-13 19:00:05 -04:00 committed by GitHub
parent 9e8e5c37f4
commit 95e72b4c4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 88 additions and 20 deletions

View File

@ -1,4 +1,5 @@
"""Support for Bond fans.""" """Support for Bond fans."""
import math
from typing import Any, Callable, List, Optional from typing import Any, Callable, List, Optional
from bond import DeviceTypes, Directions from bond import DeviceTypes, Directions
@ -67,12 +68,15 @@ class BondFan(BondEntity, FanEntity):
@property @property
def speed(self) -> Optional[str]: def speed(self) -> Optional[str]:
"""Return the current speed.""" """Return the current speed."""
if self._power is None:
return None
if self._power == 0: if self._power == 0:
return SPEED_OFF return SPEED_OFF
if not self._power or not self._speed:
return None
return self.speed_list[self._speed] if self._speed is not None else None # map 1..max_speed Bond speed to 1..3 HA speed
max_speed = self._device.props.get("max_speed", 3)
ha_speed = math.ceil(self._speed * (len(self.speed_list) - 1) / max_speed)
return self.speed_list[ha_speed]
@property @property
def speed_list(self) -> list: def speed_list(self) -> list:
@ -99,8 +103,14 @@ class BondFan(BondEntity, FanEntity):
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."""
speed_index = self.speed_list.index(speed) max_speed = self._device.props.get("max_speed", 3)
self._hub.bond.setSpeed(self._device.device_id, speed=speed_index) if speed == SPEED_LOW:
bond_speed = 1
elif speed == SPEED_HIGH:
bond_speed = max_speed
else:
bond_speed = math.ceil(max_speed / 2)
self._hub.bond.setSpeed(self._device.device_id, speed=bond_speed)
def turn_on(self, speed: Optional[str] = None, **kwargs) -> None: def turn_on(self, speed: Optional[str] = None, **kwargs) -> None:
"""Turn on the fan.""" """Turn on the fan."""

View File

@ -8,9 +8,10 @@ from bond import Actions, Bond
class BondDevice: class BondDevice:
"""Helper device class to hold ID and attributes together.""" """Helper device class to hold ID and attributes together."""
def __init__(self, device_id: str, attrs: dict): def __init__(self, device_id: str, attrs: dict, props: dict):
"""Create a helper device from ID and attributes returned by API.""" """Create a helper device from ID and attributes returned by API."""
self.device_id = device_id self.device_id = device_id
self.props = props
self._attrs = attrs self._attrs = attrs
@property @property
@ -67,7 +68,11 @@ class BondHub:
"""Fetch all available devices using Bond API.""" """Fetch all available devices using Bond API."""
device_ids = self.bond.getDeviceIds() device_ids = self.bond.getDeviceIds()
devices = [ devices = [
BondDevice(device_id, self.bond.getDevice(device_id)) BondDevice(
device_id,
self.bond.getDevice(device_id),
self.bond.getProperties(device_id),
)
for device_id in device_ids for device_id in device_ids
] ]
return devices return devices

View File

@ -28,9 +28,16 @@ async def setup_bond_entity(
async def setup_platform( async def setup_platform(
hass: core.HomeAssistant, platform: str, discovered_device: Dict[str, Any] hass: core.HomeAssistant,
platform: str,
discovered_device: Dict[str, Any],
bond_device_id: str = "bond-device-id",
props: Dict[str, Any] = None,
): ):
"""Set up the specified Bond platform.""" """Set up the specified Bond platform."""
if not props:
props = {}
mock_entry = MockConfigEntry( mock_entry = MockConfigEntry(
domain=BOND_DOMAIN, domain=BOND_DOMAIN,
data={CONF_HOST: "1.1.1.1", CONF_ACCESS_TOKEN: "test-token"}, data={CONF_HOST: "1.1.1.1", CONF_ACCESS_TOKEN: "test-token"},
@ -41,11 +48,13 @@ async def setup_platform(
"homeassistant.components.bond.Bond.getVersion", return_value=MOCK_HUB_VERSION "homeassistant.components.bond.Bond.getVersion", return_value=MOCK_HUB_VERSION
), patch( ), patch(
"homeassistant.components.bond.Bond.getDeviceIds", "homeassistant.components.bond.Bond.getDeviceIds",
return_value=["bond-device-id"], return_value=[bond_device_id],
), patch( ), patch(
"homeassistant.components.bond.Bond.getDevice", return_value=discovered_device "homeassistant.components.bond.Bond.getDevice", return_value=discovered_device
), patch( ), patch(
"homeassistant.components.bond.Bond.getDeviceState", return_value={} "homeassistant.components.bond.Bond.getDeviceState", return_value={}
), patch(
"homeassistant.components.bond.Bond.getProperties", return_value=props
): ):
assert await async_setup_component(hass, BOND_DOMAIN, {}) assert await async_setup_component(hass, BOND_DOMAIN, {})
await hass.async_block_till_done() await hass.async_block_till_done()

View File

@ -7,6 +7,7 @@ from homeassistant import core
from homeassistant.components import fan from homeassistant.components import fan
from homeassistant.components.fan import ( from homeassistant.components.fan import (
ATTR_DIRECTION, ATTR_DIRECTION,
ATTR_SPEED_LIST,
DIRECTION_FORWARD, DIRECTION_FORWARD,
DIRECTION_REVERSE, DIRECTION_REVERSE,
DOMAIN as FAN_DOMAIN, DOMAIN as FAN_DOMAIN,
@ -16,10 +17,10 @@ from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_O
from homeassistant.helpers.entity_registry import EntityRegistry from homeassistant.helpers.entity_registry import EntityRegistry
from homeassistant.util import utcnow from homeassistant.util import utcnow
from ...common import async_fire_time_changed
from .common import setup_platform from .common import setup_platform
from tests.async_mock import patch from tests.async_mock import patch
from tests.common import async_fire_time_changed
def ceiling_fan(name: str): def ceiling_fan(name: str):
@ -31,6 +32,17 @@ def ceiling_fan(name: str):
} }
async def turn_fan_on(hass: core.HomeAssistant, fan_id: str, speed: str) -> None:
"""Turn the fan on at the specified speed."""
await hass.services.async_call(
FAN_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: fan_id, fan.ATTR_SPEED: speed},
blocking=True,
)
await hass.async_block_till_done()
async def test_entity_registry(hass: core.HomeAssistant): async def test_entity_registry(hass: core.HomeAssistant):
"""Tests that the devices are registered in the entity registry.""" """Tests that the devices are registered in the entity registry."""
await setup_platform(hass, FAN_DOMAIN, ceiling_fan("name-1")) await setup_platform(hass, FAN_DOMAIN, ceiling_fan("name-1"))
@ -39,6 +51,43 @@ async def test_entity_registry(hass: core.HomeAssistant):
assert [key for key in registry.entities] == ["fan.name_1"] assert [key for key in registry.entities] == ["fan.name_1"]
async def test_entity_non_standard_speed_list(hass: core.HomeAssistant):
"""Tests that the device is registered with custom speed list if number of supported speeds differs form 3."""
await setup_platform(
hass,
FAN_DOMAIN,
ceiling_fan("name-1"),
bond_device_id="test-device-id",
props={"max_speed": 6},
)
actual_speeds = hass.states.get("fan.name_1").attributes[ATTR_SPEED_LIST]
assert actual_speeds == [
fan.SPEED_OFF,
fan.SPEED_LOW,
fan.SPEED_MEDIUM,
fan.SPEED_HIGH,
]
with patch("homeassistant.components.bond.Bond.turnOn"), patch(
"homeassistant.components.bond.Bond.setSpeed"
) as mock_set_speed_low:
await turn_fan_on(hass, "fan.name_1", fan.SPEED_LOW)
mock_set_speed_low.assert_called_once_with("test-device-id", speed=1)
with patch("homeassistant.components.bond.Bond.turnOn"), patch(
"homeassistant.components.bond.Bond.setSpeed"
) as mock_set_speed_medium:
await turn_fan_on(hass, "fan.name_1", fan.SPEED_MEDIUM)
mock_set_speed_medium.assert_called_once_with("test-device-id", speed=3)
with patch("homeassistant.components.bond.Bond.turnOn"), patch(
"homeassistant.components.bond.Bond.setSpeed"
) as mock_set_speed_high:
await turn_fan_on(hass, "fan.name_1", fan.SPEED_HIGH)
mock_set_speed_high.assert_called_once_with("test-device-id", speed=6)
async def test_turn_on_fan(hass: core.HomeAssistant): async def test_turn_on_fan(hass: core.HomeAssistant):
"""Tests that turn on command delegates to API.""" """Tests that turn on command delegates to API."""
await setup_platform(hass, FAN_DOMAIN, ceiling_fan("name-1")) await setup_platform(hass, FAN_DOMAIN, ceiling_fan("name-1"))
@ -46,16 +95,10 @@ async def test_turn_on_fan(hass: core.HomeAssistant):
with patch("homeassistant.components.bond.Bond.turnOn") as mock_turn_on, patch( with patch("homeassistant.components.bond.Bond.turnOn") as mock_turn_on, patch(
"homeassistant.components.bond.Bond.setSpeed" "homeassistant.components.bond.Bond.setSpeed"
) as mock_set_speed: ) as mock_set_speed:
await hass.services.async_call( await turn_fan_on(hass, "fan.name_1", fan.SPEED_LOW)
FAN_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: "fan.name_1", fan.ATTR_SPEED: fan.SPEED_LOW},
blocking=True,
)
await hass.async_block_till_done()
mock_set_speed.assert_called_once() mock_set_speed.assert_called_once()
mock_turn_on.assert_called_once() mock_turn_on.assert_called_once()
async def test_turn_off_fan(hass: core.HomeAssistant): async def test_turn_off_fan(hass: core.HomeAssistant):
@ -67,7 +110,8 @@ async def test_turn_off_fan(hass: core.HomeAssistant):
FAN_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: "fan.name_1"}, blocking=True, FAN_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: "fan.name_1"}, blocking=True,
) )
await hass.async_block_till_done() await hass.async_block_till_done()
mock_turn_off.assert_called_once()
mock_turn_off.assert_called_once()
async def test_update_reports_fan_on(hass: core.HomeAssistant): async def test_update_reports_fan_on(hass: core.HomeAssistant):