Home Assistant Unofficial Reference 2024.12.1
hub.py
Go to the documentation of this file.
1 """The sia hub."""
2 
3 from __future__ import annotations
4 
5 from copy import deepcopy
6 import logging
7 from typing import Any
8 
9 from pysiaalarm.aio import CommunicationsProtocol, SIAAccount, SIAClient, SIAEvent
10 
11 from homeassistant.config_entries import ConfigEntry
12 from homeassistant.const import CONF_PORT, CONF_PROTOCOL, EVENT_HOMEASSISTANT_STOP
13 from homeassistant.core import Event, HomeAssistant, callback
14 from homeassistant.helpers import device_registry as dr
15 from homeassistant.helpers.dispatcher import async_dispatcher_send
16 
17 from .const import (
18  CONF_ACCOUNT,
19  CONF_ACCOUNTS,
20  CONF_ENCRYPTION_KEY,
21  CONF_IGNORE_TIMESTAMPS,
22  CONF_ZONES,
23  DOMAIN,
24  PLATFORMS,
25  SIA_EVENT,
26 )
27 from .utils import get_event_data_from_sia_event
28 
29 _LOGGER = logging.getLogger(__name__)
30 
31 DEFAULT_TIMEBAND = (80, 40)
32 
33 
34 class SIAHub:
35  """Class for SIA Hubs."""
36 
37  def __init__(
38  self,
39  hass: HomeAssistant,
40  entry: ConfigEntry,
41  ) -> None:
42  """Create the SIAHub."""
43  self._hass: HomeAssistant = hass
44  self._entry: ConfigEntry = entry
45  self._port: int = entry.data[CONF_PORT]
46  self._title: str = entry.title
47  self._accounts: list[dict[str, Any]] = deepcopy(entry.data[CONF_ACCOUNTS])
48  self._protocol: str = entry.data[CONF_PROTOCOL]
49  self.sia_accountssia_accounts: list[SIAAccount] | None = None
50  self.sia_clientsia_client: SIAClient | None = None
51 
52  @callback
53  def async_setup_hub(self) -> None:
54  """Add a device to the device_registry, register shutdown listener, load reactions."""
55  self.update_accountsupdate_accounts()
56  device_registry = dr.async_get(self._hass)
57  for acc in self._accounts:
58  account = acc[CONF_ACCOUNT]
59  device_registry.async_get_or_create(
60  config_entry_id=self._entry.entry_id,
61  identifiers={(DOMAIN, f"{self._port}_{account}")},
62  name=f"{self._port} - {account}",
63  )
64  self._entry.async_on_unload(
65  self._entry.add_update_listener(self.async_config_entry_updatedasync_config_entry_updated)
66  )
67  self._entry.async_on_unload(
68  self._hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, self.async_shutdownasync_shutdown)
69  )
70 
71  async def async_shutdown(self, _: Event | None = None) -> None:
72  """Shutdown the SIA server."""
73  if self.sia_clientsia_client:
74  await self.sia_clientsia_client.async_stop()
75 
76  async def async_create_and_fire_event(self, event: SIAEvent) -> None:
77  """Create a event on HA dispatcher and then on HA's bus, with the data from the SIAEvent.
78 
79  The created event is handled by default for only a small subset for each platform (there are about 320 SIA Codes defined, only 22 of those are used in the alarm_control_panel), a user can choose to build other automation or even entities on the same event for SIA codes not handled by the built-in platforms.
80 
81  """
82  _LOGGER.debug(
83  "Adding event to dispatch and bus for code %s for port %s and account %s",
84  event.code,
85  self._port,
86  event.account,
87  )
89  self._hass, SIA_EVENT.format(self._port, event.account), event
90  )
91  self._hass.bus.async_fire(
92  event_type=SIA_EVENT.format(self._port, event.account),
93  event_data=get_event_data_from_sia_event(event),
94  )
95 
96  def update_accounts(self):
97  """Update the SIA_Accounts variable."""
98  self._load_options_load_options()
99  self.sia_accountssia_accounts = [
100  SIAAccount(
101  account_id=a[CONF_ACCOUNT],
102  key=a.get(CONF_ENCRYPTION_KEY),
103  allowed_timeband=None
104  if a[CONF_IGNORE_TIMESTAMPS]
105  else DEFAULT_TIMEBAND,
106  )
107  for a in self._accounts
108  ]
109  if self.sia_clientsia_client is not None:
110  self.sia_clientsia_client.accounts = self.sia_accountssia_accounts
111  return
112  # the new client class method creates a subclass based on protocol, hence the type ignore
113  self.sia_clientsia_client = SIAClient(
114  host="",
115  port=self._port,
116  accounts=self.sia_accountssia_accounts,
117  function=self.async_create_and_fire_eventasync_create_and_fire_event,
118  protocol=CommunicationsProtocol(self._protocol),
119  )
120 
121  def _load_options(self) -> None:
122  """Store attributes to avoid property call overhead since they are called frequently."""
123  options = dict(self._entry.options)
124  for acc in self._accounts:
125  acc_id = acc[CONF_ACCOUNT]
126  if acc_id in options[CONF_ACCOUNTS]:
127  acc[CONF_IGNORE_TIMESTAMPS] = options[CONF_ACCOUNTS][acc_id][
128  CONF_IGNORE_TIMESTAMPS
129  ]
130  acc[CONF_ZONES] = options[CONF_ACCOUNTS][acc_id][CONF_ZONES]
131 
132  @staticmethod
134  hass: HomeAssistant, config_entry: ConfigEntry
135  ) -> None:
136  """Handle signals of config entry being updated.
137 
138  First, update the accounts, this will reflect any changes with ignore_timestamps.
139  Second, unload underlying platforms, and then setup platforms, this reflects any changes in number of zones.
140 
141  """
142  if not (hub := hass.data[DOMAIN].get(config_entry.entry_id)):
143  return
144  hub.update_accounts()
145  await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS)
146  await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
None async_create_and_fire_event(self, SIAEvent event)
Definition: hub.py:76
None __init__(self, HomeAssistant hass, ConfigEntry entry)
Definition: hub.py:41
None async_shutdown(self, Event|None _=None)
Definition: hub.py:71
None async_config_entry_updated(HomeAssistant hass, ConfigEntry config_entry)
Definition: hub.py:135
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
None async_stop(HomeAssistant hass)
Definition: discovery.py:694
dict[str, Any] get_event_data_from_sia_event(SIAEvent event)
Definition: utils.py:67
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)
Definition: dispatcher.py:193