Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Support for Envisalink devices."""
2 
3 import asyncio
4 import logging
5 
6 from pyenvisalink import EnvisalinkAlarmPanel
7 import voluptuous as vol
8 
9 from homeassistant.const import (
10  CONF_CODE,
11  CONF_HOST,
12  CONF_TIMEOUT,
13  EVENT_HOMEASSISTANT_STOP,
14  Platform,
15 )
16 from homeassistant.core import HomeAssistant, ServiceCall, callback
18 from homeassistant.helpers.discovery import async_load_platform
19 from homeassistant.helpers.dispatcher import async_dispatcher_send
20 from homeassistant.helpers.typing import ConfigType
21 
22 _LOGGER = logging.getLogger(__name__)
23 
24 DOMAIN = "envisalink"
25 
26 DATA_EVL = "envisalink"
27 
28 CONF_EVL_KEEPALIVE = "keepalive_interval"
29 CONF_EVL_PORT = "port"
30 CONF_EVL_VERSION = "evl_version"
31 CONF_PANEL_TYPE = "panel_type"
32 CONF_PANIC = "panic_type"
33 CONF_PARTITIONNAME = "name"
34 CONF_PARTITIONS = "partitions"
35 CONF_PASS = "password"
36 CONF_USERNAME = "user_name"
37 CONF_ZONEDUMP_INTERVAL = "zonedump_interval"
38 CONF_ZONENAME = "name"
39 CONF_ZONES = "zones"
40 CONF_ZONETYPE = "type"
41 
42 PANEL_TYPE_HONEYWELL = "HONEYWELL"
43 PANEL_TYPE_DSC = "DSC"
44 
45 DEFAULT_PORT = 4025
46 DEFAULT_EVL_VERSION = 3
47 DEFAULT_KEEPALIVE = 60
48 DEFAULT_ZONEDUMP_INTERVAL = 30
49 DEFAULT_ZONETYPE = "opening"
50 DEFAULT_PANIC = "Police"
51 DEFAULT_TIMEOUT = 10
52 
53 SIGNAL_ZONE_UPDATE = "envisalink.zones_updated"
54 SIGNAL_PARTITION_UPDATE = "envisalink.partition_updated"
55 SIGNAL_KEYPAD_UPDATE = "envisalink.keypad_updated"
56 SIGNAL_ZONE_BYPASS_UPDATE = "envisalink.zone_bypass_updated"
57 
58 ZONE_SCHEMA = vol.Schema(
59  {
60  vol.Required(CONF_ZONENAME): cv.string,
61  vol.Optional(CONF_ZONETYPE, default=DEFAULT_ZONETYPE): cv.string,
62  }
63 )
64 
65 PARTITION_SCHEMA = vol.Schema({vol.Required(CONF_PARTITIONNAME): cv.string})
66 
67 CONFIG_SCHEMA = vol.Schema(
68  {
69  DOMAIN: vol.Schema(
70  {
71  vol.Required(CONF_HOST): cv.string,
72  vol.Required(CONF_PANEL_TYPE): vol.All(
73  cv.string, vol.In([PANEL_TYPE_HONEYWELL, PANEL_TYPE_DSC])
74  ),
75  vol.Required(CONF_USERNAME): cv.string,
76  vol.Required(CONF_PASS): cv.string,
77  vol.Optional(CONF_CODE): cv.string,
78  vol.Optional(CONF_PANIC, default=DEFAULT_PANIC): cv.string,
79  vol.Optional(CONF_ZONES): {vol.Coerce(int): ZONE_SCHEMA},
80  vol.Optional(CONF_PARTITIONS): {vol.Coerce(int): PARTITION_SCHEMA},
81  vol.Optional(CONF_EVL_PORT, default=DEFAULT_PORT): cv.port,
82  vol.Optional(CONF_EVL_VERSION, default=DEFAULT_EVL_VERSION): vol.All(
83  vol.Coerce(int), vol.Range(min=3, max=4)
84  ),
85  vol.Optional(CONF_EVL_KEEPALIVE, default=DEFAULT_KEEPALIVE): vol.All(
86  vol.Coerce(int), vol.Range(min=15)
87  ),
88  vol.Optional(
89  CONF_ZONEDUMP_INTERVAL, default=DEFAULT_ZONEDUMP_INTERVAL
90  ): vol.Coerce(int),
91  vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): vol.Coerce(int),
92  }
93  )
94  },
95  extra=vol.ALLOW_EXTRA,
96 )
97 
98 SERVICE_CUSTOM_FUNCTION = "invoke_custom_function"
99 ATTR_CUSTOM_FUNCTION = "pgm"
100 ATTR_PARTITION = "partition"
101 
102 SERVICE_SCHEMA = vol.Schema(
103  {
104  vol.Required(ATTR_CUSTOM_FUNCTION): cv.string,
105  vol.Required(ATTR_PARTITION): cv.string,
106  }
107 )
108 
109 
110 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
111  """Set up for Envisalink devices."""
112  conf = config[DOMAIN]
113 
114  host = conf.get(CONF_HOST)
115  port = conf.get(CONF_EVL_PORT)
116  code = conf.get(CONF_CODE)
117  panel_type = conf.get(CONF_PANEL_TYPE)
118  panic_type = conf.get(CONF_PANIC)
119  version = conf.get(CONF_EVL_VERSION)
120  user = conf.get(CONF_USERNAME)
121  password = conf.get(CONF_PASS)
122  keep_alive = conf.get(CONF_EVL_KEEPALIVE)
123  zone_dump = conf.get(CONF_ZONEDUMP_INTERVAL)
124  zones = conf.get(CONF_ZONES)
125  partitions = conf.get(CONF_PARTITIONS)
126  connection_timeout = conf.get(CONF_TIMEOUT)
127  sync_connect: asyncio.Future[bool] = hass.loop.create_future()
128 
129  controller = EnvisalinkAlarmPanel(
130  host,
131  port,
132  panel_type,
133  version,
134  user,
135  password,
136  zone_dump,
137  keep_alive,
138  hass.loop,
139  connection_timeout,
140  False,
141  )
142  hass.data[DATA_EVL] = controller
143 
144  @callback
145  def async_login_fail_callback(data):
146  """Handle when the evl rejects our login."""
147  _LOGGER.error("The Envisalink rejected your credentials")
148  if not sync_connect.done():
149  sync_connect.set_result(False)
150 
151  @callback
152  def async_connection_fail_callback(data):
153  """Network failure callback."""
154  _LOGGER.error("Could not establish a connection with the Envisalink- retrying")
155  if not sync_connect.done():
156  hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_envisalink)
157  sync_connect.set_result(True)
158 
159  @callback
160  def async_connection_success_callback(data):
161  """Handle a successful connection."""
162  _LOGGER.debug("Established a connection with the Envisalink")
163  if not sync_connect.done():
164  hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_envisalink)
165  sync_connect.set_result(True)
166 
167  @callback
168  def async_zones_updated_callback(data):
169  """Handle zone timer updates."""
170  _LOGGER.debug("Envisalink sent a zone update event. Updating zones")
171  async_dispatcher_send(hass, SIGNAL_ZONE_UPDATE, data)
172 
173  @callback
174  def async_alarm_data_updated_callback(data):
175  """Handle non-alarm based info updates."""
176  _LOGGER.debug("Envisalink sent new alarm info. Updating alarms")
177  async_dispatcher_send(hass, SIGNAL_KEYPAD_UPDATE, data)
178 
179  @callback
180  def async_partition_updated_callback(data):
181  """Handle partition changes thrown by evl (including alarms)."""
182  _LOGGER.debug("The envisalink sent a partition update event")
183  async_dispatcher_send(hass, SIGNAL_PARTITION_UPDATE, data)
184 
185  @callback
186  def stop_envisalink(event):
187  """Shutdown envisalink connection and thread on exit."""
188  _LOGGER.debug("Shutting down Envisalink")
189  controller.stop()
190 
191  async def handle_custom_function(call: ServiceCall) -> None:
192  """Handle custom/PGM service."""
193  custom_function = call.data.get(ATTR_CUSTOM_FUNCTION)
194  partition = call.data.get(ATTR_PARTITION)
195  controller.command_output(code, partition, custom_function)
196 
197  controller.callback_zone_timer_dump = async_zones_updated_callback
198  controller.callback_zone_state_change = async_zones_updated_callback
199  controller.callback_partition_state_change = async_partition_updated_callback
200  controller.callback_keypad_update = async_alarm_data_updated_callback
201  controller.callback_login_failure = async_login_fail_callback
202  controller.callback_login_timeout = async_connection_fail_callback
203  controller.callback_login_success = async_connection_success_callback
204 
205  _LOGGER.debug("Start envisalink")
206  controller.start()
207 
208  if not await sync_connect:
209  return False
210 
211  # Load sub-components for Envisalink
212  if partitions:
213  hass.async_create_task(
215  hass,
216  Platform.ALARM_CONTROL_PANEL,
217  "envisalink",
218  {CONF_PARTITIONS: partitions, CONF_CODE: code, CONF_PANIC: panic_type},
219  config,
220  )
221  )
222  hass.async_create_task(
224  hass,
225  Platform.SENSOR,
226  "envisalink",
227  {CONF_PARTITIONS: partitions, CONF_CODE: code},
228  config,
229  )
230  )
231  if zones:
232  hass.async_create_task(
234  hass, Platform.BINARY_SENSOR, "envisalink", {CONF_ZONES: zones}, config
235  )
236  )
237 
238  # Zone bypass switches are not currently created due to an issue with some panels.
239  # These switches will be re-added in the future after some further refactoring of the integration.
240 
241  hass.services.async_register(
242  DOMAIN, SERVICE_CUSTOM_FUNCTION, handle_custom_function, schema=SERVICE_SCHEMA
243  )
244 
245  return True
None async_load_platform(core.HomeAssistant hass, Platform|str component, str platform, DiscoveryInfoType|None discovered, ConfigType hass_config)
Definition: discovery.py:152
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)
Definition: dispatcher.py:193