async def async_setup(self): """Start interacting with the NAS.""" # init SynologyDSM object and login self.dsm = SynologyDSM( self._entry.data[CONF_HOST], self._entry.data[CONF_PORT], self._entry.data[CONF_USERNAME], self._entry.data[CONF_PASSWORD], self._entry.data[CONF_SSL], self._entry.data[CONF_VERIFY_SSL], timeout=self._entry.options.get(CONF_TIMEOUT), device_token=self._entry.data.get("device_token"), ) await self._hass.async_add_executor_job(self.dsm.login) # check if surveillance station is used self._with_surveillance_station = bool( self.dsm.apis.get(SynoSurveillanceStation.CAMERA_API_KEY)) _LOGGER.debug( "SynoAPI.async_setup() - self._with_surveillance_station:%s", self._with_surveillance_station, ) self._async_setup_api_requests() await self._hass.async_add_executor_job( self._fetch_device_configuration) await self.async_update() self._unsub_dispatcher = async_track_time_interval( self._hass, self.async_update, timedelta(minutes=self._entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)), )
async def async_setup(self): """Start interacting with the NAS.""" self.dsm = SynologyDSM( self._entry.data[CONF_HOST], self._entry.data[CONF_PORT], self._entry.data[CONF_USERNAME], self._entry.data[CONF_PASSWORD], self._entry.data[CONF_SSL], timeout=self._entry.options.get(CONF_TIMEOUT), device_token=self._entry.data.get("device_token"), ) await self._hass.async_add_executor_job(self.dsm.discover_apis) self._with_surveillance_station = bool( self.dsm.apis.get(SynoSurveillanceStation.CAMERA_API_KEY)) self._async_setup_api_requests() await self._hass.async_add_executor_job( self._fetch_device_configuration) await self.async_update() self._unsub_dispatcher = async_track_time_interval( self._hass, self.async_update, timedelta(minutes=self._entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)), )
async def async_setup(self) -> None: """Start interacting with the NAS.""" self.dsm = SynologyDSM( self._entry.data[CONF_HOST], self._entry.data[CONF_PORT], self._entry.data[CONF_USERNAME], self._entry.data[CONF_PASSWORD], self._entry.data[CONF_SSL], self._entry.data[CONF_VERIFY_SSL], timeout=self._entry.options.get(CONF_TIMEOUT), device_token=self._entry.data.get(CONF_DEVICE_TOKEN), ) await self._hass.async_add_executor_job(self.dsm.login) # check if surveillance station is used self._with_surveillance_station = bool( self.dsm.apis.get(SynoSurveillanceStation.CAMERA_API_KEY) ) _LOGGER.debug( "State of Surveillance_station during setup of '%s': %s", self._entry.unique_id, self._with_surveillance_station, ) self._async_setup_api_requests() await self._hass.async_add_executor_job(self._fetch_device_configuration) await self.async_update() self.initialized = True
def __init__( self, dsm_ip, dsm_port, username, password, use_https=False, timeout=None, device_token=None, debugmode=False, ): SynologyDSM.__init__( self, dsm_ip, dsm_port, username, password, use_https, timeout, device_token, debugmode, ) self.dsm_version = 6 # 5 or 6 self.disks_redundancy = "RAID" # RAID or SHR[number][_EXPANSION] self.error = False self.with_surveillance = False
def _login_and_fetch_syno_info(api: SynologyDSM, otp_code: str) -> str: """Login to the NAS and fetch basic data.""" # These do i/o api.login(otp_code) api.utilisation.update() api.storage.update() api.network.update() if (not api.information.serial or api.utilisation.cpu_user_load is None or not api.storage.volumes_ids or not api.network.macs): raise InvalidData return api.information.serial # type: ignore[no-any-return]
async def async_step_user(self, user_input=None): """Handle a flow initiated by the user.""" errors = {} if user_input is None: return await self._show_setup_form(user_input, None) if self.discovered_conf: user_input.update(self.discovered_conf) host = user_input[CONF_HOST] port = user_input.get(CONF_PORT) username = user_input[CONF_USERNAME] password = user_input[CONF_PASSWORD] use_ssl = user_input.get(CONF_SSL, DEFAULT_SSL) api_version = user_input.get(CONF_API_VERSION, DEFAULT_DSM_VERSION) if not port: if use_ssl is True: port = DEFAULT_PORT_SSL else: port = DEFAULT_PORT api = SynologyDSM( host, port, username, password, use_ssl, dsm_version=api_version, ) try: serial = await self.hass.async_add_executor_job( _login_and_fetch_syno_info, api) except InvalidAuth: errors[CONF_USERNAME] = "login" except InvalidData: errors["base"] = "missing_data" if errors: return await self._show_setup_form(user_input, errors) # Check if already configured await self.async_set_unique_id(serial) self._abort_if_unique_id_configured() config_data = { CONF_HOST: host, CONF_PORT: port, CONF_SSL: use_ssl, CONF_USERNAME: username, CONF_PASSWORD: password, } if user_input.get(CONF_DISKS): config_data[CONF_DISKS] = user_input[CONF_DISKS] if user_input.get(CONF_VOLUMES): config_data[CONF_VOLUMES] = user_input[CONF_VOLUMES] return self.async_create_entry(title=host, data=config_data)
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._conn = SynologyDSM( self.config['host'], self.config['port'], self.config['username'], self.config['password'], )
def create_api_client(syno_config=None): print("Creating Valid API Client") if not syno_config: syno_config = SynoConfig() client = SynologyDSM(syno_config.ip, syno_config.port, syno_config.username, syno_config.password) return client
def connect(self): """ 連接 Synology NAS """ print('=====================================================') print('======== Connect to the remote NAS server ========') print('=====================================================') print('Time : {}\n'.format(strftime('%Y-%m-%d_%H_%M'))) self.api = SynologyDSM(self.host, self.port, self.user, self.password)
def get_synology_client(name, auth): log.debug(f"[synology] Get client for {name}") try: client = SynologyDSM(dsm_ip=auth["address"], dsm_port=auth["port"], username=auth["username"], password=auth["password"]) except Exception as e: log.error(f"[synology] Problem to retrieve client of {name}: {e}") exit(1) return client
def _setup(self) -> None: """Start interacting with the NAS in the executor.""" self.dsm = SynologyDSM( self._entry.data[CONF_HOST], self._entry.data[CONF_PORT], self._entry.data[CONF_USERNAME], self._entry.data[CONF_PASSWORD], self._entry.data[CONF_SSL], self._entry.data[CONF_VERIFY_SSL], timeout=self._entry.options.get(CONF_TIMEOUT), device_token=self._entry.data.get(CONF_DEVICE_TOKEN), ) self.dsm.login() # check if surveillance station is used self._with_surveillance_station = bool( self.dsm.apis.get(SynoSurveillanceStation.CAMERA_API_KEY)) LOGGER.debug( "State of Surveillance_station during setup of '%s': %s", self._entry.unique_id, self._with_surveillance_station, ) # check if upgrade is available try: self.dsm.upgrade.update() except SynologyDSMAPIErrorException as ex: self._with_upgrade = False LOGGER.debug("Disabled fetching upgrade data during setup: %s", ex) self._fetch_device_configuration() try: self._update() except SYNOLOGY_CONNECTION_EXCEPTIONS as err: LOGGER.debug( "Connection error during setup of '%s' with exception: %s", self._entry.unique_id, err, ) raise err
async def async_setup(self): """Start interacting with the NAS.""" self.dsm = SynologyDSM( self._entry.data[CONF_HOST], self._entry.data[CONF_PORT], self._entry.data[CONF_USERNAME], self._entry.data[CONF_PASSWORD], self._entry.data[CONF_SSL], device_token=self._entry.data.get("device_token"), ) self._async_setup_api_requests() await self._hass.async_add_executor_job( self._fetch_device_configuration) await self.async_update() self._unsub_dispatcher = async_track_time_interval( self._hass, self.async_update, timedelta(minutes=self._entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)), )
async def async_setup(self): """Start interacting with the NAS.""" self.dsm = SynologyDSM( self._host, self._port, self._username, self._password, self._use_ssl, device_token=self._device_token, ) await self._hass.async_add_executor_job( self._fetch_device_configuration) await self.update() self._unsub_dispatcher = async_track_time_interval( self._hass, self.update, SCAN_INTERVAL)
async def async_setup(self): """Start interacting with the NAS.""" self._dsm = SynologyDSM( self._host, self._port, self._username, self._password, self._use_ssl, dsm_version=self._api_version, ) self.information = self._dsm.information self.utilisation = self._dsm.utilisation self.storage = self._dsm.storage await self.update() self._unsub_dispatcher = async_track_time_interval( self._hass, self.update, SCAN_INTERVAL)
class SynoApi: """Class to interface with Synology DSM API.""" def __init__(self, hass: HomeAssistantType, entry: ConfigEntry): """Initialize the API wrapper class.""" self._hass = hass self._entry = entry # DSM APIs self.dsm: SynologyDSM = None self.information: SynoDSMInformation = None self.network: SynoDSMNetwork = None self.security: SynoCoreSecurity = None self.storage: SynoStorage = None self.utilisation: SynoCoreUtilization = None self.surveillance_station: SynoSurveillanceStation = None # Should we fetch them self._fetching_entities = {} self._with_security = True self._with_storage = True self._with_utilisation = True self._with_information = True self._with_surveillance_station = True self._unsub_dispatcher = None @property def signal_sensor_update(self) -> str: """Event specific per Synology DSM entry to signal updates in sensors.""" return f"{DOMAIN}-{self.information.serial}-sensor-update" async def async_setup(self): """Start interacting with the NAS.""" self.dsm = SynologyDSM( self._entry.data[CONF_HOST], self._entry.data[CONF_PORT], self._entry.data[CONF_USERNAME], self._entry.data[CONF_PASSWORD], self._entry.data[CONF_SSL], timeout=self._entry.options.get(CONF_TIMEOUT), device_token=self._entry.data.get("device_token"), ) await self._hass.async_add_executor_job(self.dsm.discover_apis) self._with_surveillance_station = bool( self.dsm.apis.get(SynoSurveillanceStation.CAMERA_API_KEY)) self._async_setup_api_requests() await self._hass.async_add_executor_job( self._fetch_device_configuration) await self.async_update() self._unsub_dispatcher = async_track_time_interval( self._hass, self.async_update, timedelta(minutes=self._entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)), ) @callback def subscribe(self, api_key, unique_id): """Subscribe an entity from API fetches.""" if api_key not in self._fetching_entities: self._fetching_entities[api_key] = set() self._fetching_entities[api_key].add(unique_id) @callback def unsubscribe() -> None: """Unsubscribe an entity from API fetches (when disable).""" self._fetching_entities[api_key].remove(unique_id) return unsubscribe @callback def _async_setup_api_requests(self): """Determine if we should fetch each API, if one entity needs it.""" # Entities not added yet, fetch all if not self._fetching_entities: return # Determine if we should fetch an API self._with_security = bool( self._fetching_entities.get(SynoCoreSecurity.API_KEY)) self._with_storage = bool( self._fetching_entities.get(SynoStorage.API_KEY)) self._with_utilisation = bool( self._fetching_entities.get(SynoCoreUtilization.API_KEY)) self._with_information = bool( self._fetching_entities.get(SynoDSMInformation.API_KEY)) self._with_surveillance_station = bool( self._fetching_entities.get( SynoSurveillanceStation.CAMERA_API_KEY)) # Reset not used API, information is not reset since it's used in device_info if not self._with_security: self.dsm.reset(self.security) self.security = None if not self._with_storage: self.dsm.reset(self.storage) self.storage = None if not self._with_utilisation: self.dsm.reset(self.utilisation) self.utilisation = None if not self._with_surveillance_station: self.dsm.reset(self.surveillance_station) self.surveillance_station = None def _fetch_device_configuration(self): """Fetch initial device config.""" self.information = self.dsm.information self.information.update() self.network = self.dsm.network self.network.update() if self._with_security: self.security = self.dsm.security if self._with_storage: self.storage = self.dsm.storage if self._with_utilisation: self.utilisation = self.dsm.utilisation if self._with_surveillance_station: self.surveillance_station = self.dsm.surveillance_station async def async_unload(self): """Stop interacting with the NAS and prepare for removal from hass.""" self._unsub_dispatcher() async def async_update(self, now=None): """Update function for updating API information.""" self._async_setup_api_requests() await self._hass.async_add_executor_job(self.dsm.update, self._with_information) async_dispatcher_send(self._hass, self.signal_sensor_update)
async def async_validate_input_create_entry(self, user_input: dict[str, Any], step_id: str) -> FlowResult: """Process user input and create new or update existing config entry.""" host = user_input[CONF_HOST] port = user_input.get(CONF_PORT) username = user_input[CONF_USERNAME] password = user_input[CONF_PASSWORD] use_ssl = user_input.get(CONF_SSL, DEFAULT_USE_SSL) verify_ssl = user_input.get(CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL) otp_code = user_input.get(CONF_OTP_CODE) if not port: if use_ssl is True: port = DEFAULT_PORT_SSL else: port = DEFAULT_PORT api = SynologyDSM(host, port, username, password, use_ssl, verify_ssl, timeout=30) errors = {} try: serial = await self.hass.async_add_executor_job( _login_and_fetch_syno_info, api, otp_code) except SynologyDSMLogin2SARequiredException: return await self.async_step_2sa(user_input) except SynologyDSMLogin2SAFailedException: errors[CONF_OTP_CODE] = "otp_failed" user_input[CONF_OTP_CODE] = None return await self.async_step_2sa(user_input, errors) except SynologyDSMLoginInvalidException as ex: _LOGGER.error(ex) errors[CONF_USERNAME] = "invalid_auth" except SynologyDSMRequestException as ex: _LOGGER.error(ex) errors[CONF_HOST] = "cannot_connect" except SynologyDSMException as ex: _LOGGER.error(ex) errors["base"] = "unknown" except InvalidData: errors["base"] = "missing_data" if errors: return self._show_form(step_id, user_input, errors) # unique_id should be serial for services purpose existing_entry = await self.async_set_unique_id( serial, raise_on_progress=False) config_data = { CONF_HOST: host, CONF_PORT: port, CONF_SSL: use_ssl, CONF_VERIFY_SSL: verify_ssl, CONF_USERNAME: username, CONF_PASSWORD: password, CONF_MAC: api.network.macs, } if otp_code: config_data[CONF_DEVICE_TOKEN] = api.device_token if user_input.get(CONF_DISKS): config_data[CONF_DISKS] = user_input[CONF_DISKS] if user_input.get(CONF_VOLUMES): config_data[CONF_VOLUMES] = user_input[CONF_VOLUMES] if existing_entry: self.hass.config_entries.async_update_entry(existing_entry, data=config_data) await self.hass.config_entries.async_reload(existing_entry.entry_id ) if self.reauth_conf: return self.async_abort(reason="reauth_successful") return self.async_abort(reason="reconfigure_successful") return self.async_create_entry(title=host, data=config_data)
class SynoApi: """Class to interface with Synology DSM API.""" def __init__(self, hass: HomeAssistantType, entry: ConfigEntry): """Initialize the API wrapper class.""" self._hass = hass self._entry = entry # DSM APIs self.dsm: SynologyDSM = None self.information: SynoDSMInformation = None self.network: SynoDSMNetwork = None self.security: SynoCoreSecurity = None self.storage: SynoStorage = None self.surveillance_station: SynoSurveillanceStation = None self.system: SynoCoreSystem = None self.upgrade: SynoCoreUpgrade = None self.utilisation: SynoCoreUtilization = None # Should we fetch them self._fetching_entities = {} self._with_information = True self._with_security = True self._with_storage = True self._with_surveillance_station = True self._with_system = True self._with_upgrade = True self._with_utilisation = True async def async_setup(self): """Start interacting with the NAS.""" self.dsm = SynologyDSM( self._entry.data[CONF_HOST], self._entry.data[CONF_PORT], self._entry.data[CONF_USERNAME], self._entry.data[CONF_PASSWORD], self._entry.data[CONF_SSL], self._entry.data[CONF_VERIFY_SSL], timeout=self._entry.options.get(CONF_TIMEOUT), device_token=self._entry.data.get(CONF_DEVICE_TOKEN), ) await self._hass.async_add_executor_job(self.dsm.login) # check if surveillance station is used self._with_surveillance_station = bool( self.dsm.apis.get(SynoSurveillanceStation.CAMERA_API_KEY) ) _LOGGER.debug( "SynoAPI.async_setup() - self._with_surveillance_station:%s", self._with_surveillance_station, ) self._setup_api_requests() await self._hass.async_add_executor_job(self._fetch_device_configuration) await self.async_update() @callback def subscribe(self, api_key, unique_id): """Subscribe an entity to API fetches.""" _LOGGER.debug( "SynoAPI.subscribe() - api_key:%s, unique_id:%s", api_key, unique_id ) if api_key not in self._fetching_entities: self._fetching_entities[api_key] = set() self._fetching_entities[api_key].add(unique_id) @callback def unsubscribe() -> None: """Unsubscribe an entity from API fetches (when disable).""" _LOGGER.debug( "SynoAPI.unsubscribe() - api_key:%s, unique_id:%s", api_key, unique_id ) self._fetching_entities[api_key].remove(unique_id) if len(self._fetching_entities[api_key]) == 0: self._fetching_entities.pop(api_key) return unsubscribe @callback def _setup_api_requests(self): """Determine if we should fetch each API, if one entity needs it.""" # Entities not added yet, fetch all if not self._fetching_entities: _LOGGER.debug( "SynoAPI._setup_api_requests() - Entities not added yet, fetch all" ) return # Determine if we should fetch an API self._with_system = bool(self.dsm.apis.get(SynoCoreSystem.API_KEY)) self._with_surveillance_station = bool( self.dsm.apis.get(SynoSurveillanceStation.CAMERA_API_KEY) ) or bool(self.dsm.apis.get(SynoSurveillanceStation.HOME_MODE_API_KEY)) self._with_security = bool( self._fetching_entities.get(SynoCoreSecurity.API_KEY) ) self._with_storage = bool(self._fetching_entities.get(SynoStorage.API_KEY)) self._with_upgrade = bool(self._fetching_entities.get(SynoCoreUpgrade.API_KEY)) self._with_utilisation = bool( self._fetching_entities.get(SynoCoreUtilization.API_KEY) ) self._with_information = bool( self._fetching_entities.get(SynoDSMInformation.API_KEY) ) # Reset not used API, information is not reset since it's used in device_info if not self._with_security: _LOGGER.debug("SynoAPI._setup_api_requests() - disable security") self.dsm.reset(self.security) self.security = None if not self._with_storage: _LOGGER.debug("SynoAPI._setup_api_requests() - disable storage") self.dsm.reset(self.storage) self.storage = None if not self._with_system: _LOGGER.debug("SynoAPI._setup_api_requests() - disable system") self.dsm.reset(self.system) self.system = None if not self._with_upgrade: _LOGGER.debug("SynoAPI._setup_api_requests() - disable upgrade") self.dsm.reset(self.upgrade) self.upgrade = None if not self._with_utilisation: _LOGGER.debug("SynoAPI._setup_api_requests() - disable utilisation") self.dsm.reset(self.utilisation) self.utilisation = None if not self._with_surveillance_station: _LOGGER.debug( "SynoAPI._setup_api_requests() - disable surveillance_station" ) self.dsm.reset(self.surveillance_station) self.surveillance_station = None def _fetch_device_configuration(self): """Fetch initial device config.""" self.information = self.dsm.information self.network = self.dsm.network self.network.update() if self._with_security: _LOGGER.debug("SynoAPI._fetch_device_configuration() - fetch security") self.security = self.dsm.security if self._with_storage: _LOGGER.debug("SynoAPI._fetch_device_configuration() - fetch storage") self.storage = self.dsm.storage if self._with_upgrade: _LOGGER.debug("SynoAPI._fetch_device_configuration() - fetch upgrade") self.upgrade = self.dsm.upgrade if self._with_system: _LOGGER.debug("SynoAPI._fetch_device_configuration() - fetch system") self.system = self.dsm.system if self._with_utilisation: _LOGGER.debug("SynoAPI._fetch_device_configuration() - fetch utilisation") self.utilisation = self.dsm.utilisation if self._with_surveillance_station: _LOGGER.debug( "SynoAPI._fetch_device_configuration() - fetch surveillance_station" ) self.surveillance_station = self.dsm.surveillance_station async def async_reboot(self): """Reboot NAS.""" try: await self._hass.async_add_executor_job(self.system.reboot) except (SynologyDSMLoginFailedException, SynologyDSMRequestException) as err: _LOGGER.error("Reboot not possible, please try again later") _LOGGER.debug("Exception:%s", err) async def async_shutdown(self): """Shutdown NAS.""" try: await self._hass.async_add_executor_job(self.system.shutdown) except (SynologyDSMLoginFailedException, SynologyDSMRequestException) as err: _LOGGER.error("Shutdown not possible, please try again later") _LOGGER.debug("Exception:%s", err) async def async_unload(self): """Stop interacting with the NAS and prepare for removal from hass.""" try: await self._hass.async_add_executor_job(self.dsm.logout) except (SynologyDSMAPIErrorException, SynologyDSMRequestException) as err: _LOGGER.debug("Logout not possible:%s", err) async def async_update(self, now=None): """Update function for updating API information.""" _LOGGER.debug("SynoAPI.async_update()") self._setup_api_requests() try: await self._hass.async_add_executor_job( self.dsm.update, self._with_information ) except (SynologyDSMLoginFailedException, SynologyDSMRequestException) as err: _LOGGER.warning( "async_update - connection error during update, fallback by reloading the entry" ) _LOGGER.debug("SynoAPI.async_update() - exception: %s", err) await self._hass.config_entries.async_reload(self._entry.entry_id) return
class SynoApi: """Class to interface with Synology DSM API.""" def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: """Initialize the API wrapper class.""" self._hass = hass self._entry = entry if entry.data.get(CONF_SSL): self.config_url = f"https://{entry.data[CONF_HOST]}:{entry.data[CONF_PORT]}" else: self.config_url = f"http://{entry.data[CONF_HOST]}:{entry.data[CONF_PORT]}" self.initialized = False # DSM APIs self.dsm: SynologyDSM = None self.information: SynoDSMInformation = None self.network: SynoDSMNetwork = None self.security: SynoCoreSecurity = None self.storage: SynoStorage = None self.surveillance_station: SynoSurveillanceStation = None self.system: SynoCoreSystem = None self.upgrade: SynoCoreUpgrade = None self.utilisation: SynoCoreUtilization = None # Should we fetch them self._fetching_entities: dict[str, set[str]] = {} self._with_information = True self._with_security = True self._with_storage = True self._with_surveillance_station = True self._with_system = True self._with_upgrade = True self._with_utilisation = True async def async_setup(self) -> None: """Start interacting with the NAS.""" self.dsm = SynologyDSM( self._entry.data[CONF_HOST], self._entry.data[CONF_PORT], self._entry.data[CONF_USERNAME], self._entry.data[CONF_PASSWORD], self._entry.data[CONF_SSL], self._entry.data[CONF_VERIFY_SSL], timeout=self._entry.options.get(CONF_TIMEOUT), device_token=self._entry.data.get(CONF_DEVICE_TOKEN), ) await self._hass.async_add_executor_job(self.dsm.login) # check if surveillance station is used self._with_surveillance_station = bool( self.dsm.apis.get(SynoSurveillanceStation.CAMERA_API_KEY) ) _LOGGER.debug( "State of Surveillance_station during setup of '%s': %s", self._entry.unique_id, self._with_surveillance_station, ) self._async_setup_api_requests() await self._hass.async_add_executor_job(self._fetch_device_configuration) await self.async_update() self.initialized = True @callback def subscribe(self, api_key: str, unique_id: str) -> Callable[[], None]: """Subscribe an entity to API fetches.""" _LOGGER.debug("Subscribe new entity: %s", unique_id) if api_key not in self._fetching_entities: self._fetching_entities[api_key] = set() self._fetching_entities[api_key].add(unique_id) @callback def unsubscribe() -> None: """Unsubscribe an entity from API fetches (when disable).""" _LOGGER.debug("Unsubscribe entity: %s", unique_id) self._fetching_entities[api_key].remove(unique_id) if len(self._fetching_entities[api_key]) == 0: self._fetching_entities.pop(api_key) return unsubscribe @callback def _async_setup_api_requests(self) -> None: """Determine if we should fetch each API, if one entity needs it.""" # Entities not added yet, fetch all if not self._fetching_entities: _LOGGER.debug( "Entities not added yet, fetch all for '%s'", self._entry.unique_id ) return # surveillance_station is updated by own coordinator self.dsm.reset(self.surveillance_station) # Determine if we should fetch an API self._with_system = bool(self.dsm.apis.get(SynoCoreSystem.API_KEY)) self._with_security = bool( self._fetching_entities.get(SynoCoreSecurity.API_KEY) ) self._with_storage = bool(self._fetching_entities.get(SynoStorage.API_KEY)) self._with_upgrade = bool(self._fetching_entities.get(SynoCoreUpgrade.API_KEY)) self._with_utilisation = bool( self._fetching_entities.get(SynoCoreUtilization.API_KEY) ) self._with_information = bool( self._fetching_entities.get(SynoDSMInformation.API_KEY) ) # Reset not used API, information is not reset since it's used in device_info if not self._with_security: _LOGGER.debug( "Disable security api from being updated for '%s'", self._entry.unique_id, ) self.dsm.reset(self.security) self.security = None if not self._with_storage: _LOGGER.debug( "Disable storage api from being updatedf or '%s'", self._entry.unique_id ) self.dsm.reset(self.storage) self.storage = None if not self._with_system: _LOGGER.debug( "Disable system api from being updated for '%s'", self._entry.unique_id ) self.dsm.reset(self.system) self.system = None if not self._with_upgrade: _LOGGER.debug( "Disable upgrade api from being updated for '%s'", self._entry.unique_id ) self.dsm.reset(self.upgrade) self.upgrade = None if not self._with_utilisation: _LOGGER.debug( "Disable utilisation api from being updated for '%s'", self._entry.unique_id, ) self.dsm.reset(self.utilisation) self.utilisation = None def _fetch_device_configuration(self) -> None: """Fetch initial device config.""" self.information = self.dsm.information self.network = self.dsm.network self.network.update() if self._with_security: _LOGGER.debug("Enable security api updates for '%s'", self._entry.unique_id) self.security = self.dsm.security if self._with_storage: _LOGGER.debug("Enable storage api updates for '%s'", self._entry.unique_id) self.storage = self.dsm.storage if self._with_upgrade: _LOGGER.debug("Enable upgrade api updates for '%s'", self._entry.unique_id) self.upgrade = self.dsm.upgrade if self._with_system: _LOGGER.debug("Enable system api updates for '%s'", self._entry.unique_id) self.system = self.dsm.system if self._with_utilisation: _LOGGER.debug( "Enable utilisation api updates for '%s'", self._entry.unique_id ) self.utilisation = self.dsm.utilisation if self._with_surveillance_station: _LOGGER.debug( "Enable surveillance_station api updates for '%s'", self._entry.unique_id, ) self.surveillance_station = self.dsm.surveillance_station async def async_reboot(self) -> None: """Reboot NAS.""" try: await self._hass.async_add_executor_job(self.system.reboot) except (SynologyDSMLoginFailedException, SynologyDSMRequestException) as err: _LOGGER.error( "Reboot of '%s' not possible, please try again later", self._entry.unique_id, ) _LOGGER.debug("Exception:%s", err) async def async_shutdown(self) -> None: """Shutdown NAS.""" try: await self._hass.async_add_executor_job(self.system.shutdown) except (SynologyDSMLoginFailedException, SynologyDSMRequestException) as err: _LOGGER.error( "Shutdown of '%s' not possible, please try again later", self._entry.unique_id, ) _LOGGER.debug("Exception:%s", err) async def async_unload(self) -> None: """Stop interacting with the NAS and prepare for removal from hass.""" try: await self._hass.async_add_executor_job(self.dsm.logout) except (SynologyDSMAPIErrorException, SynologyDSMRequestException) as err: _LOGGER.debug( "Logout from '%s' not possible:%s", self._entry.unique_id, err ) async def async_update(self, now: timedelta | None = None) -> None: """Update function for updating API information.""" _LOGGER.debug("Start data update for '%s'", self._entry.unique_id) self._async_setup_api_requests() try: await self._hass.async_add_executor_job( self.dsm.update, self._with_information ) except (SynologyDSMLoginFailedException, SynologyDSMRequestException) as err: if not self.initialized: raise err _LOGGER.warning( "Connection error during update, fallback by reloading the entry" ) _LOGGER.debug( "Connection error during update of '%s' with exception: %s", self._entry.unique_id, err, ) await self._hass.config_entries.async_reload(self._entry.entry_id) return
async def async_step_user(self, user_input=None): """Handle a flow initiated by the user.""" errors = {} if user_input is None: return await self._show_setup_form(user_input, None) if self.discovered_conf: user_input.update(self.discovered_conf) host = user_input[CONF_HOST] port = user_input.get(CONF_PORT) username = user_input[CONF_USERNAME] password = user_input[CONF_PASSWORD] use_ssl = user_input.get(CONF_SSL, DEFAULT_SSL) otp_code = user_input.get(CONF_OTP_CODE) if not port: if use_ssl is True: port = DEFAULT_PORT_SSL else: port = DEFAULT_PORT api = SynologyDSM(host, port, username, password, use_ssl) try: serial = await self.hass.async_add_executor_job( _login_and_fetch_syno_info, api, otp_code ) except SynologyDSMLogin2SARequiredException: return await self.async_step_2sa(user_input) except SynologyDSMLogin2SAFailedException: errors[CONF_OTP_CODE] = "otp_failed" user_input[CONF_OTP_CODE] = None return await self.async_step_2sa(user_input, errors) except SynologyDSMLoginInvalidException as ex: _LOGGER.error(ex) errors[CONF_USERNAME] = "login" except SynologyDSMRequestException as ex: _LOGGER.error(ex) errors[CONF_HOST] = "connection" except SynologyDSMException as ex: _LOGGER.error(ex) errors["base"] = "unknown" except InvalidData: errors["base"] = "missing_data" if errors: return await self._show_setup_form(user_input, errors) # Check if already configured await self.async_set_unique_id(serial, raise_on_progress=False) self._abort_if_unique_id_configured() config_data = { CONF_HOST: host, CONF_PORT: port, CONF_SSL: use_ssl, CONF_USERNAME: username, CONF_PASSWORD: password, CONF_MAC: api.network.macs, } if otp_code: config_data["device_token"] = api.device_token if user_input.get(CONF_DISKS): config_data[CONF_DISKS] = user_input[CONF_DISKS] if user_input.get(CONF_VOLUMES): config_data[CONF_VOLUMES] = user_input[CONF_VOLUMES] return self.async_create_entry(title=host, data=config_data)
class SynoApi: """Class to interface with Synology DSM API.""" def __init__(self, hass: HomeAssistantType, entry: ConfigEntry): """Initialize the API wrapper class.""" self._hass = hass self._entry = entry # DSM APIs self.dsm: SynologyDSM = None self.information: SynoDSMInformation = None self.network: SynoDSMNetwork = None self.security: SynoCoreSecurity = None self.storage: SynoStorage = None self.surveillance_station: SynoSurveillanceStation = None self.system: SynoCoreSystem = None self.upgrade: SynoCoreUpgrade = None self.utilisation: SynoCoreUtilization = None # Should we fetch them self._fetching_entities = {} self._with_information = True self._with_security = True self._with_storage = True self._with_surveillance_station = True self._with_system = True self._with_upgrade = True self._with_utilisation = True self._unsub_dispatcher = None @property def signal_sensor_update(self) -> str: """Event specific per Synology DSM entry to signal updates in sensors.""" return f"{DOMAIN}-{self.information.serial}-sensor-update" async def async_setup(self): """Start interacting with the NAS.""" # init SynologyDSM object and login self.dsm = SynologyDSM( self._entry.data[CONF_HOST], self._entry.data[CONF_PORT], self._entry.data[CONF_USERNAME], self._entry.data[CONF_PASSWORD], self._entry.data[CONF_SSL], self._entry.data[CONF_VERIFY_SSL], timeout=self._entry.options.get(CONF_TIMEOUT), device_token=self._entry.data.get("device_token"), ) await self._hass.async_add_executor_job(self.dsm.login) # check if surveillance station is used self._with_surveillance_station = bool( self.dsm.apis.get(SynoSurveillanceStation.CAMERA_API_KEY)) _LOGGER.debug( "SynoAPI.async_setup() - self._with_surveillance_station:%s", self._with_surveillance_station, ) self._async_setup_api_requests() await self._hass.async_add_executor_job( self._fetch_device_configuration) await self.async_update() self._unsub_dispatcher = async_track_time_interval( self._hass, self.async_update, timedelta(minutes=self._entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)), ) @callback def subscribe(self, api_key, unique_id): """Subscribe an entity from API fetches.""" _LOGGER.debug("SynoAPI.subscribe() - api_key:%s, unique_id:%s", api_key, unique_id) if api_key not in self._fetching_entities: self._fetching_entities[api_key] = set() self._fetching_entities[api_key].add(unique_id) @callback def unsubscribe() -> None: """Unsubscribe an entity from API fetches (when disable).""" self._fetching_entities[api_key].remove(unique_id) return unsubscribe @callback def _async_setup_api_requests(self): """Determine if we should fetch each API, if one entity needs it.""" _LOGGER.debug( "SynoAPI._async_setup_api_requests() - self._fetching_entities:%s", self._fetching_entities, ) # Entities not added yet, fetch all if not self._fetching_entities: _LOGGER.debug( "SynoAPI._async_setup_api_requests() - Entities not added yet, fetch all" ) return # Determine if we should fetch an API self._with_security = bool( self._fetching_entities.get(SynoCoreSecurity.API_KEY)) self._with_storage = bool( self._fetching_entities.get(SynoStorage.API_KEY)) self._with_system = bool( self._fetching_entities.get(SynoCoreSystem.API_KEY)) self._with_upgrade = bool( self._fetching_entities.get(SynoCoreUpgrade.API_KEY)) self._with_utilisation = bool( self._fetching_entities.get(SynoCoreUtilization.API_KEY)) self._with_information = bool( self._fetching_entities.get(SynoDSMInformation.API_KEY)) self._with_surveillance_station = bool( self.dsm.apis.get(SynoSurveillanceStation.CAMERA_API_KEY)) # Reset not used API, information is not reset since it's used in device_info if not self._with_security: _LOGGER.debug( "SynoAPI._async_setup_api_requests() - disable security") self.dsm.reset(self.security) self.security = None if not self._with_storage: _LOGGER.debug( "SynoAPI._async_setup_api_requests() - disable storage") self.dsm.reset(self.storage) self.storage = None if not self._with_system: _LOGGER.debug( "SynoAPI._async_setup_api_requests() - disable system") self.dsm.reset(self.system) self.system = None if not self._with_upgrade: _LOGGER.debug( "SynoAPI._async_setup_api_requests() - disable upgrade") self.dsm.reset(self.upgrade) self.upgrade = None if not self._with_utilisation: _LOGGER.debug( "SynoAPI._async_setup_api_requests() - disable utilisation") self.dsm.reset(self.utilisation) self.utilisation = None if not self._with_surveillance_station: _LOGGER.debug( "SynoAPI._async_setup_api_requests() - disable surveillance_station" ) self.dsm.reset(self.surveillance_station) self.surveillance_station = None def _fetch_device_configuration(self): """Fetch initial device config.""" self.information = self.dsm.information self.network = self.dsm.network self.network.update() if self._with_security: _LOGGER.debug( "SynoAPI._fetch_device_configuration() - fetch security") self.security = self.dsm.security if self._with_storage: _LOGGER.debug( "SynoAPI._fetch_device_configuration() - fetch storage") self.storage = self.dsm.storage if self._with_upgrade: _LOGGER.debug( "SynoAPI._fetch_device_configuration() - fetch upgrade") self.upgrade = self.dsm.upgrade if self._with_system: _LOGGER.debug( "SynoAPI._fetch_device_configuration() - fetch system") self.system = self.dsm.system if self._with_utilisation: _LOGGER.debug( "SynoAPI._fetch_device_configuration() - fetch utilisation") self.utilisation = self.dsm.utilisation if self._with_surveillance_station: _LOGGER.debug( "SynoAPI._fetch_device_configuration() - fetch surveillance_station" ) self.surveillance_station = self.dsm.surveillance_station async def async_reboot(self): """Reboot NAS.""" if not self.system: _LOGGER.debug("SynoAPI.async_reboot() - System API not ready: %s", self) return await self._hass.async_add_executor_job(self.system.reboot) async def async_shutdown(self): """Shutdown NAS.""" if not self.system: _LOGGER.debug( "SynoAPI.async_shutdown() - System API not ready: %s", self) return await self._hass.async_add_executor_job(self.system.shutdown) async def async_unload(self): """Stop interacting with the NAS and prepare for removal from hass.""" self._unsub_dispatcher() async def async_update(self, now=None): """Update function for updating API information.""" _LOGGER.debug("SynoAPI.async_update()") self._async_setup_api_requests() try: await self._hass.async_add_executor_job(self.dsm.update, self._with_information) except (SynologyDSMLoginFailedException, SynologyDSMRequestException) as err: _LOGGER.warning( "async_update - connection error during update, fallback by reloading the entry" ) _LOGGER.debug("SynoAPI.async_update() - exception: %s", err) await self._hass.config_entries.async_reload(self._entry.entry_id) return async_dispatcher_send(self._hass, self.signal_sensor_update)
status = str(api.storage.disk_status(disk_id)) status_enum.labels(disk_id, disk_name).state(status) disk_temp = api.storage.disk_temp(disk_id) disk_temp_gauge.labels(disk_id, disk_name).set(disk_temp) if __name__ == '__main__': url = require_environmental_variable('SYNOLOGY_URL') port = require_environmental_variable('SYNOLOGY_PORT') usr = require_environmental_variable('SYNOLOGY_USER') password = require_environmental_variable('SYNOLOGY_PASSWORD') frequency = int(os.environ.get('FREQUENCY', 15)) api = SynologyDSM(url, port, usr, password, timeout=60) start_http_server(9999) set_static_info(api) temp_gauge = Gauge(metric("temperature"), "Temperature") uptime_gauge = Gauge(metric("uptime"), "Uptime") cpu_gauge = Gauge(metric("cpu_load"), "DSM version") memory_used_gauge = Gauge(metric("memory_used"), "Total memory used") memory_total_gauge = Gauge(metric("memory_total"), "Total memory") network_up_gauge = Gauge(metric("network_up"), "Network up") network_down_gauge = Gauge(metric("network_down"), "Network down") volume_status_enum = Enum(metric("volume_status"), "Status of volume", labelnames=["Volume_ID"], states=["normal"]) volume_size_gauge = Gauge(metric("volume_size"), "Size of volume", ["Volume_ID"])