1 """Provide entity classes for group entities."""
3 from __future__
import annotations
5 from abc
import abstractmethod
6 from collections.abc
import Callable, Collection, Mapping
14 EventStateChangedData,
25 from .const
import ATTR_AUTO, ATTR_ORDER, DATA_COMPONENT, DOMAIN, GROUP_ORDER, REG_KEY
26 from .registry
import GroupIntegrationRegistry, SingleStateType
28 ENTITY_ID_FORMAT = DOMAIN +
".{}"
30 _PACKAGE_LOGGER = logging.getLogger(__package__)
32 _LOGGER = logging.getLogger(__name__)
36 """Representation of a Group of entities."""
38 _unrecorded_attributes = frozenset({ATTR_ENTITY_ID})
40 _attr_should_poll =
False
41 _entity_ids: list[str]
46 preview_callback: Callable[[str, Mapping[str, Any]],
None],
48 """Render a preview."""
50 for entity_id
in self._entity_ids:
51 if (state := self.
hasshass.states.get(entity_id))
is None:
56 def async_state_changed_listener(
57 event: Event[EventStateChangedData] |
None,
59 """Handle child updates."""
63 event.data[
"entity_id"], event.data[
"new_state"]
66 preview_callback(calculated_state.state, calculated_state.attributes)
68 async_state_changed_listener(
None)
70 self.
hasshass, self._entity_ids, async_state_changed_listener
74 """Register listeners."""
75 for entity_id
in self._entity_ids:
76 if (state := self.
hasshass.states.get(entity_id))
is None:
81 def async_state_changed_listener(
82 event: Event[EventStateChangedData],
84 """Handle child updates."""
87 event.data[
"entity_id"], event.data[
"new_state"]
93 self.
hasshass, self._entity_ids, async_state_changed_listener
100 """Update the group state at start."""
106 """Only update once at start."""
107 if not self.
hasshass.is_running:
116 """Abstract method to update the entity."""
122 new_state: State |
None,
124 """Update dictionaries with supported features."""
128 """Track a group of entity ids."""
130 _unrecorded_attributes = frozenset({ATTR_ENTITY_ID, ATTR_ORDER, ATTR_AUTO})
132 _attr_should_poll =
False
133 tracking: tuple[str, ...]
134 trackable: tuple[str, ...]
135 single_state_type_key: SingleStateType |
None
136 _registry: GroupIntegrationRegistry
143 created_by_service: bool,
144 entity_ids: Collection[str] |
None,
149 """Initialize a group.
151 This Object has factory function for creation.
155 self.
_state_state: str |
None =
None
158 self.
_on_off_on_off: dict[str, bool] = {}
159 self.
_assumed_assumed: dict[str, bool] = {}
175 created_by_service: bool,
176 entity_ids: Collection[str] |
None,
179 object_id: str |
None,
182 """Create a group entity."""
184 hass.data.setdefault(GROUP_ORDER, 0)
185 order = hass.data[GROUP_ORDER]
189 hass.data[GROUP_ORDER] += 1
194 created_by_service=created_by_service,
195 entity_ids=entity_ids,
202 ENTITY_ID_FORMAT, object_id
or name, hass=hass
212 created_by_service: bool,
213 entity_ids: Collection[str] |
None,
216 object_id: str |
None,
219 """Initialize a group.
221 This method must be run in the event loop.
223 group = Group.async_create_group_entity(
226 created_by_service=created_by_service,
227 entity_ids=entity_ids,
239 """Set Group name."""
244 """Return the state of the group."""
248 """Set Icon for group."""
253 """Return the state attributes for the group."""
254 data = {ATTR_ENTITY_ID: self.
trackingtracking, ATTR_ORDER: self.
_order_order}
256 data[ATTR_AUTO] =
True
262 """Test if any member has an assumed state."""
267 self, entity_ids: Collection[str] |
None
269 """Update the member entity IDs.
271 This method must be run in the event loop.
279 """Tuple of entities to be tracked."""
290 excluded_domains = registry.exclude_domains
292 tracking: list[str] = []
293 trackable: list[str] = []
294 single_state_type_set: set[SingleStateType] = set()
295 for ent_id
in entity_ids:
296 ent_id_lower = ent_id.lower()
298 tracking.append(ent_id_lower)
299 if domain
not in excluded_domains:
300 trackable.append(ent_id_lower)
301 if domain
in registry.state_group_mapping:
302 single_state_type_set.add(registry.state_group_mapping[domain])
303 elif domain == DOMAIN:
306 if ent_id
in registry.state_group_mapping:
307 single_state_type_set.add(registry.state_group_mapping[ent_id])
311 if len(single_state_type_set) == 1:
324 """Deregister group entity from the registry."""
326 if self.
entity_identity_id
in registry.state_group_mapping:
327 registry.state_group_mapping.pop(self.
entity_identity_id)
331 """Start tracking members and write state."""
338 """Start tracking members.
340 This method must be run in the event loop.
351 """Unregister the group from Home Assistant.
353 This method must be run in the event loop.
361 """Query all members and determine current group state."""
366 """Handle addition to Home Assistant."""
373 """Handle removal from Home Assistant."""
377 self, event: Event[EventStateChangedData]
379 """Respond to a member state changing.
381 This method must be run in the event loop.
389 if (new_state := event.data[
"new_state"])
is None:
397 """Reset tracked state."""
402 for entity_id
in self.
trackabletrackable:
403 if (state := self.
hasshasshass.states.get(entity_id))
is not None:
407 """Keep track of the state."""
408 entity_id = new_state.entity_id
409 domain = new_state.domain
410 state = new_state.state
412 self.
_assumed_assumed[entity_id] =
bool(new_state.attributes.get(ATTR_ASSUMED_STATE))
414 if domain
not in registry.on_states_by_domain:
416 if state
in registry.on_off_mapping:
418 elif state
in registry.off_on_mapping:
419 self.
_on_states_on_states.
add(registry.off_on_mapping[state])
420 self.
_on_off_on_off[entity_id] = state
in registry.on_off_mapping
422 entity_on_state = registry.on_states_by_domain[domain]
423 if domain
in registry.on_states_by_domain:
425 self.
_on_off_on_off[entity_id] = state
in entity_on_state
429 """Update group state.
431 Optionally you can provide the only state changed since last update
432 allowing this method to take shortcuts.
434 This method must be run in the event loop.
446 and not tr_state.attributes.get(ATTR_ASSUMED_STATE)
450 elif tr_state.attributes.get(ATTR_ASSUMED_STATE):
453 num_on_states = len(self.
_on_states_on_states)
457 if num_on_states == 1:
458 on_state = next(iter(self.
_on_states_on_states))
461 elif num_on_states == 0:
470 group_is_on = self.
modemode(self.
_on_off_on_off.values())
472 self.
_state_state = on_state
476 self.
_state_state = STATE_OFF
480 """Get the group entity component."""
481 if (component := hass.data.get(DATA_COMPONENT))
is None:
482 component = hass.data[DATA_COMPONENT] = EntityComponent[Group](
483 _PACKAGE_LOGGER, DOMAIN, hass
None _update_at_start(self, HomeAssistant _)
CALLBACK_TYPE async_start_preview(self, Callable[[str, Mapping[str, Any]], None] preview_callback)
None async_update_group_state(self)
None async_update_supported_features(self, str entity_id, State|None new_state)
None async_added_to_hass(self)
None async_defer_or_update_ha_state(self)
_async_unsub_state_changed
None _see_state(self, State new_state)
None _async_deregister(self)
None _async_start_tracking(self)
dict[str, Any] extra_state_attributes(self)
None async_update_group_state(self)
None async_update_tracked_entity_ids(self, Collection[str]|None entity_ids)
None _reset_tracked_state(self)
None set_icon(self, str|None value)
None _async_update_group_state(self, State|None tr_state=None)
None _async_state_changed_listener(self, Event[EventStateChangedData] event)
None set_name(self, str value)
None _set_tracked(self, Collection[str]|None entity_ids)
Group async_create_group(HomeAssistant hass, str name, *bool created_by_service, Collection[str]|None entity_ids, str|None icon, bool|None mode, str|None object_id, int|None order)
Group async_create_group_entity(HomeAssistant hass, str name, *bool created_by_service, Collection[str]|None entity_ids, str|None icon, bool|None mode, str|None object_id, int|None order)
None async_added_to_hass(self)
None _async_start(self, HomeAssistant|None _=None)
None async_will_remove_from_hass(self)
None __init__(self, HomeAssistant hass, str name, *bool created_by_service, Collection[str]|None entity_ids, str|None icon, bool|None mode, int|None order)
None async_write_ha_state(self)
CalculatedState _async_calculate_state(self)
None async_on_remove(self, CALLBACK_TYPE func)
None async_set_context(self, Context context)
bool add(self, _T matcher)
EntityComponent[Group] async_get_component(HomeAssistant hass)
tuple[str, str] split_entity_id(str entity_id)
str async_generate_entity_id(str entity_id_format, str|None name, Iterable[str]|None current_ids=None, HomeAssistant|None hass=None)
CALLBACK_TYPE async_track_state_change_event(HomeAssistant hass, str|Iterable[str] entity_ids, Callable[[Event[EventStateChangedData]], Any] action, HassJobType|None job_type=None)