Add bond cover assumed state and local polling (#37666)

* Declare Bond covers as having assumed state, setup local polling for state updates

* Declare Bond covers as having assumed state, setup local polling for state updates (apply feedback from PR review)

* Declare Bond covers as having assumed state, setup local polling for state updates (apply feedback from PR review)

* Declare Bond covers as having assumed state, setup local polling for state updates (apply feedback from PR review)
This commit is contained in:
Eugene Prystupa 2020-07-09 19:25:18 -04:00 committed by GitHub
parent ef254a1c3d
commit 69a8ba2af8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 86 additions and 15 deletions

View File

@ -1,5 +1,4 @@
"""Support for Bond covers.""" """Support for Bond covers."""
import logging
from typing import Any, Callable, Dict, List, Optional from typing import Any, Callable, Dict, List, Optional
from bond import BOND_DEVICE_TYPE_MOTORIZED_SHADES, Bond from bond import BOND_DEVICE_TYPE_MOTORIZED_SHADES, Bond
@ -13,13 +12,11 @@ from homeassistant.helpers.entity import Entity
from .const import DOMAIN from .const import DOMAIN
from .utils import BondDevice, get_bond_devices from .utils import BondDevice, get_bond_devices
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
entry: ConfigEntry, entry: ConfigEntry,
async_add_entities: Callable[[List[Entity]], None], async_add_entities: Callable[[List[Entity], bool], None],
) -> None: ) -> None:
"""Set up Bond cover devices.""" """Set up Bond cover devices."""
bond: Bond = hass.data[DOMAIN][entry.entry_id] bond: Bond = hass.data[DOMAIN][entry.entry_id]
@ -32,7 +29,7 @@ async def async_setup_entry(
if device.type == BOND_DEVICE_TYPE_MOTORIZED_SHADES if device.type == BOND_DEVICE_TYPE_MOTORIZED_SHADES
] ]
async_add_entities(covers) async_add_entities(covers, True)
class BondCover(CoverEntity): class BondCover(CoverEntity):
@ -42,6 +39,7 @@ class BondCover(CoverEntity):
"""Create HA entity representing Bond cover.""" """Create HA entity representing Bond cover."""
self._bond = bond self._bond = bond
self._device = device self._device = device
self._closed: Optional[bool] = None
@property @property
def device_class(self) -> Optional[str]: def device_class(self) -> Optional[str]:
@ -63,10 +61,21 @@ class BondCover(CoverEntity):
"""Get a an HA device representing this cover.""" """Get a an HA device representing this cover."""
return {ATTR_NAME: self.name, "identifiers": {(DOMAIN, self._device.device_id)}} return {ATTR_NAME: self.name, "identifiers": {(DOMAIN, self._device.device_id)}}
@property
def assumed_state(self) -> bool:
"""Let HA know this entity relies on an assumed state tracked by Bond."""
return True
def update(self):
"""Fetch assumed state of the cover from the hub using API."""
state: dict = self._bond.getDeviceState(self._device.device_id)
cover_open = state.get("open")
self._closed = True if cover_open == 0 else False if cover_open == 1 else None
@property @property
def is_closed(self): def is_closed(self):
"""Return if the cover is closed or not.""" """Return if the cover is closed or not."""
return None return self._closed
def open_cover(self, **kwargs: Any) -> None: def open_cover(self, **kwargs: Any) -> None:
"""Open the cover.""" """Open the cover."""

View File

