Refactor bond integration to be completely async (#38066)

This commit is contained in:
Eugene Prystupa 2020-07-22 18:22:25 -07:00 committed by GitHub
parent 15fe727f2c
commit 3480fb6996
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 251 additions and 259 deletions

View file

@ -1,7 +1,7 @@
"""The Bond integration."""
import asyncio
from bond import Bond
from bond_api import Bond
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST
@ -25,9 +25,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
host = entry.data[CONF_HOST]
token = entry.data[CONF_ACCESS_TOKEN]
bond = Bond(bondIp=host, bondToken=token)
bond = Bond(host=host, token=token)
hub = BondHub(bond)
await hass.async_add_executor_job(hub.setup)
await hub.setup()
hass.data[DOMAIN][entry.entry_id] = hub
device_registry = await dr.async_get_registry(hass)

View file

@ -1,12 +1,11 @@
"""Config flow for Bond integration."""
from json import JSONDecodeError
import logging
from bond import Bond
from requests.exceptions import ConnectionError as RequestConnectionError
from aiohttp import ClientConnectionError, ClientResponseError
from bond_api import Bond
import voluptuous as vol
from homeassistant import config_entries, core, exceptions
from homeassistant import config_entries, exceptions
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST
from .const import DOMAIN # pylint:disable=unused-import
@ -18,24 +17,20 @@ DATA_SCHEMA = vol.Schema(
)
async def validate_input(hass: core.HomeAssistant, data):
async def validate_input(data):
"""Validate the user input allows us to connect."""
def authenticate(bond_hub: Bond) -> bool:
try:
bond_hub.getDeviceIds()
return True
except RequestConnectionError:
raise CannotConnect
except JSONDecodeError:
return False
try:
bond = Bond(data[CONF_HOST], data[CONF_ACCESS_TOKEN])
await bond.devices()
except ClientConnectionError:
raise CannotConnect
except ClientResponseError as error:
if error.status == 401:
raise InvalidAuth
raise
bond = Bond(data[CONF_HOST], data[CONF_ACCESS_TOKEN])
if not await hass.async_add_executor_job(authenticate, bond):
raise InvalidAuth
# Return info that you want to store in the config entry.
# Return info to be stored in the config entry.
return {"title": data[CONF_HOST]}
@ -50,7 +45,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
errors = {}
if user_input is not None:
try:
info = await validate_input(self.hass, user_input)
info = await validate_input(user_input)
except CannotConnect:
errors["base"] = "cannot_connect"
except InvalidAuth:

View file

@ -1,7 +1,7 @@
"""Support for Bond covers."""
from typing import Any, Callable, List, Optional
from bond import DeviceTypes
from bond_api import Action, DeviceType
from homeassistant.components.cover import DEVICE_CLASS_SHADE, CoverEntity
from homeassistant.config_entries import ConfigEntry
@ -24,7 +24,7 @@ async def async_setup_entry(
covers = [
BondCover(hub, device)
for device in hub.devices
if device.type == DeviceTypes.MOTORIZED_SHADES
if device.type == DeviceType.MOTORIZED_SHADES
]
async_add_entities(covers, True)
@ -44,9 +44,9 @@ class BondCover(BondEntity, CoverEntity):
"""Get device class."""
return DEVICE_CLASS_SHADE
def update(self):
async def async_update(self):
"""Fetch assumed state of the cover from the hub using API."""
state: dict = self._hub.bond.getDeviceState(self._device.device_id)
state: dict = await self._hub.bond.device_state(self._device.device_id)
cover_open = state.get("open")
self._closed = True if cover_open == 0 else False if cover_open == 1 else None
@ -55,14 +55,14 @@ class BondCover(BondEntity, CoverEntity):
"""Return if the cover is closed or not."""
return self._closed
def open_cover(self, **kwargs: Any) -> None:
async def async_open_cover(self, **kwargs: Any) -> None:
"""Open the cover."""
self._hub.bond.open(self._device.device_id)
await self._hub.bond.action(self._device.device_id, Action.open())
def close_cover(self, **kwargs: Any) -> None:
async def async_close_cover(self, **kwargs: Any) -> None:
"""Close cover."""
self._hub.bond.close(self._device.device_id)
await self._hub.bond.action(self._device.device_id, Action.close())
def stop_cover(self, **kwargs):
async def async_stop_cover(self, **kwargs):
"""Hold cover."""
self._hub.bond.hold(self._device.device_id)
await self._hub.bond.action(self._device.device_id, Action.hold())

View file

@ -2,7 +2,7 @@
import math
from typing import Any, Callable, List, Optional
from bond import DeviceTypes, Directions
from bond_api import Action, DeviceType, Direction
from homeassistant.components.fan import (
DIRECTION_FORWARD,
@ -33,9 +33,7 @@ async def async_setup_entry(
hub: BondHub = hass.data[DOMAIN][entry.entry_id]
fans = [
BondFan(hub, device)
for device in hub.devices
if device.type == DeviceTypes.CEILING_FAN
BondFan(hub, device) for device in hub.devices if DeviceType.is_fan(device.type)
]
async_add_entities(fans, True)
@ -85,21 +83,21 @@ class BondFan(BondEntity, FanEntity):
def current_direction(self) -> Optional[str]:
"""Return fan rotation direction."""
direction = None
if self._direction == Directions.FORWARD:
if self._direction == Direction.FORWARD:
direction = DIRECTION_FORWARD
elif self._direction == Directions.REVERSE:
elif self._direction == Direction.REVERSE:
direction = DIRECTION_REVERSE
return direction
def update(self):
async def async_update(self):
"""Fetch assumed state of the fan from the hub using API."""
state: dict = self._hub.bond.getDeviceState(self._device.device_id)
state: dict = await self._hub.bond.device_state(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:
async def async_set_speed(self, speed: str) -> None:
"""Set the desired speed for the fan."""
max_speed = self._device.props.get("max_speed", 3)
if speed == SPEED_LOW:
@ -108,21 +106,27 @@ class BondFan(BondEntity, FanEntity):
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:
await self._hub.bond.action(
self._device.device_id, Action.set_speed(bond_speed)
)
async def async_turn_on(self, speed: Optional[str] = None, **kwargs) -> None:
"""Turn on the fan."""
if speed is not None:
self.set_speed(speed)
self._hub.bond.turnOn(self._device.device_id)
await self.async_set_speed(speed)
else:
await self._hub.bond.action(self._device.device_id, Action.turn_on())
def turn_off(self, **kwargs: Any) -> None:
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the fan off."""
self._hub.bond.turnOff(self._device.device_id)
await self._hub.bond.action(self._device.device_id, Action.turn_off())
def set_direction(self, direction: str) -> None:
async def async_set_direction(self, direction: str):
"""Set fan rotation direction."""
bond_direction = (
Directions.REVERSE if direction == DIRECTION_REVERSE else Directions.FORWARD
Direction.REVERSE if direction == DIRECTION_REVERSE else Direction.FORWARD
)
await self._hub.bond.action(
self._device.device_id, Action.set_direction(bond_direction)
)
self._hub.bond.setDirection(self._device.device_id, bond_direction)

View file

@ -1,7 +1,7 @@
"""Support for Bond lights."""
from typing import Any, Callable, List, Optional
from bond import DeviceTypes
from bond_api import Action, DeviceType
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
@ -29,13 +29,13 @@ async def async_setup_entry(
lights: List[Entity] = [
BondLight(hub, device)
for device in hub.devices
if device.type == DeviceTypes.CEILING_FAN and device.supports_light()
if DeviceType.is_fan(device.type) and device.supports_light()
]
fireplaces: List[Entity] = [
BondFireplace(hub, device)
for device in hub.devices
if device.type == DeviceTypes.FIREPLACE
if DeviceType.is_fireplace(device.type)
]
async_add_entities(lights + fireplaces, True)
@ -55,18 +55,18 @@ class BondLight(BondEntity, LightEntity):
"""Return if light is currently on."""
return self._light == 1
def update(self):
async def async_update(self):
"""Fetch assumed state of the light from the hub using API."""
state: dict = self._hub.bond.getDeviceState(self._device.device_id)
state: dict = await self._hub.bond.device_state(self._device.device_id)
self._light = state.get("light")
def turn_on(self, **kwargs: Any) -> None:
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on the light."""
self._hub.bond.turnLightOn(self._device.device_id)
await self._hub.bond.action(self._device.device_id, Action.turn_light_on())
def turn_off(self, **kwargs: Any) -> None:
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off the light."""
self._hub.bond.turnLightOff(self._device.device_id)
await self._hub.bond.action(self._device.device_id, Action.turn_light_off())
class BondFireplace(BondEntity, LightEntity):
@ -90,18 +90,18 @@ class BondFireplace(BondEntity, LightEntity):
"""Return True if power is on."""
return self._power == 1
def turn_on(self, **kwargs: Any) -> None:
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the fireplace on."""
self._hub.bond.turnOn(self._device.device_id)
brightness = kwargs.get(ATTR_BRIGHTNESS)
if brightness:
flame = round((brightness * 100) / 255)
self._hub.bond.setFlame(self._device.device_id, flame)
await self._hub.bond.action(self._device.device_id, Action.set_flame(flame))
else:
await self._hub.bond.action(self._device.device_id, Action.turn_on())
def turn_off(self, **kwargs: Any) -> None:
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the fireplace off."""
self._hub.bond.turnOff(self._device.device_id)
await self._hub.bond.action(self._device.device_id, Action.turn_off())
@property
def brightness(self):
@ -113,8 +113,8 @@ class BondFireplace(BondEntity, LightEntity):
"""Show fireplace icon for the entity."""
return "mdi:fireplace" if self._power == 1 else "mdi:fireplace-off"
def update(self):
async def async_update(self):
"""Fetch assumed state of the device from the hub using API."""
state: dict = self._hub.bond.getDeviceState(self._device.device_id)
state: dict = await self._hub.bond.device_state(self._device.device_id)
self._power = state.get("power")
self._flame = state.get("flame")

View file

@ -4,7 +4,7 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/bond",
"requirements": [
"bond-home==0.0.9"
"bond-api==0.1.4"
],
"codeowners": [
"@prystupa"

View file

@ -1,13 +1,13 @@
"""Support for Bond generic devices."""
from typing import Any, Callable, List, Optional
from bond import DeviceTypes
from bond_api import Action, DeviceType
from homeassistant.components.switch import SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import Entity
from ..switch import SwitchEntity
from .const import DOMAIN
from .entity import BondEntity
from .utils import BondDevice, BondHub
@ -24,7 +24,7 @@ async def async_setup_entry(
switches = [
BondSwitch(hub, device)
for device in hub.devices
if device.type == DeviceTypes.GENERIC_DEVICE
if DeviceType.is_generic(device.type)
]
async_add_entities(switches, True)
@ -44,15 +44,15 @@ class BondSwitch(BondEntity, SwitchEntity):
"""Return True if power is on."""
return self._power == 1
def turn_on(self, **kwargs: Any) -> None:
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the device on."""
self._hub.bond.turnOn(self._device.device_id)
await self._hub.bond.action(self._device.device_id, Action.turn_on())
def turn_off(self, **kwargs: Any) -> None:
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the device off."""
self._hub.bond.turnOff(self._device.device_id)
await self._hub.bond.action(self._device.device_id, Action.turn_off())
def update(self):
async def async_update(self):
"""Fetch assumed state of the device from the hub using API."""
state: dict = self._hub.bond.getDeviceState(self._device.device_id)
state: dict = await self._hub.bond.device_state(self._device.device_id)
self._power = state.get("power")

View file

@ -2,7 +2,7 @@
from typing import List, Optional
from bond import Actions, Bond
from bond_api import Action, Bond
class BondDevice:
@ -27,18 +27,12 @@ class BondDevice:
def supports_speed(self) -> bool:
"""Return True if this device supports any of the speed related commands."""
actions: List[str] = self._attrs["actions"]
return bool([action for action in actions if action in [Actions.SET_SPEED]])
return bool([action for action in actions if action in [Action.SET_SPEED]])
def supports_direction(self) -> bool:
"""Return True if this device supports any of the direction related commands."""
actions: List[str] = self._attrs["actions"]
return bool(
[
action
for action in actions
if action in [Actions.SET_DIRECTION, Actions.TOGGLE_DIRECTION]
]
)
return bool([action for action in actions if action in [Action.SET_DIRECTION]])
def supports_light(self) -> bool:
"""Return True if this device supports any of the light related commands."""
@ -47,7 +41,7 @@ class BondDevice:
[
action
for action in actions
if action in [Actions.TURN_LIGHT_ON, Actions.TOGGLE_LIGHT]
if action in [Action.TURN_LIGHT_ON, Action.TURN_LIGHT_OFF]
]
)
@ -61,17 +55,17 @@ class BondHub:
self._version: Optional[dict] = None
self._devices: Optional[List[BondDevice]] = None
def setup(self):
async def setup(self):
"""Read hub version information."""
self._version = self.bond.getVersion()
self._version = await self.bond.version()
# Fetch all available devices using Bond API.
device_ids = self.bond.getDeviceIds()
device_ids = await self.bond.devices()
self._devices = [
BondDevice(
device_id,
self.bond.getDevice(device_id),
self.bond.getProperties(device_id),
await self.bond.device(device_id),
await self.bond.device_properties(device_id),
)
for device_id in device_ids
]

View file

@ -356,7 +356,7 @@ blockchain==1.4.4
bomradarloop==0.1.4
# homeassistant.components.bond
bond-home==0.0.9
bond-api==0.1.4
# homeassistant.components.amazon_polly
# homeassistant.components.route53

View file

@ -181,7 +181,7 @@ blinkpy==0.15.1
bomradarloop==0.1.4
# homeassistant.components.bond
bond-home==0.0.9
bond-api==0.1.4
# homeassistant.components.braviatv
bravia-tv==1.0.6

View file

@ -21,9 +21,7 @@ async def setup_bond_entity(
config_entry.add_to_hass(hass)
with patch(
"homeassistant.components.bond.Bond.getVersion", return_value=hub_version
):
with patch("homeassistant.components.bond.Bond.version", return_value=hub_version):
return await hass.config_entries.async_setup(config_entry.entry_id)
@ -45,13 +43,15 @@ async def setup_platform(
mock_entry.add_to_hass(hass)
with patch("homeassistant.components.bond.PLATFORMS", [platform]), patch(
"homeassistant.components.bond.Bond.getVersion", return_value=MOCK_HUB_VERSION
"homeassistant.components.bond.Bond.version", return_value=MOCK_HUB_VERSION
), patch_bond_device_ids(return_value=[bond_device_id],), patch(
"homeassistant.components.bond.Bond.getDevice", return_value=discovered_device
"homeassistant.components.bond.Bond.device", return_value=discovered_device
), patch_bond_device_state(
return_value={}
), patch(
"homeassistant.components.bond.Bond.getProperties", return_value=props
"homeassistant.components.bond.Bond.device_properties", return_value=props
), patch(
"homeassistant.components.bond.Bond.device_state", return_value={}
):
assert await async_setup_component(hass, BOND_DOMAIN, {})
await hass.async_block_till_done()
@ -60,70 +60,25 @@ async def setup_platform(
def patch_bond_device_ids(return_value=None):
"""Patch Bond API getDeviceIds command."""
"""Patch Bond API devices command."""
if return_value is None:
return_value = []
return patch(
"homeassistant.components.bond.Bond.getDeviceIds", return_value=return_value,
"homeassistant.components.bond.Bond.devices", return_value=return_value,
)
def patch_bond_turn_on():
"""Patch Bond API turnOn command."""
return patch("homeassistant.components.bond.Bond.turnOn")
def patch_bond_turn_off():
"""Patch Bond API turnOff command."""
return patch("homeassistant.components.bond.Bond.turnOff")
def patch_bond_set_speed():
"""Patch Bond API setSpeed command."""
return patch("homeassistant.components.bond.Bond.setSpeed")
def patch_bond_set_flame():
"""Patch Bond API setFlame command."""
return patch("homeassistant.components.bond.Bond.setFlame")
def patch_bond_open():
"""Patch Bond API open command."""
return patch("homeassistant.components.bond.Bond.open")
def patch_bond_close():
"""Patch Bond API close command."""
return patch("homeassistant.components.bond.Bond.close")
def patch_bond_hold():
"""Patch Bond API hold command."""
return patch("homeassistant.components.bond.Bond.hold")
def patch_bond_set_direction():
"""Patch Bond API setDirection command."""
return patch("homeassistant.components.bond.Bond.setDirection")
def patch_turn_light_on():
"""Patch Bond API turnLightOn command."""
return patch("homeassistant.components.bond.Bond.turnLightOn")
def patch_turn_light_off():
"""Patch Bond API turnLightOff command."""
return patch("homeassistant.components.bond.Bond.turnLightOff")
def patch_bond_action():
"""Patch Bond API action command."""
return patch("homeassistant.components.bond.Bond.action")
def patch_bond_device_state(return_value=None):
"""Patch Bond API getDeviceState command."""
"""Patch Bond API device state endpoint."""
if return_value is None:
return_value = {}
return patch(
"homeassistant.components.bond.Bond.getDeviceState", return_value=return_value
"homeassistant.components.bond.Bond.device_state", return_value=return_value
)

View file

@ -1,13 +1,12 @@
"""Test the Bond config flow."""
from json import JSONDecodeError
from requests.exceptions import ConnectionError
from aiohttp import ClientConnectionError, ClientResponseError
from homeassistant import config_entries, core, setup
from homeassistant.components.bond.const import DOMAIN
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST
from tests.async_mock import patch
from tests.async_mock import Mock, patch
async def test_form(hass: core.HomeAssistant):
@ -20,7 +19,7 @@ async def test_form(hass: core.HomeAssistant):
assert result["errors"] == {}
with patch(
"homeassistant.components.bond.config_flow.Bond.getDeviceIds", return_value=[],
"homeassistant.components.bond.config_flow.Bond.devices", return_value=[],
), patch(
"homeassistant.components.bond.async_setup", return_value=True
) as mock_setup, patch(
@ -48,8 +47,8 @@ async def test_form_invalid_auth(hass: core.HomeAssistant):
)
with patch(
"homeassistant.components.bond.config_flow.Bond.getDeviceIds",
side_effect=JSONDecodeError("test-message", "test-doc", 0),
"homeassistant.components.bond.config_flow.Bond.devices",
side_effect=ClientResponseError(Mock(), Mock(), status=401),
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_HOST: "1.1.1.1", CONF_ACCESS_TOKEN: "test-token"},
@ -66,8 +65,8 @@ async def test_form_cannot_connect(hass: core.HomeAssistant):
)
with patch(
"homeassistant.components.bond.config_flow.Bond.getDeviceIds",
side_effect=ConnectionError,
"homeassistant.components.bond.config_flow.Bond.devices",
side_effect=ClientConnectionError(),
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_HOST: "1.1.1.1", CONF_ACCESS_TOKEN: "test-token"},
@ -84,8 +83,8 @@ async def test_form_unexpected_error(hass: core.HomeAssistant):
)
with patch(
"homeassistant.components.bond.config_flow.Bond.getDeviceIds",
side_effect=Exception,
"homeassistant.components.bond.config_flow.Bond.devices",
side_effect=ClientResponseError(Mock(), Mock(), status=500),
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_HOST: "1.1.1.1", CONF_ACCESS_TOKEN: "test-token"},

View file

@ -2,7 +2,7 @@
from datetime import timedelta
import logging
from bond import DeviceTypes
from bond_api import Action, DeviceType
from homeassistant import core
from homeassistant.components.cover import DOMAIN as COVER_DOMAIN
@ -15,13 +15,7 @@ from homeassistant.const import (
from homeassistant.helpers.entity_registry import EntityRegistry
from homeassistant.util import utcnow
from .common import (
patch_bond_close,
patch_bond_device_state,
patch_bond_hold,
patch_bond_open,
setup_platform,
)
from .common import patch_bond_action, patch_bond_device_state, setup_platform
from tests.common import async_fire_time_changed
@ -30,7 +24,7 @@ _LOGGER = logging.getLogger(__name__)
def shades(name: str):
"""Create motorized shades with given name."""
return {"name": name, "type": DeviceTypes.MOTORIZED_SHADES}
return {"name": name, "type": DeviceType.MOTORIZED_SHADES}
async def test_entity_registry(hass: core.HomeAssistant):
@ -43,9 +37,11 @@ async def test_entity_registry(hass: core.HomeAssistant):
async def test_open_cover(hass: core.HomeAssistant):
"""Tests that open cover command delegates to API."""
await setup_platform(hass, COVER_DOMAIN, shades("name-1"))
await setup_platform(
hass, COVER_DOMAIN, shades("name-1"), bond_device_id="test-device-id"
)
with patch_bond_open() as mock_open, patch_bond_device_state():
with patch_bond_action() as mock_open, patch_bond_device_state():
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_OPEN_COVER,
@ -53,14 +49,17 @@ async def test_open_cover(hass: core.HomeAssistant):
blocking=True,
)
await hass.async_block_till_done()
mock_open.assert_called_once()
mock_open.assert_called_once_with("test-device-id", Action.open())
async def test_close_cover(hass: core.HomeAssistant):
"""Tests that close cover command delegates to API."""
await setup_platform(hass, COVER_DOMAIN, shades("name-1"))
await setup_platform(
hass, COVER_DOMAIN, shades("name-1"), bond_device_id="test-device-id"
)
with patch_bond_close() as mock_close, patch_bond_device_state():
with patch_bond_action() as mock_close, patch_bond_device_state():
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_CLOSE_COVER,
@ -68,14 +67,17 @@ async def test_close_cover(hass: core.HomeAssistant):
blocking=True,
)
await hass.async_block_till_done()
mock_close.assert_called_once()
mock_close.assert_called_once_with("test-device-id", Action.close())
async def test_stop_cover(hass: core.HomeAssistant):
"""Tests that stop cover command delegates to API."""
await setup_platform(hass, COVER_DOMAIN, shades("name-1"))
await setup_platform(
hass, COVER_DOMAIN, shades("name-1"), bond_device_id="test-device-id"
)
with patch_bond_hold() as mock_hold, patch_bond_device_state():
with patch_bond_action() as mock_hold, patch_bond_device_state():
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_STOP_COVER,
@ -83,7 +85,8 @@ async def test_stop_cover(hass: core.HomeAssistant):
blocking=True,
)
await hass.async_block_till_done()
mock_hold.assert_called_once()
mock_hold.assert_called_once_with("test-device-id", Action.hold())
async def test_update_reports_open_cover(hass: core.HomeAssistant):

View file

@ -1,7 +1,8 @@
"""Tests for the Bond fan device."""
from datetime import timedelta
from typing import Optional
from bond import DeviceTypes, Directions
from bond_api import Action, DeviceType, Direction
from homeassistant import core
from homeassistant.components import fan
@ -17,14 +18,7 @@ from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_O
from homeassistant.helpers.entity_registry import EntityRegistry
from homeassistant.util import utcnow
from .common import (
patch_bond_device_state,
patch_bond_set_direction,
patch_bond_set_speed,
patch_bond_turn_off,
patch_bond_turn_on,
setup_platform,
)
from .common import patch_bond_action, patch_bond_device_state, setup_platform
from tests.common import async_fire_time_changed
@ -33,18 +27,20 @@ def ceiling_fan(name: str):
"""Create a ceiling fan with given name."""
return {
"name": name,
"type": DeviceTypes.CEILING_FAN,
"type": DeviceType.CEILING_FAN,
"actions": ["SetSpeed", "SetDirection"],
}
async def turn_fan_on(hass: core.HomeAssistant, fan_id: str, speed: str) -> None:
async def turn_fan_on(
hass: core.HomeAssistant, fan_id: str, speed: Optional[str] = None
) -> None:
"""Turn the fan on at the specified speed."""
service_data = {ATTR_ENTITY_ID: fan_id}
if speed:
service_data[fan.ATTR_SPEED] = speed
await hass.services.async_call(
FAN_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: fan_id, fan.ATTR_SPEED: speed},
blocking=True,
FAN_DOMAIN, SERVICE_TURN_ON, service_data=service_data, blocking=True,
)
await hass.async_block_till_done()
@ -57,7 +53,7 @@ async def test_entity_registry(hass: core.HomeAssistant):
assert [key for key in registry.entities] == ["fan.name_1"]
async def test_entity_non_standard_speed_list(hass: core.HomeAssistant):
async def test_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,
@ -76,41 +72,62 @@ async def test_entity_non_standard_speed_list(hass: core.HomeAssistant):
]
with patch_bond_device_state():
with patch_bond_turn_on(), patch_bond_set_speed() as mock_set_speed_low:
with patch_bond_action() 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)
mock_set_speed_low.assert_called_once_with(
"test-device-id", Action.set_speed(1)
)
with patch_bond_turn_on(), patch_bond_set_speed() as mock_set_speed_medium:
with patch_bond_action() 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)
mock_set_speed_medium.assert_called_once_with(
"test-device-id", Action.set_speed(3)
)
with patch_bond_turn_on(), patch_bond_set_speed() as mock_set_speed_high:
with patch_bond_action() 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)
mock_set_speed_high.assert_called_once_with(
"test-device-id", Action.set_speed(6)
)
async def test_turn_on_fan(hass: core.HomeAssistant):
"""Tests that turn on command delegates to API."""
await setup_platform(hass, FAN_DOMAIN, ceiling_fan("name-1"))
async def test_turn_on_fan_with_speed(hass: core.HomeAssistant):
"""Tests that turn on command delegates to set speed API."""
await setup_platform(
hass, FAN_DOMAIN, ceiling_fan("name-1"), bond_device_id="test-device-id"
)
with patch_bond_turn_on() as mock_turn_on, patch_bond_set_speed() as mock_set_speed, patch_bond_device_state():
with patch_bond_action() as mock_set_speed, patch_bond_device_state():
await turn_fan_on(hass, "fan.name_1", fan.SPEED_LOW)
mock_set_speed.assert_called_once()
mock_turn_on.assert_called_once()
mock_set_speed.assert_called_with("test-device-id", Action.set_speed(1))
async def test_turn_on_fan_without_speed(hass: core.HomeAssistant):
"""Tests that turn on command delegates to turn on API."""
await setup_platform(
hass, FAN_DOMAIN, ceiling_fan("name-1"), bond_device_id="test-device-id"
)
with patch_bond_action() as mock_turn_on, patch_bond_device_state():
await turn_fan_on(hass, "fan.name_1")
mock_turn_on.assert_called_with("test-device-id", Action.turn_on())
async def test_turn_off_fan(hass: core.HomeAssistant):
"""Tests that turn off command delegates to API."""
await setup_platform(hass, FAN_DOMAIN, ceiling_fan("name-1"))
await setup_platform(
hass, FAN_DOMAIN, ceiling_fan("name-1"), bond_device_id="test-device-id"
)
with patch_bond_turn_off() as mock_turn_off, patch_bond_device_state():
with patch_bond_action() as mock_turn_off, patch_bond_device_state():
await hass.services.async_call(
FAN_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: "fan.name_1"}, blocking=True,
)
await hass.async_block_till_done()
mock_turn_off.assert_called_once()
mock_turn_off.assert_called_once_with("test-device-id", Action.turn_off())
async def test_update_reports_fan_on(hass: core.HomeAssistant):
@ -139,7 +156,7 @@ 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_bond_device_state(return_value={"direction": Directions.FORWARD}):
with patch_bond_device_state(return_value={"direction": Direction.FORWARD}):
async_fire_time_changed(hass, utcnow() + timedelta(seconds=30))
await hass.async_block_till_done()
@ -150,7 +167,7 @@ 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_bond_device_state(return_value={"direction": Directions.REVERSE}):
with patch_bond_device_state(return_value={"direction": Direction.REVERSE}):
async_fire_time_changed(hass, utcnow() + timedelta(seconds=30))
await hass.async_block_till_done()
@ -159,9 +176,11 @@ async def test_update_reports_direction_reverse(hass: core.HomeAssistant):
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"))
await setup_platform(
hass, FAN_DOMAIN, ceiling_fan("name-1"), bond_device_id="test-device-id"
)
with patch_bond_set_direction() as mock_set_direction, patch_bond_device_state():
with patch_bond_action() as mock_set_direction, patch_bond_device_state():
await hass.services.async_call(
FAN_DOMAIN,
SERVICE_SET_DIRECTION,
@ -169,4 +188,7 @@ async def test_set_fan_direction(hass: core.HomeAssistant):
blocking=True,
)
await hass.async_block_till_done()
mock_set_direction.assert_called_once()
mock_set_direction.assert_called_once_with(
"test-device-id", Action.set_direction(Direction.FORWARD)
)

View file

@ -2,7 +2,7 @@
from datetime import timedelta
import logging
from bond import Actions, DeviceTypes
from bond_api import Action, DeviceType
from homeassistant import core
from homeassistant.components.light import ATTR_BRIGHTNESS, DOMAIN as LIGHT_DOMAIN
@ -10,15 +10,7 @@ from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_O
from homeassistant.helpers.entity_registry import EntityRegistry
from homeassistant.util import utcnow
from .common import (
patch_bond_device_state,
patch_bond_set_flame,
patch_bond_turn_off,
patch_bond_turn_on,
patch_turn_light_off,
patch_turn_light_on,
setup_platform,
)
from .common import patch_bond_action, patch_bond_device_state, setup_platform
from tests.common import async_fire_time_changed
@ -29,14 +21,14 @@ def ceiling_fan(name: str):
"""Create a ceiling fan (that has built-in light) with given name."""
return {
"name": name,
"type": DeviceTypes.CEILING_FAN,
"actions": [Actions.TOGGLE_LIGHT],
"type": DeviceType.CEILING_FAN,
"actions": [Action.TURN_LIGHT_ON, Action.TURN_LIGHT_OFF],
}
def fireplace(name: str):
"""Create a fireplace with given name."""
return {"name": name, "type": DeviceTypes.FIREPLACE}
return {"name": name, "type": DeviceType.FIREPLACE}
async def test_entity_registry(hass: core.HomeAssistant):
@ -49,9 +41,11 @@ async def test_entity_registry(hass: core.HomeAssistant):
async def test_turn_on_light(hass: core.HomeAssistant):
"""Tests that turn on command delegates to API."""
await setup_platform(hass, LIGHT_DOMAIN, ceiling_fan("name-1"))
await setup_platform(
hass, LIGHT_DOMAIN, ceiling_fan("name-1"), bond_device_id="test-device-id"
)
with patch_turn_light_on() as mock_turn_light_on, patch_bond_device_state():
with patch_bond_action() as mock_turn_light_on, patch_bond_device_state():
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
@ -59,14 +53,17 @@ async def test_turn_on_light(hass: core.HomeAssistant):
blocking=True,
)
await hass.async_block_till_done()
mock_turn_light_on.assert_called_once()
mock_turn_light_on.assert_called_once_with("test-device-id", Action.turn_light_on())
async def test_turn_off_light(hass: core.HomeAssistant):
"""Tests that turn off command delegates to API."""
await setup_platform(hass, LIGHT_DOMAIN, ceiling_fan("name-1"))
await setup_platform(
hass, LIGHT_DOMAIN, ceiling_fan("name-1"), bond_device_id="test-device-id"
)
with patch_turn_light_off() as mock_turn_light_off, patch_bond_device_state():
with patch_bond_action() as mock_turn_light_off, patch_bond_device_state():
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_OFF,
@ -74,7 +71,10 @@ async def test_turn_off_light(hass: core.HomeAssistant):
blocking=True,
)
await hass.async_block_till_done()
mock_turn_light_off.assert_called_once()
mock_turn_light_off.assert_called_once_with(
"test-device-id", Action.turn_light_off()
)
async def test_update_reports_light_is_on(hass: core.HomeAssistant):
@ -99,13 +99,13 @@ async def test_update_reports_light_is_off(hass: core.HomeAssistant):
assert hass.states.get("light.name_1").state == "off"
async def test_turn_on_fireplace(hass: core.HomeAssistant):
"""Tests that turn on command delegates to API."""
async def test_turn_on_fireplace_with_brightness(hass: core.HomeAssistant):
"""Tests that turn on command delegates to set flame API."""
await setup_platform(
hass, LIGHT_DOMAIN, fireplace("name-1"), bond_device_id="test-device-id"
)
with patch_bond_turn_on() as mock_turn_on, patch_bond_set_flame() as mock_set_flame, patch_bond_device_state():
with patch_bond_action() as mock_set_flame, patch_bond_device_state():
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
@ -114,15 +114,34 @@ async def test_turn_on_fireplace(hass: core.HomeAssistant):
)
await hass.async_block_till_done()
mock_turn_on.assert_called_once()
mock_set_flame.assert_called_once_with("test-device-id", 50)
mock_set_flame.assert_called_once_with("test-device-id", Action.set_flame(50))
async def test_turn_on_fireplace_without_brightness(hass: core.HomeAssistant):
"""Tests that turn on command delegates to turn on API."""
await setup_platform(
hass, LIGHT_DOMAIN, fireplace("name-1"), bond_device_id="test-device-id"
)
with patch_bond_action() as mock_turn_on, patch_bond_device_state():
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: "light.name_1"},
blocking=True,
)
await hass.async_block_till_done()
mock_turn_on.assert_called_once_with("test-device-id", Action.turn_on())
async def test_turn_off_fireplace(hass: core.HomeAssistant):
"""Tests that turn off command delegates to API."""
await setup_platform(hass, LIGHT_DOMAIN, fireplace("name-1"))
await setup_platform(
hass, LIGHT_DOMAIN, fireplace("name-1"), bond_device_id="test-device-id"
)
with patch_bond_turn_off() as mock_turn_off, patch_bond_device_state():
with patch_bond_action() as mock_turn_off, patch_bond_device_state():
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_OFF,
@ -130,7 +149,8 @@ async def test_turn_off_fireplace(hass: core.HomeAssistant):
blocking=True,
)
await hass.async_block_till_done()
mock_turn_off.assert_called_once()
mock_turn_off.assert_called_once_with("test-device-id", Action.turn_off())
async def test_flame_converted_to_brightness(hass: core.HomeAssistant):
@ -141,5 +161,4 @@ async def test_flame_converted_to_brightness(hass: core.HomeAssistant):
async_fire_time_changed(hass, utcnow() + timedelta(seconds=30))
await hass.async_block_till_done()
_LOGGER.warning(hass.states.get("light.name_1").attributes)
assert hass.states.get("light.name_1").attributes[ATTR_BRIGHTNESS] == 128

View file

@ -2,7 +2,7 @@
from datetime import timedelta
import logging
from bond import DeviceTypes
from bond_api import Action, DeviceType
from homeassistant import core
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
@ -10,12 +10,7 @@ from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_O
from homeassistant.helpers.entity_registry import EntityRegistry
from homeassistant.util import utcnow
from .common import (
patch_bond_device_state,
patch_bond_turn_off,
patch_bond_turn_on,
setup_platform,
)
from .common import patch_bond_action, patch_bond_device_state, setup_platform
from tests.common import async_fire_time_changed
@ -24,7 +19,7 @@ _LOGGER = logging.getLogger(__name__)
def generic_device(name: str):
"""Create a generic device with given name."""
return {"name": name, "type": DeviceTypes.GENERIC_DEVICE}
return {"name": name, "type": DeviceType.GENERIC_DEVICE}
async def test_entity_registry(hass: core.HomeAssistant):
@ -37,9 +32,11 @@ async def test_entity_registry(hass: core.HomeAssistant):
async def test_turn_on_switch(hass: core.HomeAssistant):
"""Tests that turn on command delegates to API."""
await setup_platform(hass, SWITCH_DOMAIN, generic_device("name-1"))
await setup_platform(
hass, SWITCH_DOMAIN, generic_device("name-1"), bond_device_id="test-device-id"
)
with patch_bond_turn_on() as mock_turn_on, patch_bond_device_state():
with patch_bond_action() as mock_turn_on, patch_bond_device_state():
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_ON,
@ -47,14 +44,17 @@ async def test_turn_on_switch(hass: core.HomeAssistant):
blocking=True,
)
await hass.async_block_till_done()
mock_turn_on.assert_called_once()
mock_turn_on.assert_called_once_with("test-device-id", Action.turn_on())
async def test_turn_off_switch(hass: core.HomeAssistant):
"""Tests that turn off command delegates to API."""
await setup_platform(hass, SWITCH_DOMAIN, generic_device("name-1"))
await setup_platform(
hass, SWITCH_DOMAIN, generic_device("name-1"), bond_device_id="test-device-id"
)
with patch_bond_turn_off() as mock_turn_off, patch_bond_device_state():
with patch_bond_action() as mock_turn_off, patch_bond_device_state():
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
@ -62,7 +62,8 @@ async def test_turn_off_switch(hass: core.HomeAssistant):
blocking=True,
)
await hass.async_block_till_done()
mock_turn_off.assert_called_once()
mock_turn_off.assert_called_once_with("test-device-id", Action.turn_off())
async def test_update_reports_switch_is_on(hass: core.HomeAssistant):