diff --git a/homeassistant/components/group/cover.py b/homeassistant/components/group/cover.py index 45d88a07f88..68ad61c33fc 100644 --- a/homeassistant/components/group/cover.py +++ b/homeassistant/components/group/cover.py @@ -259,25 +259,31 @@ class CoverGroup(GroupEntity, CoverEntity): """Update state and attributes.""" self._attr_assumed_state = False - self._attr_is_closed = None + self._attr_is_closed = True self._attr_is_closing = False self._attr_is_opening = False + has_valid_state = False for entity_id in self._entities: state = self.hass.states.get(entity_id) if not state: continue if state.state == STATE_OPEN: self._attr_is_closed = False + has_valid_state = True continue if state.state == STATE_CLOSED: - self._attr_is_closed = True + has_valid_state = True continue if state.state == STATE_CLOSING: self._attr_is_closing = True + has_valid_state = True continue if state.state == STATE_OPENING: self._attr_is_opening = True + has_valid_state = True continue + if not has_valid_state: + self._attr_is_closed = None position_covers = self._covers[KEY_POSITION] all_position_states = [self.hass.states.get(x) for x in position_covers] diff --git a/tests/components/group/test_cover.py b/tests/components/group/test_cover.py index 9d16be9150b..cf1fba992e7 100644 --- a/tests/components/group/test_cover.py +++ b/tests/components/group/test_cover.py @@ -96,6 +96,106 @@ async def setup_comp(hass, config_count): await hass.async_block_till_done() +@pytest.mark.parametrize("config_count", [(CONFIG_ATTRIBUTES, 1)]) +async def test_state(hass, setup_comp): + """Test handling of state.""" + state = hass.states.get(COVER_GROUP) + # No entity has a valid state -> group state unknown + assert state.state == STATE_UNKNOWN + assert state.attributes[ATTR_FRIENDLY_NAME] == DEFAULT_NAME + assert state.attributes[ATTR_ENTITY_ID] == [ + DEMO_COVER, + DEMO_COVER_POS, + DEMO_COVER_TILT, + DEMO_TILT, + ] + assert ATTR_ASSUMED_STATE not in state.attributes + assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0 + assert ATTR_CURRENT_POSITION not in state.attributes + assert ATTR_CURRENT_TILT_POSITION not in state.attributes + + # Set all entities as closed -> group state closed + hass.states.async_set(DEMO_COVER, STATE_CLOSED, {}) + hass.states.async_set(DEMO_COVER_POS, STATE_CLOSED, {}) + hass.states.async_set(DEMO_COVER_TILT, STATE_CLOSED, {}) + hass.states.async_set(DEMO_TILT, STATE_CLOSED, {}) + await hass.async_block_till_done() + state = hass.states.get(COVER_GROUP) + assert state.state == STATE_CLOSED + + # Set all entities as open -> group state open + hass.states.async_set(DEMO_COVER, STATE_OPEN, {}) + hass.states.async_set(DEMO_COVER_POS, STATE_OPEN, {}) + hass.states.async_set(DEMO_COVER_TILT, STATE_OPEN, {}) + hass.states.async_set(DEMO_TILT, STATE_OPEN, {}) + await hass.async_block_till_done() + state = hass.states.get(COVER_GROUP) + assert state.state == STATE_OPEN + + # Set first entity as open -> group state open + hass.states.async_set(DEMO_COVER, STATE_OPEN, {}) + hass.states.async_set(DEMO_COVER_POS, STATE_CLOSED, {}) + hass.states.async_set(DEMO_COVER_TILT, STATE_CLOSED, {}) + hass.states.async_set(DEMO_TILT, STATE_CLOSED, {}) + await hass.async_block_till_done() + state = hass.states.get(COVER_GROUP) + assert state.state == STATE_OPEN + + # Set last entity as open -> group state open + hass.states.async_set(DEMO_COVER, STATE_OPEN, {}) + hass.states.async_set(DEMO_COVER_POS, STATE_CLOSED, {}) + hass.states.async_set(DEMO_COVER_TILT, STATE_CLOSED, {}) + hass.states.async_set(DEMO_TILT, STATE_CLOSED, {}) + await hass.async_block_till_done() + state = hass.states.get(COVER_GROUP) + assert state.state == STATE_OPEN + + # Set conflicting valid states -> opening state has priority + hass.states.async_set(DEMO_COVER, STATE_OPEN, {}) + hass.states.async_set(DEMO_COVER_POS, STATE_OPENING, {}) + hass.states.async_set(DEMO_COVER_TILT, STATE_CLOSING, {}) + hass.states.async_set(DEMO_TILT, STATE_CLOSED, {}) + await hass.async_block_till_done() + state = hass.states.get(COVER_GROUP) + assert state.state == STATE_OPENING + + # Set all entities to unknown state -> group state unknown + hass.states.async_set(DEMO_COVER, STATE_UNKNOWN, {}) + hass.states.async_set(DEMO_COVER_POS, STATE_UNKNOWN, {}) + hass.states.async_set(DEMO_COVER_TILT, STATE_UNKNOWN, {}) + hass.states.async_set(DEMO_TILT, STATE_UNKNOWN, {}) + await hass.async_block_till_done() + state = hass.states.get(COVER_GROUP) + assert state.state == STATE_UNKNOWN + + # Set one entity to unknown state -> open state has priority + hass.states.async_set(DEMO_COVER, STATE_OPEN, {}) + hass.states.async_set(DEMO_COVER_POS, STATE_UNKNOWN, {}) + hass.states.async_set(DEMO_COVER_TILT, STATE_CLOSED, {}) + hass.states.async_set(DEMO_TILT, STATE_OPEN, {}) + await hass.async_block_till_done() + state = hass.states.get(COVER_GROUP) + assert state.state == STATE_OPEN + + # Set one entity to unknown state -> opening state has priority + hass.states.async_set(DEMO_COVER, STATE_OPEN, {}) + hass.states.async_set(DEMO_COVER_POS, STATE_OPENING, {}) + hass.states.async_set(DEMO_COVER_TILT, STATE_UNKNOWN, {}) + hass.states.async_set(DEMO_TILT, STATE_CLOSED, {}) + await hass.async_block_till_done() + state = hass.states.get(COVER_GROUP) + assert state.state == STATE_OPENING + + # Set one entity to unknown state -> closing state has priority + hass.states.async_set(DEMO_COVER, STATE_OPEN, {}) + hass.states.async_set(DEMO_COVER_POS, STATE_UNKNOWN, {}) + hass.states.async_set(DEMO_COVER_TILT, STATE_CLOSING, {}) + hass.states.async_set(DEMO_TILT, STATE_CLOSED, {}) + await hass.async_block_till_done() + state = hass.states.get(COVER_GROUP) + assert state.state == STATE_CLOSING + + @pytest.mark.parametrize("config_count", [(CONFIG_ATTRIBUTES, 1)]) async def test_attributes(hass, setup_comp): """Test handling of state attributes.""" @@ -196,7 +296,7 @@ async def test_attributes(hass, setup_comp): # ### Test assumed state ### # ########################## - # For covers + # For covers - assumed state set true if position differ hass.states.async_set( DEMO_COVER, STATE_OPEN, {ATTR_SUPPORTED_FEATURES: 4, ATTR_CURRENT_POSITION: 100} ) @@ -220,7 +320,7 @@ async def test_attributes(hass, setup_comp): assert ATTR_CURRENT_POSITION not in state.attributes assert state.attributes[ATTR_CURRENT_TILT_POSITION] == 60 - # For tilts + # For tilts - assumed state set true if tilt position differ hass.states.async_set( DEMO_TILT, STATE_OPEN, @@ -252,6 +352,7 @@ async def test_attributes(hass, setup_comp): state = hass.states.get(COVER_GROUP) assert state.attributes[ATTR_ASSUMED_STATE] is True + # Test entity registry integration entity_registry = er.async_get(hass) entry = entity_registry.async_get(COVER_GROUP) assert entry