1 """The Synology DSM component."""
3 from __future__
import annotations
6 from collections.abc
import Callable
7 from contextlib
import suppress
10 from synology_dsm
import SynologyDSM
11 from synology_dsm.api.core.security
import SynoCoreSecurity
12 from synology_dsm.api.core.system
import SynoCoreSystem
13 from synology_dsm.api.core.upgrade
import SynoCoreUpgrade
14 from synology_dsm.api.core.utilization
import SynoCoreUtilization
15 from synology_dsm.api.dsm.information
import SynoDSMInformation
16 from synology_dsm.api.dsm.network
import SynoDSMNetwork
17 from synology_dsm.api.photos
import SynoPhotos
18 from synology_dsm.api.storage.storage
import SynoStorage
19 from synology_dsm.api.surveillance_station
import SynoSurveillanceStation
20 from synology_dsm.exceptions
import (
21 SynologyDSMAPIErrorException,
23 SynologyDSMRequestException,
44 SYNOLOGY_CONNECTION_EXCEPTIONS,
47 LOGGER = logging.getLogger(__name__)
51 """Class to interface with Synology DSM API."""
55 def __init__(self, hass: HomeAssistant, entry: ConfigEntry) ->
None:
56 """Initialize the API wrapper class."""
59 if entry.data.get(CONF_SSL):
60 self.
config_urlconfig_url = f
"https://{entry.data[CONF_HOST]}:{entry.data[CONF_PORT]}"
62 self.
config_urlconfig_url = f
"http://{entry.data[CONF_HOST]}:{entry.data[CONF_PORT]}"
65 self.
informationinformation: SynoDSMInformation |
None =
None
66 self.
networknetwork: SynoDSMNetwork |
None =
None
67 self.
securitysecurity: SynoCoreSecurity |
None =
None
68 self.
storagestorage: SynoStorage |
None =
None
69 self.
photosphotos: SynoPhotos |
None =
None
71 self.
systemsystem: SynoCoreSystem |
None =
None
72 self.
upgradeupgrade: SynoCoreUpgrade |
None =
None
73 self.
utilisationutilisation: SynoCoreUtilization |
None =
None
76 self._fetching_entities: dict[str, set[str]] = {}
86 self.
_login_future_login_future: asyncio.Future[
None] |
None =
None
89 """Login to the Synology DSM API.
91 This function will only login once if called multiple times
92 by multiple different callers.
94 If a login is already in progress, the function will await the
95 login to complete before returning.
102 await self.
dsmdsm.login()
104 except BaseException
as err:
107 with suppress(BaseException):
117 """Start interacting with the NAS."""
119 self.
dsmdsm = SynologyDSM(
121 self.
_entry_entry.data[CONF_HOST],
122 self.
_entry_entry.data[CONF_PORT],
123 self.
_entry_entry.data[CONF_USERNAME],
124 self.
_entry_entry.data[CONF_PASSWORD],
125 self.
_entry_entry.data[CONF_SSL],
126 timeout=DEFAULT_TIMEOUT,
127 device_token=self.
_entry_entry.data.get(CONF_DEVICE_TOKEN),
133 self.
dsmdsm.apis.get(SynoSurveillanceStation.CAMERA_API_KEY)
137 await self.
dsmdsm.surveillance_station.update()
138 except SYNOLOGY_CONNECTION_EXCEPTIONS:
140 self.
dsmdsm.reset(SynoSurveillanceStation.API_KEY)
142 "Surveillance Station found, but disabled due to missing user"
147 "State of Surveillance_station during setup of '%s': %s",
148 self.
_entry_entry.unique_id,
154 await self.
dsmdsm.upgrade.update()
155 except SYNOLOGY_CONNECTION_EXCEPTIONS
as ex:
157 self.
dsmdsm.reset(SynoCoreUpgrade.API_KEY)
158 LOGGER.debug(
"Disabled fetching upgrade data during setup: %s", ex)
164 except SYNOLOGY_CONNECTION_EXCEPTIONS
as err:
166 "Connection error during setup of '%s' with exception: %s",
167 self.
_entry_entry.unique_id,
173 def subscribe(self, api_key: str, unique_id: str) -> Callable[[],
None]:
174 """Subscribe an entity to API fetches."""
175 LOGGER.debug(
"Subscribe new entity: %s", unique_id)
176 if api_key
not in self._fetching_entities:
177 self._fetching_entities[api_key] = set()
178 self._fetching_entities[api_key].
add(unique_id)
181 def unsubscribe() -> None:
182 """Unsubscribe an entity from API fetches (when disable)."""
183 LOGGER.debug(
"Unsubscribe entity: %s", unique_id)
184 self._fetching_entities[api_key].
remove(unique_id)
185 if len(self._fetching_entities[api_key]) == 0:
186 self._fetching_entities.pop(api_key)
191 """Determine if we should fetch each API, if one entity needs it."""
193 if not self._fetching_entities:
195 "Entities not added yet, fetch all for '%s'", self.
_entry_entry.unique_id
204 self.
_with_system_with_system = bool(self.
dsmdsm.apis.get(SynoCoreSystem.API_KEY))
206 self._fetching_entities.
get(SynoCoreSecurity.API_KEY)
208 self.
_with_storage_with_storage = bool(self._fetching_entities.
get(SynoStorage.API_KEY))
209 self.
_with_photos_with_photos = bool(self._fetching_entities.
get(SynoStorage.API_KEY))
210 self.
_with_upgrade_with_upgrade = bool(self._fetching_entities.
get(SynoCoreUpgrade.API_KEY))
212 self._fetching_entities.
get(SynoCoreUtilization.API_KEY)
215 self._fetching_entities.
get(SynoDSMInformation.API_KEY)
221 "Disable security api from being updated for '%s'",
222 self.
_entry_entry.unique_id,
230 "Disable photos api from being updated or '%s'", self.
_entry_entry.unique_id
238 "Disable storage api from being updatedf or '%s'", self.
_entry_entry.unique_id
246 "Disable system api from being updated for '%s'", self.
_entry_entry.unique_id
254 "Disable upgrade api from being updated for '%s'", self.
_entry_entry.unique_id
262 "Disable utilisation api from being updated for '%s'",
263 self.
_entry_entry.unique_id,
270 """Fetch initial device config."""
276 LOGGER.debug(
"Enable security api updates for '%s'", self.
_entry_entry.unique_id)
280 LOGGER.debug(
"Enable photos api updates for '%s'", self.
_entry_entry.unique_id)
284 LOGGER.debug(
"Enable storage api updates for '%s'", self.
_entry_entry.unique_id)
288 LOGGER.debug(
"Enable upgrade api updates for '%s'", self.
_entry_entry.unique_id)
292 LOGGER.debug(
"Enable system api updates for '%s'", self.
_entry_entry.unique_id)
297 "Enable utilisation api updates for '%s'", self.
_entry_entry.unique_id
303 "Enable surveillance_station api updates for '%s'",
304 self.
_entry_entry.unique_id,
309 """Synology api call wrapper."""
312 except (SynologyDSMAPIErrorException, SynologyDSMRequestException)
as err:
314 "Error from '%s': %s", self.
_entry_entry.unique_id, err, exc_info=
True
329 """Stop interacting with the NAS and prepare for removal from hass."""
331 with suppress(SynologyDSMException):
335 """Update function for updating API information."""
339 """Update function for updating API information."""
340 LOGGER.debug(
"Start data update for '%s'", self.
_entry_entry.unique_id)
346 """Raise ConfigEntryAuthFailed if error is related to authentication."""
347 if err.args[0]
and isinstance(err.args[0], dict):
348 details = err.args[0].
get(EXCEPTION_DETAILS, EXCEPTION_UNKNOWN)
350 details = EXCEPTION_UNKNOWN
_with_surveillance_station
None _setup_api_requests(self)
None _fetch_device_configuration(self)
None __init__(self, HomeAssistant hass, ConfigEntry entry)
Callable[[], None] subscribe(self, str api_key, str unique_id)
None _syno_api_executer(self, Callable api_call)
None async_shutdown(self)
bool add(self, _T matcher)
bool remove(self, _T matcher)
web.Response get(self, web.Request request, str config_key)
IssData update(pyiss.ISS iss)
None raise_config_entry_auth_error(Exception err)
aiohttp.ClientSession async_get_clientsession(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)