Cleanup coordinators in synology_dsm (#73257)

Co-authored-by: Michael <35783820+mib1185@users.noreply.github.com>
pull/73293/head
J. Nick Koston 2022-06-09 10:22:16 -10:00 committed by GitHub
parent 1d6068fa09
commit 22daea27c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 287 additions and 233 deletions

View File

@ -1200,6 +1200,7 @@ omit =
homeassistant/components/synology_dsm/binary_sensor.py
homeassistant/components/synology_dsm/button.py
homeassistant/components/synology_dsm/camera.py
homeassistant/components/synology_dsm/coordinator.py
homeassistant/components/synology_dsm/diagnostics.py
homeassistant/components/synology_dsm/common.py
homeassistant/components/synology_dsm/entity.py

View File

@ -1,47 +1,32 @@
"""The Synology DSM component."""
from __future__ import annotations
from datetime import timedelta
import logging
from typing import Any
import async_timeout
from synology_dsm.api.surveillance_station import SynoSurveillanceStation
from synology_dsm.api.surveillance_station.camera import SynoCamera
from synology_dsm.exceptions import (
SynologyDSMAPIErrorException,
SynologyDSMLogin2SARequiredException,
SynologyDSMLoginDisabledAccountException,
SynologyDSMLoginFailedException,
SynologyDSMLoginInvalidException,
SynologyDSMLoginPermissionDeniedException,
SynologyDSMRequestException,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_MAC, CONF_SCAN_INTERVAL, CONF_VERIFY_SSL
from homeassistant.const import CONF_MAC, CONF_VERIFY_SSL
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv, device_registry as dr
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .common import SynoApi
from .const import (
COORDINATOR_CAMERAS,
COORDINATOR_CENTRAL,
COORDINATOR_SWITCHES,
DEFAULT_SCAN_INTERVAL,
DEFAULT_VERIFY_SSL,
DOMAIN,
EXCEPTION_DETAILS,
EXCEPTION_UNKNOWN,
PLATFORMS,
SIGNAL_CAMERA_SOURCE_CHANGED,
SYNO_API,
SYSTEM_LOADED,
UNDO_UPDATE_LISTENER,
SYNOLOGY_AUTH_FAILED_EXCEPTIONS,
SYNOLOGY_CONNECTION_EXCEPTIONS,
)
from .coordinator import (
SynologyDSMCameraUpdateCoordinator,
SynologyDSMCentralUpdateCoordinator,
SynologyDSMSwitchUpdateCoordinator,
)
from .models import SynologyDSMData
from .service import async_setup_services
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
@ -79,31 +64,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
api = SynoApi(hass, entry)
try:
await api.async_setup()
except (
SynologyDSMLogin2SARequiredException,
SynologyDSMLoginDisabledAccountException,
SynologyDSMLoginInvalidException,
SynologyDSMLoginPermissionDeniedException,
) as err:
except SYNOLOGY_AUTH_FAILED_EXCEPTIONS as err:
if err.args[0] and isinstance(err.args[0], dict):
details = err.args[0].get(EXCEPTION_DETAILS, EXCEPTION_UNKNOWN)
else:
details = EXCEPTION_UNKNOWN
raise ConfigEntryAuthFailed(f"reason: {details}") from err
except (SynologyDSMLoginFailedException, SynologyDSMRequestException) as err:
except SYNOLOGY_CONNECTION_EXCEPTIONS as err:
if err.args[0] and isinstance(err.args[0], dict):
details = err.args[0].get(EXCEPTION_DETAILS, EXCEPTION_UNKNOWN)
else:
details = EXCEPTION_UNKNOWN
raise ConfigEntryNotReady(details) from err
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.unique_id] = {
UNDO_UPDATE_LISTENER: entry.add_update_listener(_async_update_listener),
SYNO_API: api,
SYSTEM_LOADED: True,
}
# Services
await async_setup_services(hass)
@ -114,111 +87,50 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
entry, data={**entry.data, CONF_MAC: network.macs}
)
async def async_coordinator_update_data_cameras() -> dict[
str, dict[str, SynoCamera]
] | None:
"""Fetch all camera data from api."""
if not hass.data[DOMAIN][entry.unique_id][SYSTEM_LOADED]:
raise UpdateFailed("System not fully loaded")
# These all create executor jobs so we do not gather here
coordinator_central = SynologyDSMCentralUpdateCoordinator(hass, entry, api)
await coordinator_central.async_config_entry_first_refresh()
if SynoSurveillanceStation.CAMERA_API_KEY not in api.dsm.apis:
return None
available_apis = api.dsm.apis
surveillance_station = api.surveillance_station
current_data: dict[str, SynoCamera] = {
camera.id: camera for camera in surveillance_station.get_all_cameras()
}
# The central coordinator needs to be refreshed first since
# the next two rely on data from it
coordinator_cameras: SynologyDSMCameraUpdateCoordinator | None = None
if SynoSurveillanceStation.CAMERA_API_KEY in available_apis:
coordinator_cameras = SynologyDSMCameraUpdateCoordinator(hass, entry, api)
await coordinator_cameras.async_config_entry_first_refresh()
try:
async with async_timeout.timeout(30):
await hass.async_add_executor_job(surveillance_station.update)
except SynologyDSMAPIErrorException as err:
raise UpdateFailed(f"Error communicating with API: {err}") from err
new_data: dict[str, SynoCamera] = {
camera.id: camera for camera in surveillance_station.get_all_cameras()
}
for cam_id, cam_data_new in new_data.items():
coordinator_switches: SynologyDSMSwitchUpdateCoordinator | None = None
if (
(cam_data_current := current_data.get(cam_id)) is not None
and cam_data_current.live_view.rtsp != cam_data_new.live_view.rtsp
SynoSurveillanceStation.INFO_API_KEY in available_apis
and SynoSurveillanceStation.HOME_MODE_API_KEY in available_apis
):
async_dispatcher_send(
hass,
f"{SIGNAL_CAMERA_SOURCE_CHANGED}_{entry.entry_id}_{cam_id}",
cam_data_new.live_view.rtsp,
)
return {"cameras": new_data}
async def async_coordinator_update_data_central() -> None:
"""Fetch all device and sensor data from api."""
coordinator_switches = SynologyDSMSwitchUpdateCoordinator(hass, entry, api)
await coordinator_switches.async_config_entry_first_refresh()
try:
await api.async_update()
except Exception as err:
raise UpdateFailed(f"Error communicating with API: {err}") from err
return None
await coordinator_switches.async_setup()
except SYNOLOGY_CONNECTION_EXCEPTIONS as ex:
raise ConfigEntryNotReady from ex
async def async_coordinator_update_data_switches() -> dict[
str, dict[str, Any]
] | None:
"""Fetch all switch data from api."""
if not hass.data[DOMAIN][entry.unique_id][SYSTEM_LOADED]:
raise UpdateFailed("System not fully loaded")
if SynoSurveillanceStation.HOME_MODE_API_KEY not in api.dsm.apis:
return None
surveillance_station = api.surveillance_station
return {
"switches": {
"home_mode": await hass.async_add_executor_job(
surveillance_station.get_home_mode_status
synology_data = SynologyDSMData(
api=api,
coordinator_central=coordinator_central,
coordinator_cameras=coordinator_cameras,
coordinator_switches=coordinator_switches,
)
}
}
hass.data[DOMAIN][entry.unique_id][COORDINATOR_CAMERAS] = DataUpdateCoordinator(
hass,
_LOGGER,
name=f"{entry.unique_id}_cameras",
update_method=async_coordinator_update_data_cameras,
update_interval=timedelta(seconds=30),
)
hass.data[DOMAIN][entry.unique_id][COORDINATOR_CENTRAL] = DataUpdateCoordinator(
hass,
_LOGGER,
name=f"{entry.unique_id}_central",
update_method=async_coordinator_update_data_central,
update_interval=timedelta(
minutes=entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
),
)
hass.data[DOMAIN][entry.unique_id][COORDINATOR_SWITCHES] = DataUpdateCoordinator(
hass,
_LOGGER,
name=f"{entry.unique_id}_switches",
update_method=async_coordinator_update_data_switches,
update_interval=timedelta(seconds=30),
)
hass.data.setdefault(DOMAIN, {})[entry.unique_id] = synology_data
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
entry.async_on_unload(entry.add_update_listener(_async_update_listener))
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload Synology DSM sensors."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
entry_data = hass.data[DOMAIN][entry.unique_id]
entry_data[UNDO_UPDATE_LISTENER]()
await entry_data[SYNO_API].async_unload()
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
entry_data: SynologyDSMData = hass.data[DOMAIN][entry.unique_id]
await entry_data.api.async_unload()
hass.data[DOMAIN].pop(entry.unique_id)
return unload_ok

View File

@ -22,12 +22,13 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from . import SynoApi
from .const import COORDINATOR_CENTRAL, DOMAIN, SYNO_API
from .const import DOMAIN
from .entity import (
SynologyDSMBaseEntity,
SynologyDSMDeviceEntity,
SynologyDSMEntityDescription,
)
from .models import SynologyDSMData
@dataclass
@ -80,10 +81,9 @@ async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the Synology NAS binary sensor."""
data = hass.data[DOMAIN][entry.unique_id]
api: SynoApi = data[SYNO_API]
coordinator = data[COORDINATOR_CENTRAL]
data: SynologyDSMData = hass.data[DOMAIN][entry.unique_id]
api = data.api
coordinator = data.coordinator_central
entities: list[
SynoDSMSecurityBinarySensor

View File

@ -17,7 +17,8 @@ from homeassistant.helpers.entity import DeviceInfo, EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import SynoApi
from .const import DOMAIN, SYNO_API
from .const import DOMAIN
from .models import SynologyDSMData
LOGGER = logging.getLogger(__name__)
@ -60,10 +61,8 @@ async def async_setup_entry(
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)
data: SynologyDSMData = hass.data[DOMAIN][entry.unique_id]
async_add_entities(SynologyDSMButton(data.api, button) for button in BUTTONS)
class SynologyDSMButton(ButtonEntity):

View File

@ -25,13 +25,12 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from . import SynoApi
from .const import (
CONF_SNAPSHOT_QUALITY,
COORDINATOR_CAMERAS,
DEFAULT_SNAPSHOT_QUALITY,
DOMAIN,
SIGNAL_CAMERA_SOURCE_CHANGED,
SYNO_API,
)
from .entity import SynologyDSMBaseEntity, SynologyDSMEntityDescription
from .models import SynologyDSMData
_LOGGER = logging.getLogger(__name__)
@ -47,21 +46,10 @@ async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the Synology NAS cameras."""
data = hass.data[DOMAIN][entry.unique_id]
api: SynoApi = data[SYNO_API]
if SynoSurveillanceStation.CAMERA_API_KEY not in api.dsm.apis:
return
# initial data fetch
coordinator: DataUpdateCoordinator[dict[str, dict[str, SynoCamera]]] = data[
COORDINATOR_CAMERAS
]
await coordinator.async_config_entry_first_refresh()
data: SynologyDSMData = hass.data[DOMAIN][entry.unique_id]
if coordinator := data.coordinator_cameras:
async_add_entities(
SynoDSMCamera(api, coordinator, camera_id)
SynoDSMCamera(data.api, coordinator, camera_id)
for camera_id in coordinator.data["cameras"]
)

View File

@ -32,7 +32,7 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant, callback
from .const import CONF_DEVICE_TOKEN, DOMAIN, SYSTEM_LOADED
from .const import CONF_DEVICE_TOKEN
LOGGER = logging.getLogger(__name__)
@ -217,11 +217,6 @@ class SynoApi:
)
self.surveillance_station = self.dsm.surveillance_station
def _set_system_loaded(self, state: bool = False) -> None:
"""Set system loaded flag."""
dsm_device = self._hass.data[DOMAIN].get(self.information.serial)
dsm_device[SYSTEM_LOADED] = state
async def _syno_api_executer(self, api_call: Callable) -> None:
"""Synology api call wrapper."""
try:
@ -235,12 +230,10 @@ class SynoApi:
async def async_reboot(self) -> None:
"""Reboot NAS."""
await self._syno_api_executer(self.system.reboot)
self._set_system_loaded()
async def async_shutdown(self) -> None:
"""Shutdown NAS."""
await self._syno_api_executer(self.system.shutdown)
self._set_system_loaded()
async def async_unload(self) -> None:
"""Stop interacting with the NAS and prepare for removal from hass."""

