"""Representation of groups (scenes) from an ISY.""" from __future__ import annotations from typing import TYPE_CHECKING from xml.dom import minidom from ..constants import ( FAMILY_GENERIC, INSTEON_STATELESS_NODEDEFID, ISY_VALUE_UNKNOWN, PROTO_GROUP, ) from ..helpers import now from .node import Node from .nodebase import NodeBase if TYPE_CHECKING: from . import Nodes class Group(NodeBase): """ Interact with ISY groups (scenes). | nodes: The node manager object. | address: The node ID. | name: The node name. | members: List of the members in this group. | controllers: List of the controllers in this group. | spoken: The string of the Notes Spoken field. :ivar has_children: Boolean value indicating that group has no children. :ivar members: List of the members of this group. :ivar controllers: List of the controllers of this group. :ivar name: The name of this group. :ivar status: Watched property indicating the status of the group. :ivar group_all_on: Watched property indicating if all devices in group are on. """ def __init__( self, nodes: Nodes, address: str, name: str, members: list[str] | None = None, controllers: list[str] | None = None, family_id: str = FAMILY_GENERIC, pnode: str | None = None, flag: int = 0, ) -> None: """Initialize a Group class.""" self._all_on: bool = False self._controllers: list[str] = controllers or [] self._members: list[str] = members or [] super().__init__(nodes, address, name, 0, family_id=family_id, pnode=pnode, flag=flag) # listen for changes in children self._members_handlers = [ self._nodes[m].status_events.subscribe(self.update_callback) for m in self.members ] # get and update the status self._update() def __del__(self) -> None: """Cleanup event handlers before deleting.""" for handler in self._members_handlers: handler.unsubscribe() @property def controllers(self) -> list[str]: """Get the controller nodes of the scene/group.""" return self._controllers @property def group_all_on(self) -> bool: """Return the current node state.""" return self._all_on @group_all_on.setter def group_all_on(self, value: bool) -> bool: """Set the current node state and notify listeners.""" if self._all_on != value: self._all_on = value self._last_changed = now() # Re-publish the current status. Let users pick up the all on change. self.status_events.notify(self._status) return self._all_on @property def members(self) -> list[str]: """Get the members of the scene/group.""" return self._members @property def protocol(self) -> str: """Return the protocol for this entity.""" return PROTO_GROUP async def update( self, event: object = None, wait_time: float = 0.0, xmldoc: minidom.Document | None = None, ) -> None: """Update the group with values from the controller.""" return self._update(event, wait_time, xmldoc) def _update( self, event: object = None, wait_time: float = 0.0, xmldoc: minidom.Document | None = None, ) -> None: """Update the group with values from the controller.""" self._last_update = now() address_to_node: dict[str, Node] = {address: self._nodes[address] for address in self.members} valid_nodes = [ address for address, node_obj in address_to_node.items() if ( node_obj.status is not None and node_obj.status != ISY_VALUE_UNKNOWN and node_obj.node_def_id not in INSTEON_STATELESS_NODEDEFID ) ] on_nodes = [node for node in valid_nodes if int(address_to_node[node].status) > 0] if on_nodes: self.group_all_on = len(on_nodes) == len(valid_nodes) self.status = 255 return self.status = 0 self.group_all_on = False def update_callback(self, event=None): """Handle synchronous callbacks for subscriber events.""" self._update(event)