mirror of
https://github.com/home-assistant/core.git
synced 2026-04-25 10:45:06 +00:00
Set assumed state to group if at least one child has assumed state (#154163)
This commit is contained in:
@@ -282,6 +282,7 @@ class CoverGroup(GroupEntity, CoverEntity):
|
||||
self._attr_is_closed = True
|
||||
self._attr_is_closing = False
|
||||
self._attr_is_opening = False
|
||||
self._update_assumed_state_from_members()
|
||||
for entity_id in self._entity_ids:
|
||||
if not (state := self.hass.states.get(entity_id)):
|
||||
continue
|
||||
|
||||
@@ -115,6 +115,17 @@ class GroupEntity(Entity):
|
||||
def async_update_group_state(self) -> None:
|
||||
"""Abstract method to update the entity."""
|
||||
|
||||
@callback
|
||||
def _update_assumed_state_from_members(self) -> None:
|
||||
"""Update assumed_state based on member entities."""
|
||||
self._attr_assumed_state = False
|
||||
for entity_id in self._entity_ids:
|
||||
if (state := self.hass.states.get(entity_id)) is None:
|
||||
continue
|
||||
if state.attributes.get(ATTR_ASSUMED_STATE):
|
||||
self._attr_assumed_state = True
|
||||
return
|
||||
|
||||
@callback
|
||||
def async_update_supported_features(
|
||||
self,
|
||||
|
||||
@@ -252,6 +252,7 @@ class FanGroup(GroupEntity, FanEntity):
|
||||
@callback
|
||||
def async_update_group_state(self) -> None:
|
||||
"""Update state and attributes."""
|
||||
self._update_assumed_state_from_members()
|
||||
|
||||
states = [
|
||||
state
|
||||
|
||||
@@ -205,6 +205,8 @@ class LightGroup(GroupEntity, LightEntity):
|
||||
@callback
|
||||
def async_update_group_state(self) -> None:
|
||||
"""Query all members and determine the light group state."""
|
||||
self._update_assumed_state_from_members()
|
||||
|
||||
states = [
|
||||
state
|
||||
for entity_id in self._entity_ids
|
||||
|
||||
@@ -156,6 +156,8 @@ class SwitchGroup(GroupEntity, SwitchEntity):
|
||||
@callback
|
||||
def async_update_group_state(self) -> None:
|
||||
"""Query all members and determine the switch group state."""
|
||||
self._update_assumed_state_from_members()
|
||||
|
||||
states = [
|
||||
state.state
|
||||
for entity_id in self._entity_ids
|
||||
|
||||
@@ -421,13 +421,6 @@ async def test_attributes(
|
||||
assert ATTR_CURRENT_POSITION not in state.attributes
|
||||
assert ATTR_CURRENT_TILT_POSITION not in state.attributes
|
||||
|
||||
# Group member has set assumed_state
|
||||
hass.states.async_set(DEMO_TILT, CoverState.CLOSED, {ATTR_ASSUMED_STATE: True})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(COVER_GROUP)
|
||||
assert ATTR_ASSUMED_STATE not in state.attributes
|
||||
|
||||
# Test entity registry integration
|
||||
entry = entity_registry.async_get(COVER_GROUP)
|
||||
assert entry
|
||||
@@ -859,6 +852,61 @@ async def test_is_opening_closing(hass: HomeAssistant) -> None:
|
||||
assert hass.states.get(COVER_GROUP).state == CoverState.OPENING
|
||||
|
||||
|
||||
@pytest.mark.parametrize("config_count", [(CONFIG_ATTRIBUTES, 1)])
|
||||
@pytest.mark.usefixtures("setup_comp")
|
||||
async def test_assumed_state(hass: HomeAssistant) -> None:
|
||||
"""Test assumed_state attribute behavior."""
|
||||
# No members with assumed_state -> group doesn't have assumed_state in attributes
|
||||
hass.states.async_set(DEMO_COVER, CoverState.OPEN, {})
|
||||
hass.states.async_set(DEMO_COVER_POS, CoverState.OPEN, {})
|
||||
hass.states.async_set(DEMO_COVER_TILT, CoverState.CLOSED, {})
|
||||
hass.states.async_set(DEMO_TILT, CoverState.CLOSED, {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(COVER_GROUP)
|
||||
assert ATTR_ASSUMED_STATE not in state.attributes
|
||||
|
||||
# One member with assumed_state=True -> group has assumed_state=True
|
||||
hass.states.async_set(DEMO_COVER, CoverState.OPEN, {ATTR_ASSUMED_STATE: True})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(COVER_GROUP)
|
||||
assert state.attributes.get(ATTR_ASSUMED_STATE) is True
|
||||
|
||||
# Multiple members with assumed_state=True -> group has assumed_state=True
|
||||
hass.states.async_set(
|
||||
DEMO_COVER_TILT, CoverState.CLOSED, {ATTR_ASSUMED_STATE: True}
|
||||
)
|
||||
hass.states.async_set(DEMO_TILT, CoverState.CLOSED, {ATTR_ASSUMED_STATE: True})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(COVER_GROUP)
|
||||
assert state.attributes.get(ATTR_ASSUMED_STATE) is True
|
||||
|
||||
# Unavailable member with assumed_state=True -> group has assumed_state=True
|
||||
hass.states.async_set(DEMO_COVER, CoverState.OPEN, {})
|
||||
hass.states.async_set(DEMO_COVER_TILT, CoverState.CLOSED, {})
|
||||
hass.states.async_set(DEMO_TILT, STATE_UNAVAILABLE, {ATTR_ASSUMED_STATE: True})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(COVER_GROUP)
|
||||
assert state.attributes.get(ATTR_ASSUMED_STATE) is True
|
||||
|
||||
# Unknown member with assumed_state=True -> group has assumed_state=True
|
||||
hass.states.async_set(DEMO_TILT, STATE_UNKNOWN, {ATTR_ASSUMED_STATE: True})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(COVER_GROUP)
|
||||
assert state.attributes.get(ATTR_ASSUMED_STATE) is True
|
||||
|
||||
# All members without assumed_state -> group doesn't have assumed_state in attributes
|
||||
hass.states.async_set(DEMO_TILT, CoverState.CLOSED, {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(COVER_GROUP)
|
||||
assert ATTR_ASSUMED_STATE not in state.attributes
|
||||
|
||||
|
||||
async def test_nested_group(hass: HomeAssistant) -> None:
|
||||
"""Test nested cover group."""
|
||||
await async_setup_component(
|
||||
|
||||
@@ -587,3 +587,47 @@ async def test_nested_group(hass: HomeAssistant) -> None:
|
||||
assert hass.states.get(PERCENTAGE_FULL_FAN_ENTITY_ID).state == STATE_ON
|
||||
assert hass.states.get("fan.bedroom_group").state == STATE_ON
|
||||
assert hass.states.get("fan.nested_group").state == STATE_ON
|
||||
|
||||
|
||||
async def test_assumed_state(hass: HomeAssistant) -> None:
|
||||
"""Test assumed_state attribute behavior."""
|
||||
await async_setup_component(
|
||||
hass,
|
||||
FAN_DOMAIN,
|
||||
{
|
||||
FAN_DOMAIN: [
|
||||
{"platform": "demo"},
|
||||
{
|
||||
"platform": "group",
|
||||
CONF_ENTITIES: [LIVING_ROOM_FAN_ENTITY_ID, CEILING_FAN_ENTITY_ID],
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# No members with assumed_state -> group doesn't have assumed_state in attributes
|
||||
hass.states.async_set(LIVING_ROOM_FAN_ENTITY_ID, STATE_ON, {})
|
||||
hass.states.async_set(CEILING_FAN_ENTITY_ID, STATE_OFF, {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(FAN_GROUP)
|
||||
assert ATTR_ASSUMED_STATE not in state.attributes
|
||||
|
||||
# One member with assumed_state=True -> group has assumed_state=True
|
||||
hass.states.async_set(
|
||||
LIVING_ROOM_FAN_ENTITY_ID, STATE_ON, {ATTR_ASSUMED_STATE: True}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(FAN_GROUP)
|
||||
assert state.attributes.get(ATTR_ASSUMED_STATE) is True
|
||||
|
||||
# All members without assumed_state -> group doesn't have assumed_state in attributes
|
||||
hass.states.async_set(LIVING_ROOM_FAN_ENTITY_ID, STATE_ON, {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(FAN_GROUP)
|
||||
assert ATTR_ASSUMED_STATE not in state.attributes
|
||||
|
||||
@@ -30,6 +30,7 @@ from homeassistant.components.light import (
|
||||
ColorMode,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ASSUMED_STATE,
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_SUPPORTED_FEATURES,
|
||||
EVENT_CALL_SERVICE,
|
||||
@@ -1647,3 +1648,72 @@ async def test_nested_group(hass: HomeAssistant) -> None:
|
||||
assert hass.states.get("light.kitchen_lights").state == STATE_OFF
|
||||
assert hass.states.get("light.bedroom_group").state == STATE_OFF
|
||||
assert hass.states.get("light.nested_group").state == STATE_OFF
|
||||
|
||||
|
||||
async def test_assumed_state(hass: HomeAssistant) -> None:
|
||||
"""Test assumed_state attribute behavior."""
|
||||
await async_setup_component(
|
||||
hass,
|
||||
LIGHT_DOMAIN,
|
||||
{
|
||||
LIGHT_DOMAIN: {
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.kitchen", "light.bedroom", "light.living_room"],
|
||||
"name": "Light Group",
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# No members with assumed_state -> group doesn't have assumed_state in attributes
|
||||
hass.states.async_set("light.kitchen", STATE_ON, {})
|
||||
hass.states.async_set("light.bedroom", STATE_ON, {})
|
||||
hass.states.async_set("light.living_room", STATE_OFF, {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("light.light_group")
|
||||
assert ATTR_ASSUMED_STATE not in state.attributes
|
||||
|
||||
# One member with assumed_state=True -> group has assumed_state=True
|
||||
hass.states.async_set("light.kitchen", STATE_ON, {ATTR_ASSUMED_STATE: True})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.attributes.get(ATTR_ASSUMED_STATE) is True
|
||||
|
||||
# Multiple members with assumed_state=True -> group has assumed_state=True
|
||||
hass.states.async_set("light.bedroom", STATE_OFF, {ATTR_ASSUMED_STATE: True})
|
||||
hass.states.async_set("light.living_room", STATE_OFF, {ATTR_ASSUMED_STATE: True})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.attributes.get(ATTR_ASSUMED_STATE) is True
|
||||
|
||||
# Unavailable member with assumed_state=True -> group has assumed_state=True
|
||||
hass.states.async_set("light.kitchen", STATE_ON, {})
|
||||
hass.states.async_set("light.bedroom", STATE_OFF, {})
|
||||
hass.states.async_set(
|
||||
"light.living_room", STATE_UNAVAILABLE, {ATTR_ASSUMED_STATE: True}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.attributes.get(ATTR_ASSUMED_STATE) is True
|
||||
|
||||
# Unknown member with assumed_state=True -> group has assumed_state=True
|
||||
hass.states.async_set(
|
||||
"light.living_room", STATE_UNKNOWN, {ATTR_ASSUMED_STATE: True}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.attributes.get(ATTR_ASSUMED_STATE) is True
|
||||
|
||||
# All members without assumed_state -> group doesn't have assumed_state in attributes
|
||||
hass.states.async_set("light.living_room", STATE_OFF, {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("light.light_group")
|
||||
assert ATTR_ASSUMED_STATE not in state.attributes
|
||||
|
||||
@@ -14,6 +14,7 @@ from homeassistant.components.switch import (
|
||||
SERVICE_TURN_ON,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ASSUMED_STATE,
|
||||
ATTR_ENTITY_ID,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
@@ -458,3 +459,43 @@ async def test_nested_group(hass: HomeAssistant) -> None:
|
||||
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
|
||||
assert hass.states.get("switch.some_group").state == STATE_OFF
|
||||
assert hass.states.get("switch.nested_group").state == STATE_OFF
|
||||
|
||||
|
||||
async def test_assumed_state(hass: HomeAssistant) -> None:
|
||||
"""Test assumed_state attribute behavior."""
|
||||
await async_setup_component(
|
||||
hass,
|
||||
SWITCH_DOMAIN,
|
||||
{
|
||||
SWITCH_DOMAIN: {
|
||||
"platform": DOMAIN,
|
||||
"entities": ["switch.tv", "switch.soundbar"],
|
||||
"name": "Media Group",
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# No members with assumed_state -> group doesn't have assumed_state in attributes
|
||||
hass.states.async_set("switch.tv", STATE_ON, {})
|
||||
hass.states.async_set("switch.soundbar", STATE_OFF, {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("switch.media_group")
|
||||
assert ATTR_ASSUMED_STATE not in state.attributes
|
||||
|
||||
# One member with assumed_state=True -> group has assumed_state=True
|
||||
hass.states.async_set("switch.tv", STATE_ON, {ATTR_ASSUMED_STATE: True})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("switch.media_group")
|
||||
assert state.attributes.get(ATTR_ASSUMED_STATE) is True
|
||||
|
||||
# All members without assumed_state -> group doesn't have assumed_state in attributes
|
||||
hass.states.async_set("switch.tv", STATE_ON, {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("switch.media_group")
|
||||
assert ATTR_ASSUMED_STATE not in state.attributes
|
||||
|
||||
Reference in New Issue
Block a user