From 2b5bb8dac0e2ef274f562671e040fd091a17daa5 Mon Sep 17 00:00:00 2001 From: Andre Lengwenus Date: Wed, 3 Jun 2020 18:44:04 +0200 Subject: [PATCH] Cover group considers opening and closing states (#36203) --- homeassistant/components/group/cover.py | 26 ++++++- tests/components/group/test_cover.py | 91 ++++++++++++++++++++----- 2 files changed, 99 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/group/cover.py b/homeassistant/components/group/cover.py index c4e691eeff9..0832d466f8c 100644 --- a/homeassistant/components/group/cover.py +++ b/homeassistant/components/group/cover.py @@ -35,7 +35,9 @@ from homeassistant.const import ( ATTR_SUPPORTED_FEATURES, CONF_ENTITIES, CONF_NAME, - STATE_CLOSED, + STATE_CLOSING, + STATE_OPEN, + STATE_OPENING, ) from homeassistant.core import State, callback import homeassistant.helpers.config_validation as cv @@ -73,6 +75,8 @@ class CoverGroup(CoverEntity): """Initialize a CoverGroup entity.""" self._name = name self._is_closed = False + self._is_closing = False + self._is_opening = False self._cover_position: Optional[int] = 100 self._tilt_position = None self._supported_features = 0 @@ -176,6 +180,16 @@ class CoverGroup(CoverEntity): """Return if all covers in group are closed.""" return self._is_closed + @property + def is_opening(self): + """Return if the cover is opening or not.""" + return self._is_opening + + @property + def is_closing(self): + """Return if the cover is closing or not.""" + return self._is_closing + @property def current_cover_position(self) -> Optional[int]: """Return current position for all covers.""" @@ -253,13 +267,21 @@ class CoverGroup(CoverEntity): self._assumed_state = False self._is_closed = True + self._is_closing = False + self._is_opening = False for entity_id in self._entities: state = self.hass.states.get(entity_id) if not state: continue - if state.state != STATE_CLOSED: + if state.state == STATE_OPEN: self._is_closed = False break + if state.state == STATE_CLOSING: + self._is_closing = True + break + if state.state == STATE_OPENING: + self._is_opening = True + break self._cover_position = None if self._covers[KEY_POSITION]: diff --git a/tests/components/group/test_cover.py b/tests/components/group/test_cover.py index ac338dd8a80..1adad3e3d85 100644 --- a/tests/components/group/test_cover.py +++ b/tests/components/group/test_cover.py @@ -28,7 +28,9 @@ from homeassistant.const import ( SERVICE_TOGGLE, SERVICE_TOGGLE_COVER_TILT, STATE_CLOSED, + STATE_CLOSING, STATE_OPEN, + STATE_OPENING, ) from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -41,7 +43,7 @@ DEMO_COVER_POS = "cover.hall_window" DEMO_COVER_TILT = "cover.living_room_window" DEMO_TILT = "cover.tilt_demo" -CONFIG = { +CONFIG_ALL = { DOMAIN: [ {"platform": "demo"}, { @@ -51,28 +53,36 @@ CONFIG = { ] } +CONFIG_POS = { + DOMAIN: [ + {"platform": "demo"}, + { + "platform": "group", + CONF_ENTITIES: [DEMO_COVER_POS, DEMO_COVER_TILT, DEMO_TILT], + }, + ] +} + +CONFIG_ATTRIBUTES = { + DOMAIN: { + "platform": "group", + CONF_ENTITIES: [DEMO_COVER, DEMO_COVER_POS, DEMO_COVER_TILT, DEMO_TILT], + } +} + @pytest.fixture -async def setup_comp(hass): +async def setup_comp(hass, config_count): """Set up group cover component.""" - with assert_setup_component(2, DOMAIN): - await async_setup_component(hass, DOMAIN, CONFIG) + config, count = config_count + with assert_setup_component(count, DOMAIN): + await async_setup_component(hass, DOMAIN, config) await hass.async_block_till_done() -async def test_attributes(hass): +@pytest.mark.parametrize("config_count", [(CONFIG_ATTRIBUTES, 1)]) +async def test_attributes(hass, setup_comp): """Test handling of state attributes.""" - config = { - DOMAIN: { - "platform": "group", - CONF_ENTITIES: [DEMO_COVER, DEMO_COVER_POS, DEMO_COVER_TILT, DEMO_TILT], - } - } - - with assert_setup_component(1, DOMAIN): - await async_setup_component(hass, DOMAIN, config) - await hass.async_block_till_done() - state = hass.states.get(COVER_GROUP) assert state.state == STATE_CLOSED assert state.attributes[ATTR_FRIENDLY_NAME] == DEFAULT_NAME @@ -193,11 +203,13 @@ async def test_attributes(hass): assert state.attributes[ATTR_ASSUMED_STATE] is True +@pytest.mark.parametrize("config_count", [(CONFIG_ALL, 2)]) async def test_open_covers(hass, setup_comp): """Test open cover function.""" await hass.services.async_call( DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: COVER_GROUP}, blocking=True ) + for _ in range(10): future = dt_util.utcnow() + timedelta(seconds=1) async_fire_time_changed(hass, future) @@ -212,11 +224,13 @@ async def test_open_covers(hass, setup_comp): assert hass.states.get(DEMO_COVER_TILT).attributes[ATTR_CURRENT_POSITION] == 100 +@pytest.mark.parametrize("config_count", [(CONFIG_ALL, 2)]) async def test_close_covers(hass, setup_comp): """Test close cover function.""" await hass.services.async_call( DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: COVER_GROUP}, blocking=True ) + for _ in range(10): future = dt_util.utcnow() + timedelta(seconds=1) async_fire_time_changed(hass, future) @@ -231,6 +245,7 @@ async def test_close_covers(hass, setup_comp): assert hass.states.get(DEMO_COVER_TILT).attributes[ATTR_CURRENT_POSITION] == 0 +@pytest.mark.parametrize("config_count", [(CONFIG_ALL, 2)]) async def test_toggle_covers(hass, setup_comp): """Test toggle cover function.""" # Start covers in open state @@ -280,6 +295,7 @@ async def test_toggle_covers(hass, setup_comp): assert hass.states.get(DEMO_COVER_TILT).attributes[ATTR_CURRENT_POSITION] == 100 +@pytest.mark.parametrize("config_count", [(CONFIG_ALL, 2)]) async def test_stop_covers(hass, setup_comp): """Test stop cover function.""" await hass.services.async_call( @@ -305,6 +321,7 @@ async def test_stop_covers(hass, setup_comp): assert hass.states.get(DEMO_COVER_TILT).attributes[ATTR_CURRENT_POSITION] == 80 +@pytest.mark.parametrize("config_count", [(CONFIG_ALL, 2)]) async def test_set_cover_position(hass, setup_comp): """Test set cover position function.""" await hass.services.async_call( @@ -327,6 +344,7 @@ async def test_set_cover_position(hass, setup_comp): assert hass.states.get(DEMO_COVER_TILT).attributes[ATTR_CURRENT_POSITION] == 50 +@pytest.mark.parametrize("config_count", [(CONFIG_ALL, 2)]) async def test_open_tilts(hass, setup_comp): """Test open tilt function.""" await hass.services.async_call( @@ -346,6 +364,7 @@ async def test_open_tilts(hass, setup_comp): ) +@pytest.mark.parametrize("config_count", [(CONFIG_ALL, 2)]) async def test_close_tilts(hass, setup_comp): """Test close tilt function.""" await hass.services.async_call( @@ -363,6 +382,7 @@ async def test_close_tilts(hass, setup_comp): assert hass.states.get(DEMO_COVER_TILT).attributes[ATTR_CURRENT_TILT_POSITION] == 0 +@pytest.mark.parametrize("config_count", [(CONFIG_ALL, 2)]) async def test_toggle_tilts(hass, setup_comp): """Test toggle tilt function.""" # Start tilted open @@ -415,6 +435,7 @@ async def test_toggle_tilts(hass, setup_comp): ) +@pytest.mark.parametrize("config_count", [(CONFIG_ALL, 2)]) async def test_stop_tilts(hass, setup_comp): """Test stop tilts function.""" await hass.services.async_call( @@ -438,6 +459,7 @@ async def test_stop_tilts(hass, setup_comp): assert hass.states.get(DEMO_COVER_TILT).attributes[ATTR_CURRENT_TILT_POSITION] == 60 +@pytest.mark.parametrize("config_count", [(CONFIG_ALL, 2)]) async def test_set_tilt_positions(hass, setup_comp): """Test set tilt position function.""" await hass.services.async_call( @@ -456,3 +478,40 @@ async def test_set_tilt_positions(hass, setup_comp): assert state.attributes[ATTR_CURRENT_TILT_POSITION] == 80 assert hass.states.get(DEMO_COVER_TILT).attributes[ATTR_CURRENT_TILT_POSITION] == 80 + + +@pytest.mark.parametrize("config_count", [(CONFIG_POS, 2)]) +async def test_is_opening_closing(hass, setup_comp): + """Test is_opening property.""" + await hass.services.async_call( + DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: COVER_GROUP}, blocking=True + ) + + assert hass.states.get(DEMO_COVER_POS).state == STATE_OPENING + assert hass.states.get(DEMO_COVER_TILT).state == STATE_OPENING + assert hass.states.get(COVER_GROUP).state == STATE_OPENING + + for _ in range(10): + future = dt_util.utcnow() + timedelta(seconds=1) + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + + await hass.services.async_call( + DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: COVER_GROUP}, blocking=True + ) + + assert hass.states.get(DEMO_COVER_POS).state == STATE_CLOSING + assert hass.states.get(DEMO_COVER_TILT).state == STATE_CLOSING + assert hass.states.get(COVER_GROUP).state == STATE_CLOSING + + hass.states.async_set(DEMO_COVER_POS, STATE_OPENING, {ATTR_SUPPORTED_FEATURES: 11}) + await hass.async_block_till_done() + + assert hass.states.get(DEMO_COVER_POS).state == STATE_OPENING + assert hass.states.get(COVER_GROUP).state == STATE_OPENING + + hass.states.async_set(DEMO_COVER_POS, STATE_CLOSING, {ATTR_SUPPORTED_FEATURES: 11}) + await hass.async_block_till_done() + + assert hass.states.get(DEMO_COVER_POS).state == STATE_CLOSING + assert hass.states.get(COVER_GROUP).state == STATE_CLOSING