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