View File

@ -2,6 +2,15 @@
from __future__ import annotations
from synology_dsm.api.surveillance_station.const import SNAPSHOT_PROFILE_BALANCED
from synology_dsm.exceptions import (
SynologyDSMAPIErrorException,
SynologyDSMLogin2SARequiredException,
SynologyDSMLoginDisabledAccountException,
SynologyDSMLoginFailedException,
SynologyDSMLoginInvalidException,
SynologyDSMLoginPermissionDeniedException,
SynologyDSMRequestException,
)
from homeassistant.const import Platform
@ -15,17 +24,9 @@ PLATFORMS = [
Platform.SWITCH,
Platform.UPDATE,
]
COORDINATOR_CAMERAS = "coordinator_cameras"
COORDINATOR_CENTRAL = "coordinator_central"
COORDINATOR_SWITCHES = "coordinator_switches"
SYSTEM_LOADED = "system_loaded"
EXCEPTION_DETAILS = "details"
EXCEPTION_UNKNOWN = "unknown"
# Entry keys
SYNO_API = "syno_api"
UNDO_UPDATE_LISTENER = "undo_update_listener"
# Configuration
CONF_SERIAL = "serial"
CONF_VOLUMES = "volumes"
@ -53,3 +54,16 @@ SERVICES = [
SERVICE_REBOOT,
SERVICE_SHUTDOWN,
]
SYNOLOGY_AUTH_FAILED_EXCEPTIONS = (
SynologyDSMLogin2SARequiredException,
SynologyDSMLoginDisabledAccountException,
SynologyDSMLoginInvalidException,
SynologyDSMLoginPermissionDeniedException,
)
SYNOLOGY_CONNECTION_EXCEPTIONS = (
SynologyDSMAPIErrorException,
SynologyDSMLoginFailedException,
SynologyDSMRequestException,
)

