Home Assistant Unofficial Reference 2024.12.1
switch.py
Go to the documentation of this file.
1 """Support for ISY switches."""
2 
3 from __future__ import annotations
4 
5 from dataclasses import dataclass
6 from typing import Any
7 
8 from pyisy.constants import (
9  ATTR_ACTION,
10  ISY_VALUE_UNKNOWN,
11  NC_NODE_ENABLED,
12  PROTO_GROUP,
13  TAG_ADDRESS,
14 )
15 from pyisy.helpers import EventListener
16 from pyisy.nodes import Node, NodeChangedEvent
17 
19  SwitchDeviceClass,
20  SwitchEntity,
21  SwitchEntityDescription,
22 )
23 from homeassistant.config_entries import ConfigEntry
24 from homeassistant.const import EntityCategory, Platform
25 from homeassistant.core import HomeAssistant, callback
26 from homeassistant.exceptions import HomeAssistantError
27 from homeassistant.helpers.device_registry import DeviceInfo
28 from homeassistant.helpers.entity_platform import AddEntitiesCallback
29 
30 from .const import DOMAIN
31 from .entity import ISYAuxControlEntity, ISYNodeEntity, ISYProgramEntity
32 from .models import IsyData
33 
34 
35 @dataclass(frozen=True)
37  """Describes ISY switch."""
38 
39  # ISYEnableSwitchEntity does not support UNDEFINED or None,
40  # restrict the type to str.
41  name: str = ""
42 
43 
45  hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
46 ) -> None:
47  """Set up the ISY switch platform."""
48  isy_data: IsyData = hass.data[DOMAIN][entry.entry_id]
49  entities: list[
50  ISYSwitchProgramEntity | ISYSwitchEntity | ISYEnableSwitchEntity
51  ] = []
52  device_info = isy_data.devices
53  for node in isy_data.nodes[Platform.SWITCH]:
54  primary = node.primary_node
55  if node.protocol == PROTO_GROUP and len(node.controllers) == 1:
56  # If Group has only 1 Controller, link to that device instead of the hub
57  primary = node.isy.nodes.get_by_id(node.controllers[0]).primary_node
58 
59  entities.append(ISYSwitchEntity(node, device_info.get(primary)))
60 
61  for name, status, actions in isy_data.programs[Platform.SWITCH]:
62  entities.append(ISYSwitchProgramEntity(name, status, actions))
63 
64  for node, control in isy_data.aux_properties[Platform.SWITCH]:
65  # Currently only used for enable switches, will need to be updated for
66  # NS support by making sure control == TAG_ENABLED
67  description = ISYSwitchEntityDescription(
68  key=control,
69  device_class=SwitchDeviceClass.SWITCH,
70  name=control.title(),
71  entity_category=EntityCategory.CONFIG,
72  )
73  entities.append(
75  node=node,
76  control=control,
77  unique_id=f"{isy_data.uid_base(node)}_{control}",
78  description=description,
79  device_info=device_info.get(node.primary_node),
80  )
81  )
82  async_add_entities(entities)
83 
84 
86  """Representation of an ISY switch device."""
87 
88  @property
89  def is_on(self) -> bool | None:
90  """Get whether the ISY device is in the on state."""
91  if self._node_node.status == ISY_VALUE_UNKNOWN:
92  return None
93  return bool(self._node_node.status)
94 
95  async def async_turn_off(self, **kwargs: Any) -> None:
96  """Send the turn off command to the ISY switch."""
97  if not await self._node_node.turn_off():
98  raise HomeAssistantError(f"Unable to turn off switch {self._node.address}")
99 
100  async def async_turn_on(self, **kwargs: Any) -> None:
101  """Send the turn on command to the ISY switch."""
102  if not await self._node_node.turn_on():
103  raise HomeAssistantError(f"Unable to turn on switch {self._node.address}")
104 
105  @property
106  def icon(self) -> str | None:
107  """Get the icon for groups."""
108  if hasattr(self._node_node, "protocol") and self._node_node.protocol == PROTO_GROUP:
109  return "mdi:google-circles-communities" # Matches isy scene icon
110  return super().icon
111 
112 
114  """A representation of an ISY program switch."""
115 
116  _attr_icon = "mdi:script-text-outline" # Matches isy program icon
117 
118  @property
119  def is_on(self) -> bool:
120  """Get whether the ISY switch program is on."""
121  return bool(self._node_node.status)
122 
123  async def async_turn_on(self, **kwargs: Any) -> None:
124  """Send the turn on command to the ISY switch program."""
125  if not await self._actions_actions.run_then():
126  raise HomeAssistantError(
127  f"Unable to run 'then' clause on program switch {self._actions.address}"
128  )
129 
130  async def async_turn_off(self, **kwargs: Any) -> None:
131  """Send the turn off command to the ISY switch program."""
132  if not await self._actions_actions.run_else():
133  raise HomeAssistantError(
134  f"Unable to run 'else' clause on program switch {self._actions.address}"
135  )
136 
137 
139  """A representation of an ISY enable/disable switch."""
140 
141  def __init__(
142  self,
143  node: Node,
144  control: str,
145  unique_id: str,
146  description: ISYSwitchEntityDescription,
147  device_info: DeviceInfo | None,
148  ) -> None:
149  """Initialize the ISY Aux Control Number entity."""
150  super().__init__(
151  node=node,
152  control=control,
153  unique_id=unique_id,
154  description=description,
155  device_info=device_info,
156  )
157  self._attr_name_attr_name_attr_name = description.name # Override super
158  self._change_handler_change_handler_change_handler: EventListener = None
159 
160  # pylint: disable-next=hass-missing-super-call
161  async def async_added_to_hass(self) -> None:
162  """Subscribe to the node control change events."""
163  self._change_handler_change_handler_change_handler = self._node_node.isy.nodes.status_events.subscribe(
164  self.async_on_updateasync_on_updateasync_on_update,
165  event_filter={
166  TAG_ADDRESS: self._node_node.address,
167  ATTR_ACTION: NC_NODE_ENABLED,
168  },
169  key=self.unique_idunique_id,
170  )
171 
172  @callback
173  def async_on_update(self, event: NodeChangedEvent, key: str) -> None:
174  """Handle a control event from the ISY Node."""
175  self.async_write_ha_stateasync_write_ha_state()
176 
177  @property
178  def available(self) -> bool:
179  """Return entity availability."""
180  return True # Enable switch is always available
181 
182  @property
183  def is_on(self) -> bool | None:
184  """Get whether the ISY device is in the on state."""
185  return bool(self._node_node.enabled)
186 
187  async def async_turn_off(self, **kwargs: Any) -> None:
188  """Send the turn off command to the ISY switch."""
189  if not await self._node_node.disable():
190  raise HomeAssistantError(f"Unable to disable device {self._node.address}")
191 
192  async def async_turn_on(self, **kwargs: Any) -> None:
193  """Send the turn on command to the ISY switch."""
194  if not await self._node_node.enable():
195  raise HomeAssistantError(f"Unable to enable device {self._node.address}")
None async_on_update(self, NodeProperty|NodeChangedEvent event, str key)
Definition: entity.py:261
None async_on_update(self, NodeChangedEvent event, str key)
Definition: switch.py:173
None __init__(self, Node node, str control, str unique_id, ISYSwitchEntityDescription description, DeviceInfo|None device_info)
Definition: switch.py:148
None turn_off(self, **Any kwargs)
Definition: entity.py:1705
None turn_on(self, **Any kwargs)
Definition: entity.py:1697
None async_setup_entry(HomeAssistant hass, ConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: switch.py:46