Home Assistant Unofficial Reference 2024.12.1
aldb.py
Go to the documentation of this file.
1 """Web socket API for Insteon devices."""
2 
3 from typing import Any
4 
5 from pyinsteon import devices
6 from pyinsteon.constants import ALDBStatus
7 import voluptuous as vol
8 
9 from homeassistant.components import websocket_api
10 from homeassistant.core import HomeAssistant, callback
11 from homeassistant.helpers import device_registry as dr
12 
13 from ..const import DEVICE_ADDRESS, ID, INSTEON_DEVICE_NOT_FOUND, TYPE
14 from ..utils import async_device_name
15 from .device import notify_device_not_found
16 
17 ALDB_RECORD = "record"
18 ALDB_RECORD_SCHEMA = vol.Schema(
19  {
20  vol.Required("mem_addr"): int,
21  vol.Required("in_use"): bool,
22  vol.Required("group"): vol.Range(0, 255),
23  vol.Required("is_controller"): bool,
24  vol.Optional("highwater"): bool,
25  vol.Required("target"): str,
26  vol.Optional("target_name"): str,
27  vol.Required("data1"): vol.Range(0, 255),
28  vol.Required("data2"): vol.Range(0, 255),
29  vol.Required("data3"): vol.Range(0, 255),
30  vol.Optional("dirty"): bool,
31  }
32 )
33 
34 
35 async def async_aldb_record_to_dict(dev_registry, record, dirty=False):
36  """Convert an ALDB record to a dict."""
37  return ALDB_RECORD_SCHEMA(
38  {
39  "mem_addr": record.mem_addr,
40  "in_use": record.is_in_use,
41  "is_controller": record.is_controller,
42  "highwater": record.is_high_water_mark,
43  "group": record.group,
44  "target": str(record.target),
45  "target_name": await async_device_name(dev_registry, record.target),
46  "data1": record.data1,
47  "data2": record.data2,
48  "data3": record.data3,
49  "dirty": dirty,
50  }
51  )
52 
53 
54 async def async_reload_and_save_aldb(hass, device):
55  """Add default links to an Insteon device."""
56  if device == devices.modem:
57  await device.aldb.async_load()
58  else:
59  await device.aldb.async_load(refresh=True)
60  await devices.async_save(workdir=hass.config.config_dir)
61 
62 
63 def any_aldb_loading() -> bool:
64  """Identify if any All-Link Databases are loading."""
65  return any(
66  device.aldb.status == ALDBStatus.LOADING for _, device in devices.items()
67  )
68 
69 
70 @websocket_api.websocket_command( {vol.Required(TYPE): "insteon/aldb/get", vol.Required(DEVICE_ADDRESS): str}
71 )
72 @websocket_api.require_admin
73 @websocket_api.async_response
74 async def websocket_get_aldb(
75  hass: HomeAssistant,
77  msg: dict[str, Any],
78 ) -> None:
79  """Get the All-Link Database for an Insteon device."""
80  if not (device := devices[msg[DEVICE_ADDRESS]]):
81  notify_device_not_found(connection, msg, INSTEON_DEVICE_NOT_FOUND)
82  return
83 
84  # Convert the ALDB to a dict merge in pending changes
85  aldb = {mem_addr: device.aldb[mem_addr] for mem_addr in device.aldb}
86  aldb.update(device.aldb.pending_changes)
87  changed_records = list(device.aldb.pending_changes.keys())
88 
89  dev_registry = dr.async_get(hass)
90 
91  records = [
93  dev_registry, aldb[mem_addr], mem_addr in changed_records
94  )
95  for mem_addr in aldb
96  ]
97 
98  connection.send_result(msg[ID], records)
99 
100 
101 @websocket_api.websocket_command( { vol.Required(TYPE): "insteon/aldb/change",
102  vol.Required(DEVICE_ADDRESS): str,
103  vol.Required(ALDB_RECORD): ALDB_RECORD_SCHEMA,
104  }
105 )
106 @websocket_api.require_admin
107 @websocket_api.async_response
109  hass: HomeAssistant,
111  msg: dict[str, Any],
112 ) -> None:
113  """Change an All-Link Database record for an Insteon device."""
114  if not (device := devices[msg[DEVICE_ADDRESS]]):
115  notify_device_not_found(connection, msg, INSTEON_DEVICE_NOT_FOUND)
116  return
117 
118  record = msg[ALDB_RECORD]
119  device.aldb.modify(
120  mem_addr=record["mem_addr"],
121  in_use=record["in_use"],
122  group=record["group"],
123  controller=record["is_controller"],
124  target=record["target"],
125  data1=record["data1"],
126  data2=record["data2"],
127  data3=record["data3"],
128  )
129  connection.send_result(msg[ID])
130 
131 
132 @websocket_api.websocket_command( { vol.Required(TYPE): "insteon/aldb/create",
133  vol.Required(DEVICE_ADDRESS): str,
134  vol.Required(ALDB_RECORD): ALDB_RECORD_SCHEMA,
135  }
136 )
137 @websocket_api.require_admin
138 @websocket_api.async_response
140  hass: HomeAssistant,
142  msg: dict[str, Any],
143 ) -> None:
144  """Create an All-Link Database record for an Insteon device."""
145  if not (device := devices[msg[DEVICE_ADDRESS]]):
146  notify_device_not_found(connection, msg, INSTEON_DEVICE_NOT_FOUND)
147  return
148 
149  record = msg[ALDB_RECORD]
150  device.aldb.add(
151  group=record["group"],
152  controller=record["is_controller"],
153  target=record["target"],
154  data1=record["data1"],
155  data2=record["data2"],
156  data3=record["data3"],
157  )
158  connection.send_result(msg[ID])
159 
160 
161 @websocket_api.websocket_command( { vol.Required(TYPE): "insteon/aldb/write",
162  vol.Required(DEVICE_ADDRESS): str,
163  }
164 )
165 @websocket_api.require_admin
166 @websocket_api.async_response
167 async def websocket_write_aldb(
168  hass: HomeAssistant,
170  msg: dict[str, Any],
171 ) -> None:
172  """Create an All-Link Database record for an Insteon device."""
173  if not (device := devices[msg[DEVICE_ADDRESS]]):
174  notify_device_not_found(connection, msg, INSTEON_DEVICE_NOT_FOUND)
175  return
176 
177  await device.aldb.async_write()
178  hass.async_create_task(async_reload_and_save_aldb(hass, device))
179  connection.send_result(msg[ID])
180 
181 
182 @websocket_api.websocket_command( { vol.Required(TYPE): "insteon/aldb/load",
183  vol.Required(DEVICE_ADDRESS): str,
184  }
185 )
186 @websocket_api.require_admin
187 @websocket_api.async_response
188 async def websocket_load_aldb(
189  hass: HomeAssistant,
191  msg: dict[str, Any],
192 ) -> None:
193  """Create an All-Link Database record for an Insteon device."""
194  if not (device := devices[msg[DEVICE_ADDRESS]]):
195  notify_device_not_found(connection, msg, INSTEON_DEVICE_NOT_FOUND)
196  return
197 
198  hass.async_create_task(async_reload_and_save_aldb(hass, device))
199  connection.send_result(msg[ID])
200 
201 
202 @websocket_api.websocket_command( { vol.Required(TYPE): "insteon/aldb/reset",
203  vol.Required(DEVICE_ADDRESS): str,
204  }
205 )
206 @websocket_api.require_admin
207 @websocket_api.async_response
208 async def websocket_reset_aldb(
209  hass: HomeAssistant,
211  msg: dict[str, Any],
212 ) -> None:
213  """Create an All-Link Database record for an Insteon device."""
214  if not (device := devices[msg[DEVICE_ADDRESS]]):
215  notify_device_not_found(connection, msg, INSTEON_DEVICE_NOT_FOUND)
216  return
217 
218  device.aldb.clear_pending()
219  connection.send_result(msg[ID])
220 
221 
222 @websocket_api.websocket_command( { vol.Required(TYPE): "insteon/aldb/add_default_links",
223  vol.Required(DEVICE_ADDRESS): str,
224  }
225 )
226 @websocket_api.require_admin
227 @websocket_api.async_response
229  hass: HomeAssistant,
231  msg: dict[str, Any],
232 ) -> None:
233  """Add the default All-Link Database records for an Insteon device."""
234  if not (device := devices[msg[DEVICE_ADDRESS]]):
235  notify_device_not_found(connection, msg, INSTEON_DEVICE_NOT_FOUND)
236  return
237 
238  device.aldb.clear_pending()
239  await device.async_add_default_links()
240  hass.async_create_task(async_reload_and_save_aldb(hass, device))
241  connection.send_result(msg[ID])
242 
243 
244 @websocket_api.websocket_command( { vol.Required(TYPE): "insteon/aldb/notify",
245  vol.Required(DEVICE_ADDRESS): str,
246  }
247 )
248 @websocket_api.require_admin
249 @websocket_api.async_response
251  hass: HomeAssistant,
253  msg: dict[str, Any],
254 ) -> None:
255  """Tell Insteon a new ALDB record was added."""
256  if not (device := devices[msg[DEVICE_ADDRESS]]):
257  notify_device_not_found(connection, msg, INSTEON_DEVICE_NOT_FOUND)
258  return
259 
260  @callback
261  def record_added(record, sender, deleted):
262  """Forward ALDB events to websocket."""
263  forward_data = {"type": "record_loaded"}
264  connection.send_message(websocket_api.event_message(msg["id"], forward_data))
265 
266  @callback
267  def aldb_loaded(status):
268  """Forward ALDB loaded event to websocket."""
269  forward_data = {
270  "type": "status_changed",
271  "is_loading": status == ALDBStatus.LOADING,
272  }
273  connection.send_message(websocket_api.event_message(msg["id"], forward_data))
274 
275  @callback
276  def async_cleanup() -> None:
277  """Remove signal listeners."""
278  device.aldb.unsubscribe_record_changed(record_added)
279  device.aldb.unsubscribe_status_changed(aldb_loaded)
280 
281  forward_data = {"type": "unsubscribed"}
282  connection.send_message(websocket_api.event_message(msg["id"], forward_data))
283 
284  connection.subscriptions[msg["id"]] = async_cleanup
285  device.aldb.subscribe_record_changed(record_added)
286  device.aldb.subscribe_status_changed(aldb_loaded)
287 
288  connection.send_result(msg[ID])
289 
290 
291 @websocket_api.websocket_command({vol.Required(TYPE): "insteon/aldb/notify_all"})
292 @websocket_api.require_admin
293 @websocket_api.async_response
295  hass: HomeAssistant,
297  msg: dict[str, Any],
298 ) -> None:
299  """Tell Insteon all ALDBs are loaded."""
300 
301  @callback
302  def aldb_status_changed(status: ALDBStatus) -> None:
303  """Forward ALDB loaded event to websocket."""
304 
305  forward_data = {
306  "type": "status",
307  "is_loading": any_aldb_loading(),
308  }
309  connection.send_message(websocket_api.event_message(msg["id"], forward_data))
310 
311  @callback
312  def async_cleanup() -> None:
313  """Remove signal listeners."""
314  for device in devices.values():
315  device.aldb.unsubscribe_status_changed(aldb_status_changed)
316 
317  forward_data = {"type": "unsubscribed"}
318  connection.send_message(websocket_api.event_message(msg["id"], forward_data))
319 
320  connection.subscriptions[msg["id"]] = async_cleanup
321  for device in devices.values():
322  device.aldb.subscribe_status_changed(aldb_status_changed)
323 
324  connection.send_result(msg[ID])
325 
326  forward_data = {
327  "type": "status",
328  "is_loading": any_aldb_loading(),
329  }
330  connection.send_message(websocket_api.event_message(msg["id"], forward_data))
331 
None websocket_create_aldb_record(HomeAssistant hass, websocket_api.connection.ActiveConnection connection, dict[str, Any] msg)
Definition: aldb.py:148
None websocket_add_default_links(HomeAssistant hass, websocket_api.connection.ActiveConnection connection, dict[str, Any] msg)
Definition: aldb.py:245
None websocket_change_aldb_record(HomeAssistant hass, websocket_api.connection.ActiveConnection connection, dict[str, Any] msg)
Definition: aldb.py:115
None websocket_load_aldb(HomeAssistant hass, websocket_api.connection.ActiveConnection connection, dict[str, Any] msg)
Definition: aldb.py:201
def async_aldb_record_to_dict(dev_registry, record, dirty=False)
Definition: aldb.py:35
None websocket_notify_on_aldb_status(HomeAssistant hass, websocket_api.connection.ActiveConnection connection, dict[str, Any] msg)
Definition: aldb.py:269
None websocket_reset_aldb(HomeAssistant hass, websocket_api.connection.ActiveConnection connection, dict[str, Any] msg)
Definition: aldb.py:223
None websocket_notify_on_aldb_status_all(HomeAssistant hass, websocket_api.connection.ActiveConnection connection, dict[str, Any] msg)
Definition: aldb.py:313
None websocket_write_aldb(HomeAssistant hass, websocket_api.connection.ActiveConnection connection, dict[str, Any] msg)
Definition: aldb.py:178
def async_reload_and_save_aldb(hass, device)
Definition: aldb.py:54
None websocket_get_aldb(HomeAssistant hass, websocket_api.connection.ActiveConnection connection, dict[str, Any] msg)
Definition: aldb.py:79
def notify_device_not_found(connection, msg, text)
Definition: device.py:51
str async_device_name(dr.DeviceRegistry dev_registry, Address address)
Definition: utils.py:481
None async_cleanup(HomeAssistant hass, DeviceRegistry dev_reg, entity_registry.EntityRegistry ent_reg)