From 7cc9a4310d0403af70b177f4343e5897179de865 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 9 Feb 2022 14:27:46 +0100 Subject: [PATCH] Fix controlling nested groups (#66176) --- homeassistant/components/group/cover.py | 2 + homeassistant/components/group/fan.py | 2 + homeassistant/components/group/light.py | 3 ++ tests/components/group/test_cover.py | 50 ++++++++++++++++++ tests/components/group/test_fan.py | 56 +++++++++++++++++++++ tests/components/group/test_light.py | 23 +++++++-- tests/components/group/test_media_player.py | 28 ++++++++--- 7 files changed, 155 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/group/cover.py b/homeassistant/components/group/cover.py index a98f75fceb8..a4c550b8119 100644 --- a/homeassistant/components/group/cover.py +++ b/homeassistant/components/group/cover.py @@ -57,6 +57,8 @@ KEY_POSITION = "position" DEFAULT_NAME = "Cover Group" +# No limit on parallel updates to enable a group calling another group +PARALLEL_UPDATES = 0 PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { diff --git a/homeassistant/components/group/fan.py b/homeassistant/components/group/fan.py index cef30dc3c69..7920e0f5d20 100644 --- a/homeassistant/components/group/fan.py +++ b/homeassistant/components/group/fan.py @@ -52,6 +52,8 @@ SUPPORTED_FLAGS = {SUPPORT_SET_SPEED, SUPPORT_DIRECTION, SUPPORT_OSCILLATE} DEFAULT_NAME = "Fan Group" +# No limit on parallel updates to enable a group calling another group +PARALLEL_UPDATES = 0 PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { diff --git a/homeassistant/components/group/light.py b/homeassistant/components/group/light.py index 201156db600..ea74136b204 100644 --- a/homeassistant/components/group/light.py +++ b/homeassistant/components/group/light.py @@ -58,6 +58,9 @@ from .util import find_state_attributes, mean_tuple, reduce_attribute DEFAULT_NAME = "Light Group" +# No limit on parallel updates to enable a group calling another group +PARALLEL_UPDATES = 0 + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, diff --git a/tests/components/group/test_cover.py b/tests/components/group/test_cover.py index cf1fba992e7..d090141a9d2 100644 --- a/tests/components/group/test_cover.py +++ b/tests/components/group/test_cover.py @@ -1,6 +1,7 @@ """The tests for the group cover platform.""" from datetime import timedelta +import async_timeout import pytest from homeassistant.components.cover import ( @@ -735,3 +736,52 @@ async def test_is_opening_closing(hass, setup_comp): assert hass.states.get(DEMO_COVER_TILT).state == STATE_OPENING assert hass.states.get(DEMO_COVER_POS).state == STATE_OPEN assert hass.states.get(COVER_GROUP).state == STATE_OPENING + + +async def test_nested_group(hass): + """Test nested cover group.""" + await async_setup_component( + hass, + DOMAIN, + { + DOMAIN: [ + {"platform": "demo"}, + { + "platform": "group", + "entities": ["cover.bedroom_group"], + "name": "Nested Group", + }, + { + "platform": "group", + CONF_ENTITIES: [DEMO_COVER_POS, DEMO_COVER_TILT], + "name": "Bedroom Group", + }, + ] + }, + ) + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + state = hass.states.get("cover.bedroom_group") + assert state is not None + assert state.state == STATE_OPEN + assert state.attributes.get(ATTR_ENTITY_ID) == [DEMO_COVER_POS, DEMO_COVER_TILT] + + state = hass.states.get("cover.nested_group") + assert state is not None + assert state.state == STATE_OPEN + assert state.attributes.get(ATTR_ENTITY_ID) == ["cover.bedroom_group"] + + # Test controlling the nested group + async with async_timeout.timeout(0.5): + await hass.services.async_call( + DOMAIN, + SERVICE_CLOSE_COVER, + {ATTR_ENTITY_ID: "cover.nested_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.bedroom_group").state == STATE_CLOSING + assert hass.states.get("cover.nested_group").state == STATE_CLOSING diff --git a/tests/components/group/test_fan.py b/tests/components/group/test_fan.py index abb1dcf245a..19b4fe4670a 100644 --- a/tests/components/group/test_fan.py +++ b/tests/components/group/test_fan.py @@ -1,6 +1,7 @@ """The tests for the group fan platform.""" from unittest.mock import patch +import async_timeout import pytest from homeassistant import config as hass_config @@ -497,3 +498,58 @@ async def test_service_calls(hass, setup_comp): assert percentage_full_fan_state.attributes[ATTR_DIRECTION] == DIRECTION_REVERSE fan_group_state = hass.states.get(FAN_GROUP) assert fan_group_state.attributes[ATTR_DIRECTION] == DIRECTION_REVERSE + + +async def test_nested_group(hass): + """Test nested fan group.""" + await async_setup_component( + hass, + DOMAIN, + { + DOMAIN: [ + {"platform": "demo"}, + { + "platform": "group", + "entities": ["fan.bedroom_group"], + "name": "Nested Group", + }, + { + "platform": "group", + CONF_ENTITIES: [ + LIVING_ROOM_FAN_ENTITY_ID, + PERCENTAGE_FULL_FAN_ENTITY_ID, + ], + "name": "Bedroom Group", + }, + ] + }, + ) + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + state = hass.states.get("fan.bedroom_group") + assert state is not None + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ENTITY_ID) == [ + LIVING_ROOM_FAN_ENTITY_ID, + PERCENTAGE_FULL_FAN_ENTITY_ID, + ] + + state = hass.states.get("fan.nested_group") + assert state is not None + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_ENTITY_ID) == ["fan.bedroom_group"] + + # Test controlling the nested group + async with async_timeout.timeout(0.5): + await hass.services.async_call( + DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "fan.nested_group"}, + blocking=True, + ) + assert hass.states.get(LIVING_ROOM_FAN_ENTITY_ID).state == STATE_ON + 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 diff --git a/tests/components/group/test_light.py b/tests/components/group/test_light.py index 843f15c7113..d356b20b40f 100644 --- a/tests/components/group/test_light.py +++ b/tests/components/group/test_light.py @@ -2,6 +2,7 @@ import unittest.mock from unittest.mock import MagicMock, patch +import async_timeout import pytest from homeassistant import config as hass_config @@ -1470,12 +1471,12 @@ async def test_reload_with_base_integration_platform_not_setup(hass): async def test_nested_group(hass): """Test nested light group.""" - hass.states.async_set("light.kitchen", "on") await async_setup_component( hass, LIGHT_DOMAIN, { LIGHT_DOMAIN: [ + {"platform": "demo"}, { "platform": DOMAIN, "entities": ["light.bedroom_group"], @@ -1483,7 +1484,7 @@ async def test_nested_group(hass): }, { "platform": DOMAIN, - "entities": ["light.kitchen", "light.bedroom"], + "entities": ["light.bed_light", "light.kitchen_lights"], "name": "Bedroom Group", }, ] @@ -1496,9 +1497,25 @@ async def test_nested_group(hass): state = hass.states.get("light.bedroom_group") assert state is not None assert state.state == STATE_ON - assert state.attributes.get(ATTR_ENTITY_ID) == ["light.kitchen", "light.bedroom"] + assert state.attributes.get(ATTR_ENTITY_ID) == [ + "light.bed_light", + "light.kitchen_lights", + ] state = hass.states.get("light.nested_group") assert state is not None assert state.state == STATE_ON assert state.attributes.get(ATTR_ENTITY_ID) == ["light.bedroom_group"] + + # Test controlling the nested group + async with async_timeout.timeout(0.5): + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TOGGLE, + {ATTR_ENTITY_ID: "light.nested_group"}, + blocking=True, + ) + assert hass.states.get("light.bed_light").state == STATE_OFF + 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 diff --git a/tests/components/group/test_media_player.py b/tests/components/group/test_media_player.py index 27962297952..f741e2d1a84 100644 --- a/tests/components/group/test_media_player.py +++ b/tests/components/group/test_media_player.py @@ -1,6 +1,7 @@ """The tests for the Media group platform.""" from unittest.mock import patch +import async_timeout import pytest from homeassistant.components.group import DOMAIN @@ -486,12 +487,12 @@ async def test_service_calls(hass, mock_media_seek): async def test_nested_group(hass): """Test nested media group.""" - hass.states.async_set("media_player.player_1", "on") await async_setup_component( hass, MEDIA_DOMAIN, { MEDIA_DOMAIN: [ + {"platform": "demo"}, { "platform": DOMAIN, "entities": ["media_player.group_1"], @@ -499,7 +500,7 @@ async def test_nested_group(hass): }, { "platform": DOMAIN, - "entities": ["media_player.player_1", "media_player.player_2"], + "entities": ["media_player.bedroom", "media_player.kitchen"], "name": "Group 1", }, ] @@ -511,13 +512,28 @@ async def test_nested_group(hass): state = hass.states.get("media_player.group_1") assert state is not None - assert state.state == STATE_ON + assert state.state == STATE_PLAYING assert state.attributes.get(ATTR_ENTITY_ID) == [ - "media_player.player_1", - "media_player.player_2", + "media_player.bedroom", + "media_player.kitchen", ] state = hass.states.get("media_player.nested_group") assert state is not None - assert state.state == STATE_ON + assert state.state == STATE_PLAYING assert state.attributes.get(ATTR_ENTITY_ID) == ["media_player.group_1"] + + # Test controlling the nested group + async with async_timeout.timeout(0.5): + await hass.services.async_call( + MEDIA_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "media_player.group_1"}, + blocking=True, + ) + + await hass.async_block_till_done() + assert hass.states.get("media_player.bedroom").state == STATE_OFF + assert hass.states.get("media_player.kitchen").state == STATE_OFF + assert hass.states.get("media_player.group_1").state == STATE_OFF + assert hass.states.get("media_player.nested_group").state == STATE_OFF