@ -1,8 +1,10 @@
"""Tests for the Bond cover device.""" """Tests for the Bond cover device."""
from datetime import timedelta
import logging import logging
from bond import BOND_DEVICE_TYPE_MOTORIZED_SHADES from bond import BOND_DEVICE_TYPE_MOTORIZED_SHADES
from homeassistant import core
from homeassistant.components.cover import DOMAIN as COVER_DOMAIN from homeassistant.components.cover import DOMAIN as COVER_DOMAIN
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
@ -11,35 +13,45 @@ from homeassistant.const import (
SERVICE_STOP_COVER, SERVICE_STOP_COVER,
) )
from homeassistant.helpers.entity_registry import EntityRegistry from homeassistant.helpers.entity_registry import EntityRegistry
from homeassistant.util import utcnow
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
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
TEST_DEVICE_IDS = ["device-1"] TEST_DEVICE_IDS = ["device-1"]
TEST_DEVICE = {"name": "name-1", "type": BOND_DEVICE_TYPE_MOTORIZED_SHADES} TEST_COVER_DEVICE = {"name": "name-1", "type": BOND_DEVICE_TYPE_MOTORIZED_SHADES}
async def test_entity_registry(hass): 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."""
with patch( with patch(
"homeassistant.components.bond.Bond.getDeviceIds", return_value=TEST_DEVICE_IDS "homeassistant.components.bond.Bond.getDeviceIds", return_value=TEST_DEVICE_IDS
), patch("homeassistant.components.bond.Bond.getDevice", return_value=TEST_DEVICE): ), patch(
"homeassistant.components.bond.Bond.getDevice", return_value=TEST_COVER_DEVICE
), patch(
"homeassistant.components.bond.Bond.getDeviceState", return_value={}
):
await setup_platform(hass, COVER_DOMAIN) await setup_platform(hass, COVER_DOMAIN)
registry: EntityRegistry = await hass.helpers.entity_registry.async_get_registry() registry: EntityRegistry = await hass.helpers.entity_registry.async_get_registry()
assert [key for key in registry.entities.keys()] == ["cover.name_1"] assert [key for key in registry.entities.keys()] == ["cover.name_1"]
async def test_open_cover(hass): async def test_open_cover(hass: core.HomeAssistant):
"""Tests that open cover command delegates to API.""" """Tests that open cover command delegates to API."""
with patch( with patch(
"homeassistant.components.bond.Bond.getDeviceIds", return_value=TEST_DEVICE_IDS "homeassistant.components.bond.Bond.getDeviceIds", return_value=TEST_DEVICE_IDS
), patch("homeassistant.components.bond.Bond.getDevice", return_value=TEST_DEVICE): ), patch(
"homeassistant.components.bond.Bond.getDevice", return_value=TEST_COVER_DEVICE
), patch(
"homeassistant.components.bond.Bond.getDeviceState", return_value={}
):
await setup_platform(hass, COVER_DOMAIN) await setup_platform(hass, COVER_DOMAIN)
with patch("homeassistant.components.bond.Bond.open") as mock_open: with patch("homeassistant.components.bond.Bond.open") as mock_open:
@ -53,12 +65,16 @@ async def test_open_cover(hass):
mock_open.assert_called_once() mock_open.assert_called_once()
async def test_close_cover(hass): async def test_close_cover(hass: core.HomeAssistant):
"""Tests that close cover command delegates to API.""" """Tests that close cover command delegates to API."""
with patch( with patch(
"homeassistant.components.bond.Bond.getDeviceIds", return_value=TEST_DEVICE_IDS "homeassistant.components.bond.Bond.getDeviceIds", return_value=TEST_DEVICE_IDS
), patch("homeassistant.components.bond.Bond.getDevice", return_value=TEST_DEVICE): ), patch(
"homeassistant.components.bond.Bond.getDevice", return_value=TEST_COVER_DEVICE
), patch(
"homeassistant.components.bond.Bond.getDeviceState", return_value={}
):
await setup_platform(hass, COVER_DOMAIN) await setup_platform(hass, COVER_DOMAIN)
with patch("homeassistant.components.bond.Bond.close") as mock_close: with patch("homeassistant.components.bond.Bond.close") as mock_close:
@ -72,12 +88,16 @@ async def test_close_cover(hass):
mock_close.assert_called_once() mock_close.assert_called_once()
async def test_stop_cover(hass): async def test_stop_cover(hass: core.HomeAssistant):
"""Tests that stop cover command delegates to API.""" """Tests that stop cover command delegates to API."""
with patch( with patch(
"homeassistant.components.bond.Bond.getDeviceIds", return_value=TEST_DEVICE_IDS "homeassistant.components.bond.Bond.getDeviceIds", return_value=TEST_DEVICE_IDS
), patch("homeassistant.components.bond.Bond.getDevice", return_value=TEST_DEVICE): ), patch(
"homeassistant.components.bond.Bond.getDevice", return_value=TEST_COVER_DEVICE
), patch(
"homeassistant.components.bond.Bond.getDeviceState", return_value={}
):
await setup_platform(hass, COVER_DOMAIN) await setup_platform(hass, COVER_DOMAIN)
with patch("homeassistant.components.bond.Bond.hold") as mock_hold: with patch("homeassistant.components.bond.Bond.hold") as mock_hold:
@ -89,3 +109,45 @@ async def test_stop_cover(hass):
) )
await hass.async_block_till_done() await hass.async_block_till_done()
mock_hold.assert_called_once() mock_hold.assert_called_once()
async def test_update_reports_open_cover(hass: core.HomeAssistant):
"""Tests that update command sets correct state when Bond API reports cover is open."""
with patch(
"homeassistant.components.bond.Bond.getDeviceIds", return_value=TEST_DEVICE_IDS
), patch(
"homeassistant.components.bond.Bond.getDevice", return_value=TEST_COVER_DEVICE
), patch(
"homeassistant.components.bond.Bond.getDeviceState", return_value={}
):
await setup_platform(hass, COVER_DOMAIN)
with patch(
"homeassistant.components.bond.Bond.getDeviceState", return_value={"open": 1}
):
async_fire_time_changed(hass, utcnow() + timedelta(seconds=30))
await hass.async_block_till_done()
assert hass.states.get("cover.name_1").state == "open"
async def test_update_reports_closed_cover(hass: core.HomeAssistant):
"""Tests that update command sets correct state when Bond API reports cover is closed."""
with patch(
"homeassistant.components.bond.Bond.getDeviceIds", return_value=TEST_DEVICE_IDS
), patch(
"homeassistant.components.bond.Bond.getDevice", return_value=TEST_COVER_DEVICE
), patch(
"homeassistant.components.bond.Bond.getDeviceState", return_value={}
):
await setup_platform(hass, COVER_DOMAIN)
with patch(
"homeassistant.components.bond.Bond.getDeviceState", return_value={"open": 0}
):
async_fire_time_changed(hass, utcnow() + timedelta(seconds=30))
await hass.async_block_till_done()
assert hass.states.get("cover.name_1").state == "closed"