Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """UniFi Protect Platform."""
2 
3 from __future__ import annotations
4 
5 from datetime import timedelta
6 import logging
7 
8 from aiohttp.client_exceptions import ServerDisconnectedError
9 from uiprotect.api import DEVICE_UPDATE_INTERVAL
10 from uiprotect.data import Bootstrap
11 from uiprotect.data.types import FirmwareReleaseChannel
12 from uiprotect.exceptions import ClientError, NotAuthorized
13 
14 # Import the test_util.anonymize module from the uiprotect package
15 # in __init__ to ensure it gets imported in the executor since the
16 # diagnostics module will not be imported in the executor.
17 from uiprotect.test_util.anonymize import anonymize_data # noqa: F401
18 
19 from homeassistant.const import EVENT_HOMEASSISTANT_STOP
20 from homeassistant.core import HomeAssistant
21 from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
22 from homeassistant.helpers import (
23  config_validation as cv,
24  device_registry as dr,
25  issue_registry as ir,
26 )
27 from homeassistant.helpers.issue_registry import IssueSeverity
28 from homeassistant.helpers.typing import ConfigType
29 
30 from .const import (
31  AUTH_RETRIES,
32  CONF_ALLOW_EA,
33  DEVICES_THAT_ADOPT,
34  DOMAIN,
35  MIN_REQUIRED_PROTECT_V,
36  OUTDATED_LOG_MESSAGE,
37  PLATFORMS,
38 )
39 from .data import ProtectData, UFPConfigEntry
40 from .discovery import async_start_discovery
41 from .migrate import async_migrate_data
42 from .services import async_setup_services
43 from .utils import (
44  _async_unifi_mac_from_hass,
45  async_create_api_client,
46  async_get_devices,
47 )
48 from .views import ThumbnailProxyView, VideoEventProxyView, VideoProxyView
49 
50 _LOGGER = logging.getLogger(__name__)
51 
52 SCAN_INTERVAL = timedelta(seconds=DEVICE_UPDATE_INTERVAL)
53 
54 CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
55 
56 EARLY_ACCESS_URL = (
57  "https://www.home-assistant.io/integrations/unifiprotect#software-support"
58 )
59 
60 
61 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
62  """Set up the UniFi Protect."""
63  # Only start discovery once regardless of how many entries they have
66  return True
67 
68 
69 async def async_setup_entry(hass: HomeAssistant, entry: UFPConfigEntry) -> bool:
70  """Set up the UniFi Protect config entries."""
71  protect = async_create_api_client(hass, entry)
72  _LOGGER.debug("Connect to UniFi Protect")
73 
74  try:
75  await protect.update()
76  except NotAuthorized as err:
77  retry_key = f"{entry.entry_id}_auth"
78  retries = hass.data.setdefault(DOMAIN, {}).get(retry_key, 0)
79  if retries < AUTH_RETRIES:
80  retries += 1
81  hass.data[DOMAIN][retry_key] = retries
82  raise ConfigEntryNotReady from err
83  raise ConfigEntryAuthFailed(err) from err
84  except (TimeoutError, ClientError, ServerDisconnectedError) as err:
85  raise ConfigEntryNotReady from err
86 
87  data_service = ProtectData(hass, protect, SCAN_INTERVAL, entry)
88  bootstrap = protect.bootstrap
89  nvr_info = bootstrap.nvr
90  auth_user = bootstrap.users.get(bootstrap.auth_user_id)
91  if auth_user and auth_user.cloud_account:
92  ir.async_create_issue(
93  hass,
94  DOMAIN,
95  "cloud_user",
96  is_fixable=True,
97  is_persistent=False,
98  learn_more_url="https://www.home-assistant.io/integrations/unifiprotect/#local-user",
99  severity=IssueSeverity.ERROR,
100  translation_key="cloud_user",
101  data={"entry_id": entry.entry_id},
102  )
103 
104  if nvr_info.version < MIN_REQUIRED_PROTECT_V:
105  _LOGGER.error(
106  OUTDATED_LOG_MESSAGE,
107  nvr_info.version,
108  MIN_REQUIRED_PROTECT_V,
109  )
110  return False
111 
112  if entry.unique_id is None:
113  hass.config_entries.async_update_entry(entry, unique_id=nvr_info.mac)
114 
115  entry.runtime_data = data_service
116  entry.async_on_unload(entry.add_update_listener(_async_options_updated))
117  entry.async_on_unload(
118  hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, data_service.async_stop)
119  )
120 
121  if not entry.options.get(CONF_ALLOW_EA, False) and (
122  await nvr_info.get_is_prerelease()
123  or nvr_info.release_channel != FirmwareReleaseChannel.RELEASE
124  ):
125  ir.async_create_issue(
126  hass,
127  DOMAIN,
128  "ea_channel_warning",
129  is_fixable=True,
130  is_persistent=False,
131  learn_more_url=EARLY_ACCESS_URL,
132  severity=IssueSeverity.WARNING,
133  translation_key="ea_channel_warning",
134  translation_placeholders={"version": str(nvr_info.version)},
135  data={"entry_id": entry.entry_id},
136  )
137 
138  try:
139  await _async_setup_entry(hass, entry, data_service, bootstrap)
140  except Exception as err:
141  if await nvr_info.get_is_prerelease():
142  # If they are running a pre-release, its quite common for setup
143  # to fail so we want to create a repair issue for them so its
144  # obvious what the problem is.
145  ir.async_create_issue(
146  hass,
147  DOMAIN,
148  f"ea_setup_failed_{nvr_info.version}",
149  is_fixable=False,
150  is_persistent=False,
151  learn_more_url="https://www.home-assistant.io/integrations/unifiprotect#about-unifi-early-access",
152  severity=IssueSeverity.ERROR,
153  translation_key="ea_setup_failed",
154  translation_placeholders={
155  "error": str(err),
156  "version": str(nvr_info.version),
157  },
158  )
159  ir.async_delete_issue(hass, DOMAIN, "ea_channel_warning")
160  _LOGGER.exception("Error setting up UniFi Protect integration")
161  raise
162 
163  return True
164 
165 
167  hass: HomeAssistant,
168  entry: UFPConfigEntry,
169  data_service: ProtectData,
170  bootstrap: Bootstrap,
171 ) -> None:
172  await async_migrate_data(hass, entry, data_service.api, bootstrap)
173  data_service.async_setup()
174  await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
175  hass.http.register_view(ThumbnailProxyView(hass))
176  hass.http.register_view(VideoProxyView(hass))
177  hass.http.register_view(VideoEventProxyView(hass))
178 
179 
180 async def _async_options_updated(hass: HomeAssistant, entry: UFPConfigEntry) -> None:
181  """Update options."""
182  await hass.config_entries.async_reload(entry.entry_id)
183 
184 
185 async def async_unload_entry(hass: HomeAssistant, entry: UFPConfigEntry) -> bool:
186  """Unload UniFi Protect config entry."""
187  if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
188  await entry.runtime_data.async_stop()
189  return unload_ok
190 
191 
193  hass: HomeAssistant, config_entry: UFPConfigEntry, device_entry: dr.DeviceEntry
194 ) -> bool:
195  """Remove ufp config entry from a device."""
196  unifi_macs = {
197  _async_unifi_mac_from_hass(connection[1])
198  for connection in device_entry.connections
199  if connection[0] == dr.CONNECTION_NETWORK_MAC
200  }
201  api = config_entry.runtime_data.api
202  if api.bootstrap.nvr.mac in unifi_macs:
203  return False
204  for device in async_get_devices(api.bootstrap, DEVICES_THAT_ADOPT):
205  if device.is_adopted_by_us and device.mac in unifi_macs:
206  return False
207  return True
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
None async_setup_services(HomeAssistant hass)
Definition: __init__.py:72
None async_start_discovery(HomeAssistant hass)
Definition: discovery.py:26
None async_migrate_data(HomeAssistant hass, UFPConfigEntry entry, ProtectApiClient protect, Bootstrap bootstrap)
Definition: migrate.py:107
ProtectApiClient async_create_api_client(HomeAssistant hass, UFPConfigEntry entry)
Definition: utils.py:109
Generator[ProtectAdoptableDeviceModel] async_get_devices(Bootstrap bootstrap, Iterable[ModelType] model_type)
Definition: utils.py:85
bool async_unload_entry(HomeAssistant hass, UFPConfigEntry entry)
Definition: __init__.py:185
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:61
None _async_setup_entry(HomeAssistant hass, UFPConfigEntry entry, ProtectData data_service, Bootstrap bootstrap)
Definition: __init__.py:171
None _async_options_updated(HomeAssistant hass, UFPConfigEntry entry)
Definition: __init__.py:180
bool async_remove_config_entry_device(HomeAssistant hass, UFPConfigEntry config_entry, dr.DeviceEntry device_entry)
Definition: __init__.py:194
bool async_setup_entry(HomeAssistant hass, UFPConfigEntry entry)
Definition: __init__.py:69