Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7659eba376 | |||
| 2f083190fc | |||
| 640ec2bf34 | |||
| fb24beccfc | |||
| 690d73b97e | |||
| 510485b711 | |||
| 3b15efab36 | |||
| 3a4d9ba425 | |||
| 6276525780 | |||
| ee9af3d1d3 | |||
| 07c4fbf7c3 | |||
| 46043165f1 | |||
| 158c0211f7 | |||
| 9160f2b42d | |||
| b30d702d3f | |||
| fdaa3175fb | |||
| 9e541fc872 |
@@ -13,6 +13,7 @@ from homeassistant.const import (
|
||||
ATTR_ICON,
|
||||
ATTR_NAME,
|
||||
CONF_ENTITIES,
|
||||
CONF_ENTITY_ID,
|
||||
CONF_ICON,
|
||||
CONF_NAME,
|
||||
SERVICE_RELOAD,
|
||||
@@ -140,6 +141,30 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Migrate a config entry."""
|
||||
|
||||
if entry.version > 2:
|
||||
# This means the user has downgraded from a future version
|
||||
return False
|
||||
|
||||
if entry.version == 1:
|
||||
# Migrate Entity selector to Target selector
|
||||
new_options = dict(entry.options)
|
||||
current_entities = new_options[CONF_ENTITIES]
|
||||
new_options[CONF_ENTITIES] = {CONF_ENTITY_ID: current_entities}
|
||||
|
||||
_LOGGER.debug(
|
||||
"Migrating from version 1 to version 2: %s -> %s",
|
||||
entry.options,
|
||||
new_options,
|
||||
)
|
||||
|
||||
hass.config_entries.async_update_entry(entry, version=2, options=new_options)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
return await hass.config_entries.async_unload_platforms(
|
||||
@@ -155,7 +180,8 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
if not entry.options[CONF_HIDE_MEMBERS]:
|
||||
return
|
||||
|
||||
for member in entry.options[CONF_ENTITIES]:
|
||||
entity_ids = entry.options[CONF_ENTITIES].get(CONF_ENTITY_ID, [])
|
||||
for member in entity_ids:
|
||||
if not (entity_id := er.async_resolve_entity_id(registry, member)):
|
||||
continue
|
||||
if (entity_entry := registry.async_get(entity_id)) is None:
|
||||
|
||||
@@ -13,7 +13,6 @@ from homeassistant.components.binary_sensor import (
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_ENTITIES,
|
||||
CONF_NAME,
|
||||
@@ -55,13 +54,14 @@ async def async_setup_platform(
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the Binary Sensor Group platform."""
|
||||
entities = {"entity_id": config[CONF_ENTITIES]}
|
||||
async_add_entities(
|
||||
[
|
||||
BinarySensorGroup(
|
||||
config.get(CONF_UNIQUE_ID),
|
||||
config[CONF_NAME],
|
||||
config.get(CONF_DEVICE_CLASS),
|
||||
config[CONF_ENTITIES],
|
||||
entities,
|
||||
config.get(CONF_ALL),
|
||||
)
|
||||
]
|
||||
@@ -74,16 +74,18 @@ async def async_setup_entry(
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Initialize Binary Sensor Group config entry."""
|
||||
registry = er.async_get(hass)
|
||||
entities = er.async_validate_entity_ids(
|
||||
registry, config_entry.options[CONF_ENTITIES]
|
||||
)
|
||||
target_config = dict(config_entry.options[CONF_ENTITIES])
|
||||
entity_ids = target_config.get("entity_id", [])
|
||||
if entity_ids:
|
||||
registry = er.async_get(hass)
|
||||
entities = er.async_validate_entity_ids(registry, entity_ids)
|
||||
target_config["entity_id"] = entities
|
||||
mode = config_entry.options[CONF_ALL]
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
BinarySensorGroup(
|
||||
config_entry.entry_id, config_entry.title, None, entities, mode
|
||||
config_entry.entry_id, config_entry.title, None, target_config, mode
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -113,14 +115,14 @@ class BinarySensorGroup(GroupEntity, BinarySensorEntity):
|
||||
unique_id: str | None,
|
||||
name: str,
|
||||
device_class: BinarySensorDeviceClass | None,
|
||||
entity_ids: list[str],
|
||||
target_config: dict[str, Any],
|
||||
mode: bool | None,
|
||||
) -> None:
|
||||
"""Initialize a BinarySensorGroup entity."""
|
||||
super().__init__()
|
||||
self._entity_ids = entity_ids
|
||||
self._target_config = target_config
|
||||
self._domains = [BINARY_SENSOR_DOMAIN]
|
||||
self._attr_name = name
|
||||
self._attr_extra_state_attributes = {ATTR_ENTITY_ID: entity_ids}
|
||||
self._attr_unique_id = unique_id
|
||||
self._device_class = device_class
|
||||
self.mode = any
|
||||
|
||||
@@ -49,12 +49,13 @@ async def async_setup_platform(
|
||||
__: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the button group platform."""
|
||||
entities = {"entity_id": config[CONF_ENTITIES]}
|
||||
async_add_entities(
|
||||
[
|
||||
ButtonGroup(
|
||||
config.get(CONF_UNIQUE_ID),
|
||||
config[CONF_NAME],
|
||||
config[CONF_ENTITIES],
|
||||
entities,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -66,16 +67,18 @@ async def async_setup_entry(
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Initialize button group config entry."""
|
||||
registry = er.async_get(hass)
|
||||
entities = er.async_validate_entity_ids(
|
||||
registry, config_entry.options[CONF_ENTITIES]
|
||||
)
|
||||
target_config = dict(config_entry.options[CONF_ENTITIES])
|
||||
entity_ids = target_config.get("entity_id", [])
|
||||
if entity_ids:
|
||||
registry = er.async_get(hass)
|
||||
entities = er.async_validate_entity_ids(registry, entity_ids)
|
||||
target_config["entity_id"] = entities
|
||||
async_add_entities(
|
||||
[
|
||||
ButtonGroup(
|
||||
config_entry.entry_id,
|
||||
config_entry.title,
|
||||
entities,
|
||||
target_config,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -103,12 +106,13 @@ class ButtonGroup(GroupEntity, ButtonEntity):
|
||||
self,
|
||||
unique_id: str | None,
|
||||
name: str,
|
||||
entity_ids: list[str],
|
||||
target_config: dict[str, Any],
|
||||
) -> None:
|
||||
"""Initialize a button group."""
|
||||
self._entity_ids = entity_ids
|
||||
super().__init__()
|
||||
self._target_config = target_config
|
||||
self._domains = [BUTTON_DOMAIN]
|
||||
self._attr_name = name
|
||||
self._attr_extra_state_attributes = {ATTR_ENTITY_ID: entity_ids}
|
||||
self._attr_unique_id = unique_id
|
||||
|
||||
async def async_press(self) -> None:
|
||||
|
||||
@@ -7,7 +7,7 @@ from typing import Any, cast
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import websocket_api
|
||||
from homeassistant.const import CONF_ENTITIES, CONF_TYPE
|
||||
from homeassistant.const import CONF_ENTITIES, CONF_ENTITY_ID, CONF_TYPE
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry as er, selector
|
||||
@@ -16,8 +16,6 @@ from homeassistant.helpers.schema_config_entry_flow import (
|
||||
SchemaConfigFlowHandler,
|
||||
SchemaFlowFormStep,
|
||||
SchemaFlowMenuStep,
|
||||
SchemaOptionsFlowHandler,
|
||||
entity_selector_without_own_entities,
|
||||
)
|
||||
|
||||
from .binary_sensor import CONF_ALL, async_create_preview_binary_sensor
|
||||
@@ -53,20 +51,14 @@ async def basic_group_options_schema(
|
||||
domain: str | list[str], handler: SchemaCommonFlowHandler | None
|
||||
) -> vol.Schema:
|
||||
"""Generate options schema."""
|
||||
entity_selector: selector.Selector[Any] | vol.Schema
|
||||
if handler is None:
|
||||
entity_selector = selector.selector(
|
||||
{"entity": {"domain": domain, "multiple": True, "reorder": True}}
|
||||
)
|
||||
else:
|
||||
entity_selector = entity_selector_without_own_entities(
|
||||
cast(SchemaOptionsFlowHandler, handler.parent_handler),
|
||||
selector.EntitySelectorConfig(domain=domain, multiple=True, reorder=True),
|
||||
)
|
||||
|
||||
return vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_ENTITIES): entity_selector,
|
||||
vol.Required(CONF_ENTITIES): selector.TargetSelector(
|
||||
selector.TargetSelectorConfig(
|
||||
entity=selector.EntityFilterSelectorConfig(domain=domain)
|
||||
)
|
||||
),
|
||||
vol.Required(CONF_HIDE_MEMBERS, default=False): selector.BooleanSelector(),
|
||||
}
|
||||
)
|
||||
@@ -77,10 +69,10 @@ def basic_group_config_schema(domain: str | list[str]) -> vol.Schema:
|
||||
return vol.Schema(
|
||||
{
|
||||
vol.Required("name"): selector.TextSelector(),
|
||||
vol.Required(CONF_ENTITIES): selector.EntitySelector(
|
||||
selector.EntitySelectorConfig(
|
||||
domain=domain, multiple=True, reorder=True
|
||||
),
|
||||
vol.Required(CONF_ENTITIES): selector.TargetSelector(
|
||||
selector.TargetSelectorConfig(
|
||||
entity=selector.EntityFilterSelectorConfig(domain=domain)
|
||||
)
|
||||
),
|
||||
vol.Required(CONF_HIDE_MEMBERS, default=False): selector.BooleanSelector(),
|
||||
}
|
||||
@@ -338,6 +330,8 @@ CREATE_PREVIEW_ENTITY: dict[
|
||||
class GroupConfigFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN):
|
||||
"""Handle a config or options flow for groups."""
|
||||
|
||||
VERSION = 2
|
||||
|
||||
config_flow = CONFIG_FLOW
|
||||
options_flow = OPTIONS_FLOW
|
||||
options_flow_reloads = True
|
||||
@@ -387,11 +381,14 @@ class GroupConfigFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN):
|
||||
|
||||
|
||||
def _async_hide_members(
|
||||
hass: HomeAssistant, members: list[str], hidden_by: er.RegistryEntryHider | None
|
||||
hass: HomeAssistant,
|
||||
members: dict[str, Any],
|
||||
hidden_by: er.RegistryEntryHider | None,
|
||||
) -> None:
|
||||
"""Hide or unhide group members."""
|
||||
registry = er.async_get(hass)
|
||||
for member in members:
|
||||
entity_ids = members.get(CONF_ENTITY_ID, [])
|
||||
for member in entity_ids:
|
||||
if not (entity_id := er.async_resolve_entity_id(registry, member)):
|
||||
continue
|
||||
if entity_id not in registry.entities:
|
||||
|
||||
@@ -69,12 +69,9 @@ async def async_setup_platform(
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the Cover Group platform."""
|
||||
entities = {"entity_id": config[CONF_ENTITIES]}
|
||||
async_add_entities(
|
||||
[
|
||||
CoverGroup(
|
||||
config.get(CONF_UNIQUE_ID), config[CONF_NAME], config[CONF_ENTITIES]
|
||||
)
|
||||
]
|
||||
[CoverGroup(config.get(CONF_UNIQUE_ID), config[CONF_NAME], entities)]
|
||||
)
|
||||
|
||||
|
||||
@@ -84,13 +81,14 @@ async def async_setup_entry(
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Initialize Cover Group config entry."""
|
||||
registry = er.async_get(hass)
|
||||
entities = er.async_validate_entity_ids(
|
||||
registry, config_entry.options[CONF_ENTITIES]
|
||||
)
|
||||
|
||||
target_config = dict(config_entry.options[CONF_ENTITIES])
|
||||
entity_ids = target_config.get("entity_id", [])
|
||||
if entity_ids:
|
||||
registry = er.async_get(hass)
|
||||
entities = er.async_validate_entity_ids(registry, entity_ids)
|
||||
target_config["entity_id"] = entities
|
||||
async_add_entities(
|
||||
[CoverGroup(config_entry.entry_id, config_entry.title, entities)]
|
||||
[CoverGroup(config_entry.entry_id, config_entry.title, target_config)]
|
||||
)
|
||||
|
||||
|
||||
@@ -115,9 +113,13 @@ class CoverGroup(GroupEntity, CoverEntity):
|
||||
_attr_is_closing: bool | None = False
|
||||
_attr_current_cover_position: int | None = 100
|
||||
|
||||
def __init__(self, unique_id: str | None, name: str, entities: list[str]) -> None:
|
||||
def __init__(
|
||||
self, unique_id: str | None, name: str, target_config: dict[str, Any]
|
||||
) -> None:
|
||||
"""Initialize a CoverGroup entity."""
|
||||
self._entity_ids = entities
|
||||
super().__init__()
|
||||
self._target_config = target_config
|
||||
self._domains = [COVER_DOMAIN]
|
||||
self._covers: dict[str, set[str]] = {
|
||||
KEY_OPEN_CLOSE: set(),
|
||||
KEY_STOP: set(),
|
||||
@@ -130,7 +132,6 @@ class CoverGroup(GroupEntity, CoverEntity):
|
||||
}
|
||||
|
||||
self._attr_name = name
|
||||
self._attr_extra_state_attributes = {ATTR_ENTITY_ID: entities}
|
||||
self._attr_unique_id = unique_id
|
||||
|
||||
@callback
|
||||
|
||||
@@ -25,6 +25,12 @@ from homeassistant.helpers import start
|
||||
from homeassistant.helpers.entity import Entity, async_generate_entity_id
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.event import async_track_state_change_event
|
||||
from homeassistant.helpers.target import (
|
||||
TargetSelection,
|
||||
TargetStateChangedData,
|
||||
async_extract_referenced_entity_ids,
|
||||
async_track_target_selector_state_change_event,
|
||||
)
|
||||
|
||||
from .const import ATTR_AUTO, ATTR_ORDER, DATA_COMPONENT, DOMAIN, GROUP_ORDER, REG_KEY
|
||||
from .registry import GroupIntegrationRegistry, SingleStateType
|
||||
@@ -43,6 +49,8 @@ class GroupEntity(Entity):
|
||||
|
||||
_attr_should_poll = False
|
||||
_entity_ids: list[str]
|
||||
_target_config: dict[str, Any]
|
||||
_domains: list[str]
|
||||
|
||||
@callback
|
||||
def async_start_preview(
|
||||
@@ -51,6 +59,7 @@ class GroupEntity(Entity):
|
||||
) -> CALLBACK_TYPE:
|
||||
"""Render a preview."""
|
||||
|
||||
self.update_entities(False)
|
||||
for entity_id in self._entity_ids:
|
||||
if (state := self.hass.states.get(entity_id)) is None:
|
||||
continue
|
||||
@@ -74,8 +83,51 @@ class GroupEntity(Entity):
|
||||
self.hass, self._entity_ids, async_state_changed_listener
|
||||
)
|
||||
|
||||
@callback
|
||||
def filter_entities_by_domain(self, entity_ids: set[str]) -> set[str]:
|
||||
"""Filter entities by domain."""
|
||||
return {
|
||||
entity_id
|
||||
for entity_id in entity_ids
|
||||
if split_entity_id(entity_id)[0] in self._domains
|
||||
and entity_id != self.entity_id
|
||||
}
|
||||
|
||||
@callback
|
||||
def update_entities(self, update_group_members: bool = True) -> None:
|
||||
"""Update the entities in the group."""
|
||||
selected = async_extract_referenced_entity_ids(
|
||||
self.hass,
|
||||
TargetSelection(self._target_config),
|
||||
expand_group=True,
|
||||
primary_entities_only=False,
|
||||
)
|
||||
|
||||
self._entity_ids = []
|
||||
# Prepend entities from config to ensure order for explicitly configured entities
|
||||
if entity_list := self._target_config.get("entity_id"):
|
||||
self._entity_ids = list(entity_list)
|
||||
|
||||
self._entity_ids.extend(
|
||||
[
|
||||
entity
|
||||
for entity in self.filter_entities_by_domain(
|
||||
selected.referenced | selected.indirectly_referenced
|
||||
)
|
||||
if entity not in self._entity_ids
|
||||
]
|
||||
)
|
||||
|
||||
self._attr_extra_state_attributes = {ATTR_ENTITY_ID: self._entity_ids}
|
||||
if update_group_members:
|
||||
self.update_group_member(self._entity_ids)
|
||||
|
||||
def update_group_member(self, entities: list[str]) -> None:
|
||||
"""Update the group member."""
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Register listeners."""
|
||||
self.update_entities()
|
||||
for entity_id in self._entity_ids:
|
||||
if (state := self.hass.states.get(entity_id)) is None:
|
||||
continue
|
||||
@@ -83,18 +135,50 @@ class GroupEntity(Entity):
|
||||
|
||||
@callback
|
||||
def async_state_changed_listener(
|
||||
event: Event[EventStateChangedData],
|
||||
target_state_change_data: TargetStateChangedData,
|
||||
) -> None:
|
||||
"""Handle child updates."""
|
||||
event = target_state_change_data.state_change_event
|
||||
self.async_set_context(event.context)
|
||||
self.async_update_supported_features(
|
||||
event.data["entity_id"], event.data["new_state"]
|
||||
)
|
||||
self.async_defer_or_update_ha_state()
|
||||
|
||||
@callback
|
||||
def async_update_entities(added: set[str], removed: set[str]) -> None:
|
||||
"""Handle entity changes."""
|
||||
for entity_id in added:
|
||||
if entity_id not in self._entity_ids:
|
||||
self._entity_ids.append(entity_id)
|
||||
for entity_id in removed:
|
||||
if entity_id in self._entity_ids:
|
||||
self._entity_ids.remove(entity_id)
|
||||
|
||||
# Ensure the group does not include itself as member
|
||||
if self.entity_id in self._entity_ids:
|
||||
self._entity_ids.remove(self.entity_id)
|
||||
|
||||
self._attr_extra_state_attributes = {
|
||||
ATTR_ENTITY_ID: sorted(self._entity_ids)
|
||||
}
|
||||
|
||||
for entity_id in self._entity_ids:
|
||||
if (state := self.hass.states.get(entity_id)) is None:
|
||||
continue
|
||||
self.async_update_supported_features(entity_id, state)
|
||||
|
||||
self.update_group_member(self._entity_ids)
|
||||
self.async_defer_or_update_ha_state()
|
||||
|
||||
self.async_on_remove(
|
||||
async_track_state_change_event(
|
||||
self.hass, self._entity_ids, async_state_changed_listener
|
||||
async_track_target_selector_state_change_event(
|
||||
self.hass,
|
||||
self._target_config,
|
||||
async_state_changed_listener,
|
||||
self.filter_entities_by_domain,
|
||||
on_entities_update=async_update_entities,
|
||||
primary_entities_only=False,
|
||||
)
|
||||
)
|
||||
self.async_on_remove(start.async_at_start(self.hass, self._update_at_start))
|
||||
|
||||
@@ -15,7 +15,6 @@ from homeassistant.components.event import (
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_DEVICE_CLASS,
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_FRIENDLY_NAME,
|
||||
CONF_ENTITIES,
|
||||
CONF_NAME,
|
||||
@@ -23,13 +22,16 @@ from homeassistant.const import (
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
from homeassistant.core import Event, EventStateChangedData, HomeAssistant, callback
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import config_validation as cv, entity_registry as er
|
||||
from homeassistant.helpers.entity_platform import (
|
||||
AddConfigEntryEntitiesCallback,
|
||||
AddEntitiesCallback,
|
||||
)
|
||||
from homeassistant.helpers.event import async_track_state_change_event
|
||||
from homeassistant.helpers.target import (
|
||||
TargetStateChangedData,
|
||||
async_track_target_selector_state_change_event,
|
||||
)
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from .entity import GroupEntity
|
||||
@@ -55,12 +57,13 @@ async def async_setup_platform(
|
||||
__: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the event group platform."""
|
||||
entities = {"entity_id": config[CONF_ENTITIES]}
|
||||
async_add_entities(
|
||||
[
|
||||
EventGroup(
|
||||
config.get(CONF_UNIQUE_ID),
|
||||
config[CONF_NAME],
|
||||
config[CONF_ENTITIES],
|
||||
entities,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -72,16 +75,18 @@ async def async_setup_entry(
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Initialize event group config entry."""
|
||||
registry = er.async_get(hass)
|
||||
entities = er.async_validate_entity_ids(
|
||||
registry, config_entry.options[CONF_ENTITIES]
|
||||
)
|
||||
target_config = dict(config_entry.options[CONF_ENTITIES])
|
||||
entity_ids = target_config.get("entity_id", [])
|
||||
if entity_ids:
|
||||
registry = er.async_get(hass)
|
||||
entities = er.async_validate_entity_ids(registry, entity_ids)
|
||||
target_config["entity_id"] = entities
|
||||
async_add_entities(
|
||||
[
|
||||
EventGroup(
|
||||
config_entry.entry_id,
|
||||
config_entry.title,
|
||||
entities,
|
||||
target_config,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -109,12 +114,13 @@ class EventGroup(GroupEntity, EventEntity):
|
||||
self,
|
||||
unique_id: str | None,
|
||||
name: str,
|
||||
entity_ids: list[str],
|
||||
target_config: dict[str, Any],
|
||||
) -> None:
|
||||
"""Initialize an event group."""
|
||||
self._entity_ids = entity_ids
|
||||
super().__init__()
|
||||
self._target_config = target_config
|
||||
self._domains = [EVENT_DOMAIN]
|
||||
self._attr_name = name
|
||||
self._attr_extra_state_attributes = {ATTR_ENTITY_ID: entity_ids}
|
||||
self._attr_unique_id = unique_id
|
||||
self._attr_event_types = []
|
||||
|
||||
@@ -123,23 +129,26 @@ class EventGroup(GroupEntity, EventEntity):
|
||||
|
||||
@callback
|
||||
def async_state_changed_listener(
|
||||
event: Event[EventStateChangedData],
|
||||
target_state_change_data: TargetStateChangedData,
|
||||
) -> None:
|
||||
"""Handle child updates."""
|
||||
if not self.hass.is_running:
|
||||
return
|
||||
|
||||
self.async_set_context(event.context)
|
||||
|
||||
# Update all properties of the group
|
||||
self.async_update_group_state()
|
||||
|
||||
# Re-fire if one of the members fires an event, but only
|
||||
# if the original state was not unavailable or unknown.
|
||||
if (
|
||||
(old_state := event.data["old_state"])
|
||||
(
|
||||
old_state := target_state_change_data.state_change_event.data[
|
||||
"old_state"
|
||||
]
|
||||
)
|
||||
and old_state.state not in (STATE_UNAVAILABLE, STATE_UNKNOWN)
|
||||
and (new_state := event.data["new_state"])
|
||||
and (
|
||||
new_state := target_state_change_data.state_change_event.data[
|
||||
"new_state"
|
||||
]
|
||||
)
|
||||
and new_state.state not in (STATE_UNAVAILABLE, STATE_UNKNOWN)
|
||||
and (event_type := new_state.attributes.get(ATTR_EVENT_TYPE))
|
||||
):
|
||||
@@ -155,11 +164,13 @@ class EventGroup(GroupEntity, EventEntity):
|
||||
# Fire the group event
|
||||
self._trigger_event(event_type, event_attributes)
|
||||
|
||||
self.async_write_ha_state()
|
||||
|
||||
self.async_on_remove(
|
||||
async_track_state_change_event(
|
||||
self.hass, self._entity_ids, async_state_changed_listener
|
||||
async_track_target_selector_state_change_event(
|
||||
self.hass,
|
||||
self._target_config,
|
||||
async_state_changed_listener,
|
||||
self.filter_entities_by_domain,
|
||||
primary_entities_only=False,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -75,8 +75,9 @@ async def async_setup_platform(
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the Fan Group platform."""
|
||||
entities = {"entity_id": config[CONF_ENTITIES]}
|
||||
async_add_entities(
|
||||
[FanGroup(config.get(CONF_UNIQUE_ID), config[CONF_NAME], config[CONF_ENTITIES])]
|
||||
[FanGroup(config.get(CONF_UNIQUE_ID), config[CONF_NAME], entities)]
|
||||
)
|
||||
|
||||
|
||||
@@ -86,13 +87,16 @@ async def async_setup_entry(
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Initialize Fan Group config entry."""
|
||||
registry = er.async_get(hass)
|
||||
entities = er.async_validate_entity_ids(
|
||||
registry, config_entry.options[CONF_ENTITIES]
|
||||
target_config = dict(config_entry.options[CONF_ENTITIES])
|
||||
entity_ids = target_config.get("entity_id", [])
|
||||
if entity_ids:
|
||||
registry = er.async_get(hass)
|
||||
entities = er.async_validate_entity_ids(registry, entity_ids)
|
||||
target_config["entity_id"] = entities
|
||||
async_add_entities(
|
||||
[FanGroup(config_entry.entry_id, config_entry.title, target_config)]
|
||||
)
|
||||
|
||||
async_add_entities([FanGroup(config_entry.entry_id, config_entry.title, entities)])
|
||||
|
||||
|
||||
@callback
|
||||
def async_create_preview_fan(
|
||||
@@ -111,9 +115,13 @@ class FanGroup(GroupEntity, FanEntity):
|
||||
|
||||
_attr_available: bool = False
|
||||
|
||||
def __init__(self, unique_id: str | None, name: str, entities: list[str]) -> None:
|
||||
def __init__(
|
||||
self, unique_id: str | None, name: str, target_config: dict[str, Any]
|
||||
) -> None:
|
||||
"""Initialize a FanGroup entity."""
|
||||
self._entity_ids = entities
|
||||
super().__init__()
|
||||
self._target_config = target_config
|
||||
self._domains = [FAN_DOMAIN]
|
||||
self._fans: dict[int, set[str]] = {flag: set() for flag in SUPPORTED_FLAGS}
|
||||
self._percentage = None
|
||||
self._oscillating = None
|
||||
@@ -121,7 +129,6 @@ class FanGroup(GroupEntity, FanEntity):
|
||||
self._speed_count = 100
|
||||
self._is_on: bool | None = False
|
||||
self._attr_name = name
|
||||
self._attr_extra_state_attributes = {ATTR_ENTITY_ID: entities}
|
||||
self._attr_unique_id = unique_id
|
||||
|
||||
@property
|
||||
|
||||
@@ -25,6 +25,7 @@ from homeassistant.components.light import (
|
||||
ATTR_TRANSITION,
|
||||
ATTR_WHITE,
|
||||
ATTR_XY_COLOR,
|
||||
DOMAIN as LIGHT_DOMAIN,
|
||||
PLATFORM_SCHEMA as LIGHT_PLATFORM_SCHEMA,
|
||||
ColorMode,
|
||||
LightEntity,
|
||||
@@ -84,12 +85,13 @@ async def async_setup_platform(
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Initialize light.group platform."""
|
||||
entities = {"entity_id": config[CONF_ENTITIES]}
|
||||
async_add_entities(
|
||||
[
|
||||
LightGroup(
|
||||
config.get(CONF_UNIQUE_ID),
|
||||
config[CONF_NAME],
|
||||
config[CONF_ENTITIES],
|
||||
entities,
|
||||
config.get(CONF_ALL),
|
||||
)
|
||||
]
|
||||
@@ -102,14 +104,16 @@ async def async_setup_entry(
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Initialize Light Group config entry."""
|
||||
registry = er.async_get(hass)
|
||||
entities = er.async_validate_entity_ids(
|
||||
registry, config_entry.options[CONF_ENTITIES]
|
||||
)
|
||||
target_config = dict(config_entry.options[CONF_ENTITIES])
|
||||
entity_ids = target_config.get("entity_id", [])
|
||||
if entity_ids:
|
||||
registry = er.async_get(hass)
|
||||
entities = er.async_validate_entity_ids(registry, entity_ids)
|
||||
target_config["entity_id"] = entities
|
||||
mode = config_entry.options.get(CONF_ALL, False)
|
||||
|
||||
async_add_entities(
|
||||
[LightGroup(config_entry.entry_id, config_entry.title, entities, mode)]
|
||||
[LightGroup(config_entry.entry_id, config_entry.title, target_config, mode)]
|
||||
)
|
||||
|
||||
|
||||
@@ -153,13 +157,17 @@ class LightGroup(GroupEntity, LightEntity):
|
||||
_attr_should_poll = False
|
||||
|
||||
def __init__(
|
||||
self, unique_id: str | None, name: str, entity_ids: list[str], mode: bool | None
|
||||
self,
|
||||
unique_id: str | None,
|
||||
name: str,
|
||||
target_config: dict[str, Any],
|
||||
mode: bool | None,
|
||||
) -> None:
|
||||
"""Initialize a light group."""
|
||||
self._entity_ids = entity_ids
|
||||
|
||||
super().__init__()
|
||||
self._target_config = target_config
|
||||
self._domains = [LIGHT_DOMAIN]
|
||||
self._attr_name = name
|
||||
self._attr_extra_state_attributes = {ATTR_ENTITY_ID: entity_ids}
|
||||
self._attr_unique_id = unique_id
|
||||
self.mode = any
|
||||
if mode:
|
||||
|
||||
@@ -14,7 +14,6 @@ from homeassistant.components.lock import (
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
CONF_ENTITIES,
|
||||
CONF_NAME,
|
||||
CONF_UNIQUE_ID,
|
||||
@@ -55,12 +54,13 @@ async def async_setup_platform(
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the Lock Group platform."""
|
||||
entities = {"entity_id": config[CONF_ENTITIES]}
|
||||
async_add_entities(
|
||||
[
|
||||
LockGroup(
|
||||
config.get(CONF_UNIQUE_ID),
|
||||
config[CONF_NAME],
|
||||
config[CONF_ENTITIES],
|
||||
entities,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -72,16 +72,18 @@ async def async_setup_entry(
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Initialize Lock Group config entry."""
|
||||
registry = er.async_get(hass)
|
||||
entities = er.async_validate_entity_ids(
|
||||
registry, config_entry.options[CONF_ENTITIES]
|
||||
)
|
||||
target_config = dict(config_entry.options[CONF_ENTITIES])
|
||||
entity_ids = target_config.get("entity_id", [])
|
||||
if entity_ids:
|
||||
registry = er.async_get(hass)
|
||||
entities = er.async_validate_entity_ids(registry, entity_ids)
|
||||
target_config["entity_id"] = entities
|
||||
async_add_entities(
|
||||
[
|
||||
LockGroup(
|
||||
config_entry.entry_id,
|
||||
config_entry.title,
|
||||
entities,
|
||||
target_config,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -104,22 +106,24 @@ class LockGroup(GroupEntity, LockEntity):
|
||||
|
||||
_attr_available = False
|
||||
_attr_should_poll = False
|
||||
group: GenericGroup
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
unique_id: str | None,
|
||||
name: str,
|
||||
entity_ids: list[str],
|
||||
self, unique_id: str | None, name: str, target_config: dict[str, Any]
|
||||
) -> None:
|
||||
"""Initialize a lock group."""
|
||||
self._entity_ids = entity_ids
|
||||
self.group = GenericGroup(self, entity_ids)
|
||||
super().__init__()
|
||||
self._target_config = target_config
|
||||
self._domains = [LOCK_DOMAIN]
|
||||
self.group = GenericGroup(self, target_config.get("entity_id", []))
|
||||
self._attr_supported_features = LockEntityFeature.OPEN
|
||||
|
||||
self._attr_name = name
|
||||
self._attr_extra_state_attributes = {ATTR_ENTITY_ID: entity_ids}
|
||||
self._attr_unique_id = unique_id
|
||||
|
||||
def update_group_member(self, entities: list[str]) -> None:
|
||||
"""Update the group member."""
|
||||
self.group._member_entity_ids = entities # noqa: SLF001
|
||||
|
||||
@callback
|
||||
def async_update_group_state(self) -> None:
|
||||
"""Query all members and determine the lock group state."""
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""Platform allowing several media players to be grouped into one media player."""
|
||||
|
||||
from collections.abc import Callable, Mapping
|
||||
from contextlib import suppress
|
||||
from typing import Any
|
||||
|
||||
@@ -43,22 +42,16 @@ from homeassistant.const import (
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
from homeassistant.core import (
|
||||
CALLBACK_TYPE,
|
||||
Event,
|
||||
EventStateChangedData,
|
||||
HomeAssistant,
|
||||
State,
|
||||
callback,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, State, callback
|
||||
from homeassistant.helpers import config_validation as cv, entity_registry as er
|
||||
from homeassistant.helpers.entity_platform import (
|
||||
AddConfigEntryEntitiesCallback,
|
||||
AddEntitiesCallback,
|
||||
)
|
||||
from homeassistant.helpers.event import async_track_state_change_event
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from .entity import GroupEntity
|
||||
|
||||
KEY_ANNOUNCE = "announce"
|
||||
KEY_CLEAR_PLAYLIST = "clear_playlist"
|
||||
KEY_ENQUEUE = "enqueue"
|
||||
@@ -88,12 +81,9 @@ async def async_setup_platform(
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the MediaPlayer Group platform."""
|
||||
entities = {"entity_id": config[CONF_ENTITIES]}
|
||||
async_add_entities(
|
||||
[
|
||||
MediaPlayerGroup(
|
||||
config.get(CONF_UNIQUE_ID), config[CONF_NAME], config[CONF_ENTITIES]
|
||||
)
|
||||
]
|
||||
[MediaPlayerGroup(config.get(CONF_UNIQUE_ID), config[CONF_NAME], entities)]
|
||||
)
|
||||
|
||||
|
||||
@@ -103,13 +93,14 @@ async def async_setup_entry(
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Initialize MediaPlayer Group config entry."""
|
||||
registry = er.async_get(hass)
|
||||
entities = er.async_validate_entity_ids(
|
||||
registry, config_entry.options[CONF_ENTITIES]
|
||||
)
|
||||
|
||||
target_config = dict(config_entry.options[CONF_ENTITIES])
|
||||
entity_ids = target_config.get("entity_id", [])
|
||||
if entity_ids:
|
||||
registry = er.async_get(hass)
|
||||
entities = er.async_validate_entity_ids(registry, entity_ids)
|
||||
target_config["entity_id"] = entities
|
||||
async_add_entities(
|
||||
[MediaPlayerGroup(config_entry.entry_id, config_entry.title, entities)]
|
||||
[MediaPlayerGroup(config_entry.entry_id, config_entry.title, target_config)]
|
||||
)
|
||||
|
||||
|
||||
@@ -125,20 +116,20 @@ def async_create_preview_media_player(
|
||||
)
|
||||
|
||||
|
||||
class MediaPlayerGroup(MediaPlayerEntity):
|
||||
class MediaPlayerGroup(GroupEntity, MediaPlayerEntity):
|
||||
"""Representation of a Media Group."""
|
||||
|
||||
_unrecorded_attributes = frozenset({ATTR_ENTITY_ID})
|
||||
|
||||
_attr_available: bool = False
|
||||
_attr_should_poll = False
|
||||
|
||||
def __init__(self, unique_id: str | None, name: str, entities: list[str]) -> None:
|
||||
def __init__(
|
||||
self, unique_id: str | None, name: str, target_config: dict[str, Any]
|
||||
) -> None:
|
||||
"""Initialize a Media Group entity."""
|
||||
super().__init__()
|
||||
self._target_config = target_config
|
||||
self._domains = [MEDIA_PLAYER_DOMAIN]
|
||||
self._name = name
|
||||
self._attr_unique_id = unique_id
|
||||
|
||||
self._entities = entities
|
||||
self._features: dict[str, set[str]] = {
|
||||
KEY_ANNOUNCE: set(),
|
||||
KEY_CLEAR_PLAYLIST: set(),
|
||||
@@ -152,16 +143,6 @@ class MediaPlayerGroup(MediaPlayerEntity):
|
||||
KEY_VOLUME: set(),
|
||||
}
|
||||
|
||||
@callback
|
||||
def async_on_state_change(self, event: Event[EventStateChangedData]) -> None:
|
||||
"""Update supported features and state when a new state is received."""
|
||||
self.async_set_context(event.context)
|
||||
self.async_update_supported_features(
|
||||
event.data["entity_id"], event.data["new_state"]
|
||||
)
|
||||
self.async_update_group_state()
|
||||
self.async_write_ha_state()
|
||||
|
||||
@callback
|
||||
def async_update_supported_features(
|
||||
self,
|
||||
@@ -229,48 +210,11 @@ class MediaPlayerGroup(MediaPlayerEntity):
|
||||
else:
|
||||
self._features[KEY_ENQUEUE].discard(entity_id)
|
||||
|
||||
@callback
|
||||
def async_start_preview(
|
||||
self,
|
||||
preview_callback: Callable[[str, Mapping[str, Any]], None],
|
||||
) -> CALLBACK_TYPE:
|
||||
"""Render a preview."""
|
||||
|
||||
@callback
|
||||
def async_state_changed_listener(
|
||||
event: Event[EventStateChangedData] | None,
|
||||
) -> None:
|
||||
"""Handle child updates."""
|
||||
self.async_update_group_state()
|
||||
calculated_state = self._async_calculate_state()
|
||||
preview_callback(calculated_state.state, calculated_state.attributes)
|
||||
|
||||
async_state_changed_listener(None)
|
||||
return async_track_state_change_event(
|
||||
self.hass, self._entities, async_state_changed_listener
|
||||
)
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Register listeners."""
|
||||
for entity_id in self._entities:
|
||||
new_state = self.hass.states.get(entity_id)
|
||||
self.async_update_supported_features(entity_id, new_state)
|
||||
async_track_state_change_event(
|
||||
self.hass, self._entities, self.async_on_state_change
|
||||
)
|
||||
self.async_update_group_state()
|
||||
self.async_write_ha_state()
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name of the entity."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> Mapping[str, Any]:
|
||||
"""Return the state attributes for the media group."""
|
||||
return {ATTR_ENTITY_ID: self._entities}
|
||||
|
||||
async def async_clear_playlist(self) -> None:
|
||||
"""Clear players playlist."""
|
||||
data = {ATTR_ENTITY_ID: self._features[KEY_CLEAR_PLAYLIST]}
|
||||
@@ -440,7 +384,7 @@ class MediaPlayerGroup(MediaPlayerEntity):
|
||||
"""Query all members and determine the media group state."""
|
||||
states = [
|
||||
state.state
|
||||
for entity_id in self._entities
|
||||
for entity_id in self._entity_ids
|
||||
if (state := self.hass.states.get(entity_id)) is not None
|
||||
]
|
||||
|
||||
|
||||
@@ -132,13 +132,14 @@ async def async_setup_entry(
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Initialize Notify Group config entry."""
|
||||
registry = er.async_get(hass)
|
||||
entities = er.async_validate_entity_ids(
|
||||
registry, config_entry.options[CONF_ENTITIES]
|
||||
)
|
||||
|
||||
target_config = dict(config_entry.options[CONF_ENTITIES])
|
||||
entity_ids = target_config.get("entity_id", [])
|
||||
if entity_ids:
|
||||
registry = er.async_get(hass)
|
||||
entities = er.async_validate_entity_ids(registry, entity_ids)
|
||||
target_config["entity_id"] = entities
|
||||
async_add_entities(
|
||||
[NotifyGroup(config_entry.entry_id, config_entry.title, entities)]
|
||||
[NotifyGroup(config_entry.entry_id, config_entry.title, target_config)]
|
||||
)
|
||||
|
||||
|
||||
@@ -160,15 +161,13 @@ class NotifyGroup(GroupEntity, NotifyEntity):
|
||||
_attr_available: bool = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
unique_id: str | None,
|
||||
name: str,
|
||||
entity_ids: list[str],
|
||||
self, unique_id: str | None, name: str, target_config: dict[str, Any]
|
||||
) -> None:
|
||||
"""Initialize a NotifyGroup."""
|
||||
self._entity_ids = entity_ids
|
||||
super().__init__()
|
||||
self._target_config = target_config
|
||||
self._domains = [NOTIFY_DOMAIN]
|
||||
self._attr_name = name
|
||||
self._attr_extra_state_attributes = {ATTR_ENTITY_ID: entity_ids}
|
||||
self._attr_unique_id = unique_id
|
||||
|
||||
async def async_send_message(self, message: str, title: str | None = None) -> None:
|
||||
|
||||
@@ -27,7 +27,6 @@ from homeassistant.components.sensor import (
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_ENTITIES,
|
||||
CONF_NAME,
|
||||
@@ -117,13 +116,14 @@ async def async_setup_platform(
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the Switch Group platform."""
|
||||
entities = {"entity_id": config[CONF_ENTITIES]}
|
||||
async_add_entities(
|
||||
[
|
||||
SensorGroup(
|
||||
hass,
|
||||
config.get(CONF_UNIQUE_ID),
|
||||
config[CONF_NAME],
|
||||
config[CONF_ENTITIES],
|
||||
entities,
|
||||
config[CONF_IGNORE_NON_NUMERIC],
|
||||
config[CONF_TYPE],
|
||||
config.get(CONF_UNIT_OF_MEASUREMENT),
|
||||
@@ -140,17 +140,19 @@ async def async_setup_entry(
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Initialize Switch Group config entry."""
|
||||
registry = er.async_get(hass)
|
||||
entities = er.async_validate_entity_ids(
|
||||
registry, config_entry.options[CONF_ENTITIES]
|
||||
)
|
||||
target_config = dict(config_entry.options[CONF_ENTITIES])
|
||||
entity_ids = target_config.get("entity_id", [])
|
||||
if entity_ids:
|
||||
registry = er.async_get(hass)
|
||||
entities = er.async_validate_entity_ids(registry, entity_ids)
|
||||
target_config["entity_id"] = entities
|
||||
async_add_entities(
|
||||
[
|
||||
SensorGroup(
|
||||
hass,
|
||||
config_entry.entry_id,
|
||||
config_entry.title,
|
||||
entities,
|
||||
target_config,
|
||||
config_entry.options.get(CONF_IGNORE_NON_NUMERIC, True),
|
||||
config_entry.options[CONF_TYPE],
|
||||
None,
|
||||
@@ -345,7 +347,7 @@ class SensorGroup(GroupEntity, SensorEntity):
|
||||
hass: HomeAssistant,
|
||||
unique_id: str | None,
|
||||
name: str,
|
||||
entity_ids: list[str],
|
||||
target_config: dict[str, Any],
|
||||
ignore_non_numeric: bool,
|
||||
sensor_type: str,
|
||||
unit_of_measurement: str | None,
|
||||
@@ -353,8 +355,10 @@ class SensorGroup(GroupEntity, SensorEntity):
|
||||
device_class: SensorDeviceClass | None,
|
||||
) -> None:
|
||||
"""Initialize a sensor group."""
|
||||
super().__init__()
|
||||
self._target_config = target_config
|
||||
self._domains = [SENSOR_DOMAIN, NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN]
|
||||
self.hass = hass
|
||||
self._entity_ids = entity_ids
|
||||
self._sensor_type = sensor_type
|
||||
self._configured_state_class = state_class
|
||||
self._configured_device_class = device_class
|
||||
@@ -482,7 +486,10 @@ class SensorGroup(GroupEntity, SensorEntity):
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict[str, Any]:
|
||||
"""Return the state attributes of the sensor."""
|
||||
return {ATTR_ENTITY_ID: self._entity_ids, **self._extra_state_attribute}
|
||||
return {
|
||||
**self._extra_state_attribute,
|
||||
**self._attr_extra_state_attributes,
|
||||
}
|
||||
|
||||
@property
|
||||
def icon(self) -> str | None:
|
||||
|
||||
@@ -57,12 +57,13 @@ async def async_setup_platform(
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the Switch Group platform."""
|
||||
entities = {"entity_id": config[CONF_ENTITIES]}
|
||||
async_add_entities(
|
||||
[
|
||||
SwitchGroup(
|
||||
config.get(CONF_UNIQUE_ID),
|
||||
config[CONF_NAME],
|
||||
config[CONF_ENTITIES],
|
||||
entities,
|
||||
config.get(CONF_ALL, False),
|
||||
)
|
||||
]
|
||||
@@ -75,16 +76,18 @@ async def async_setup_entry(
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Initialize Switch Group config entry."""
|
||||
registry = er.async_get(hass)
|
||||
entities = er.async_validate_entity_ids(
|
||||
registry, config_entry.options[CONF_ENTITIES]
|
||||
)
|
||||
target_config = dict(config_entry.options[CONF_ENTITIES])
|
||||
entity_ids = target_config.get("entity_id", [])
|
||||
if entity_ids:
|
||||
registry = er.async_get(hass)
|
||||
entities = er.async_validate_entity_ids(registry, entity_ids)
|
||||
target_config["entity_id"] = entities
|
||||
async_add_entities(
|
||||
[
|
||||
SwitchGroup(
|
||||
config_entry.entry_id,
|
||||
config_entry.title,
|
||||
entities,
|
||||
target_config,
|
||||
config_entry.options.get(CONF_ALL),
|
||||
)
|
||||
]
|
||||
@@ -114,14 +117,14 @@ class SwitchGroup(GroupEntity, SwitchEntity):
|
||||
self,
|
||||
unique_id: str | None,
|
||||
name: str,
|
||||
entity_ids: list[str],
|
||||
target_config: dict[str, Any],
|
||||
mode: bool | None,
|
||||
) -> None:
|
||||
"""Initialize a switch group."""
|
||||
self._entity_ids = entity_ids
|
||||
|
||||
super().__init__()
|
||||
self._target_config = target_config
|
||||
self._domains = [SWITCH_DOMAIN]
|
||||
self._attr_name = name
|
||||
self._attr_extra_state_attributes = {ATTR_ENTITY_ID: entity_ids}
|
||||
self._attr_unique_id = unique_id
|
||||
self.mode = any
|
||||
if mode:
|
||||
|
||||
@@ -63,12 +63,9 @@ async def async_setup_platform(
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the Valve Group platform."""
|
||||
entities = {"entity_id": config[CONF_ENTITIES]}
|
||||
async_add_entities(
|
||||
[
|
||||
ValveGroup(
|
||||
config.get(CONF_UNIQUE_ID), config[CONF_NAME], config[CONF_ENTITIES]
|
||||
)
|
||||
]
|
||||
[ValveGroup(config.get(CONF_UNIQUE_ID), config[CONF_NAME], entities)]
|
||||
)
|
||||
|
||||
|
||||
@@ -78,13 +75,14 @@ async def async_setup_entry(
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Initialize Valve Group config entry."""
|
||||
registry = er.async_get(hass)
|
||||
entities = er.async_validate_entity_ids(
|
||||
registry, config_entry.options[CONF_ENTITIES]
|
||||
)
|
||||
|
||||
target_config = dict(config_entry.options[CONF_ENTITIES])
|
||||
entity_ids = target_config.get("entity_id", [])
|
||||
if entity_ids:
|
||||
registry = er.async_get(hass)
|
||||
entities = er.async_validate_entity_ids(registry, entity_ids)
|
||||
target_config["entity_id"] = entities
|
||||
async_add_entities(
|
||||
[ValveGroup(config_entry.entry_id, config_entry.title, entities)]
|
||||
[ValveGroup(config_entry.entry_id, config_entry.title, target_config)]
|
||||
)
|
||||
|
||||
|
||||
@@ -110,17 +108,19 @@ class ValveGroup(GroupEntity, ValveEntity):
|
||||
_attr_is_opening: bool | None = False
|
||||
_attr_reports_position: bool = False
|
||||
|
||||
def __init__(self, unique_id: str | None, name: str, entities: list[str]) -> None:
|
||||
def __init__(
|
||||
self, unique_id: str | None, name: str, target_config: dict[str, Any]
|
||||
) -> None:
|
||||
"""Initialize a ValveGroup entity."""
|
||||
self._entity_ids = entities
|
||||
super().__init__()
|
||||
self._target_config = target_config
|
||||
self._domains = [VALVE_DOMAIN]
|
||||
self._valves: dict[str, set[str]] = {
|
||||
KEY_OPEN_CLOSE: set(),
|
||||
KEY_STOP: set(),
|
||||
KEY_SET_POSITION: set(),
|
||||
}
|
||||
|
||||
self._attr_name = name
|
||||
self._attr_extra_state_attributes = {ATTR_ENTITY_ID: entities}
|
||||
self._attr_unique_id = unique_id
|
||||
|
||||
@callback
|
||||
|
||||
@@ -56,7 +56,7 @@ class GenericGroup(Group):
|
||||
super().__init__(entity)
|
||||
self._member_entity_ids = member_entity_ids
|
||||
|
||||
@cached_property
|
||||
@property
|
||||
def member_entity_ids(self) -> list[str]:
|
||||
"""Return the list of member entity IDs."""
|
||||
return self._member_entity_ids
|
||||
|
||||
@@ -512,22 +512,3 @@ def wrapped_entity_config_entry_title(
|
||||
if state:
|
||||
return state.name or object_id
|
||||
return object_id
|
||||
|
||||
|
||||
@callback
|
||||
def entity_selector_without_own_entities(
|
||||
handler: SchemaOptionsFlowHandler,
|
||||
entity_selector_config: selector.EntitySelectorConfig,
|
||||
) -> selector.EntitySelector:
|
||||
"""Return an entity selector which excludes own entities."""
|
||||
entity_registry = er.async_get(handler.hass)
|
||||
entities = er.async_entries_for_config_entry(
|
||||
entity_registry,
|
||||
handler.config_entry.entry_id,
|
||||
)
|
||||
entity_ids = [ent.entity_id for ent in entities]
|
||||
|
||||
final_selector_config = entity_selector_config.copy()
|
||||
final_selector_config["exclude_entities"] = entity_ids
|
||||
|
||||
return selector.EntitySelector(final_selector_config)
|
||||
|
||||
@@ -376,6 +376,8 @@ class TargetStateChangeTracker(TargetEntityChangeTracker):
|
||||
"""Handle the tracked entities."""
|
||||
previous_entities = self._tracked_entities
|
||||
self._tracked_entities = tracked_entities
|
||||
if previous_entities == tracked_entities:
|
||||
return
|
||||
|
||||
if self._on_entities_update is not None:
|
||||
added = tracked_entities - previous_entities
|
||||
|
||||
@@ -10,9 +10,11 @@ from homeassistant.const import (
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers import entity_registry as er, label_registry as lr
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_default_state(
|
||||
hass: HomeAssistant, entity_registry: er.EntityRegistry
|
||||
@@ -41,8 +43,8 @@ async def test_default_state(
|
||||
assert state is not None
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes.get(ATTR_ENTITY_ID) == [
|
||||
"binary_sensor.kitchen",
|
||||
"binary_sensor.bedroom",
|
||||
"binary_sensor.kitchen",
|
||||
]
|
||||
|
||||
entry = entity_registry.async_get("binary_sensor.bedroom_group")
|
||||
@@ -52,6 +54,97 @@ async def test_default_state(
|
||||
assert entry.original_device_class == "presence"
|
||||
|
||||
|
||||
async def test_multiple_targets(
|
||||
hass: HomeAssistant,
|
||||
label_registry: lr.LabelRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test binary sensor from config entry with multiple targets."""
|
||||
hass.states.async_set("binary_sensor.kitchen", "on")
|
||||
hass.states.async_set("binary_sensor.bedroom", "on")
|
||||
|
||||
group_config_entry = MockConfigEntry(
|
||||
data={},
|
||||
domain=DOMAIN,
|
||||
options={
|
||||
"entities": {
|
||||
"area_id": ["bedroom"],
|
||||
"entity_id": [
|
||||
"binary_sensor.kitchen",
|
||||
"binary_sensor.bedroom",
|
||||
"binary_sensor.not_exist",
|
||||
],
|
||||
"label_id": ["test"],
|
||||
},
|
||||
"group_type": "binary_sensor",
|
||||
"name": "Bedroom Group",
|
||||
"all": False,
|
||||
},
|
||||
title="Bedroom Group",
|
||||
version=2,
|
||||
)
|
||||
group_config_entry.add_to_hass(hass)
|
||||
|
||||
label_registry.async_create("Test")
|
||||
entity_registry.async_get_or_create(
|
||||
"binary_sensor",
|
||||
"test",
|
||||
"in_a_label",
|
||||
suggested_object_id="in_a_label",
|
||||
config_entry=group_config_entry,
|
||||
)
|
||||
entity_registry.async_update_entity("binary_sensor.in_a_label", labels={"test"})
|
||||
hass.states.async_set("binary_sensor.in_a_label", "on")
|
||||
|
||||
assert await hass.config_entries.async_setup(group_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("binary_sensor.bedroom_group")
|
||||
assert state is not None
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes.get(ATTR_ENTITY_ID) == [
|
||||
"binary_sensor.bedroom",
|
||||
"binary_sensor.in_a_label",
|
||||
"binary_sensor.kitchen",
|
||||
"binary_sensor.not_exist",
|
||||
]
|
||||
|
||||
entity_registry.async_get_or_create(
|
||||
"binary_sensor",
|
||||
"test",
|
||||
"added_to_a_label",
|
||||
suggested_object_id="added_to_a_label",
|
||||
config_entry=group_config_entry,
|
||||
)
|
||||
entity_registry.async_update_entity(
|
||||
"binary_sensor.added_to_a_label", labels={"test"}
|
||||
)
|
||||
hass.states.async_set("binary_sensor.added_to_a_label", "on")
|
||||
|
||||
entity_registry.async_get_or_create(
|
||||
"test",
|
||||
"test",
|
||||
"not_to_be_included",
|
||||
suggested_object_id="not_to_be_included",
|
||||
config_entry=group_config_entry,
|
||||
)
|
||||
entity_registry.async_update_entity("test.not_to_be_included", labels={"test"})
|
||||
hass.states.async_set("test.not_to_be_included", "on")
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("binary_sensor.bedroom_group")
|
||||
assert state is not None
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes.get(ATTR_ENTITY_ID) == [
|
||||
"binary_sensor.added_to_a_label",
|
||||
"binary_sensor.bedroom",
|
||||
"binary_sensor.in_a_label",
|
||||
"binary_sensor.kitchen",
|
||||
"binary_sensor.not_exist",
|
||||
]
|
||||
|
||||
|
||||
async def test_state_reporting_all(hass: HomeAssistant) -> None:
|
||||
"""Test the state reporting in 'all' mode.
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ async def test_config_flow(
|
||||
result["flow_id"],
|
||||
{
|
||||
"name": "Living Room",
|
||||
"entities": members,
|
||||
"entities": {"entity_id": members},
|
||||
**extra_input,
|
||||
},
|
||||
)
|
||||
@@ -108,7 +108,7 @@ async def test_config_flow(
|
||||
assert result["title"] == "Living Room"
|
||||
assert result["data"] == {}
|
||||
assert result["options"] == {
|
||||
"entities": members,
|
||||
"entities": {"entity_id": members},
|
||||
"group_type": group_type,
|
||||
"hide_members": False,
|
||||
"name": "Living Room",
|
||||
@@ -119,7 +119,7 @@ async def test_config_flow(
|
||||
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
assert config_entry.data == {}
|
||||
assert config_entry.options == {
|
||||
"entities": members,
|
||||
"entities": {"entity_id": members},
|
||||
"group_type": group_type,
|
||||
"hide_members": False,
|
||||
"name": "Living Room",
|
||||
@@ -192,7 +192,7 @@ async def test_config_flow_hides_members(
|
||||
result["flow_id"],
|
||||
{
|
||||
"name": "Living Room",
|
||||
"entities": members,
|
||||
"entities": {"entity_id": members},
|
||||
"hide_members": hide_members,
|
||||
**extra_input,
|
||||
},
|
||||
@@ -232,7 +232,7 @@ async def test_options(
|
||||
) -> None:
|
||||
"""Test reconfiguring."""
|
||||
members1 = [f"{group_type}.one", f"{group_type}.two"]
|
||||
members2 = [f"{group_type}.four", f"{group_type}.five"]
|
||||
members2 = [f"{group_type}.five", f"{group_type}.four"]
|
||||
|
||||
for member in members1:
|
||||
hass.states.async_set(member, member_state, {})
|
||||
@@ -243,12 +243,13 @@ async def test_options(
|
||||
data={},
|
||||
domain=DOMAIN,
|
||||
options={
|
||||
"entities": members1,
|
||||
"entities": {"entity_id": members1},
|
||||
"group_type": group_type,
|
||||
"name": "Bed Room",
|
||||
**extra_options,
|
||||
},
|
||||
title="Bed Room",
|
||||
version=2,
|
||||
)
|
||||
group_config_entry.add_to_hass(hass)
|
||||
|
||||
@@ -263,21 +264,18 @@ async def test_options(
|
||||
result = await hass.config_entries.options.async_init(config_entry.entry_id)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == group_type
|
||||
assert (
|
||||
get_schema_suggested_value(result["data_schema"].schema, "entities") == members1
|
||||
)
|
||||
assert get_schema_suggested_value(result["data_schema"].schema, "entities") == {
|
||||
"entity_id": members1
|
||||
}
|
||||
assert "name" not in result["data_schema"].schema
|
||||
assert result["data_schema"].schema["entities"].config["exclude_entities"] == [
|
||||
f"{group_type}.bed_room"
|
||||
]
|
||||
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={"entities": members2, **options_options},
|
||||
user_input={"entities": {"entity_id": members2}, **options_options},
|
||||
)
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["data"] == {
|
||||
"entities": members2,
|
||||
"entities": {"entity_id": members2},
|
||||
"group_type": group_type,
|
||||
"hide_members": False,
|
||||
"name": "Bed Room",
|
||||
@@ -285,7 +283,7 @@ async def test_options(
|
||||
}
|
||||
assert config_entry.data == {}
|
||||
assert config_entry.options == {
|
||||
"entities": members2,
|
||||
"entities": {"entity_id": members2},
|
||||
"group_type": group_type,
|
||||
"hide_members": False,
|
||||
"name": "Bed Room",
|
||||
@@ -336,12 +334,13 @@ async def test_all_options(
|
||||
data={},
|
||||
domain=DOMAIN,
|
||||
options={
|
||||
"entities": members1,
|
||||
"entities": {"entity_id": members1},
|
||||
"group_type": group_type,
|
||||
"name": "Bed Room",
|
||||
**extra_options,
|
||||
},
|
||||
title="Bed Room",
|
||||
version=2,
|
||||
)
|
||||
group_config_entry.add_to_hass(hass)
|
||||
|
||||
@@ -359,12 +358,12 @@ async def test_all_options(
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
"entities": members2,
|
||||
"entities": {"entity_id": members2},
|
||||
},
|
||||
)
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["data"] == {
|
||||
"entities": members2,
|
||||
"entities": {"entity_id": members2},
|
||||
"group_type": group_type,
|
||||
"hide_members": False,
|
||||
"name": "Bed Room",
|
||||
@@ -372,7 +371,7 @@ async def test_all_options(
|
||||
}
|
||||
assert config_entry.data == {}
|
||||
assert config_entry.options == {
|
||||
"entities": members2,
|
||||
"entities": {"entity_id": members2},
|
||||
"group_type": group_type,
|
||||
"hide_members": False,
|
||||
"name": "Bed Room",
|
||||
@@ -439,13 +438,14 @@ async def test_options_flow_hides_members(
|
||||
data={},
|
||||
domain=DOMAIN,
|
||||
options={
|
||||
"entities": members,
|
||||
"entities": {"entity_id": members},
|
||||
"group_type": group_type,
|
||||
"hide_members": False,
|
||||
"name": "Bed Room",
|
||||
**extra_input,
|
||||
},
|
||||
title="Bed Room",
|
||||
version=2,
|
||||
)
|
||||
group_config_entry.add_to_hass(hass)
|
||||
|
||||
@@ -458,7 +458,7 @@ async def test_options_flow_hides_members(
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
"entities": members,
|
||||
"entities": {"entity_id": members},
|
||||
"hide_members": hide_members,
|
||||
},
|
||||
)
|
||||
@@ -539,7 +539,10 @@ async def test_config_flow_preview(
|
||||
"type": "group/start_preview",
|
||||
"flow_id": result["flow_id"],
|
||||
"flow_type": "config_flow",
|
||||
"user_input": {"name": "My group", "entities": input_entities}
|
||||
"user_input": {
|
||||
"name": "My group",
|
||||
"entities": {"entity_id": input_entities},
|
||||
}
|
||||
| extra_user_input,
|
||||
}
|
||||
)
|
||||
@@ -571,7 +574,10 @@ async def test_config_flow_preview(
|
||||
"type": "group/start_preview",
|
||||
"flow_id": result["flow_id"],
|
||||
"flow_type": "config_flow",
|
||||
"user_input": {"name": "My group", "entities": input_entities}
|
||||
"user_input": {
|
||||
"name": "My group",
|
||||
"entities": {"entity_id": input_entities},
|
||||
}
|
||||
| extra_user_input,
|
||||
}
|
||||
)
|
||||
@@ -642,13 +648,14 @@ async def test_option_flow_preview(
|
||||
data={},
|
||||
domain=DOMAIN,
|
||||
options={
|
||||
"entities": input_entities,
|
||||
"entities": {"entity_id": input_entities},
|
||||
"group_type": domain,
|
||||
"hide_members": False,
|
||||
"name": "My group",
|
||||
}
|
||||
| extra_config_flow_data,
|
||||
title="My group",
|
||||
version=2,
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
@@ -669,7 +676,8 @@ async def test_option_flow_preview(
|
||||
"type": "group/start_preview",
|
||||
"flow_id": result["flow_id"],
|
||||
"flow_type": "options_flow",
|
||||
"user_input": {"entities": input_entities} | extra_user_input,
|
||||
"user_input": {"entities": {"entity_id": input_entities}}
|
||||
| extra_user_input,
|
||||
}
|
||||
)
|
||||
msg = await client.receive_json()
|
||||
@@ -699,13 +707,14 @@ async def test_option_flow_sensor_preview_config_entry_removed(
|
||||
data={},
|
||||
domain=DOMAIN,
|
||||
options={
|
||||
"entities": input_entities,
|
||||
"entities": {"entity_id": input_entities},
|
||||
"group_type": "sensor",
|
||||
"hide_members": False,
|
||||
"name": "My sensor group",
|
||||
"type": "min",
|
||||
},
|
||||
title="My min_max",
|
||||
version=2,
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
|
||||
@@ -126,8 +126,8 @@ async def test_state(hass: HomeAssistant) -> None:
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get(COVER_GROUP)
|
||||
assert state.attributes[ATTR_ENTITY_ID] == [
|
||||
DEMO_COVER,
|
||||
DEMO_COVER_POS,
|
||||
DEMO_COVER,
|
||||
DEMO_COVER_TILT,
|
||||
DEMO_TILT,
|
||||
]
|
||||
@@ -287,8 +287,8 @@ async def test_attributes(
|
||||
state = hass.states.get(COVER_GROUP)
|
||||
assert state.state == CoverState.CLOSED
|
||||
assert state.attributes[ATTR_ENTITY_ID] == [
|
||||
DEMO_COVER,
|
||||
DEMO_COVER_POS,
|
||||
DEMO_COVER,
|
||||
DEMO_COVER_TILT,
|
||||
DEMO_TILT,
|
||||
]
|
||||
|
||||
@@ -135,10 +135,12 @@ async def test_state(hass: HomeAssistant, entity_registry: er.EntityRegistry) ->
|
||||
hass.states.async_set(CEILING_FAN_ENTITY_ID, STATE_UNKNOWN, {})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get(FAN_GROUP)
|
||||
assert state.attributes[ATTR_ENTITY_ID] == [
|
||||
*FULL_FAN_ENTITY_IDS,
|
||||
*LIMITED_FAN_ENTITY_IDS,
|
||||
]
|
||||
assert state.attributes[ATTR_ENTITY_ID] == sorted(
|
||||
[
|
||||
*FULL_FAN_ENTITY_IDS,
|
||||
*LIMITED_FAN_ENTITY_IDS,
|
||||
]
|
||||
)
|
||||
|
||||
# All group members unavailable -> unavailable
|
||||
hass.states.async_set(CEILING_FAN_ENTITY_ID, STATE_UNAVAILABLE)
|
||||
@@ -228,10 +230,12 @@ async def test_attributes(hass: HomeAssistant) -> None:
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get(FAN_GROUP)
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[ATTR_ENTITY_ID] == [
|
||||
*FULL_FAN_ENTITY_IDS,
|
||||
*LIMITED_FAN_ENTITY_IDS,
|
||||
]
|
||||
assert state.attributes[ATTR_ENTITY_ID] == sorted(
|
||||
[
|
||||
*FULL_FAN_ENTITY_IDS,
|
||||
*LIMITED_FAN_ENTITY_IDS,
|
||||
]
|
||||
)
|
||||
|
||||
# Add Entity that supports speed
|
||||
hass.states.async_set(
|
||||
|
||||
@@ -1973,12 +1973,13 @@ async def test_setup_and_remove_config_entry(
|
||||
data={},
|
||||
domain=group.DOMAIN,
|
||||
options={
|
||||
"entities": members1,
|
||||
"entities": {"entity_id": members1},
|
||||
"group_type": group_type,
|
||||
"name": "Bed Room",
|
||||
**extra_options,
|
||||
},
|
||||
title="Bed Room",
|
||||
version=2,
|
||||
)
|
||||
group_config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(group_config_entry.entry_id)
|
||||
@@ -2062,13 +2063,14 @@ async def test_unhide_members_on_remove(
|
||||
data={},
|
||||
domain=group.DOMAIN,
|
||||
options={
|
||||
"entities": members,
|
||||
"entities": {"entity_id": members},
|
||||
"group_type": group_type,
|
||||
"hide_members": hide_members,
|
||||
"name": "Bed Room",
|
||||
**extra_options,
|
||||
},
|
||||
title="Bed Room",
|
||||
version=2,
|
||||
)
|
||||
group_config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(group_config_entry.entry_id)
|
||||
@@ -2301,3 +2303,46 @@ async def test_entity_platforms_with_multiple_on_states_with_state_match(
|
||||
group_state2,
|
||||
grouped_groups,
|
||||
)
|
||||
|
||||
|
||||
async def test_migrate_from_version_1_to_2(hass: HomeAssistant) -> None:
|
||||
"""Test migrating from version 1 to 2."""
|
||||
hass.states.async_set("binary_sensor.kitchen", "on")
|
||||
hass.states.async_set("binary_sensor.bedroom", "on")
|
||||
|
||||
group_config_entry = MockConfigEntry(
|
||||
data={},
|
||||
domain="group",
|
||||
options={
|
||||
"entities": [
|
||||
"binary_sensor.kitchen",
|
||||
"binary_sensor.bedroom",
|
||||
],
|
||||
"group_type": "binary_sensor",
|
||||
"name": "Fancy Group",
|
||||
"all": False,
|
||||
},
|
||||
title="Fancy Group",
|
||||
)
|
||||
group_config_entry.add_to_hass(hass)
|
||||
|
||||
assert await hass.config_entries.async_setup(group_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("binary_sensor.fancy_group")
|
||||
assert state is not None
|
||||
assert state.state == STATE_ON
|
||||
|
||||
entry = hass.config_entries.async_entries("group")[0]
|
||||
assert entry.version == 2
|
||||
assert entry.options == {
|
||||
"all": False,
|
||||
"entities": {
|
||||
"entity_id": [
|
||||
"binary_sensor.kitchen",
|
||||
"binary_sensor.bedroom",
|
||||
],
|
||||
},
|
||||
"group_type": "binary_sensor",
|
||||
"name": "Fancy Group",
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ async def test_default_state(
|
||||
assert state is not None
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
|
||||
assert state.attributes.get(ATTR_ENTITY_ID) == ["light.kitchen", "light.bedroom"]
|
||||
assert state.attributes.get(ATTR_ENTITY_ID) == ["light.bedroom", "light.kitchen"]
|
||||
assert state.attributes.get(ATTR_BRIGHTNESS) is None
|
||||
assert state.attributes.get(ATTR_HS_COLOR) is None
|
||||
assert state.attributes.get(ATTR_COLOR_TEMP_KELVIN) is None
|
||||
@@ -1473,6 +1473,7 @@ async def test_invalid_service_calls(hass: HomeAssistant) -> None:
|
||||
assert add_entities.call_count == 1
|
||||
grouped_light = add_entities.call_args[0][0][0]
|
||||
grouped_light.hass = hass
|
||||
grouped_light._entity_ids = ["light.test1", "light.test2"]
|
||||
|
||||
service_call_events = async_capture_events(hass, EVENT_CALL_SERVICE)
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ async def test_default_state(
|
||||
state = hass.states.get("lock.door_group")
|
||||
assert state is not None
|
||||
assert state.state == LockState.LOCKED
|
||||
assert state.attributes.get(ATTR_ENTITY_ID) == ["lock.front", "lock.back"]
|
||||
assert state.attributes.get(ATTR_ENTITY_ID) == ["lock.back", "lock.front"]
|
||||
|
||||
entry = entity_registry.async_get("lock.door_group")
|
||||
assert entry
|
||||
|
||||
@@ -53,7 +53,7 @@ async def test_default_state(
|
||||
state = hass.states.get("switch.multimedia_group")
|
||||
assert state is not None
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes.get(ATTR_ENTITY_ID) == ["switch.tv", "switch.soundbar"]
|
||||
assert state.attributes.get(ATTR_ENTITY_ID) == ["switch.soundbar", "switch.tv"]
|
||||
|
||||
entry = entity_registry.async_get("switch.multimedia_group")
|
||||
assert entry
|
||||
|
||||
@@ -117,9 +117,9 @@ async def test_state(hass: HomeAssistant) -> None:
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert state.attributes[ATTR_ENTITY_ID] == [
|
||||
DEMO_VALVE_POS1,
|
||||
DEMO_VALVE1,
|
||||
DEMO_VALVE2,
|
||||
DEMO_VALVE_POS1,
|
||||
DEMO_VALVE_POS2,
|
||||
]
|
||||
|
||||
@@ -277,9 +277,9 @@ async def test_attributes(
|
||||
state = hass.states.get(VALVE_GROUP)
|
||||
assert state.state == ValveState.CLOSED
|
||||
assert state.attributes[ATTR_ENTITY_ID] == [
|
||||
DEMO_VALVE_POS1,
|
||||
DEMO_VALVE1,
|
||||
DEMO_VALVE2,
|
||||
DEMO_VALVE_POS1,
|
||||
DEMO_VALVE_POS2,
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user