From 69a8ba2af8b271c3357a292f28b6d2bb0b2c5975 Mon Sep 17 00:00:00 2001 From: Eugene Prystupa Date: Thu, 9 Jul 2020 19:25:18 -0400 Subject: [PATCH] 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) --- homeassistant/components/bond/cover.py | 21 +++++-- tests/components/bond/test_cover.py | 80 +++++++++++++++++++++++--- 2 files changed, 86 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/bond/cover.py b/homeassistant/components/bond/cover.py index 518c8b81f64..82db8bb4f27 100644 --- a/homeassistant/components/bond/cover.py +++ b/homeassistant/components/bond/cover.py @@ -1,5 +1,4 @@ """Support for Bond covers.""" -import logging from typing import Any, Callable, Dict, List, Optional from bond import BOND_DEVICE_TYPE_MOTORIZED_SHADES, Bond @@ -13,13 +12,11 @@ from homeassistant.helpers.entity import Entity from .const import DOMAIN from .utils import BondDevice, get_bond_devices -_LOGGER = logging.getLogger(__name__) - async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_entities: Callable[[List[Entity]], None], + async_add_entities: Callable[[List[Entity], bool], None], ) -> None: """Set up Bond cover devices.""" 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 ] - async_add_entities(covers) + async_add_entities(covers, True) class BondCover(CoverEntity): @@ -42,6 +39,7 @@ class BondCover(CoverEntity): """Create HA entity representing Bond cover.""" self._bond = bond self._device = device + self._closed: Optional[bool] = None @property def device_class(self) -> Optional[str]: @@ -63,10 +61,21 @@ class BondCover(CoverEntity): """Get a an HA device representing this cover.""" 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 def is_closed(self): """Return if the cover is closed or not.""" - return None + return self._closed def open_cover(self, **kwargs: Any) -> None: """Open the cover.""" diff --git a/tests/components/bond/test_cover.py b/tests/components/bond/test_cover.py index 579221de7a0..da81e3ad27f 100644 --- a/tests/components/bond/test_cover.py +++ b/tests/components/bond/test_cover.py @@ -1,8 +1,10 @@ """Tests for the Bond cover device.""" +from datetime import timedelta import logging from bond import BOND_DEVICE_TYPE_MOTORIZED_SHADES +from homeassistant import core from homeassistant.components.cover import DOMAIN as COVER_DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, @@ -11,35 +13,45 @@ from homeassistant.const import ( SERVICE_STOP_COVER, ) from homeassistant.helpers.entity_registry import EntityRegistry +from homeassistant.util import utcnow from .common import setup_platform from tests.async_mock import patch +from tests.common import async_fire_time_changed _LOGGER = logging.getLogger(__name__) 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.""" with patch( "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) registry: EntityRegistry = await hass.helpers.entity_registry.async_get_registry() 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.""" with patch( "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) 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() -async def test_close_cover(hass): +async def test_close_cover(hass: core.HomeAssistant): """Tests that close cover command delegates to API.""" with patch( "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) 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() -async def test_stop_cover(hass): +async def test_stop_cover(hass: core.HomeAssistant): """Tests that stop cover command delegates to API.""" with patch( "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) 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() 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"