Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Support for AlarmDecoder devices."""
2 
3 from collections.abc import Callable
4 from dataclasses import dataclass
5 from datetime import timedelta
6 import logging
7 
8 from adext import AdExt
9 from alarmdecoder.devices import SerialDevice, SocketDevice
10 from alarmdecoder.util import NoDeviceError
11 
12 from homeassistant.config_entries import ConfigEntry
13 from homeassistant.const import (
14  CONF_HOST,
15  CONF_PORT,
16  CONF_PROTOCOL,
17  EVENT_HOMEASSISTANT_STOP,
18  Platform,
19 )
20 from homeassistant.core import HomeAssistant
21 from homeassistant.helpers.dispatcher import dispatcher_send
22 from homeassistant.helpers.event import async_call_later
23 
24 from .const import (
25  CONF_DEVICE_BAUD,
26  CONF_DEVICE_PATH,
27  PROTOCOL_SERIAL,
28  PROTOCOL_SOCKET,
29  SIGNAL_PANEL_MESSAGE,
30  SIGNAL_REL_MESSAGE,
31  SIGNAL_RFX_MESSAGE,
32  SIGNAL_ZONE_FAULT,
33  SIGNAL_ZONE_RESTORE,
34 )
35 
36 _LOGGER = logging.getLogger(__name__)
37 
38 PLATFORMS = [
39  Platform.ALARM_CONTROL_PANEL,
40  Platform.BINARY_SENSOR,
41  Platform.SENSOR,
42 ]
43 
44 type AlarmDecoderConfigEntry = ConfigEntry[AlarmDecoderData]
45 
46 
47 @dataclass
49  """Runtime data for the AlarmDecoder class."""
50 
51  client: AdExt
52  remove_update_listener: Callable[[], None]
53  remove_stop_listener: Callable[[], None]
54  restart: bool
55 
56 
58  hass: HomeAssistant, entry: AlarmDecoderConfigEntry
59 ) -> bool:
60  """Set up AlarmDecoder config flow."""
61  undo_listener = entry.add_update_listener(_update_listener)
62 
63  ad_connection = entry.data
64  protocol = ad_connection[CONF_PROTOCOL]
65 
66  def stop_alarmdecoder(event):
67  """Handle the shutdown of AlarmDecoder."""
68  if not entry.runtime_data:
69  return
70  _LOGGER.debug("Shutting down alarmdecoder")
71  entry.runtime_data.restart = False
72  controller.close()
73 
74  async def open_connection(now=None):
75  """Open a connection to AlarmDecoder."""
76  try:
77  await hass.async_add_executor_job(controller.open, baud)
78  except NoDeviceError:
79  _LOGGER.debug("Failed to connect. Retrying in 5 seconds")
80  async_call_later(hass, timedelta(seconds=5), open_connection)
81  return
82  _LOGGER.debug("Established a connection with the alarmdecoder")
83  entry.runtime_data.restart = True
84 
85  def handle_closed_connection(event):
86  """Restart after unexpected loss of connection."""
87  if not entry.runtime_data.restart:
88  return
89  entry.runtime_data.restart = False
90  _LOGGER.warning("AlarmDecoder unexpectedly lost connection")
91  hass.add_job(open_connection)
92 
93  def handle_message(sender, message):
94  """Handle message from AlarmDecoder."""
95  dispatcher_send(hass, SIGNAL_PANEL_MESSAGE, message)
96 
97  def handle_rfx_message(sender, message):
98  """Handle RFX message from AlarmDecoder."""
99  dispatcher_send(hass, SIGNAL_RFX_MESSAGE, message)
100 
101  def zone_fault_callback(sender, zone):
102  """Handle zone fault from AlarmDecoder."""
103  dispatcher_send(hass, SIGNAL_ZONE_FAULT, zone)
104 
105  def zone_restore_callback(sender, zone):
106  """Handle zone restore from AlarmDecoder."""
107  dispatcher_send(hass, SIGNAL_ZONE_RESTORE, zone)
108 
109  def handle_rel_message(sender, message):
110  """Handle relay or zone expander message from AlarmDecoder."""
111  dispatcher_send(hass, SIGNAL_REL_MESSAGE, message)
112 
113  baud = ad_connection.get(CONF_DEVICE_BAUD)
114  if protocol == PROTOCOL_SOCKET:
115  host = ad_connection[CONF_HOST]
116  port = ad_connection[CONF_PORT]
117  controller = AdExt(SocketDevice(interface=(host, port)))
118  if protocol == PROTOCOL_SERIAL:
119  path = ad_connection[CONF_DEVICE_PATH]
120  controller = AdExt(SerialDevice(interface=path))
121 
122  controller.on_message += handle_message
123  controller.on_rfx_message += handle_rfx_message
124  controller.on_zone_fault += zone_fault_callback
125  controller.on_zone_restore += zone_restore_callback
126  controller.on_close += handle_closed_connection
127  controller.on_expander_message += handle_rel_message
128 
129  remove_stop_listener = hass.bus.async_listen_once(
130  EVENT_HOMEASSISTANT_STOP, stop_alarmdecoder
131  )
132 
133  entry.runtime_data = AlarmDecoderData(
134  controller, undo_listener, remove_stop_listener, False
135  )
136 
137  await open_connection()
138 
139  await controller.is_init()
140 
141  await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
142 
143  return True
144 
145 
147  hass: HomeAssistant, entry: AlarmDecoderConfigEntry
148 ) -> bool:
149  """Unload a AlarmDecoder entry."""
150  data = entry.runtime_data
151  data.restart = False
152 
153  unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
154 
155  if not unload_ok:
156  return False
157 
158  data.remove_update_listener()
159  data.remove_stop_listener()
160  await hass.async_add_executor_job(data.client.close)
161 
162  return True
163 
164 
165 async def _update_listener(hass: HomeAssistant, entry: AlarmDecoderConfigEntry) -> None:
166  """Handle options update."""
167  _LOGGER.debug("AlarmDecoder options updated: %s", entry.as_dict()["options"])
168  await hass.config_entries.async_reload(entry.entry_id)
None _update_listener(HomeAssistant hass, AlarmDecoderConfigEntry entry)
Definition: __init__.py:165
bool async_setup_entry(HomeAssistant hass, AlarmDecoderConfigEntry entry)
Definition: __init__.py:59
bool async_unload_entry(HomeAssistant hass, AlarmDecoderConfigEntry entry)
Definition: __init__.py:148
None dispatcher_send(HomeAssistant hass, str signal, *Any args)
Definition: dispatcher.py:137
CALLBACK_TYPE async_call_later(HomeAssistant hass, float|timedelta delay, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action)
Definition: event.py:1597