Add support for unavailable to cover groups (#74053)

This commit is contained in:
Erik Montnemery 2022-06-28 11:12:14 +02:00 committed by GitHub
parent af71c250d5
commit ae63cd8677
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 49 additions and 32 deletions

View file

@ -35,6 +35,8 @@ from homeassistant.const import (
STATE_CLOSING,
STATE_OPEN,
STATE_OPENING,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
from homeassistant.core import Event, HomeAssistant, State, callback
from homeassistant.helpers import config_validation as cv, entity_registry as er
@ -98,6 +100,7 @@ async def async_setup_entry(
class CoverGroup(GroupEntity, CoverEntity):
"""Representation of a CoverGroup."""
_attr_available: bool = False
_attr_is_closed: bool | None = None
_attr_is_opening: bool | None = False
_attr_is_closing: bool | None = False
@ -267,29 +270,38 @@ class CoverGroup(GroupEntity, CoverEntity):
"""Update state and attributes."""
self._attr_assumed_state = False
states = [
state.state
for entity_id in self._entities
if (state := self.hass.states.get(entity_id)) is not None
]
valid_state = any(
state not in (STATE_UNKNOWN, STATE_UNAVAILABLE) for state in states
)
# Set group as unavailable if all members are unavailable or missing
self._attr_available = any(state != STATE_UNAVAILABLE for state in states)
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:
if not (state := self.hass.states.get(entity_id)):
continue
if state.state == STATE_OPEN:
self._attr_is_closed = False
has_valid_state = True
continue
if state.state == STATE_CLOSED:
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:
if not valid_state:
# Set as unknown if all members are unknown or unavailable
self._attr_is_closed = None
position_covers = self._covers[KEY_POSITION]

View file

@ -109,32 +109,36 @@ async def test_state(hass, setup_comp):
Otherwise, the group state is closed.
"""
state = hass.states.get(COVER_GROUP)
# No entity has a valid state -> group state unknown
assert state.state == STATE_UNKNOWN
# No entity has a valid state -> group state unavailable
assert state.state == STATE_UNAVAILABLE
assert state.attributes[ATTR_FRIENDLY_NAME] == DEFAULT_NAME
assert ATTR_ENTITY_ID not in state.attributes
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
# Test group members exposed as attribute
hass.states.async_set(DEMO_COVER, STATE_UNKNOWN, {})
await hass.async_block_till_done()
state = hass.states.get(COVER_GROUP)
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
# The group state is unavailable if all group members are unavailable.
hass.states.async_set(DEMO_COVER, STATE_UNAVAILABLE, {})
hass.states.async_set(DEMO_COVER_POS, STATE_UNAVAILABLE, {})
hass.states.async_set(DEMO_COVER_TILT, STATE_UNAVAILABLE, {})
hass.states.async_set(DEMO_TILT, STATE_UNAVAILABLE, {})
await hass.async_block_till_done()
state = hass.states.get(COVER_GROUP)
assert state.state == STATE_UNAVAILABLE
# The group state is unknown if all group members are unknown or unavailable.
for state_1 in (STATE_UNAVAILABLE, STATE_UNKNOWN):
for state_2 in (STATE_UNAVAILABLE, STATE_UNKNOWN):
for state_3 in (STATE_UNAVAILABLE, STATE_UNKNOWN):
hass.states.async_set(DEMO_COVER, state_1, {})
hass.states.async_set(DEMO_COVER_POS, state_2, {})
hass.states.async_set(DEMO_COVER_TILT, state_3, {})
hass.states.async_set(DEMO_TILT, STATE_UNAVAILABLE, {})
await hass.async_block_till_done()
state = hass.states.get(COVER_GROUP)
assert state.state == STATE_UNKNOWN
for state_1 in (STATE_UNAVAILABLE, STATE_UNKNOWN):
for state_2 in (STATE_UNAVAILABLE, STATE_UNKNOWN):
for state_3 in (STATE_UNAVAILABLE, STATE_UNKNOWN):
@ -233,28 +237,23 @@ async def test_state(hass, setup_comp):
state = hass.states.get(COVER_GROUP)
assert state.state == STATE_CLOSED
# All group members removed from the state machine -> unknown
# All group members removed from the state machine -> unavailable
hass.states.async_remove(DEMO_COVER)
hass.states.async_remove(DEMO_COVER_POS)
hass.states.async_remove(DEMO_COVER_TILT)
hass.states.async_remove(DEMO_TILT)
await hass.async_block_till_done()
state = hass.states.get(COVER_GROUP)
assert state.state == STATE_UNKNOWN
assert state.state == STATE_UNAVAILABLE
@pytest.mark.parametrize("config_count", [(CONFIG_ATTRIBUTES, 1)])
async def test_attributes(hass, setup_comp):
"""Test handling of state attributes."""
state = hass.states.get(COVER_GROUP)
assert state.state == STATE_UNKNOWN
assert state.state == STATE_UNAVAILABLE
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_ENTITY_ID not in state.attributes
assert ATTR_ASSUMED_STATE not in state.attributes
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
assert ATTR_CURRENT_POSITION not in state.attributes
@ -266,6 +265,12 @@ async def test_attributes(hass, setup_comp):
state = hass.states.get(COVER_GROUP)
assert state.state == STATE_CLOSED
assert state.attributes[ATTR_ENTITY_ID] == [
DEMO_COVER,
DEMO_COVER_POS,
DEMO_COVER_TILT,
DEMO_TILT,
]
# Set entity as opening
hass.states.async_set(DEMO_COVER, STATE_OPENING, {})