Replace Synology DSM services with buttons (#57352)
This commit is contained in:
parent
19b7454161
commit
5d7d652237
7 changed files with 445 additions and 303 deletions
|
@ -1102,9 +1102,12 @@ omit =
|
|||
homeassistant/components/synology_chat/notify.py
|
||||
homeassistant/components/synology_dsm/__init__.py
|
||||
homeassistant/components/synology_dsm/binary_sensor.py
|
||||
homeassistant/components/synology_dsm/button.py
|
||||
homeassistant/components/synology_dsm/camera.py
|
||||
homeassistant/components/synology_dsm/diagnostics.py
|
||||
homeassistant/components/synology_dsm/common.py
|
||||
homeassistant/components/synology_dsm/sensor.py
|
||||
homeassistant/components/synology_dsm/service.py
|
||||
homeassistant/components/synology_dsm/switch.py
|
||||
homeassistant/components/synology_srm/device_tracker.py
|
||||
homeassistant/components/syslog/notify.py
|
||||
|
|
|
@ -1,20 +1,11 @@
|
|||
"""The Synology DSM component."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import async_timeout
|
||||
from synology_dsm import SynologyDSM
|
||||
from synology_dsm.api.core.security import SynoCoreSecurity
|
||||
from synology_dsm.api.core.system import SynoCoreSystem
|
||||
from synology_dsm.api.core.upgrade import SynoCoreUpgrade
|
||||
from synology_dsm.api.core.utilization import SynoCoreUtilization
|
||||
from synology_dsm.api.dsm.information import SynoDSMInformation
|
||||
from synology_dsm.api.dsm.network import SynoDSMNetwork
|
||||
from synology_dsm.api.storage.storage import SynoStorage
|
||||
from synology_dsm.api.surveillance_station import SynoSurveillanceStation
|
||||
from synology_dsm.api.surveillance_station.camera import SynoCamera
|
||||
from synology_dsm.exceptions import (
|
||||
|
@ -28,18 +19,8 @@ from synology_dsm.exceptions import (
|
|||
)
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_MAC,
|
||||
CONF_PASSWORD,
|
||||
CONF_PORT,
|
||||
CONF_SCAN_INTERVAL,
|
||||
CONF_SSL,
|
||||
CONF_TIMEOUT,
|
||||
CONF_USERNAME,
|
||||
CONF_VERIFY_SSL,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||
from homeassistant.const import CONF_MAC, CONF_SCAN_INTERVAL, CONF_VERIFY_SSL
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers import device_registry
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
@ -54,9 +35,8 @@ from homeassistant.helpers.update_coordinator import (
|
|||
UpdateFailed,
|
||||
)
|
||||
|
||||
from .common import SynoApi
|
||||
from .const import (
|
||||
CONF_DEVICE_TOKEN,
|
||||
CONF_SERIAL,
|
||||
COORDINATOR_CAMERAS,
|
||||
COORDINATOR_CENTRAL,
|
||||
COORDINATOR_SWITCHES,
|
||||
|
@ -66,14 +46,12 @@ from .const import (
|
|||
EXCEPTION_DETAILS,
|
||||
EXCEPTION_UNKNOWN,
|
||||
PLATFORMS,
|
||||
SERVICE_REBOOT,
|
||||
SERVICE_SHUTDOWN,
|
||||
SERVICES,
|
||||
SYNO_API,
|
||||
SYSTEM_LOADED,
|
||||
UNDO_UPDATE_LISTENER,
|
||||
SynologyDSMEntityDescription,
|
||||
)
|
||||
from .service import async_setup_services
|
||||
|
||||
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
|
||||
|
||||
|
@ -141,7 +119,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
}
|
||||
|
||||
# Services
|
||||
await _async_setup_services(hass)
|
||||
await async_setup_services(hass)
|
||||
|
||||
# For SSDP compat
|
||||
if not entry.data.get(CONF_MAC):
|
||||
|
@ -249,279 +227,6 @@ async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> Non
|
|||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
||||
|
||||
async def _async_setup_services(hass: HomeAssistant) -> None:
|
||||
"""Service handler setup."""
|
||||
|
||||
async def service_handler(call: ServiceCall) -> None:
|
||||
"""Handle service call."""
|
||||
serial = call.data.get(CONF_SERIAL)
|
||||
dsm_devices = hass.data[DOMAIN]
|
||||
|
||||
if serial:
|
||||
dsm_device = dsm_devices.get(serial)
|
||||
elif len(dsm_devices) == 1:
|
||||
dsm_device = next(iter(dsm_devices.values()))
|
||||
serial = next(iter(dsm_devices))
|
||||
else:
|
||||
_LOGGER.error(
|
||||
"More than one DSM configured, must specify one of serials %s",
|
||||
sorted(dsm_devices),
|
||||
)
|
||||
return
|
||||
|
||||
if not dsm_device:
|
||||
_LOGGER.error("DSM with specified serial %s not found", serial)
|
||||
return
|
||||
|
||||
_LOGGER.debug("%s DSM with serial %s", call.service, serial)
|
||||
dsm_api = dsm_device[SYNO_API]
|
||||
dsm_device[SYSTEM_LOADED] = False
|
||||
if call.service == SERVICE_REBOOT:
|
||||
await dsm_api.async_reboot()
|
||||
elif call.service == SERVICE_SHUTDOWN:
|
||||
await dsm_api.async_shutdown()
|
||||
|
||||
for service in SERVICES:
|
||||
hass.services.async_register(DOMAIN, service, service_handler)
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
class SynologyDSMBaseEntity(CoordinatorEntity):
|
||||
"""Representation of a Synology NAS entry."""
|
||||
|
||||
|
|
96
homeassistant/components/synology_dsm/button.py
Normal file
96
homeassistant/components/synology_dsm/button.py
Normal file
|
@ -0,0 +1,96 @@
|
|||
"""Support for Synology DSM buttons."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import Any, Final
|
||||
|
||||
from homeassistant.components.button import (
|
||||
ButtonDeviceClass,
|
||||
ButtonEntity,
|
||||
ButtonEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity import DeviceInfo, EntityCategory
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import SynoApi
|
||||
from .const import DOMAIN, SYNO_API
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SynologyDSMbuttonDescriptionMixin:
|
||||
"""Mixin to describe a Synology DSM button entity."""
|
||||
|
||||
press_action: Callable[[SynoApi], Any]
|
||||
|
||||
|
||||
@dataclass
|
||||
class SynologyDSMbuttonDescription(
|
||||
ButtonEntityDescription, SynologyDSMbuttonDescriptionMixin
|
||||
):
|
||||
"""Class to describe a Synology DSM button entity."""
|
||||
|
||||
|
||||
BUTTONS: Final = [
|
||||
SynologyDSMbuttonDescription(
|
||||
key="reboot",
|
||||
name="Reboot",
|
||||
device_class=ButtonDeviceClass.RESTART,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
press_action=lambda syno_api: syno_api.async_reboot(),
|
||||
),
|
||||
SynologyDSMbuttonDescription(
|
||||
key="shutdown",
|
||||
name="Shutdown",
|
||||
icon="mdi:power",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
press_action=lambda syno_api: syno_api.async_shutdown(),
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set buttons for device."""
|
||||
data = hass.data[DOMAIN][entry.unique_id]
|
||||
syno_api: SynoApi = data[SYNO_API]
|
||||
|
||||
async_add_entities(SynologyDSMButton(syno_api, button) for button in BUTTONS)
|
||||
|
||||
|
||||
class SynologyDSMButton(ButtonEntity):
|
||||
"""Defines a Synology DSM button."""
|
||||
|
||||
entity_description: SynologyDSMbuttonDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api: SynoApi,
|
||||
description: SynologyDSMbuttonDescription,
|
||||
) -> None:
|
||||
"""Initialize the Synology DSM binary_sensor entity."""
|
||||
self.entity_description = description
|
||||
self.syno_api = api
|
||||
|
||||
self._attr_name = f"{api.network.hostname} {description.name}"
|
||||
self._attr_unique_id = f"{api.information.serial}_{description.key}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, api.information.serial)}
|
||||
)
|
||||
|
||||
async def async_press(self) -> None:
|
||||
"""Triggers the Synology DSM button press service."""
|
||||
LOGGER.debug(
|
||||
"Trigger %s for %s",
|
||||
self.entity_description.key,
|
||||
self.syno_api.network.hostname,
|
||||
)
|
||||
await self.entity_description.press_action(self.syno_api)
|
260
homeassistant/components/synology_dsm/common.py
Normal file
260
homeassistant/components/synology_dsm/common.py
Normal file
|
@ -0,0 +1,260 @@
|
|||
"""The Synology DSM component."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from synology_dsm import SynologyDSM
|
||||
from synology_dsm.api.core.security import SynoCoreSecurity
|
||||
from synology_dsm.api.core.system import SynoCoreSystem
|
||||
from synology_dsm.api.core.upgrade import SynoCoreUpgrade
|
||||
from synology_dsm.api.core.utilization import SynoCoreUtilization
|
||||
from synology_dsm.api.dsm.information import SynoDSMInformation
|
||||
from synology_dsm.api.dsm.network import SynoDSMNetwork
|
||||
from synology_dsm.api.storage.storage import SynoStorage
|
||||
from synology_dsm.api.surveillance_station import SynoSurveillanceStation
|
||||
from synology_dsm.exceptions import (
|
||||
SynologyDSMAPIErrorException,
|
||||
SynologyDSMLoginFailedException,
|
||||
SynologyDSMRequestException,
|
||||
)
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_PASSWORD,
|
||||
CONF_PORT,
|
||||
CONF_SSL,
|
||||
CONF_TIMEOUT,
|
||||
CONF_USERNAME,
|
||||
CONF_VERIFY_SSL,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
|
||||
from .const import CONF_DEVICE_TOKEN
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
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]}"
|
||||
|
||||
# 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()
|
||||
|
||||
@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 _syno_api_executer(self, api_call: Callable) -> None:
|
||||
"""Synology api call wrapper."""
|
||||
try:
|
||||
await self._hass.async_add_executor_job(api_call)
|
||||
except (SynologyDSMAPIErrorException, SynologyDSMRequestException) as err:
|
||||
LOGGER.debug(
|
||||
"Error from '%s': %s", self._entry.unique_id, err, exc_info=True
|
||||
)
|
||||
raise err
|
||||
|
||||
async def async_reboot(self) -> None:
|
||||
"""Reboot NAS."""
|
||||
await self._syno_api_executer(self.system.reboot)
|
||||
|
||||
async def async_shutdown(self) -> None:
|
||||
"""Shutdown NAS."""
|
||||
await self._syno_api_executer(self.system.shutdown)
|
||||
|
||||
async def async_unload(self) -> None:
|
||||
"""Stop interacting with the NAS and prepare for removal from hass."""
|
||||
await self._syno_api_executer(self.dsm.logout)
|
||||
|
||||
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:
|
||||
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
|
|
@ -32,7 +32,13 @@ from homeassistant.const import (
|
|||
from homeassistant.helpers.entity import EntityCategory, EntityDescription
|
||||
|
||||
DOMAIN = "synology_dsm"
|
||||
PLATFORMS = [Platform.BINARY_SENSOR, Platform.CAMERA, Platform.SENSOR, Platform.SWITCH]
|
||||
PLATFORMS = [
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.BUTTON,
|
||||
Platform.CAMERA,
|
||||
Platform.SENSOR,
|
||||
Platform.SWITCH,
|
||||
]
|
||||
COORDINATOR_CAMERAS = "coordinator_cameras"
|
||||
COORDINATOR_CENTRAL = "coordinator_central"
|
||||
COORDINATOR_SWITCHES = "coordinator_switches"
|
||||
|
|
72
homeassistant/components/synology_dsm/service.py
Normal file
72
homeassistant/components/synology_dsm/service.py
Normal file
|
@ -0,0 +1,72 @@
|
|||
"""The Synology DSM component."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from synology_dsm.exceptions import SynologyDSMException
|
||||
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
|
||||
from .common import SynoApi
|
||||
from .const import (
|
||||
CONF_SERIAL,
|
||||
DOMAIN,
|
||||
SERVICE_REBOOT,
|
||||
SERVICE_SHUTDOWN,
|
||||
SERVICES,
|
||||
SYNO_API,
|
||||
SYSTEM_LOADED,
|
||||
)
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_services(hass: HomeAssistant) -> None:
|
||||
"""Service handler setup."""
|
||||
|
||||
async def service_handler(call: ServiceCall) -> None:
|
||||
"""Handle service call."""
|
||||
serial = call.data.get(CONF_SERIAL)
|
||||
dsm_devices = hass.data[DOMAIN]
|
||||
|
||||
if serial:
|
||||
dsm_device = dsm_devices.get(serial)
|
||||
elif len(dsm_devices) == 1:
|
||||
dsm_device = next(iter(dsm_devices.values()))
|
||||
serial = next(iter(dsm_devices))
|
||||
else:
|
||||
LOGGER.error(
|
||||
"More than one DSM configured, must specify one of serials %s",
|
||||
sorted(dsm_devices),
|
||||
)
|
||||
return
|
||||
|
||||
if not dsm_device:
|
||||
LOGGER.error("DSM with specified serial %s not found", serial)
|
||||
return
|
||||
|
||||
if call.service in [SERVICE_REBOOT, SERVICE_SHUTDOWN]:
|
||||
dsm_device = hass.data[DOMAIN].get(serial)
|
||||
if not dsm_device:
|
||||
LOGGER.error("DSM with specified serial %s not found", serial)
|
||||
return
|
||||
LOGGER.debug("%s DSM with serial %s", call.service, serial)
|
||||
LOGGER.warning(
|
||||
"The %s service is deprecated and will be removed in future release. Please use the corresponding button entity",
|
||||
call.service,
|
||||
)
|
||||
dsm_api: SynoApi = dsm_device[SYNO_API]
|
||||
try:
|
||||
dsm_device[SYSTEM_LOADED] = False
|
||||
await getattr(dsm_api, f"async_{call.service}")()
|
||||
except SynologyDSMException as ex:
|
||||
LOGGER.error(
|
||||
"%s of DSM with serial %s not possible, because of %s",
|
||||
call.service,
|
||||
serial,
|
||||
ex,
|
||||
)
|
||||
return
|
||||
|
||||
for service in SERVICES:
|
||||
hass.services.async_register(DOMAIN, service, service_handler)
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
reboot:
|
||||
name: Reboot
|
||||
description: Reboot the NAS.
|
||||
description: Reboot the NAS. This service is deprecated and will be removed in future release. Please use the corresponding button entity.
|
||||
fields:
|
||||
serial:
|
||||
name: Serial
|
||||
|
@ -13,7 +13,7 @@ reboot:
|
|||
|
||||
shutdown:
|
||||
name: Shutdown
|
||||
description: Shutdown the NAS.
|
||||
description: Shutdown the NAS. This service is deprecated and will be removed in future release. Please use the corresponding button entity.
|
||||
fields:
|
||||
serial:
|
||||
name: Serial
|
||||
|
|
Loading…
Add table
Reference in a new issue