Ensure lights added after group is created have the correct state (#41034)
This commit is contained in:
parent
ed171a885b
commit
78dfaa72a2
2 changed files with 283 additions and 23 deletions
|
@ -91,6 +91,7 @@ class GroupIntegrationRegistry:
|
|||
"""Class to hold a registry of integrations."""
|
||||
|
||||
on_off_mapping: Dict[str, str] = {STATE_ON: STATE_OFF}
|
||||
off_on_mapping: Dict[str, str] = {STATE_OFF: STATE_ON}
|
||||
on_states_by_domain: Dict[str, Set] = {}
|
||||
exclude_domains: Set = set()
|
||||
|
||||
|
@ -99,11 +100,14 @@ class GroupIntegrationRegistry:
|
|||
self.exclude_domains.add(current_domain.get())
|
||||
|
||||
def on_off_states(self, on_states: Set, off_state: str) -> None:
|
||||
"""Registry on and off states for the current domain."""
|
||||
"""Register on and off states for the current domain."""
|
||||
for on_state in on_states:
|
||||
if on_state not in self.on_off_mapping:
|
||||
self.on_off_mapping[on_state] = off_state
|
||||
|
||||
if len(on_states) == 1 and off_state not in self.off_on_mapping:
|
||||
self.off_on_mapping[off_state] = list(on_states)[0]
|
||||
|
||||
self.on_states_by_domain[current_domain.get()] = set(on_states)
|
||||
|
||||
|
||||
|
@ -543,6 +547,7 @@ class Group(Entity):
|
|||
data = {ATTR_ENTITY_ID: self.tracking, ATTR_ORDER: self._order}
|
||||
if not self.user_defined:
|
||||
data[ATTR_AUTO] = True
|
||||
|
||||
return data
|
||||
|
||||
@property
|
||||
|
@ -577,6 +582,7 @@ class Group(Entity):
|
|||
return
|
||||
|
||||
excluded_domains = self.hass.data[REG_KEY].exclude_domains
|
||||
|
||||
tracking = []
|
||||
trackable = []
|
||||
for ent_id in entity_ids:
|
||||
|
@ -592,6 +598,7 @@ class Group(Entity):
|
|||
@callback
|
||||
def _async_start(self, *_):
|
||||
"""Start tracking members and write state."""
|
||||
self._reset_tracked_state()
|
||||
self._async_start_tracking()
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
@ -625,15 +632,14 @@ class Group(Entity):
|
|||
|
||||
async def async_added_to_hass(self):
|
||||
"""Handle addition to Home Assistant."""
|
||||
if self.tracking:
|
||||
self._reset_tracked_state()
|
||||
|
||||
if self.hass.state != CoreState.running:
|
||||
self.hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_START, self._async_start
|
||||
)
|
||||
return
|
||||
|
||||
if self.tracking:
|
||||
self._reset_tracked_state()
|
||||
self._async_start_tracking()
|
||||
|
||||
async def async_will_remove_from_hass(self):
|
||||
|
@ -671,19 +677,26 @@ class Group(Entity):
|
|||
if state is not None:
|
||||
self._see_state(state)
|
||||
|
||||
def _see_state(self, state):
|
||||
def _see_state(self, new_state):
|
||||
"""Keep track of the the state."""
|
||||
entity_id = state.entity_id
|
||||
domain = state.domain
|
||||
|
||||
domain_on_state = self.hass.data[REG_KEY].on_states_by_domain.get(
|
||||
domain, {STATE_ON}
|
||||
)
|
||||
self._on_off[entity_id] = state.state in domain_on_state
|
||||
self._assumed[entity_id] = state.attributes.get(ATTR_ASSUMED_STATE)
|
||||
entity_id = new_state.entity_id
|
||||
domain = new_state.domain
|
||||
state = new_state.state
|
||||
registry = self.hass.data[REG_KEY]
|
||||
self._assumed[entity_id] = new_state.attributes.get(ATTR_ASSUMED_STATE)
|
||||
|
||||
if domain not in registry.on_states_by_domain:
|
||||
# Handle the group of a group case
|
||||
if state in registry.on_off_mapping:
|
||||
self._on_states.add(state)
|
||||
elif state in registry.off_on_mapping:
|
||||
self._on_states.add(registry.off_on_mapping[state])
|
||||
self._on_off[entity_id] = state in registry.on_off_mapping
|
||||
else:
|
||||
entity_on_state = registry.on_states_by_domain[domain]
|
||||
if domain in self.hass.data[REG_KEY].on_states_by_domain:
|
||||
self._on_states.update(domain_on_state)
|
||||
self._on_states.update(entity_on_state)
|
||||
self._on_off[entity_id] = state in entity_on_state
|
||||
|
||||
@callback
|
||||
def _async_update_group_state(self, tr_state=None):
|
||||
|
@ -726,7 +739,6 @@ class Group(Entity):
|
|||
# on state, we use STATE_ON/STATE_OFF
|
||||
else:
|
||||
on_state = STATE_ON
|
||||
|
||||
group_is_on = self.mode(self._on_off.values())
|
||||
if group_is_on:
|
||||
self._state = on_state
|
||||
|
|
|
@ -763,7 +763,6 @@ async def test_group_climate_all_cool(hass):
|
|||
hass.states.async_set("climate.two", "cool")
|
||||
hass.states.async_set("climate.three", "cool")
|
||||
|
||||
assert await async_setup_component(hass, "climate", {})
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"group",
|
||||
|
@ -773,6 +772,7 @@ async def test_group_climate_all_cool(hass):
|
|||
}
|
||||
},
|
||||
)
|
||||
assert await async_setup_component(hass, "climate", {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("group.group_zero").state == STATE_ON
|
||||
|
@ -804,8 +804,8 @@ async def test_group_alarm(hass):
|
|||
hass.states.async_set("alarm_control_panel.one", "armed_away")
|
||||
hass.states.async_set("alarm_control_panel.two", "armed_home")
|
||||
hass.states.async_set("alarm_control_panel.three", "armed_away")
|
||||
hass.state = CoreState.stopped
|
||||
|
||||
assert await async_setup_component(hass, "alarm_control_panel", {})
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"group",
|
||||
|
@ -817,8 +817,10 @@ async def test_group_alarm(hass):
|
|||
}
|
||||
},
|
||||
)
|
||||
assert await async_setup_component(hass, "alarm_control_panel", {})
|
||||
await hass.async_block_till_done()
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("group.group_zero").state == STATE_ON
|
||||
|
||||
|
||||
|
@ -850,8 +852,8 @@ async def test_group_vacuum_off(hass):
|
|||
hass.states.async_set("vacuum.one", "docked")
|
||||
hass.states.async_set("vacuum.two", "off")
|
||||
hass.states.async_set("vacuum.three", "off")
|
||||
hass.state = CoreState.stopped
|
||||
|
||||
assert await async_setup_component(hass, "vacuum", {})
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"group",
|
||||
|
@ -861,8 +863,11 @@ async def test_group_vacuum_off(hass):
|
|||
}
|
||||
},
|
||||
)
|
||||
assert await async_setup_component(hass, "vacuum", {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get("group.group_zero").state == STATE_OFF
|
||||
|
||||
|
||||
|
@ -893,7 +898,6 @@ async def test_device_tracker_not_home(hass):
|
|||
hass.states.async_set("device_tracker.two", "not_home")
|
||||
hass.states.async_set("device_tracker.three", "not_home")
|
||||
|
||||
assert await async_setup_component(hass, "device_tracker", {})
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"group",
|
||||
|
@ -916,7 +920,6 @@ async def test_light_removed(hass):
|
|||
hass.states.async_set("light.two", "off")
|
||||
hass.states.async_set("light.three", "on")
|
||||
|
||||
assert await async_setup_component(hass, "light", {})
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"group",
|
||||
|
@ -943,7 +946,6 @@ async def test_switch_removed(hass):
|
|||
hass.states.async_set("switch.three", "on")
|
||||
|
||||
hass.state = CoreState.stopped
|
||||
assert await async_setup_component(hass, "switch", {})
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"group",
|
||||
|
@ -956,6 +958,8 @@ async def test_switch_removed(hass):
|
|||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("group.group_zero").state == "unknown"
|
||||
assert await async_setup_component(hass, "switch", {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
await hass.async_block_till_done()
|
||||
|
@ -965,3 +969,247 @@ async def test_switch_removed(hass):
|
|||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("group.group_zero").state == "off"
|
||||
|
||||
|
||||
async def test_lights_added_after_group(hass):
|
||||
"""Test lights added after group."""
|
||||
|
||||
entity_ids = [
|
||||
"light.living_front_ri",
|
||||
"light.living_back_lef",
|
||||
"light.living_back_cen",
|
||||
"light.living_front_le",
|
||||
"light.living_front_ce",
|
||||
"light.living_back_rig",
|
||||
]
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"group",
|
||||
{
|
||||
"group": {
|
||||
"living_room_downlights": {"entities": entity_ids},
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("group.living_room_downlights").state == "unknown"
|
||||
|
||||
for entity_id in entity_ids:
|
||||
hass.states.async_set(entity_id, "off")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("group.living_room_downlights").state == "off"
|
||||
|
||||
|
||||
async def test_lights_added_before_group(hass):
|
||||
"""Test lights added before group."""
|
||||
|
||||
entity_ids = [
|
||||
"light.living_front_ri",
|
||||
"light.living_back_lef",
|
||||
"light.living_back_cen",
|
||||
"light.living_front_le",
|
||||
"light.living_front_ce",
|
||||
"light.living_back_rig",
|
||||
]
|
||||
|
||||
for entity_id in entity_ids:
|
||||
hass.states.async_set(entity_id, "off")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"group",
|
||||
{
|
||||
"group": {
|
||||
"living_room_downlights": {"entities": entity_ids},
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("group.living_room_downlights").state == "off"
|
||||
|
||||
|
||||
async def test_cover_added_after_group(hass):
|
||||
"""Test cover added after group."""
|
||||
|
||||
entity_ids = [
|
||||
"cover.upstairs",
|
||||
"cover.downstairs",
|
||||
]
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"group",
|
||||
{
|
||||
"group": {
|
||||
"shades": {"entities": entity_ids},
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
for entity_id in entity_ids:
|
||||
hass.states.async_set(entity_id, "open")
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("group.shades").state == "open"
|
||||
|
||||
for entity_id in entity_ids:
|
||||
hass.states.async_set(entity_id, "closed")
|
||||
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get("group.shades").state == "closed"
|
||||
|
||||
|
||||
async def test_group_that_references_a_group_of_lights(hass):
|
||||
"""Group that references a group of lights."""
|
||||
|
||||
entity_ids = [
|
||||
"light.living_front_ri",
|
||||
"light.living_back_lef",
|
||||
]
|
||||
hass.state = CoreState.stopped
|
||||
|
||||
for entity_id in entity_ids:
|
||||
hass.states.async_set(entity_id, "off")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"group",
|
||||
{
|
||||
"group": {
|
||||
"living_room_downlights": {"entities": entity_ids},
|
||||
"grouped_group": {
|
||||
"entities": ["group.living_room_downlights", *entity_ids]
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("group.living_room_downlights").state == "off"
|
||||
assert hass.states.get("group.grouped_group").state == "off"
|
||||
|
||||
|
||||
async def test_group_that_references_a_group_of_covers(hass):
|
||||
"""Group that references a group of covers."""
|
||||
|
||||
entity_ids = [
|
||||
"cover.living_front_ri",
|
||||
"cover.living_back_lef",
|
||||
]
|
||||
hass.state = CoreState.stopped
|
||||
|
||||
for entity_id in entity_ids:
|
||||
hass.states.async_set(entity_id, "closed")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"group",
|
||||
{
|
||||
"group": {
|
||||
"living_room_downcover": {"entities": entity_ids},
|
||||
"grouped_group": {
|
||||
"entities": ["group.living_room_downlights", *entity_ids]
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("group.living_room_downcover").state == "closed"
|
||||
assert hass.states.get("group.grouped_group").state == "closed"
|
||||
|
||||
|
||||
async def test_group_that_references_two_groups_of_covers(hass):
|
||||
"""Group that references a group of covers."""
|
||||
|
||||
entity_ids = [
|
||||
"cover.living_front_ri",
|
||||
"cover.living_back_lef",
|
||||
]
|
||||
hass.state = CoreState.stopped
|
||||
|
||||
for entity_id in entity_ids:
|
||||
hass.states.async_set(entity_id, "closed")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"group",
|
||||
{
|
||||
"group": {
|
||||
"living_room_downcover": {"entities": entity_ids},
|
||||
"living_room_upcover": {"entities": entity_ids},
|
||||
"grouped_group": {
|
||||
"entities": [
|
||||
"group.living_room_downlights",
|
||||
"group.living_room_upcover",
|
||||
]
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("group.living_room_downcover").state == "closed"
|
||||
assert hass.states.get("group.living_room_upcover").state == "closed"
|
||||
assert hass.states.get("group.grouped_group").state == "closed"
|
||||
|
||||
|
||||
async def test_group_that_references_two_types_of_groups(hass):
|
||||
"""Group that references a group of covers and device_trackers."""
|
||||
|
||||
group_1_entity_ids = [
|
||||
"cover.living_front_ri",
|
||||
"cover.living_back_lef",
|
||||
]
|
||||
group_2_entity_ids = [
|
||||
"device_tracker.living_front_ri",
|
||||
"device_tracker.living_back_lef",
|
||||
]
|
||||
hass.state = CoreState.stopped
|
||||
|
||||
for entity_id in group_1_entity_ids:
|
||||
hass.states.async_set(entity_id, "closed")
|
||||
for entity_id in group_2_entity_ids:
|
||||
hass.states.async_set(entity_id, "home")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"group",
|
||||
{
|
||||
"group": {
|
||||
"covers": {"entities": group_1_entity_ids},
|
||||
"device_trackers": {"entities": group_2_entity_ids},
|
||||
"grouped_group": {
|
||||
"entities": ["group.covers", "group.device_trackers"]
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("group.covers").state == "closed"
|
||||
assert hass.states.get("group.device_trackers").state == "home"
|
||||
assert hass.states.get("group.grouped_group").state == "on"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue