diff --git a/homeassistant/components/group/binary_sensor.py b/homeassistant/components/group/binary_sensor.py index de0c3d393ca..54a98a68e43 100644 --- a/homeassistant/components/group/binary_sensor.py +++ b/homeassistant/components/group/binary_sensor.py @@ -9,6 +9,7 @@ from homeassistant.components.binary_sensor import ( PLATFORM_SCHEMA, BinarySensorEntity, ) +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_ENTITY_ID, CONF_DEVICE_CLASS, @@ -20,7 +21,7 @@ from homeassistant.const import ( STATE_UNKNOWN, ) from homeassistant.core import Event, HomeAssistant, callback -import homeassistant.helpers.config_validation as cv +from homeassistant.helpers import config_validation as cv, entity_registry as er from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType @@ -49,7 +50,7 @@ async def async_setup_platform( async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: - """Set up the Group Binary Sensor platform.""" + """Set up the Binary Sensor Group platform.""" async_add_entities( [ BinarySensorGroup( @@ -63,6 +64,27 @@ async def async_setup_platform( ) +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Initialize Binary Sensor Group config entry.""" + registry = er.async_get(hass) + entities = er.async_validate_entity_ids( + registry, config_entry.options[CONF_ENTITIES] + ) + mode = config_entry.options[CONF_ALL] + + async_add_entities( + [ + BinarySensorGroup( + config_entry.entry_id, config_entry.title, None, entities, mode + ) + ] + ) + + class BinarySensorGroup(GroupEntity, BinarySensorEntity): """Representation of a BinarySensorGroup.""" diff --git a/homeassistant/components/group/config_flow.py b/homeassistant/components/group/config_flow.py index 0547578b131..d1cd258b7b8 100644 --- a/homeassistant/components/group/config_flow.py +++ b/homeassistant/components/group/config_flow.py @@ -15,6 +15,7 @@ from homeassistant.helpers.helper_config_entry_flow import ( ) from . import DOMAIN +from .binary_sensor import CONF_ALL def basic_group_options_schema(domain: str) -> vol.Schema: @@ -35,12 +36,24 @@ def basic_group_config_schema(domain: str) -> vol.Schema: ) +BINARY_SENSOR_OPTIONS_SCHEMA = basic_group_options_schema("binary_sensor").extend( + { + vol.Required(CONF_ALL, default=False): selector.selector({"boolean": {}}), + } +) + +BINARY_SENSOR_CONFIG_SCHEMA = vol.Schema( + {vol.Required("name"): selector.selector({"text": {}})} +).extend(BINARY_SENSOR_OPTIONS_SCHEMA.schema) + + INITIAL_STEP_SCHEMA = vol.Schema( { vol.Required("group_type"): selector.selector( { "select": { "options": [ + "binary_sensor", "cover", "fan", "light", @@ -61,6 +74,7 @@ def choose_config_step(options: dict[str, Any]) -> str: CONFIG_FLOW = { "user": HelperFlowStep(INITIAL_STEP_SCHEMA, next_step=choose_config_step), + "binary_sensor": HelperFlowStep(BINARY_SENSOR_CONFIG_SCHEMA), "cover": HelperFlowStep(basic_group_config_schema("cover")), "fan": HelperFlowStep(basic_group_config_schema("fan")), "light": HelperFlowStep(basic_group_config_schema("light")), @@ -70,6 +84,7 @@ CONFIG_FLOW = { OPTIONS_FLOW = { "init": HelperFlowStep(None, next_step=choose_config_step), + "binary_sensor": HelperFlowStep(BINARY_SENSOR_OPTIONS_SCHEMA), "cover": HelperFlowStep(basic_group_options_schema("cover")), "fan": HelperFlowStep(basic_group_options_schema("fan")), "light": HelperFlowStep(basic_group_options_schema("light")), diff --git a/homeassistant/components/group/cover.py b/homeassistant/components/group/cover.py index 851079ad5e4..c2d263ab8ad 100644 --- a/homeassistant/components/group/cover.py +++ b/homeassistant/components/group/cover.py @@ -76,7 +76,7 @@ async def async_setup_platform( async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: - """Set up the Group Cover platform.""" + """Set up the Cover Group platform.""" async_add_entities( [ CoverGroup( @@ -91,14 +91,14 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Initialize Light Switch config entry.""" + """Initialize Cover Group config entry.""" registry = er.async_get(hass) - entity_id = er.async_validate_entity_ids( + entities = er.async_validate_entity_ids( registry, config_entry.options[CONF_ENTITIES] ) async_add_entities( - [CoverGroup(config_entry.entry_id, config_entry.title, entity_id)] + [CoverGroup(config_entry.entry_id, config_entry.title, entities)] ) diff --git a/homeassistant/components/group/fan.py b/homeassistant/components/group/fan.py index a5f7cff127f..0f39e9de974 100644 --- a/homeassistant/components/group/fan.py +++ b/homeassistant/components/group/fan.py @@ -73,7 +73,7 @@ async def async_setup_platform( async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: - """Set up the Group Cover platform.""" + """Set up the Fan Group platform.""" async_add_entities( [FanGroup(config.get(CONF_UNIQUE_ID), config[CONF_NAME], config[CONF_ENTITIES])] ) @@ -84,13 +84,13 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Initialize Light Switch config entry.""" + """Initialize Fan Group config entry.""" registry = er.async_get(hass) - entity_id = er.async_validate_entity_ids( + entities = er.async_validate_entity_ids( registry, config_entry.options[CONF_ENTITIES] ) - async_add_entities([FanGroup(config_entry.entry_id, config_entry.title, entity_id)]) + async_add_entities([FanGroup(config_entry.entry_id, config_entry.title, entities)]) class FanGroup(GroupEntity, FanEntity): diff --git a/homeassistant/components/group/light.py b/homeassistant/components/group/light.py index c51baf0ff66..12f316497e6 100644 --- a/homeassistant/components/group/light.py +++ b/homeassistant/components/group/light.py @@ -98,14 +98,14 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Initialize Light Switch config entry.""" + """Initialize Light Group config entry.""" registry = er.async_get(hass) - entity_id = er.async_validate_entity_ids( + entities = er.async_validate_entity_ids( registry, config_entry.options[CONF_ENTITIES] ) async_add_entities( - [LightGroup(config_entry.entry_id, config_entry.title, entity_id)] + [LightGroup(config_entry.entry_id, config_entry.title, entities)] ) diff --git a/homeassistant/components/group/media_player.py b/homeassistant/components/group/media_player.py index e1c044fd3e3..97d8f51536c 100644 --- a/homeassistant/components/group/media_player.py +++ b/homeassistant/components/group/media_player.py @@ -87,10 +87,10 @@ async def async_setup_platform( async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: - """Set up the Media Group platform.""" + """Set up the MediaPlayer Group platform.""" async_add_entities( [ - MediaGroup( + MediaPlayerGroup( config.get(CONF_UNIQUE_ID), config[CONF_NAME], config[CONF_ENTITIES] ) ] @@ -102,18 +102,18 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Initialize Light Switch config entry.""" + """Initialize MediaPlayer Group config entry.""" registry = er.async_get(hass) - entity_id = er.async_validate_entity_ids( + entities = er.async_validate_entity_ids( registry, config_entry.options[CONF_ENTITIES] ) async_add_entities( - [MediaGroup(config_entry.entry_id, config_entry.title, entity_id)] + [MediaPlayerGroup(config_entry.entry_id, config_entry.title, entities)] ) -class MediaGroup(MediaPlayerEntity): +class MediaPlayerGroup(MediaPlayerEntity): """Representation of a Media Group.""" def __init__(self, unique_id: str | None, name: str, entities: list[str]) -> None: diff --git a/homeassistant/components/group/strings.json b/homeassistant/components/group/strings.json index 8f0531ac7e4..e0b1eb1ab23 100644 --- a/homeassistant/components/group/strings.json +++ b/homeassistant/components/group/strings.json @@ -2,62 +2,77 @@ "title": "Group", "config": { "step": { - "init": { - "description": "Select group type", + "user": { + "title": "New Group", "data": { "group_type": "Group type" } }, - "cover": { - "description": "Select group options", + "binary_sensor": { + "title": "[%key:component::group::config::step::user::title%]", + "description": "If \"all entities\" is enabled, the group's state is on only if all members are on. If \"all entities\" is disabled, the group's state is on if any member is on.", "data": { - "entities": "Group members", - "name": "Group name" + "all": "All entities", + "entities": "Members", + "name": "Name" } }, - "cover_options": { - "description": "Select group options", + "cover": { "data": { - "entities": "Group members" + "title": "[%key:component::group::config::step::user::title%]", + "entities": "[%key:component::group::config::step::binary_sensor::data::entities%]", + "name": "[%key:component::group::config::step::binary_sensor::data::name%]" } }, "fan": { - "description": "Select group options", "data": { - "entities": "Group members", - "name": "Group name" - } - }, - "fan_options": { - "description": "Select group options", - "data": { - "entities": "Group members" + "title": "[%key:component::group::config::step::user::title%]", + "entities": "[%key:component::group::config::step::binary_sensor::data::entities%]", + "name": "[%key:component::group::config::step::binary_sensor::data::name%]" } }, "light": { - "description": "Select group options", "data": { - "entities": "Group members", - "name": "Group name" - } - }, - "light_options": { - "description": "Select group options", - "data": { - "entities": "Group members" + "title": "[%key:component::group::config::step::user::title%]", + "entities": "[%key:component::group::config::step::binary_sensor::data::entities%]", + "name": "[%key:component::group::config::step::binary_sensor::data::name%]" } }, "media_player": { - "description": "Select group options", "data": { - "entities": "Group members", - "name": "Group name" + "title": "[%key:component::group::config::step::user::title%]", + "entities": "[%key:component::group::config::step::binary_sensor::data::entities%]", + "name": "[%key:component::group::config::step::binary_sensor::data::name%]" + } + } + } + }, + "options": { + "step": { + "binary_sensor_options": { + "data": { + "all": "[%key:component::group::config::step::binary_sensor::data::all%]", + "entities": "[%key:component::group::config::step::binary_sensor::data::entities%]" + } + }, + "cover_options": { + "data": { + "entities": "[%key:component::group::config::step::binary_sensor::data::entities%]" + } + }, + "fan_options": { + "data": { + "entities": "[%key:component::group::config::step::binary_sensor::data::entities%]" + } + }, + "light_options": { + "data": { + "entities": "[%key:component::group::config::step::binary_sensor::data::entities%]" } }, "media_player_options": { - "description": "Select group options", "data": { - "entities": "Group members" + "entities": "[%key:component::group::config::step::binary_sensor::data::entities%]" } } } diff --git a/tests/components/group/test_config_flow.py b/tests/components/group/test_config_flow.py index 121ecc717e6..d720b051e53 100644 --- a/tests/components/group/test_config_flow.py +++ b/tests/components/group/test_config_flow.py @@ -10,16 +10,25 @@ from homeassistant.data_entry_flow import RESULT_TYPE_CREATE_ENTRY, RESULT_TYPE_ @pytest.mark.parametrize( - "group_type,group_state,member_state,member_attributes", + "group_type,group_state,member_state,member_attributes,extra_input,extra_options,extra_attrs", ( - ("cover", "open", "open", {}), - ("fan", "on", "on", {}), - ("light", "on", "on", {}), - ("media_player", "on", "on", {}), + ("binary_sensor", "on", "on", {}, {}, {"all": False}, {}), + ("binary_sensor", "on", "on", {}, {"all": True}, {"all": True}, {}), + ("cover", "open", "open", {}, {}, {}, {}), + ("fan", "on", "on", {}, {}, {}, {}), + ("light", "on", "on", {}, {}, {}, {}), + ("media_player", "on", "on", {}, {}, {}, {}), ), ) async def test_config_flow( - hass: HomeAssistant, group_type, group_state, member_state, member_attributes + hass: HomeAssistant, + group_type, + group_state, + member_state, + member_attributes, + extra_input, + extra_options, + extra_attrs, ) -> None: """Test the config flow.""" members = [f"{group_type}.one", f"{group_type}.two"] @@ -48,6 +57,7 @@ async def test_config_flow( { "name": "Living Room", "entities": members, + **extra_input, }, ) await hass.async_block_till_done() @@ -59,6 +69,7 @@ async def test_config_flow( "group_type": group_type, "entities": members, "name": "Living Room", + **extra_options, } assert len(mock_setup_entry.mock_calls) == 1 @@ -68,11 +79,14 @@ async def test_config_flow( "group_type": group_type, "name": "Living Room", "entities": members, + **extra_options, } state = hass.states.get(f"{group_type}.living_room") assert state.state == group_state assert state.attributes["entity_id"] == members + for key in extra_attrs: + assert state.attributes[key] == extra_attrs[key] def get_suggested(schema, key): @@ -87,10 +101,18 @@ def get_suggested(schema, key): @pytest.mark.parametrize( - "group_type,member_state", - (("cover", "open"), ("fan", "on"), ("light", "on"), ("media_player", "on")), + "group_type,member_state,extra_options", + ( + ("binary_sensor", "on", {"all": False}), + ("cover", "open", {}), + ("fan", "on", {}), + ("light", "on", {}), + ("media_player", "on", {}), + ), ) -async def test_options(hass: HomeAssistant, group_type, member_state) -> None: +async def test_options( + hass: HomeAssistant, group_type, member_state, extra_options +) -> None: """Test reconfiguring.""" members1 = [f"{group_type}.one", f"{group_type}.two"] members2 = [f"{group_type}.four", f"{group_type}.five"] @@ -138,6 +160,7 @@ async def test_options(hass: HomeAssistant, group_type, member_state) -> None: "group_type": group_type, "entities": members1, "name": "Bed Room", + **extra_options, } result = await hass.config_entries.options.async_init(config_entry.entry_id) @@ -157,12 +180,14 @@ async def test_options(hass: HomeAssistant, group_type, member_state) -> None: "group_type": group_type, "entities": members2, "name": "Bed Room", + **extra_options, } assert config_entry.data == {} assert config_entry.options == { "group_type": group_type, "entities": members2, "name": "Bed Room", + **extra_options, } assert config_entry.title == "Bed Room"