1 """Provide the functionality to group entities."""
3 from __future__
import annotations
6 from collections.abc
import Collection
10 import voluptuous
as vol
26 expand_entity_ids
as _expand_entity_ids,
27 get_entity_ids
as _get_entity_ids,
41 from .
import config_flow
as config_flow_pre_import
56 from .entity
import Group, async_get_component
57 from .registry
import async_setup
as async_setup_registry
63 SERVICE_REMOVE =
"remove"
66 Platform.BINARY_SENSOR,
71 Platform.MEDIA_PLAYER,
77 _LOGGER = logging.getLogger(__name__)
81 """Preprocess alternative configuration formats."""
82 if not isinstance(value, dict):
83 return {CONF_ENTITIES: value}
88 GROUP_SCHEMA = vol.All(
91 vol.Optional(CONF_ENTITIES): vol.Any(cv.entity_ids,
None),
99 CONFIG_SCHEMA = vol.Schema(
100 {DOMAIN: vol.Schema({cv.match_all: vol.All(_conf_preprocess, GROUP_SCHEMA)})},
101 extra=vol.ALLOW_EXTRA,
106 def is_on(hass: HomeAssistant, entity_id: str) -> bool:
107 """Test if the group state is in its ON-state."""
108 if REG_KEY
not in hass.data:
112 if (state := hass.states.get(entity_id))
is not None:
113 return state.state
in hass.data[REG_KEY].on_off_mapping
119 expand_entity_ids = bind_hass(_expand_entity_ids)
120 get_entity_ids = bind_hass(_get_entity_ids)
125 """Get all groups that contain this entity.
129 if DOMAIN
not in hass.data:
134 for group
in hass.data[DATA_COMPONENT].entities
135 if entity_id
in group.tracking
140 """Set up a config entry."""
141 await hass.config_entries.async_forward_entry_setups(
142 entry, (entry.options[
"group_type"],)
144 entry.async_on_unload(entry.add_update_listener(config_entry_update_listener))
149 """Update listener, called when the config entry options are changed."""
150 await hass.config_entries.async_reload(entry.entry_id)
154 """Unload a config entry."""
155 return await hass.config_entries.async_unload_platforms(
156 entry, (entry.options[
"group_type"],)
161 """Remove a config entry."""
163 registry = er.async_get(hass)
165 if not entry.options[CONF_HIDE_MEMBERS]:
168 for member
in entry.options[CONF_ENTITIES]:
169 if not (entity_id := er.async_resolve_entity_id(registry, member)):
171 if (entity_entry := registry.async_get(entity_id))
is None:
173 if entity_entry.hidden_by != er.RegistryEntryHider.INTEGRATION:
176 registry.async_update_entity(entity_id, hidden_by=
None)
179 async
def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
180 """Set up all groups found defined in the configuration."""
183 await async_setup_registry(hass)
187 async
def reload_service_handler(service: ServiceCall) ->
None:
188 """Group reload handler.
190 - Remove group.group entities not created by service calls and set them up again
191 - Reload xxx.group platforms
193 if (conf := await component.async_prepare_reload(skip_reset=
True))
is None:
202 entity.async_remove()
203 for entity
in component.entities
204 if entity.entity_id.startswith(
"group.")
and not entity.created_by_service
208 await asyncio.gather(*tasks)
210 component.config =
None
216 hass.services.async_register(
217 DOMAIN, SERVICE_RELOAD, reload_service_handler, schema=vol.Schema({})
220 service_lock = asyncio.Lock()
222 async
def locked_service_handler(service: ServiceCall) ->
None:
223 """Handle a service with an async lock."""
224 async
with service_lock:
225 await groups_service_handler(service)
227 async
def groups_service_handler(service: ServiceCall) ->
None:
228 """Handle dynamic group service functions."""
229 object_id = service.data[ATTR_OBJECT_ID]
230 entity_id = f
"{DOMAIN}.{object_id}"
231 group = component.get_entity(entity_id)
234 if service.service == SERVICE_SET
and group
is None:
236 service.data.get(ATTR_ENTITIES)
237 or service.data.get(ATTR_ADD_ENTITIES)
241 await Group.async_create_group(
243 service.data.get(ATTR_NAME, object_id),
244 created_by_service=
True,
245 entity_ids=entity_ids,
246 icon=service.data.get(ATTR_ICON),
247 mode=service.data.get(ATTR_ALL),
254 _LOGGER.warning(
"%s:Group '%s' doesn't exist!", service.service, object_id)
258 if service.service == SERVICE_SET:
261 if ATTR_ADD_ENTITIES
in service.data:
262 delta = service.data[ATTR_ADD_ENTITIES]
263 entity_ids = set(group.tracking) | set(delta)
264 group.async_update_tracked_entity_ids(entity_ids)
266 if ATTR_REMOVE_ENTITIES
in service.data:
267 delta = service.data[ATTR_REMOVE_ENTITIES]
268 entity_ids = set(group.tracking) - set(delta)
269 group.async_update_tracked_entity_ids(entity_ids)
271 if ATTR_ENTITIES
in service.data:
272 entity_ids = service.data[ATTR_ENTITIES]
273 group.async_update_tracked_entity_ids(entity_ids)
275 if ATTR_NAME
in service.data:
276 group.set_name(service.data[ATTR_NAME])
279 if ATTR_ICON
in service.data:
280 group.set_icon(service.data[ATTR_ICON])
283 if ATTR_ALL
in service.data:
284 group.mode = all
if service.data[ATTR_ALL]
else any
288 group.async_write_ha_state()
293 if service.service == SERVICE_REMOVE:
294 await component.async_remove_entity(entity_id)
296 hass.services.async_register(
299 locked_service_handler,
303 vol.Required(ATTR_OBJECT_ID): cv.slug,
304 vol.Optional(ATTR_NAME): cv.string,
305 vol.Optional(ATTR_ICON): cv.string,
306 vol.Optional(ATTR_ALL): cv.boolean,
307 vol.Exclusive(ATTR_ENTITIES,
"entities"): cv.entity_ids,
308 vol.Exclusive(ATTR_ADD_ENTITIES,
"entities"): cv.entity_ids,
309 vol.Exclusive(ATTR_REMOVE_ENTITIES,
"entities"): cv.entity_ids,
315 hass.services.async_register(
318 groups_service_handler,
319 schema=vol.Schema({vol.Required(ATTR_OBJECT_ID): cv.slug}),
326 """Process group configuration."""
327 hass.data.setdefault(GROUP_ORDER, 0)
330 domain_config: dict[str, dict[str, Any]] = config.get(DOMAIN, {})
332 for object_id, conf
in domain_config.items():
333 name: str = conf.get(CONF_NAME, object_id)
334 entity_ids: Collection[str] = conf.get(CONF_ENTITIES)
or []
335 icon: str |
None = conf.get(CONF_ICON)
336 mode = bool(conf.get(CONF_ALL))
337 order = hass.data[GROUP_ORDER]
344 Group.async_create_group_entity(
347 created_by_service=
False,
348 entity_ids=entity_ids,
359 hass.data[GROUP_ORDER] += 1
EntityComponent[Group] async_get_component(HomeAssistant hass)
None _async_process_config(HomeAssistant hass, ConfigType config)
dict[str, Any] _conf_preprocess(Any value)
list[str] groups_with_entity(HomeAssistant hass, str entity_id)
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
None config_entry_update_listener(HomeAssistant hass, ConfigEntry entry)
bool is_on(HomeAssistant hass, str entity_id)
bool async_setup(HomeAssistant hass, ConfigType config)
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
None async_remove_entry(HomeAssistant hass, ConfigEntry entry)
None async_reload_integration_platforms(HomeAssistant hass, str integration_domain, Iterable[str] platform_domains)