View File

@ -0,0 +1,148 @@
"""synology_dsm coordinators."""
from __future__ import annotations
from datetime import timedelta
import logging
import async_timeout
from synology_dsm.api.surveillance_station.camera import SynoCamera
from synology_dsm.exceptions import SynologyDSMAPIErrorException
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_SCAN_INTERVAL
from homeassistant.core import HomeAssistant
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .common import SynoApi
from .const import (
DEFAULT_SCAN_INTERVAL,
SIGNAL_CAMERA_SOURCE_CHANGED,
SYNOLOGY_CONNECTION_EXCEPTIONS,
)
_LOGGER = logging.getLogger(__name__)
class SynologyDSMUpdateCoordinator(DataUpdateCoordinator):
"""DataUpdateCoordinator base class for synology_dsm."""
def __init__(
self,
hass: HomeAssistant,
entry: ConfigEntry,
api: SynoApi,
update_interval: timedelta,
) -> None:
"""Initialize synology_dsm DataUpdateCoordinator."""
self.api = api
self.entry = entry
super().__init__(
hass,
_LOGGER,
name=f"{entry.title} {self.__class__.__name__}",
update_interval=update_interval,
)
class SynologyDSMSwitchUpdateCoordinator(SynologyDSMUpdateCoordinator):
"""DataUpdateCoordinator to gather data for a synology_dsm switch devices."""
def __init__(
self,
hass: HomeAssistant,
entry: ConfigEntry,
api: SynoApi,
) -> None:
"""Initialize DataUpdateCoordinator for switch devices."""
super().__init__(hass, entry, api, timedelta(seconds=30))
self.version: str | None = None
async def async_setup(self) -> None:
"""Set up the coordinator initial data."""
info = await self.hass.async_add_executor_job(
self.api.dsm.surveillance_station.get_info
)
self.version = info["data"]["CMSMinVersion"]
async def _async_update_data(self) -> dict[str, dict[str, SynoCamera]] | None:
"""Fetch all data from api."""
surveillance_station = self.api.surveillance_station
return {
"switches": {
"home_mode": await self.hass.async_add_executor_job(
surveillance_station.get_home_mode_status
)
}
}
class SynologyDSMCentralUpdateCoordinator(SynologyDSMUpdateCoordinator):
"""DataUpdateCoordinator to gather data for a synology_dsm central device."""
def __init__(
self,
hass: HomeAssistant,
entry: ConfigEntry,
api: SynoApi,
) -> None:
"""Initialize DataUpdateCoordinator for central device."""
super().__init__(
hass,
entry,
api,
timedelta(
minutes=entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
),
)
async def _async_update_data(self) -> dict[str, dict[str, SynoCamera]] | None:
"""Fetch all data from api."""
try:
await self.api.async_update()
except SYNOLOGY_CONNECTION_EXCEPTIONS as err:
raise UpdateFailed(f"Error communicating with API: {err}") from err
return None
class SynologyDSMCameraUpdateCoordinator(SynologyDSMUpdateCoordinator):
"""DataUpdateCoordinator to gather data for a synology_dsm cameras."""
def __init__(
self,
hass: HomeAssistant,
entry: ConfigEntry,
api: SynoApi,
) -> None:
"""Initialize DataUpdateCoordinator for cameras."""
super().__init__(hass, entry, api, timedelta(seconds=30))
async def _async_update_data(self) -> dict[str, dict[str, SynoCamera]] | None:
"""Fetch all camera data from api."""
surveillance_station = self.api.surveillance_station
current_data: dict[str, SynoCamera] = {
camera.id: camera for camera in surveillance_station.get_all_cameras()
}
try:
async with async_timeout.timeout(30):
await self.hass.async_add_executor_job(surveillance_station.update)
except SynologyDSMAPIErrorException as err:
raise UpdateFailed(f"Error communicating with API: {err}") from err
new_data: dict[str, SynoCamera] = {
camera.id: camera for camera in surveillance_station.get_all_cameras()
}
for cam_id, cam_data_new in new_data.items():
if (
(cam_data_current := current_data.get(cam_id)) is not None
and cam_data_current.live_view.rtsp != cam_data_new.live_view.rtsp
):
async_dispatcher_send(
self.hass,
f"{SIGNAL_CAMERA_SOURCE_CHANGED}_{self.entry.entry_id}_{cam_id}",
cam_data_new.live_view.rtsp,
)
return {"cameras": new_data}

