Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Support for the Abode Security System."""
2 
3 from __future__ import annotations
4 
5 from dataclasses import dataclass, field
6 from functools import partial
7 from pathlib import Path
8 
9 from jaraco.abode.client import Client as Abode
10 import jaraco.abode.config
11 from jaraco.abode.exceptions import (
12  AuthenticationException as AbodeAuthenticationException,
13  Exception as AbodeException,
14 )
15 from jaraco.abode.helpers.timeline import Groups as GROUPS
16 from requests.exceptions import ConnectTimeout, HTTPError
17 import voluptuous as vol
18 
19 from homeassistant.config_entries import ConfigEntry
20 from homeassistant.const import (
21  ATTR_DATE,
22  ATTR_DEVICE_ID,
23  ATTR_ENTITY_ID,
24  ATTR_TIME,
25  CONF_PASSWORD,
26  CONF_USERNAME,
27  EVENT_HOMEASSISTANT_STOP,
28  Platform,
29 )
30 from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, ServiceCall
31 from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
32 from homeassistant.helpers import config_validation as cv
33 from homeassistant.helpers.dispatcher import dispatcher_send
34 from homeassistant.helpers.typing import ConfigType
35 
36 from .const import CONF_POLLING, DOMAIN, LOGGER
37 
38 SERVICE_SETTINGS = "change_setting"
39 SERVICE_CAPTURE_IMAGE = "capture_image"
40 SERVICE_TRIGGER_AUTOMATION = "trigger_automation"
41 
42 ATTR_DEVICE_NAME = "device_name"
43 ATTR_DEVICE_TYPE = "device_type"
44 ATTR_EVENT_CODE = "event_code"
45 ATTR_EVENT_NAME = "event_name"
46 ATTR_EVENT_TYPE = "event_type"
47 ATTR_EVENT_UTC = "event_utc"
48 ATTR_SETTING = "setting"
49 ATTR_USER_NAME = "user_name"
50 ATTR_APP_TYPE = "app_type"
51 ATTR_EVENT_BY = "event_by"
52 ATTR_VALUE = "value"
53 
54 CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
55 
56 CHANGE_SETTING_SCHEMA = vol.Schema(
57  {vol.Required(ATTR_SETTING): cv.string, vol.Required(ATTR_VALUE): cv.string}
58 )
59 
60 CAPTURE_IMAGE_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids})
61 
62 AUTOMATION_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids})
63 
64 PLATFORMS = [
65  Platform.ALARM_CONTROL_PANEL,
66  Platform.BINARY_SENSOR,
67  Platform.CAMERA,
68  Platform.COVER,
69  Platform.LIGHT,
70  Platform.LOCK,
71  Platform.SENSOR,
72  Platform.SWITCH,
73 ]
74 
75 
76 @dataclass
78  """Abode System class."""
79 
80  abode: Abode
81  polling: bool
82  entity_ids: set[str | None] = field(default_factory=set)
83  logout_listener: CALLBACK_TYPE | None = None
84 
85 
86 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
87  """Set up the Abode component."""
89  return True
90 
91 
92 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
93  """Set up Abode integration from a config entry."""
94  username = entry.data[CONF_USERNAME]
95  password = entry.data[CONF_PASSWORD]
96  polling = entry.data[CONF_POLLING]
97 
98  # Configure abode library to use config directory for storing data
99  jaraco.abode.config.paths.override(user_data=Path(hass.config.path("Abode")))
100 
101  # For previous config entries where unique_id is None
102  if entry.unique_id is None:
103  hass.config_entries.async_update_entry(
104  entry, unique_id=entry.data[CONF_USERNAME]
105  )
106 
107  try:
108  abode = await hass.async_add_executor_job(
109  Abode, username, password, True, True, True
110  )
111 
112  except AbodeAuthenticationException as ex:
113  raise ConfigEntryAuthFailed(f"Invalid credentials: {ex}") from ex
114 
115  except (AbodeException, ConnectTimeout, HTTPError) as ex:
116  raise ConfigEntryNotReady(f"Unable to connect to Abode: {ex}") from ex
117 
118  hass.data[DOMAIN] = AbodeSystem(abode, polling)
119 
120  await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
121 
122  await setup_hass_events(hass)
123  await hass.async_add_executor_job(setup_abode_events, hass)
124 
125  return True
126 
127 
128 async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
129  """Unload a config entry."""
130  unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
131 
132  await hass.async_add_executor_job(hass.data[DOMAIN].abode.events.stop)
133  await hass.async_add_executor_job(hass.data[DOMAIN].abode.logout)
134 
135  hass.data[DOMAIN].logout_listener()
136  hass.data.pop(DOMAIN)
137 
138  return unload_ok
139 
140 
141 def setup_hass_services(hass: HomeAssistant) -> None:
142  """Home Assistant services."""
143 
144  def change_setting(call: ServiceCall) -> None:
145  """Change an Abode system setting."""
146  setting = call.data[ATTR_SETTING]
147  value = call.data[ATTR_VALUE]
148 
149  try:
150  hass.data[DOMAIN].abode.set_setting(setting, value)
151  except AbodeException as ex:
152  LOGGER.warning(ex)
153 
154  def capture_image(call: ServiceCall) -> None:
155  """Capture a new image."""
156  entity_ids = call.data[ATTR_ENTITY_ID]
157 
158  target_entities = [
159  entity_id
160  for entity_id in hass.data[DOMAIN].entity_ids
161  if entity_id in entity_ids
162  ]
163 
164  for entity_id in target_entities:
165  signal = f"abode_camera_capture_{entity_id}"
166  dispatcher_send(hass, signal)
167 
168  def trigger_automation(call: ServiceCall) -> None:
169  """Trigger an Abode automation."""
170  entity_ids = call.data[ATTR_ENTITY_ID]
171 
172  target_entities = [
173  entity_id
174  for entity_id in hass.data[DOMAIN].entity_ids
175  if entity_id in entity_ids
176  ]
177 
178  for entity_id in target_entities:
179  signal = f"abode_trigger_automation_{entity_id}"
180  dispatcher_send(hass, signal)
181 
182  hass.services.async_register(
183  DOMAIN, SERVICE_SETTINGS, change_setting, schema=CHANGE_SETTING_SCHEMA
184  )
185 
186  hass.services.async_register(
187  DOMAIN, SERVICE_CAPTURE_IMAGE, capture_image, schema=CAPTURE_IMAGE_SCHEMA
188  )
189 
190  hass.services.async_register(
191  DOMAIN, SERVICE_TRIGGER_AUTOMATION, trigger_automation, schema=AUTOMATION_SCHEMA
192  )
193 
194 
195 async def setup_hass_events(hass: HomeAssistant) -> None:
196  """Home Assistant start and stop callbacks."""
197 
198  def logout(event: Event) -> None:
199  """Logout of Abode."""
200  if not hass.data[DOMAIN].polling:
201  hass.data[DOMAIN].abode.events.stop()
202 
203  hass.data[DOMAIN].abode.logout()
204  LOGGER.info("Logged out of Abode")
205 
206  if not hass.data[DOMAIN].polling:
207  await hass.async_add_executor_job(hass.data[DOMAIN].abode.events.start)
208 
209  hass.data[DOMAIN].logout_listener = hass.bus.async_listen_once(
210  EVENT_HOMEASSISTANT_STOP, logout
211  )
212 
213 
214 def setup_abode_events(hass: HomeAssistant) -> None:
215  """Event callbacks."""
216 
217  def event_callback(event: str, event_json: dict[str, str]) -> None:
218  """Handle an event callback from Abode."""
219  data = {
220  ATTR_DEVICE_ID: event_json.get(ATTR_DEVICE_ID, ""),
221  ATTR_DEVICE_NAME: event_json.get(ATTR_DEVICE_NAME, ""),
222  ATTR_DEVICE_TYPE: event_json.get(ATTR_DEVICE_TYPE, ""),
223  ATTR_EVENT_CODE: event_json.get(ATTR_EVENT_CODE, ""),
224  ATTR_EVENT_NAME: event_json.get(ATTR_EVENT_NAME, ""),
225  ATTR_EVENT_TYPE: event_json.get(ATTR_EVENT_TYPE, ""),
226  ATTR_EVENT_UTC: event_json.get(ATTR_EVENT_UTC, ""),
227  ATTR_USER_NAME: event_json.get(ATTR_USER_NAME, ""),
228  ATTR_APP_TYPE: event_json.get(ATTR_APP_TYPE, ""),
229  ATTR_EVENT_BY: event_json.get(ATTR_EVENT_BY, ""),
230  ATTR_DATE: event_json.get(ATTR_DATE, ""),
231  ATTR_TIME: event_json.get(ATTR_TIME, ""),
232  }
233 
234  hass.bus.fire(event, data)
235 
236  events = [
237  GROUPS.ALARM,
238  GROUPS.ALARM_END,
239  GROUPS.PANEL_FAULT,
240  GROUPS.PANEL_RESTORE,
241  GROUPS.AUTOMATION,
242  GROUPS.DISARM,
243  GROUPS.ARM,
244  GROUPS.ARM_FAULT,
245  GROUPS.TEST,
246  GROUPS.CAPTURE,
247  GROUPS.DEVICE,
248  ]
249 
250  for event in events:
251  hass.data[DOMAIN].abode.events.add_event_callback(
252  event, partial(event_callback, event)
253  )
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:128
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:86
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:92
None setup_abode_events(HomeAssistant hass)
Definition: __init__.py:214
None setup_hass_events(HomeAssistant hass)
Definition: __init__.py:195
None setup_hass_services(HomeAssistant hass)
Definition: __init__.py:141
None dispatcher_send(HomeAssistant hass, str signal, *Any args)
Definition: dispatcher.py:137