Esempio n. 1
0
    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)),
        )
Esempio n. 2
0
    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)),
        )
Esempio n. 3
0
    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
Esempio n. 4
0
    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
Esempio n. 5
0
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]
Esempio n. 6
0
    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)
Esempio n. 7
0
 def __init__(self, *args, **kwargs):
     super().__init__(*args, **kwargs)
     self._conn = SynologyDSM(
         self.config['host'],
         self.config['port'],
         self.config['username'],
         self.config['password'],
     )
Esempio n. 8
0
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
Esempio n. 9
0
    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)
Esempio n. 10
0
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
Esempio n. 11
0
    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
Esempio n. 12
0
    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)),
        )
Esempio n. 13
0
    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)
Esempio n. 14
0
    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)
Esempio n. 15
0
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)
Esempio n. 16
0
    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)
Esempio n. 17
0
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
Esempio n. 18
0
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
Esempio n. 19
0
    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)
Esempio n. 20
0
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)
Esempio n. 21
0
        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"])