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:
parent
e5523ef6b6
commit
5ae48bcf74
8 changed files with 137 additions and 60 deletions
|
@ -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."""
|
||||
|
||||
|
|
|
@ -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")),
|
||||
|
|
|
@ -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)]
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)]
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue