Home Assistant Unofficial Reference 2024.12.1
entity.py
Go to the documentation of this file.
1 """Representation of ISYEntity Types."""
2 
3 from __future__ import annotations
4 
5 from typing import Any, cast
6 
7 from pyisy.constants import (
8  ATTR_ACTION,
9  ATTR_CONTROL,
10  COMMAND_FRIENDLY_NAME,
11  EMPTY_TIME,
12  EVENT_PROPS_IGNORED,
13  NC_NODE_ENABLED,
14  PROTO_INSTEON,
15  PROTO_ZWAVE,
16  TAG_ADDRESS,
17  TAG_ENABLED,
18 )
19 from pyisy.helpers import EventListener, NodeProperty
20 from pyisy.nodes import Group, Node, NodeChangedEvent
21 from pyisy.programs import Program
22 from pyisy.variables import Variable
23 
24 from homeassistant.const import STATE_OFF, STATE_ON
25 from homeassistant.core import callback
26 from homeassistant.exceptions import HomeAssistantError
27 from homeassistant.helpers.device_registry import DeviceInfo
28 from homeassistant.helpers.entity import Entity, EntityDescription
29 
30 from .const import DOMAIN
31 
32 
34  """Representation of an ISY device."""
35 
36  _attr_has_entity_name = False
37  _attr_should_poll = False
38  _node: Node | Program | Variable
39 
40  def __init__(
41  self,
42  node: Node | Group | Variable | Program,
43  device_info: DeviceInfo | None = None,
44  ) -> None:
45  """Initialize the ISY/IoX entity."""
46  self._node_node = node
47  self._attr_name_attr_name = node.name
48  if device_info is None:
49  device_info = DeviceInfo(identifiers={(DOMAIN, node.isy.uuid)})
50  self._attr_device_info_attr_device_info = device_info
51  self._attr_unique_id_attr_unique_id = f"{node.isy.uuid}_{node.address}"
52  self._attrs: dict[str, Any] = {}
53  self._change_handler_change_handler: EventListener | None = None
54  self._control_handler_control_handler: EventListener | None = None
55 
56  async def async_added_to_hass(self) -> None:
57  """Subscribe to the node change events."""
58  self._change_handler_change_handler = self._node_node.status_events.subscribe(self.async_on_updateasync_on_update)
59 
60  if hasattr(self._node_node, "control_events"):
61  self._control_handler_control_handler = self._node_node.control_events.subscribe(
62  self.async_on_controlasync_on_control
63  )
64 
65  @callback
66  def async_on_update(self, event: NodeProperty) -> None:
67  """Handle the update event from the ISY Node."""
68  self.async_write_ha_stateasync_write_ha_state()
69 
70  @callback
71  def async_on_control(self, event: NodeProperty) -> None:
72  """Handle a control event from the ISY Node."""
73  event_data = {
74  "entity_id": self.entity_identity_id,
75  "control": event.control,
76  "value": event.value,
77  "formatted": event.formatted,
78  "uom": event.uom,
79  "precision": event.prec,
80  }
81 
82  if event.control not in EVENT_PROPS_IGNORED:
83  # New state attributes may be available, update the state.
84  self.async_write_ha_stateasync_write_ha_state()
85 
86  self.hasshass.bus.async_fire("isy994_control", event_data)
87 
88 
90  """Representation of a ISY Nodebase (Node/Group) entity."""
91 
92  def __init__(
93  self,
94  node: Node | Group | Variable | Program,
95  device_info: DeviceInfo | None = None,
96  ) -> None:
97  """Initialize the ISY/IoX node entity."""
98  super().__init__(node, device_info=device_info)
99  if hasattr(node, "parent_node") and node.parent_node is None:
100  self._attr_has_entity_name_attr_has_entity_name_attr_has_entity_name = True
101  self._attr_name_attr_name_attr_name = None
102 
103  @property
104  def available(self) -> bool:
105  """Return entity availability."""
106  return getattr(self._node_node, TAG_ENABLED, True)
107 
108  @property
109  def extra_state_attributes(self) -> dict:
110  """Get the state attributes for the device.
111 
112  The 'aux_properties' in the pyisy Node class are combined with the
113  other attributes which have been picked up from the event stream and
114  the combined result are returned as the device state attributes.
115  """
116  attrs = self._attrs
117  node = self._node_node
118  # Insteon aux_properties are now their own sensors
119  # so we no longer need to add them to the attributes
120  if node.protocol != PROTO_INSTEON and hasattr(node, "aux_properties"):
121  for name, value in self._node_node.aux_properties.items():
122  attr_name = COMMAND_FRIENDLY_NAME.get(name, name)
123  attrs[attr_name] = str(value.formatted).lower()
124 
125  # If a Group/Scene, set a property if the entire scene is on/off
126  if hasattr(node, "group_all_on"):
127  attrs["group_all_on"] = STATE_ON if node.group_all_on else STATE_OFF
128 
129  return self._attrs
130 
131  async def async_send_node_command(self, command: str) -> None:
132  """Respond to an entity service command call."""
133  if not hasattr(self._node_node, command):
134  raise HomeAssistantError(
135  f"Invalid service call: {command} for device {self.entity_id}"
136  )
137  await getattr(self._node_node, command)()
138 
140  self,
141  command: str,
142  value: Any | None = None,
143  unit_of_measurement: str | None = None,
144  parameters: Any | None = None,
145  ) -> None:
146  """Respond to an entity service raw command call."""
147  if not hasattr(self._node_node, "send_cmd"):
148  raise HomeAssistantError(
149  f"Invalid service call: {command} for device {self.entity_id}"
150  )
151  await self._node_node.send_cmd(command, value, unit_of_measurement, parameters)
152 
153  async def async_get_zwave_parameter(self, parameter: Any) -> None:
154  """Respond to an entity service command to request a Z-Wave device parameter from the ISY."""
155  if self._node_node.protocol != PROTO_ZWAVE:
156  raise HomeAssistantError(
157  "Invalid service call: cannot request Z-Wave Parameter for non-Z-Wave"
158  f" device {self.entity_id}"
159  )
160  await self._node_node.get_zwave_parameter(parameter)
161 
163  self, parameter: Any, value: Any | None, size: int | None
164  ) -> None:
165  """Respond to an entity service command to set a Z-Wave device parameter via the ISY."""
166  if self._node_node.protocol != PROTO_ZWAVE:
167  raise HomeAssistantError(
168  "Invalid service call: cannot set Z-Wave Parameter for non-Z-Wave"
169  f" device {self.entity_id}"
170  )
171  await self._node_node.set_zwave_parameter(parameter, value, size)
172  await self._node_node.get_zwave_parameter(parameter)
173 
174  async def async_rename_node(self, name: str) -> None:
175  """Respond to an entity service command to rename a node on the ISY."""
176  await self._node_node.rename(name)
177 
178 
180  """Representation of an ISY program base."""
181 
182  _actions: Program
183  _status: Program
184 
185  def __init__(self, name: str, status: Program, actions: Program = None) -> None:
186  """Initialize the ISY program-based entity."""
187  super().__init__(status)
188  self._attr_name_attr_name_attr_name = name
189  self._actions_actions = actions
190 
191  @property
192  def extra_state_attributes(self) -> dict:
193  """Get the state attributes for the device."""
194  attr = {}
195  if self._actions_actions:
196  attr["actions_enabled"] = self._actions_actions.enabled
197  if self._actions_actions.last_finished != EMPTY_TIME:
198  attr["actions_last_finished"] = self._actions_actions.last_finished
199  if self._actions_actions.last_run != EMPTY_TIME:
200  attr["actions_last_run"] = self._actions_actions.last_run
201  if self._actions_actions.last_update != EMPTY_TIME:
202  attr["actions_last_update"] = self._actions_actions.last_update
203  attr["ran_else"] = self._actions_actions.ran_else
204  attr["ran_then"] = self._actions_actions.ran_then
205  attr["run_at_startup"] = self._actions_actions.run_at_startup
206  attr["running"] = self._actions_actions.running
207  attr["status_enabled"] = self._node_node.enabled
208  if self._node_node.last_finished != EMPTY_TIME:
209  attr["status_last_finished"] = self._node_node.last_finished
210  if self._node_node.last_run != EMPTY_TIME:
211  attr["status_last_run"] = self._node_node.last_run
212  if self._node_node.last_update != EMPTY_TIME:
213  attr["status_last_update"] = self._node_node.last_update
214  return attr
215 
216 
218  """Representation of a ISY/IoX Aux Control base entity."""
219 
220  _attr_should_poll = False
221 
222  def __init__(
223  self,
224  node: Node,
225  control: str,
226  unique_id: str,
227  description: EntityDescription,
228  device_info: DeviceInfo | None,
229  ) -> None:
230  """Initialize the ISY Aux Control Number entity."""
231  self._node_node = node
232  self._control_control = control
233  name = COMMAND_FRIENDLY_NAME.get(control, control).replace("_", " ").title()
234  if node.address != node.primary_node:
235  name = f"{node.name} {name}"
236  self._attr_name_attr_name = name
237  self.entity_descriptionentity_description = description
238  self._attr_has_entity_name_attr_has_entity_name = node.address == node.primary_node
239  self._attr_unique_id_attr_unique_id = unique_id
240  self._attr_device_info_attr_device_info = device_info
241  self._change_handler_change_handler: EventListener = None
242  self._availability_handler_availability_handler: EventListener = None
243 
244  async def async_added_to_hass(self) -> None:
245  """Subscribe to the node control change events."""
246  self._change_handler_change_handler = self._node_node.control_events.subscribe(
247  self.async_on_updateasync_on_update,
248  event_filter={ATTR_CONTROL: self._control_control},
249  key=self.unique_idunique_id,
250  )
251  self._availability_handler_availability_handler = self._node_node.isy.nodes.status_events.subscribe(
252  self.async_on_updateasync_on_update,
253  event_filter={
254  TAG_ADDRESS: self._node_node.address,
255  ATTR_ACTION: NC_NODE_ENABLED,
256  },
257  key=self.unique_idunique_id,
258  )
259 
260  @callback
261  def async_on_update(self, event: NodeProperty | NodeChangedEvent, key: str) -> None:
262  """Handle a control event from the ISY Node."""
263  self.async_write_ha_stateasync_write_ha_state()
264 
265  @property
266  def available(self) -> bool:
267  """Return entity availability."""
268  return cast(bool, self._node_node.enabled)
None async_on_update(self, NodeProperty|NodeChangedEvent event, str key)
Definition: entity.py:261
None __init__(self, Node node, str control, str unique_id, EntityDescription description, DeviceInfo|None device_info)
Definition: entity.py:229
None __init__(self, Node|Group|Variable|Program node, DeviceInfo|None device_info=None)
Definition: entity.py:44
None async_on_update(self, NodeProperty event)
Definition: entity.py:66
None async_on_control(self, NodeProperty event)
Definition: entity.py:71
None async_get_zwave_parameter(self, Any parameter)
Definition: entity.py:153
None async_send_raw_node_command(self, str command, Any|None value=None, str|None unit_of_measurement=None, Any|None parameters=None)
Definition: entity.py:145
None __init__(self, Node|Group|Variable|Program node, DeviceInfo|None device_info=None)
Definition: entity.py:96
None async_set_zwave_parameter(self, Any parameter, Any|None value, int|None size)
Definition: entity.py:164
None __init__(self, str name, Program status, Program actions=None)
Definition: entity.py:185