Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """The Synology DSM component."""
2 
3 from __future__ import annotations
4 
5 from itertools import chain
6 import logging
7 
8 from synology_dsm.api.surveillance_station import SynoSurveillanceStation
9 from synology_dsm.api.surveillance_station.camera import SynoCamera
10 from synology_dsm.exceptions import SynologyDSMNotLoggedInException
11 
12 from homeassistant.config_entries import ConfigEntry
13 from homeassistant.const import CONF_MAC, CONF_VERIFY_SSL
14 from homeassistant.core import HomeAssistant
15 from homeassistant.exceptions import ConfigEntryNotReady
16 from homeassistant.helpers import device_registry as dr
17 
18 from .common import SynoApi, raise_config_entry_auth_error
19 from .const import (
20  DEFAULT_VERIFY_SSL,
21  DOMAIN,
22  EXCEPTION_DETAILS,
23  EXCEPTION_UNKNOWN,
24  PLATFORMS,
25  SYNOLOGY_AUTH_FAILED_EXCEPTIONS,
26  SYNOLOGY_CONNECTION_EXCEPTIONS,
27 )
28 from .coordinator import (
29  SynologyDSMCameraUpdateCoordinator,
30  SynologyDSMCentralUpdateCoordinator,
31  SynologyDSMSwitchUpdateCoordinator,
32 )
33 from .models import SynologyDSMData
34 from .service import async_setup_services
35 
36 _LOGGER = logging.getLogger(__name__)
37 
38 
39 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
40  """Set up Synology DSM sensors."""
41 
42  # Migrate device identifiers
43  dev_reg = dr.async_get(hass)
44  devices: list[dr.DeviceEntry] = dr.async_entries_for_config_entry(
45  dev_reg, entry.entry_id
46  )
47  for device in devices:
48  old_identifier = list(next(iter(device.identifiers)))
49  if len(old_identifier) > 2:
50  new_identifier = {
51  (old_identifier.pop(0), "_".join([str(x) for x in old_identifier]))
52  }
53  _LOGGER.debug(
54  "migrate identifier '%s' to '%s'", device.identifiers, new_identifier
55  )
56  dev_reg.async_update_device(device.id, new_identifiers=new_identifier)
57 
58  # Migrate existing entry configuration
59  if entry.data.get(CONF_VERIFY_SSL) is None:
60  hass.config_entries.async_update_entry(
61  entry, data={**entry.data, CONF_VERIFY_SSL: DEFAULT_VERIFY_SSL}
62  )
63 
64  # Continue setup
65  api = SynoApi(hass, entry)
66  try:
67  await api.async_setup()
68  except SYNOLOGY_AUTH_FAILED_EXCEPTIONS as err:
70  except (*SYNOLOGY_CONNECTION_EXCEPTIONS, SynologyDSMNotLoggedInException) as err:
71  # SynologyDSMNotLoggedInException may be raised even if the user is
72  # logged in because the session may have expired, and we need to retry
73  # the login later.
74  if err.args[0] and isinstance(err.args[0], dict):
75  details = err.args[0].get(EXCEPTION_DETAILS, EXCEPTION_UNKNOWN)
76  else:
77  details = EXCEPTION_UNKNOWN
78  raise ConfigEntryNotReady(details) from err
79 
80  # Services
81  await async_setup_services(hass)
82 
83  # For SSDP compat
84  if not entry.data.get(CONF_MAC):
85  hass.config_entries.async_update_entry(
86  entry, data={**entry.data, CONF_MAC: api.dsm.network.macs}
87  )
88 
89  coordinator_central = SynologyDSMCentralUpdateCoordinator(hass, entry, api)
90 
91  available_apis = api.dsm.apis
92 
93  coordinator_cameras: SynologyDSMCameraUpdateCoordinator | None = None
94  if api.surveillance_station is not None:
95  coordinator_cameras = SynologyDSMCameraUpdateCoordinator(hass, entry, api)
96  await coordinator_cameras.async_config_entry_first_refresh()
97 
98  coordinator_switches: SynologyDSMSwitchUpdateCoordinator | None = None
99  if (
100  SynoSurveillanceStation.INFO_API_KEY in available_apis
101  and SynoSurveillanceStation.HOME_MODE_API_KEY in available_apis
102  and api.surveillance_station is not None
103  ):
104  coordinator_switches = SynologyDSMSwitchUpdateCoordinator(hass, entry, api)
105  await coordinator_switches.async_config_entry_first_refresh()
106  try:
107  await coordinator_switches.async_setup()
108  except SYNOLOGY_CONNECTION_EXCEPTIONS as ex:
109  raise ConfigEntryNotReady from ex
110 
111  synology_data = SynologyDSMData(
112  api=api,
113  coordinator_central=coordinator_central,
114  coordinator_cameras=coordinator_cameras,
115  coordinator_switches=coordinator_switches,
116  )
117  hass.data.setdefault(DOMAIN, {})[entry.unique_id] = synology_data
118  await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
119  entry.async_on_unload(entry.add_update_listener(_async_update_listener))
120 
121  return True
122 
123 
124 async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
125  """Unload Synology DSM sensors."""
126  if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
127  entry_data: SynologyDSMData = hass.data[DOMAIN][entry.unique_id]
128  await entry_data.api.async_unload()
129  hass.data[DOMAIN].pop(entry.unique_id)
130  return unload_ok
131 
132 
133 async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
134  """Handle options update."""
135  await hass.config_entries.async_reload(entry.entry_id)
136 
137 
139  hass: HomeAssistant, entry: ConfigEntry, device_entry: dr.DeviceEntry
140 ) -> bool:
141  """Remove synology_dsm config entry from a device."""
142  data: SynologyDSMData = hass.data[DOMAIN][entry.unique_id]
143  api = data.api
144  assert api.information is not None
145  serial = api.information.serial
146  storage = api.storage
147  assert storage is not None
148  all_cameras: list[SynoCamera] = []
149  if api.surveillance_station is not None:
150  # get_all_cameras does not do I/O
151  all_cameras = api.surveillance_station.get_all_cameras()
152  device_ids = chain(
153  (camera.id for camera in all_cameras),
154  storage.volumes_ids,
155  storage.disks_ids,
156  storage.volumes_ids,
157  (SynoSurveillanceStation.INFO_API_KEY,), # Camera home/away
158  )
159  return not device_entry.identifiers.intersection(
160  (
161  (DOMAIN, serial), # Base device
162  *(
163  (DOMAIN, f"{serial}_{device_id}") for device_id in device_ids
164  ), # Storage and cameras
165  )
166  )
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 raise_config_entry_auth_error(Exception err)
Definition: common.py:345
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:124
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:39
bool async_remove_config_entry_device(HomeAssistant hass, ConfigEntry entry, dr.DeviceEntry device_entry)
Definition: __init__.py:140
None _async_update_listener(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:133