View File

@ -8,8 +8,8 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from . import SynoApi
from .const import CONF_DEVICE_TOKEN, DOMAIN, SYNO_API, SYSTEM_LOADED
from .const import CONF_DEVICE_TOKEN, DOMAIN
from .models import SynologyDSMData
TO_REDACT = {CONF_USERNAME, CONF_PASSWORD, CONF_DEVICE_TOKEN}
@ -18,8 +18,8 @@ async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: ConfigEntry
) -> dict:
"""Return diagnostics for a config entry."""
data: dict = hass.data[DOMAIN][entry.unique_id]
syno_api: SynoApi = data[SYNO_API]
data: SynologyDSMData = hass.data[DOMAIN][entry.unique_id]
syno_api = data.api
dsm_info = syno_api.dsm.information
diag_data = {
@ -36,7 +36,7 @@ async def async_get_config_entry_diagnostics(
"surveillance_station": {"cameras": {}},
"upgrade": {},
"utilisation": {},
"is_system_loaded": data[SYSTEM_LOADED],
"is_system_loaded": True,
"api_details": {
"fetching_entities": syno_api._fetching_entities, # pylint: disable=protected-access
},
@ -45,7 +45,7 @@ async def async_get_config_entry_diagnostics(
if syno_api.network is not None:
intf: dict
for intf in syno_api.network.interfaces:
diag_data["network"]["interfaces"][intf["id"]] = {
diag_data["network"]["interfaces"][intf["id"]] = { # type: ignore[index]
"type": intf["type"],
"ip": intf["ip"],
}
@ -53,7 +53,7 @@ async def async_get_config_entry_diagnostics(
if syno_api.storage is not None:
disk: dict
for disk in syno_api.storage.disks:
diag_data["storage"]["disks"][disk["id"]] = {
diag_data["storage"]["disks"][disk["id"]] = { # type: ignore[index]
"name": disk["name"],
"vendor": disk["vendor"],
"model": disk["model"],
@ -64,7 +64,7 @@ async def async_get_config_entry_diagnostics(
volume: dict
for volume in syno_api.storage.volumes:
diag_data["storage"]["volumes"][volume["id"]] = {
diag_data["storage"]["volumes"][volume["id"]] = { # type: ignore[index]
"name": volume["fs_type"],
"size": volume["size"],
}
@ -72,7 +72,7 @@ async def async_get_config_entry_diagnostics(
if syno_api.surveillance_station is not None:
camera: SynoCamera
for camera in syno_api.surveillance_station.get_all_cameras():
diag_data["surveillance_station"]["cameras"][camera.id] = {
diag_data["surveillance_station"]["cameras"][camera.id] = { # type: ignore[index]
"name": camera.name,
"is_enabled": camera.is_enabled,
"is_motion_detection_enabled": camera.is_motion_detection_enabled,

View File

@ -0,0 +1,21 @@
"""The synology_dsm integration models."""
from __future__ import annotations
from dataclasses import dataclass
from .common import SynoApi
from .coordinator import (
SynologyDSMCameraUpdateCoordinator,
SynologyDSMCentralUpdateCoordinator,
SynologyDSMSwitchUpdateCoordinator,
)
@dataclass
class SynologyDSMData:
"""Data for the synology_dsm integration."""
api: SynoApi
coordinator_central: SynologyDSMCentralUpdateCoordinator
coordinator_cameras: SynologyDSMCameraUpdateCoordinator | None
coordinator_switches: SynologyDSMSwitchUpdateCoordinator | None

View File

@ -31,12 +31,13 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from homeassistant.util.dt import utcnow
from . import SynoApi
from .const import CONF_VOLUMES, COORDINATOR_CENTRAL, DOMAIN, ENTITY_UNIT_LOAD, SYNO_API
from .const import CONF_VOLUMES, DOMAIN, ENTITY_UNIT_LOAD
from .entity import (
SynologyDSMBaseEntity,
SynologyDSMDeviceEntity,
SynologyDSMEntityDescription,
)
from .models import SynologyDSMData
@dataclass
@ -279,10 +280,9 @@ async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the Synology NAS Sensor."""
data = hass.data[DOMAIN][entry.unique_id]
api: SynoApi = data[SYNO_API]
coordinator = data[COORDINATOR_CENTRAL]
data: SynologyDSMData = hass.data[DOMAIN][entry.unique_id]
api = data.api
coordinator = data.coordinator_central
entities: list[SynoDSMUtilSensor | SynoDSMStorageSensor | SynoDSMInfoSensor] = [
SynoDSMUtilSensor(api, coordinator, description)

View File

@ -7,15 +7,8 @@ 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,
)
from .const import CONF_SERIAL, DOMAIN, SERVICE_REBOOT, SERVICE_SHUTDOWN, SERVICES
from .models import SynologyDSMData
LOGGER = logging.getLogger(__name__)
@ -29,7 +22,7 @@ async def async_setup_services(hass: HomeAssistant) -> None:
dsm_devices = hass.data[DOMAIN]
if serial:
dsm_device = dsm_devices.get(serial)
dsm_device: SynologyDSMData = hass.data[DOMAIN][serial]
elif len(dsm_devices) == 1:
dsm_device = next(iter(dsm_devices.values()))
serial = next(iter(dsm_devices))
@ -45,7 +38,7 @@ async def async_setup_services(hass: HomeAssistant) -> None:
return
if call.service in [SERVICE_REBOOT, SERVICE_SHUTDOWN]:
if not (dsm_device := hass.data[DOMAIN].get(serial)):
if serial not in hass.data[DOMAIN]:
LOGGER.error("DSM with specified serial %s not found", serial)
return
LOGGER.debug("%s DSM with serial %s", call.service, serial)
@ -53,7 +46,8 @@ async def async_setup_services(hass: HomeAssistant) -> None:
"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]
dsm_device = hass.data[DOMAIN][serial]
dsm_api = dsm_device.api
try:
await getattr(dsm_api, f"async_{call.service}")()
except SynologyDSMException as ex:

View File

@ -15,8 +15,9 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from . import SynoApi
from .const import COORDINATOR_SWITCHES, DOMAIN, SYNO_API
from .const import DOMAIN
from .entity import SynologyDSMBaseEntity, SynologyDSMEntityDescription
from .models import SynologyDSMData
_LOGGER = logging.getLogger(__name__)
@ -42,30 +43,16 @@ async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the Synology NAS switch."""
data = hass.data[DOMAIN][entry.unique_id]
api: SynoApi = data[SYNO_API]
entities = []
if SynoSurveillanceStation.INFO_API_KEY in api.dsm.apis:
info = await hass.async_add_executor_job(api.dsm.surveillance_station.get_info)
version = info["data"]["CMSMinVersion"]
# initial data fetch
coordinator: DataUpdateCoordinator = data[COORDINATOR_SWITCHES]
await coordinator.async_refresh()
entities.extend(
[
data: SynologyDSMData = hass.data[DOMAIN][entry.unique_id]
if coordinator := data.coordinator_switches:
assert coordinator.version is not None
async_add_entities(
SynoDSMSurveillanceHomeModeToggle(
api, version, coordinator, description
data.api, coordinator.version, coordinator, description
)
for description in SURVEILLANCE_SWITCH
]
)
async_add_entities(entities, True)
class SynoDSMSurveillanceHomeModeToggle(SynologyDSMBaseEntity, SwitchEntity):
"""Representation a Synology Surveillance Station Home Mode toggle."""

View File

@ -13,9 +13,9 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import SynoApi
from .const import COORDINATOR_CENTRAL, DOMAIN, SYNO_API
from .const import DOMAIN
from .entity import SynologyDSMBaseEntity, SynologyDSMEntityDescription
from .models import SynologyDSMData
@dataclass
@ -39,12 +39,9 @@ async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up Synology DSM update entities."""
data = hass.data[DOMAIN][entry.unique_id]
api: SynoApi = data[SYNO_API]
coordinator = data[COORDINATOR_CENTRAL]
data: SynologyDSMData = hass.data[DOMAIN][entry.unique_id]
async_add_entities(
SynoDSMUpdateEntity(api, coordinator, description)
SynoDSMUpdateEntity(data.api, data.coordinator_central, description)
for description in UPDATE_ENTITIES
)

View File

@ -24,9 +24,9 @@ from tests.common import MockConfigEntry
@pytest.mark.no_bypass_setup
async def test_services_registered(hass: HomeAssistant):
"""Test if all services are registered."""
with patch(
"homeassistant.components.synology_dsm.SynoApi.async_setup", return_value=True
), patch("homeassistant.components.synology_dsm.PLATFORMS", return_value=[]):
with patch("homeassistant.components.synology_dsm.common.SynologyDSM"), patch(
"homeassistant.components.synology_dsm.PLATFORMS", return_value=[]
):
entry = MockConfigEntry(
domain=DOMAIN,
data={