Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Integrates Native Apps to Home Assistant."""
2 
3 from contextlib import suppress
4 from functools import partial
5 from typing import Any
6 
7 from homeassistant.auth import EVENT_USER_REMOVED
8 from homeassistant.components import cloud, intent, notify as hass_notify
10  async_register as webhook_register,
11  async_unregister as webhook_unregister,
12 )
13 from homeassistant.config_entries import ConfigEntry
14 from homeassistant.const import ATTR_DEVICE_ID, CONF_WEBHOOK_ID, Platform
15 from homeassistant.core import Event, HomeAssistant
16 from homeassistant.helpers import (
17  config_validation as cv,
18  device_registry as dr,
19  discovery,
20 )
21 from homeassistant.helpers.storage import Store
22 from homeassistant.helpers.typing import ConfigType
23 
24 # Pre-import the platforms so they get loaded when the integration
25 # is imported as they are almost always going to be loaded and its
26 # cheaper to import them all at once.
27 from . import ( # noqa: F401
28  binary_sensor as binary_sensor_pre_import,
29  device_tracker as device_tracker_pre_import,
30  notify as notify_pre_import,
31  sensor as sensor_pre_import,
32  websocket_api,
33 )
34 from .const import (
35  ATTR_DEVICE_NAME,
36  ATTR_MANUFACTURER,
37  ATTR_MODEL,
38  ATTR_OS_VERSION,
39  CONF_CLOUDHOOK_URL,
40  CONF_USER_ID,
41  DATA_CONFIG_ENTRIES,
42  DATA_DELETED_IDS,
43  DATA_DEVICES,
44  DATA_PUSH_CHANNEL,
45  DATA_STORE,
46  DOMAIN,
47  STORAGE_KEY,
48  STORAGE_VERSION,
49 )
50 from .helpers import savable_state
51 from .http_api import RegistrationsView
52 from .timers import async_handle_timer_event
53 from .util import async_create_cloud_hook, supports_push
54 from .webhook import handle_webhook
55 
56 PLATFORMS = [Platform.BINARY_SENSOR, Platform.DEVICE_TRACKER, Platform.SENSOR]
57 
58 CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
59 
60 
61 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
62  """Set up the mobile app component."""
63  store = Store[dict[str, Any]](hass, STORAGE_VERSION, STORAGE_KEY)
64  if (app_config := await store.async_load()) is None or not isinstance(
65  app_config, dict
66  ):
67  app_config = {
68  DATA_CONFIG_ENTRIES: {},
69  DATA_DELETED_IDS: [],
70  }
71 
72  hass.data[DOMAIN] = {
73  DATA_CONFIG_ENTRIES: {},
74  DATA_DELETED_IDS: app_config.get(DATA_DELETED_IDS, []),
75  DATA_DEVICES: {},
76  DATA_PUSH_CHANNEL: {},
77  DATA_STORE: store,
78  }
79 
80  hass.http.register_view(RegistrationsView())
81 
82  for deleted_id in hass.data[DOMAIN][DATA_DELETED_IDS]:
83  with suppress(ValueError):
84  webhook_register(
85  hass, DOMAIN, "Deleted Webhook", deleted_id, handle_webhook
86  )
87 
88  hass.async_create_task(
89  discovery.async_load_platform(hass, Platform.NOTIFY, DOMAIN, {}, config),
90  eager_start=True,
91  )
92 
93  websocket_api.async_setup_commands(hass)
94 
95  async def _handle_user_removed(event: Event) -> None:
96  """Remove an entry when the user is removed."""
97  user_id = event.data["user_id"]
98  for entry in hass.config_entries.async_entries(DOMAIN):
99  if entry.data[CONF_USER_ID] == user_id:
100  await hass.config_entries.async_remove(entry.entry_id)
101 
102  hass.bus.async_listen(EVENT_USER_REMOVED, _handle_user_removed)
103 
104  return True
105 
106 
107 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
108  """Set up a mobile_app entry."""
109  registration = entry.data
110 
111  webhook_id = registration[CONF_WEBHOOK_ID]
112 
113  hass.data[DOMAIN][DATA_CONFIG_ENTRIES][webhook_id] = entry
114 
115  device_registry = dr.async_get(hass)
116 
117  device = device_registry.async_get_or_create(
118  config_entry_id=entry.entry_id,
119  identifiers={(DOMAIN, registration[ATTR_DEVICE_ID])},
120  manufacturer=registration[ATTR_MANUFACTURER],
121  model=registration[ATTR_MODEL],
122  name=registration[ATTR_DEVICE_NAME],
123  sw_version=registration[ATTR_OS_VERSION],
124  )
125 
126  hass.data[DOMAIN][DATA_DEVICES][webhook_id] = device
127 
128  registration_name = f"Mobile App: {registration[ATTR_DEVICE_NAME]}"
129  webhook_register(hass, DOMAIN, registration_name, webhook_id, handle_webhook)
130 
131  async def manage_cloudhook(state: cloud.CloudConnectionState) -> None:
132  if (
133  state is cloud.CloudConnectionState.CLOUD_CONNECTED
134  and CONF_CLOUDHOOK_URL not in entry.data
135  ):
136  await async_create_cloud_hook(hass, webhook_id, entry)
137 
138  if cloud.async_is_logged_in(hass):
139  if (
140  CONF_CLOUDHOOK_URL not in entry.data
141  and cloud.async_active_subscription(hass)
142  and cloud.async_is_connected(hass)
143  ):
144  await async_create_cloud_hook(hass, webhook_id, entry)
145  elif CONF_CLOUDHOOK_URL in entry.data:
146  # If we have a cloudhook but no longer logged in to the cloud, remove it from the entry
147  data = dict(entry.data)
148  data.pop(CONF_CLOUDHOOK_URL)
149  hass.config_entries.async_update_entry(entry, data=data)
150 
151  entry.async_on_unload(cloud.async_listen_connection_change(hass, manage_cloudhook))
152 
153  await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
154 
155  if supports_push(hass, webhook_id):
156  entry.async_on_unload(
157  intent.async_register_timer_handler(
158  hass, device.id, partial(async_handle_timer_event, hass, entry)
159  )
160  )
161 
162  await hass_notify.async_reload(hass, DOMAIN)
163 
164  return True
165 
166 
167 async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
168  """Unload a mobile app entry."""
169  unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
170  if not unload_ok:
171  return False
172 
173  webhook_id = entry.data[CONF_WEBHOOK_ID]
174 
175  webhook_unregister(hass, webhook_id)
176  del hass.data[DOMAIN][DATA_CONFIG_ENTRIES][webhook_id]
177  del hass.data[DOMAIN][DATA_DEVICES][webhook_id]
178  await hass_notify.async_reload(hass, DOMAIN)
179 
180  return True
181 
182 
183 async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
184  """Cleanup when entry is removed."""
185  hass.data[DOMAIN][DATA_DELETED_IDS].append(entry.data[CONF_WEBHOOK_ID])
186  store = hass.data[DOMAIN][DATA_STORE]
187  await store.async_save(savable_state(hass))
188 
189  if CONF_CLOUDHOOK_URL in entry.data:
190  with suppress(cloud.CloudNotAvailable, ValueError):
191  await cloud.async_delete_cloudhook(hass, entry.data[CONF_WEBHOOK_ID])
dict savable_state(HomeAssistant hass)
Definition: helpers.py:161
bool supports_push(HomeAssistant hass, str webhook_id)
Definition: util.py:42
str async_create_cloud_hook(HomeAssistant hass, str webhook_id, ConfigEntry|None entry)
Definition: util.py:68
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:167
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:107
None async_remove_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:183
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:61