Add config flow for binary_sensor group (#67802)

* Add config flow for binary_sensor group

* Address review comments

* Remove device class selection from flow

* Update translation strings
This commit is contained in:
Erik Montnemery 2022-03-10 10:39:51 +01:00 committed by GitHub
parent e5523ef6b6
commit 5ae48bcf74
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 137 additions and 60 deletions

View file

@ -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."""

View file

@ -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")),

View file

@ -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)]
)

View file

@ -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):

View file

@ -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)]
)

View file

@ -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:

View file

@ -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%]"
}
}
}

View file

@ -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"