Cleanup coordinators in synology_dsm (#73257)
Co-authored-by: Michael <35783820+mib1185@users.noreply.github.com>
This commit is contained in:
parent
1d6068fa09
commit
22daea27c2
15 changed files with 287 additions and 233 deletions
|
@ -1200,6 +1200,7 @@ omit =
|
||||||
homeassistant/components/synology_dsm/binary_sensor.py
|
homeassistant/components/synology_dsm/binary_sensor.py
|
||||||
homeassistant/components/synology_dsm/button.py
|
homeassistant/components/synology_dsm/button.py
|
||||||
homeassistant/components/synology_dsm/camera.py
|
homeassistant/components/synology_dsm/camera.py
|
||||||
|
homeassistant/components/synology_dsm/coordinator.py
|
||||||
homeassistant/components/synology_dsm/diagnostics.py
|
homeassistant/components/synology_dsm/diagnostics.py
|
||||||
homeassistant/components/synology_dsm/common.py
|
homeassistant/components/synology_dsm/common.py
|
||||||
homeassistant/components/synology_dsm/entity.py
|
homeassistant/components/synology_dsm/entity.py
|
||||||
|
|
|
@ -1,47 +1,32 @@
|
||||||
"""The Synology DSM component."""
|
"""The Synology DSM component."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import timedelta
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
import async_timeout
|
|
||||||
from synology_dsm.api.surveillance_station import SynoSurveillanceStation
|
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.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.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||||
from homeassistant.helpers import config_validation as cv, device_registry as dr
|
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 .common import SynoApi
|
||||||
from .const import (
|
from .const import (
|
||||||
COORDINATOR_CAMERAS,
|
|
||||||
COORDINATOR_CENTRAL,
|
|
||||||
COORDINATOR_SWITCHES,
|
|
||||||
DEFAULT_SCAN_INTERVAL,
|
|
||||||
DEFAULT_VERIFY_SSL,
|
DEFAULT_VERIFY_SSL,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
EXCEPTION_DETAILS,
|
EXCEPTION_DETAILS,
|
||||||
EXCEPTION_UNKNOWN,
|
EXCEPTION_UNKNOWN,
|
||||||
PLATFORMS,
|
PLATFORMS,
|
||||||
SIGNAL_CAMERA_SOURCE_CHANGED,
|
SYNOLOGY_AUTH_FAILED_EXCEPTIONS,
|
||||||
SYNO_API,
|
SYNOLOGY_CONNECTION_EXCEPTIONS,
|
||||||
SYSTEM_LOADED,
|
|
||||||
UNDO_UPDATE_LISTENER,
|
|
||||||
)
|
)
|
||||||
|
from .coordinator import (
|
||||||
|
SynologyDSMCameraUpdateCoordinator,
|
||||||
|
SynologyDSMCentralUpdateCoordinator,
|
||||||
|
SynologyDSMSwitchUpdateCoordinator,
|
||||||
|
)
|
||||||
|
from .models import SynologyDSMData
|
||||||
from .service import async_setup_services
|
from .service import async_setup_services
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
|
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)
|
api = SynoApi(hass, entry)
|
||||||
try:
|
try:
|
||||||
await api.async_setup()
|
await api.async_setup()
|
||||||
except (
|
except SYNOLOGY_AUTH_FAILED_EXCEPTIONS as err:
|
||||||
SynologyDSMLogin2SARequiredException,
|
|
||||||
SynologyDSMLoginDisabledAccountException,
|
|
||||||
SynologyDSMLoginInvalidException,
|
|
||||||
SynologyDSMLoginPermissionDeniedException,
|
|
||||||
) as err:
|
|
||||||
if err.args[0] and isinstance(err.args[0], dict):
|
if err.args[0] and isinstance(err.args[0], dict):
|
||||||
details = err.args[0].get(EXCEPTION_DETAILS, EXCEPTION_UNKNOWN)
|
details = err.args[0].get(EXCEPTION_DETAILS, EXCEPTION_UNKNOWN)
|
||||||
else:
|
else:
|
||||||
details = EXCEPTION_UNKNOWN
|
details = EXCEPTION_UNKNOWN
|
||||||
raise ConfigEntryAuthFailed(f"reason: {details}") from err
|
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):
|
if err.args[0] and isinstance(err.args[0], dict):
|
||||||
details = err.args[0].get(EXCEPTION_DETAILS, EXCEPTION_UNKNOWN)
|
details = err.args[0].get(EXCEPTION_DETAILS, EXCEPTION_UNKNOWN)
|
||||||
else:
|
else:
|
||||||
details = EXCEPTION_UNKNOWN
|
details = EXCEPTION_UNKNOWN
|
||||||
raise ConfigEntryNotReady(details) from err
|
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
|
# Services
|
||||||
await async_setup_services(hass)
|
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}
|
entry, data={**entry.data, CONF_MAC: network.macs}
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_coordinator_update_data_cameras() -> dict[
|
# These all create executor jobs so we do not gather here
|
||||||
str, dict[str, SynoCamera]
|
coordinator_central = SynologyDSMCentralUpdateCoordinator(hass, entry, api)
|
||||||
] | None:
|
await coordinator_central.async_config_entry_first_refresh()
|
||||||
"""Fetch all camera data from api."""
|
|
||||||
if not hass.data[DOMAIN][entry.unique_id][SYSTEM_LOADED]:
|
|
||||||
raise UpdateFailed("System not fully loaded")
|
|
||||||
|
|
||||||
if SynoSurveillanceStation.CAMERA_API_KEY not in api.dsm.apis:
|
available_apis = api.dsm.apis
|
||||||
return None
|
|
||||||
|
|
||||||
surveillance_station = api.surveillance_station
|
# The central coordinator needs to be refreshed first since
|
||||||
current_data: dict[str, SynoCamera] = {
|
# the next two rely on data from it
|
||||||
camera.id: camera for camera in surveillance_station.get_all_cameras()
|
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:
|
coordinator_switches: SynologyDSMSwitchUpdateCoordinator | None = None
|
||||||
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():
|
|
||||||
if (
|
if (
|
||||||
(cam_data_current := current_data.get(cam_id)) is not None
|
SynoSurveillanceStation.INFO_API_KEY in available_apis
|
||||||
and cam_data_current.live_view.rtsp != cam_data_new.live_view.rtsp
|
and SynoSurveillanceStation.HOME_MODE_API_KEY in available_apis
|
||||||
):
|
):
|
||||||
async_dispatcher_send(
|
coordinator_switches = SynologyDSMSwitchUpdateCoordinator(hass, entry, api)
|
||||||
hass,
|
await coordinator_switches.async_config_entry_first_refresh()
|
||||||
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."""
|
|
||||||
try:
|
try:
|
||||||
await api.async_update()
|
await coordinator_switches.async_setup()
|
||||||
except Exception as err:
|
except SYNOLOGY_CONNECTION_EXCEPTIONS as ex:
|
||||||
raise UpdateFailed(f"Error communicating with API: {err}") from err
|
raise ConfigEntryNotReady from ex
|
||||||
return None
|
|
||||||
|
|
||||||
async def async_coordinator_update_data_switches() -> dict[
|
synology_data = SynologyDSMData(
|
||||||
str, dict[str, Any]
|
api=api,
|
||||||
] | None:
|
coordinator_central=coordinator_central,
|
||||||
"""Fetch all switch data from api."""
|
coordinator_cameras=coordinator_cameras,
|
||||||
if not hass.data[DOMAIN][entry.unique_id][SYSTEM_LOADED]:
|
coordinator_switches=coordinator_switches,
|
||||||
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
|
|
||||||
)
|
)
|
||||||
}
|
hass.data.setdefault(DOMAIN, {})[entry.unique_id] = synology_data
|
||||||
}
|
|
||||||
|
|
||||||
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.config_entries.async_setup_platforms(entry, PLATFORMS)
|
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||||
|
entry.async_on_unload(entry.add_update_listener(_async_update_listener))
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Unload Synology DSM sensors."""
|
"""Unload Synology DSM sensors."""
|
||||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||||
if unload_ok:
|
entry_data: SynologyDSMData = hass.data[DOMAIN][entry.unique_id]
|
||||||
entry_data = hass.data[DOMAIN][entry.unique_id]
|
await entry_data.api.async_unload()
|
||||||
entry_data[UNDO_UPDATE_LISTENER]()
|
|
||||||
await entry_data[SYNO_API].async_unload()
|
|
||||||
hass.data[DOMAIN].pop(entry.unique_id)
|
hass.data[DOMAIN].pop(entry.unique_id)
|
||||||
|
|
||||||
return unload_ok
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -22,12 +22,13 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
|
|
||||||
from . import SynoApi
|
from . import SynoApi
|
||||||
from .const import COORDINATOR_CENTRAL, DOMAIN, SYNO_API
|
from .const import DOMAIN
|
||||||
from .entity import (
|
from .entity import (
|
||||||
SynologyDSMBaseEntity,
|
SynologyDSMBaseEntity,
|
||||||
SynologyDSMDeviceEntity,
|
SynologyDSMDeviceEntity,
|
||||||
SynologyDSMEntityDescription,
|
SynologyDSMEntityDescription,
|
||||||
)
|
)
|
||||||
|
from .models import SynologyDSMData
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -80,10 +81,9 @@ async def async_setup_entry(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Synology NAS binary sensor."""
|
"""Set up the Synology NAS binary sensor."""
|
||||||
|
data: SynologyDSMData = hass.data[DOMAIN][entry.unique_id]
|
||||||
data = hass.data[DOMAIN][entry.unique_id]
|
api = data.api
|
||||||
api: SynoApi = data[SYNO_API]
|
coordinator = data.coordinator_central
|
||||||
coordinator = data[COORDINATOR_CENTRAL]
|
|
||||||
|
|
||||||
entities: list[
|
entities: list[
|
||||||
SynoDSMSecurityBinarySensor
|
SynoDSMSecurityBinarySensor
|
||||||
|
|
|
@ -17,7 +17,8 @@ from homeassistant.helpers.entity import DeviceInfo, EntityCategory
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import SynoApi
|
from . import SynoApi
|
||||||
from .const import DOMAIN, SYNO_API
|
from .const import DOMAIN
|
||||||
|
from .models import SynologyDSMData
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -60,10 +61,8 @@ async def async_setup_entry(
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set buttons for device."""
|
"""Set buttons for device."""
|
||||||
data = hass.data[DOMAIN][entry.unique_id]
|
data: SynologyDSMData = hass.data[DOMAIN][entry.unique_id]
|
||||||
syno_api: SynoApi = data[SYNO_API]
|
async_add_entities(SynologyDSMButton(data.api, button) for button in BUTTONS)
|
||||||
|
|
||||||
async_add_entities(SynologyDSMButton(syno_api, button) for button in BUTTONS)
|
|
||||||
|
|
||||||
|
|
||||||
class SynologyDSMButton(ButtonEntity):
|
class SynologyDSMButton(ButtonEntity):
|
||||||
|
|
|
@ -25,13 +25,12 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
from . import SynoApi
|
from . import SynoApi
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_SNAPSHOT_QUALITY,
|
CONF_SNAPSHOT_QUALITY,
|
||||||
COORDINATOR_CAMERAS,
|
|
||||||
DEFAULT_SNAPSHOT_QUALITY,
|
DEFAULT_SNAPSHOT_QUALITY,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SIGNAL_CAMERA_SOURCE_CHANGED,
|
SIGNAL_CAMERA_SOURCE_CHANGED,
|
||||||
SYNO_API,
|
|
||||||
)
|
)
|
||||||
from .entity import SynologyDSMBaseEntity, SynologyDSMEntityDescription
|
from .entity import SynologyDSMBaseEntity, SynologyDSMEntityDescription
|
||||||
|
from .models import SynologyDSMData
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -47,21 +46,10 @@ async def async_setup_entry(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Synology NAS cameras."""
|
"""Set up the Synology NAS cameras."""
|
||||||
|
data: SynologyDSMData = hass.data[DOMAIN][entry.unique_id]
|
||||||
data = hass.data[DOMAIN][entry.unique_id]
|
if coordinator := data.coordinator_cameras:
|
||||||
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()
|
|
||||||
|
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
SynoDSMCamera(api, coordinator, camera_id)
|
SynoDSMCamera(data.api, coordinator, camera_id)
|
||||||
for camera_id in coordinator.data["cameras"]
|
for camera_id in coordinator.data["cameras"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ from homeassistant.const import (
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
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__)
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -217,11 +217,6 @@ class SynoApi:
|
||||||
)
|
)
|
||||||
self.surveillance_station = self.dsm.surveillance_station
|
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:
|
async def _syno_api_executer(self, api_call: Callable) -> None:
|
||||||
"""Synology api call wrapper."""
|
"""Synology api call wrapper."""
|
||||||
try:
|
try:
|
||||||
|
@ -235,12 +230,10 @@ class SynoApi:
|
||||||
async def async_reboot(self) -> None:
|
async def async_reboot(self) -> None:
|
||||||
"""Reboot NAS."""
|
"""Reboot NAS."""
|
||||||
await self._syno_api_executer(self.system.reboot)
|
await self._syno_api_executer(self.system.reboot)
|
||||||
self._set_system_loaded()
|
|
||||||
|
|
||||||
async def async_shutdown(self) -> None:
|
async def async_shutdown(self) -> None:
|
||||||
"""Shutdown NAS."""
|
"""Shutdown NAS."""
|
||||||
await self._syno_api_executer(self.system.shutdown)
|
await self._syno_api_executer(self.system.shutdown)
|
||||||
self._set_system_loaded()
|
|
||||||
|
|
||||||
async def async_unload(self) -> None:
|
async def async_unload(self) -> None:
|
||||||
"""Stop interacting with the NAS and prepare for removal from hass."""
|
"""Stop interacting with the NAS and prepare for removal from hass."""
|
||||||
|
|
|
@ -2,6 +2,15 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from synology_dsm.api.surveillance_station.const import SNAPSHOT_PROFILE_BALANCED
|
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
|
from homeassistant.const import Platform
|
||||||
|
|
||||||
|
@ -15,17 +24,9 @@ PLATFORMS = [
|
||||||
Platform.SWITCH,
|
Platform.SWITCH,
|
||||||
Platform.UPDATE,
|
Platform.UPDATE,
|
||||||
]
|
]
|
||||||
COORDINATOR_CAMERAS = "coordinator_cameras"
|
|
||||||
COORDINATOR_CENTRAL = "coordinator_central"
|
|
||||||
COORDINATOR_SWITCHES = "coordinator_switches"
|
|
||||||
SYSTEM_LOADED = "system_loaded"
|
|
||||||
EXCEPTION_DETAILS = "details"
|
EXCEPTION_DETAILS = "details"
|
||||||
EXCEPTION_UNKNOWN = "unknown"
|
EXCEPTION_UNKNOWN = "unknown"
|
||||||
|
|
||||||
# Entry keys
|
|
||||||
SYNO_API = "syno_api"
|
|
||||||
UNDO_UPDATE_LISTENER = "undo_update_listener"
|
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
CONF_SERIAL = "serial"
|
CONF_SERIAL = "serial"
|
||||||
CONF_VOLUMES = "volumes"
|
CONF_VOLUMES = "volumes"
|
||||||
|
@ -53,3 +54,16 @@ SERVICES = [
|
||||||
SERVICE_REBOOT,
|
SERVICE_REBOOT,
|
||||||
SERVICE_SHUTDOWN,
|
SERVICE_SHUTDOWN,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
SYNOLOGY_AUTH_FAILED_EXCEPTIONS = (
|
||||||
|
SynologyDSMLogin2SARequiredException,
|
||||||
|
SynologyDSMLoginDisabledAccountException,
|
||||||
|
SynologyDSMLoginInvalidException,
|
||||||
|
SynologyDSMLoginPermissionDeniedException,
|
||||||
|
)
|
||||||
|
|
||||||
|
SYNOLOGY_CONNECTION_EXCEPTIONS = (
|
||||||
|
SynologyDSMAPIErrorException,
|
||||||
|
SynologyDSMLoginFailedException,
|
||||||
|
SynologyDSMRequestException,
|
||||||
|
)
|
||||||
|
|
148
homeassistant/components/synology_dsm/coordinator.py
Normal file
148
homeassistant/components/synology_dsm/coordinator.py
Normal 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}
|
|
@ -8,8 +8,8 @@ from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from . import SynoApi
|
from .const import CONF_DEVICE_TOKEN, DOMAIN
|
||||||
from .const import CONF_DEVICE_TOKEN, DOMAIN, SYNO_API, SYSTEM_LOADED
|
from .models import SynologyDSMData
|
||||||
|
|
||||||
TO_REDACT = {CONF_USERNAME, CONF_PASSWORD, CONF_DEVICE_TOKEN}
|
TO_REDACT = {CONF_USERNAME, CONF_PASSWORD, CONF_DEVICE_TOKEN}
|
||||||
|
|
||||||
|
@ -18,8 +18,8 @@ async def async_get_config_entry_diagnostics(
|
||||||
hass: HomeAssistant, entry: ConfigEntry
|
hass: HomeAssistant, entry: ConfigEntry
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""Return diagnostics for a config entry."""
|
"""Return diagnostics for a config entry."""
|
||||||
data: dict = hass.data[DOMAIN][entry.unique_id]
|
data: SynologyDSMData = hass.data[DOMAIN][entry.unique_id]
|
||||||
syno_api: SynoApi = data[SYNO_API]
|
syno_api = data.api
|
||||||
dsm_info = syno_api.dsm.information
|
dsm_info = syno_api.dsm.information
|
||||||
|
|
||||||
diag_data = {
|
diag_data = {
|
||||||
|
@ -36,7 +36,7 @@ async def async_get_config_entry_diagnostics(
|
||||||
"surveillance_station": {"cameras": {}},
|
"surveillance_station": {"cameras": {}},
|
||||||
"upgrade": {},
|
"upgrade": {},
|
||||||
"utilisation": {},
|
"utilisation": {},
|
||||||
"is_system_loaded": data[SYSTEM_LOADED],
|
"is_system_loaded": True,
|
||||||
"api_details": {
|
"api_details": {
|
||||||
"fetching_entities": syno_api._fetching_entities, # pylint: disable=protected-access
|
"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:
|
if syno_api.network is not None:
|
||||||
intf: dict
|
intf: dict
|
||||||
for intf in syno_api.network.interfaces:
|
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"],
|
"type": intf["type"],
|
||||||
"ip": intf["ip"],
|
"ip": intf["ip"],
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ async def async_get_config_entry_diagnostics(
|
||||||
if syno_api.storage is not None:
|
if syno_api.storage is not None:
|
||||||
disk: dict
|
disk: dict
|
||||||
for disk in syno_api.storage.disks:
|
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"],
|
"name": disk["name"],
|
||||||
"vendor": disk["vendor"],
|
"vendor": disk["vendor"],
|
||||||
"model": disk["model"],
|
"model": disk["model"],
|
||||||
|
@ -64,7 +64,7 @@ async def async_get_config_entry_diagnostics(
|
||||||
|
|
||||||
volume: dict
|
volume: dict
|
||||||
for volume in syno_api.storage.volumes:
|
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"],
|
"name": volume["fs_type"],
|
||||||
"size": volume["size"],
|
"size": volume["size"],
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,7 @@ async def async_get_config_entry_diagnostics(
|
||||||
if syno_api.surveillance_station is not None:
|
if syno_api.surveillance_station is not None:
|
||||||
camera: SynoCamera
|
camera: SynoCamera
|
||||||
for camera in syno_api.surveillance_station.get_all_cameras():
|
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,
|
"name": camera.name,
|
||||||
"is_enabled": camera.is_enabled,
|
"is_enabled": camera.is_enabled,
|
||||||
"is_motion_detection_enabled": camera.is_motion_detection_enabled,
|
"is_motion_detection_enabled": camera.is_motion_detection_enabled,
|
||||||
|
|
21
homeassistant/components/synology_dsm/models.py
Normal file
21
homeassistant/components/synology_dsm/models.py
Normal 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
|
|
@ -31,12 +31,13 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
from homeassistant.util.dt import utcnow
|
from homeassistant.util.dt import utcnow
|
||||||
|
|
||||||
from . import SynoApi
|
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 (
|
from .entity import (
|
||||||
SynologyDSMBaseEntity,
|
SynologyDSMBaseEntity,
|
||||||
SynologyDSMDeviceEntity,
|
SynologyDSMDeviceEntity,
|
||||||
SynologyDSMEntityDescription,
|
SynologyDSMEntityDescription,
|
||||||
)
|
)
|
||||||
|
from .models import SynologyDSMData
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -279,10 +280,9 @@ async def async_setup_entry(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Synology NAS Sensor."""
|
"""Set up the Synology NAS Sensor."""
|
||||||
|
data: SynologyDSMData = hass.data[DOMAIN][entry.unique_id]
|
||||||
data = hass.data[DOMAIN][entry.unique_id]
|
api = data.api
|
||||||
api: SynoApi = data[SYNO_API]
|
coordinator = data.coordinator_central
|
||||||
coordinator = data[COORDINATOR_CENTRAL]
|
|
||||||
|
|
||||||
entities: list[SynoDSMUtilSensor | SynoDSMStorageSensor | SynoDSMInfoSensor] = [
|
entities: list[SynoDSMUtilSensor | SynoDSMStorageSensor | SynoDSMInfoSensor] = [
|
||||||
SynoDSMUtilSensor(api, coordinator, description)
|
SynoDSMUtilSensor(api, coordinator, description)
|
||||||
|
|
|
@ -7,15 +7,8 @@ from synology_dsm.exceptions import SynologyDSMException
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant, ServiceCall
|
from homeassistant.core import HomeAssistant, ServiceCall
|
||||||
|
|
||||||
from .common import SynoApi
|
from .const import CONF_SERIAL, DOMAIN, SERVICE_REBOOT, SERVICE_SHUTDOWN, SERVICES
|
||||||
from .const import (
|
from .models import SynologyDSMData
|
||||||
CONF_SERIAL,
|
|
||||||
DOMAIN,
|
|
||||||
SERVICE_REBOOT,
|
|
||||||
SERVICE_SHUTDOWN,
|
|
||||||
SERVICES,
|
|
||||||
SYNO_API,
|
|
||||||
)
|
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -29,7 +22,7 @@ async def async_setup_services(hass: HomeAssistant) -> None:
|
||||||
dsm_devices = hass.data[DOMAIN]
|
dsm_devices = hass.data[DOMAIN]
|
||||||
|
|
||||||
if serial:
|
if serial:
|
||||||
dsm_device = dsm_devices.get(serial)
|
dsm_device: SynologyDSMData = hass.data[DOMAIN][serial]
|
||||||
elif len(dsm_devices) == 1:
|
elif len(dsm_devices) == 1:
|
||||||
dsm_device = next(iter(dsm_devices.values()))
|
dsm_device = next(iter(dsm_devices.values()))
|
||||||
serial = next(iter(dsm_devices))
|
serial = next(iter(dsm_devices))
|
||||||
|
@ -45,7 +38,7 @@ async def async_setup_services(hass: HomeAssistant) -> None:
|
||||||
return
|
return
|
||||||
|
|
||||||
if call.service in [SERVICE_REBOOT, SERVICE_SHUTDOWN]:
|
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)
|
LOGGER.error("DSM with specified serial %s not found", serial)
|
||||||
return
|
return
|
||||||
LOGGER.debug("%s DSM with serial %s", call.service, serial)
|
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",
|
"The %s service is deprecated and will be removed in future release. Please use the corresponding button entity",
|
||||||
call.service,
|
call.service,
|
||||||
)
|
)
|
||||||
dsm_api: SynoApi = dsm_device[SYNO_API]
|
dsm_device = hass.data[DOMAIN][serial]
|
||||||
|
dsm_api = dsm_device.api
|
||||||
try:
|
try:
|
||||||
await getattr(dsm_api, f"async_{call.service}")()
|
await getattr(dsm_api, f"async_{call.service}")()
|
||||||
except SynologyDSMException as ex:
|
except SynologyDSMException as ex:
|
||||||
|
|
|
@ -15,8 +15,9 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
|
|
||||||
from . import SynoApi
|
from . import SynoApi
|
||||||
from .const import COORDINATOR_SWITCHES, DOMAIN, SYNO_API
|
from .const import DOMAIN
|
||||||
from .entity import SynologyDSMBaseEntity, SynologyDSMEntityDescription
|
from .entity import SynologyDSMBaseEntity, SynologyDSMEntityDescription
|
||||||
|
from .models import SynologyDSMData
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -42,30 +43,16 @@ async def async_setup_entry(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Synology NAS switch."""
|
"""Set up the Synology NAS switch."""
|
||||||
|
data: SynologyDSMData = hass.data[DOMAIN][entry.unique_id]
|
||||||
data = hass.data[DOMAIN][entry.unique_id]
|
if coordinator := data.coordinator_switches:
|
||||||
api: SynoApi = data[SYNO_API]
|
assert coordinator.version is not None
|
||||||
|
async_add_entities(
|
||||||
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(
|
|
||||||
[
|
|
||||||
SynoDSMSurveillanceHomeModeToggle(
|
SynoDSMSurveillanceHomeModeToggle(
|
||||||
api, version, coordinator, description
|
data.api, coordinator.version, coordinator, description
|
||||||
)
|
)
|
||||||
for description in SURVEILLANCE_SWITCH
|
for description in SURVEILLANCE_SWITCH
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
async_add_entities(entities, True)
|
|
||||||
|
|
||||||
|
|
||||||
class SynoDSMSurveillanceHomeModeToggle(SynologyDSMBaseEntity, SwitchEntity):
|
class SynoDSMSurveillanceHomeModeToggle(SynologyDSMBaseEntity, SwitchEntity):
|
||||||
"""Representation a Synology Surveillance Station Home Mode toggle."""
|
"""Representation a Synology Surveillance Station Home Mode toggle."""
|
||||||
|
|
|
@ -13,9 +13,9 @@ from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity import EntityCategory
|
from homeassistant.helpers.entity import EntityCategory
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import SynoApi
|
from .const import DOMAIN
|
||||||
from .const import COORDINATOR_CENTRAL, DOMAIN, SYNO_API
|
|
||||||
from .entity import SynologyDSMBaseEntity, SynologyDSMEntityDescription
|
from .entity import SynologyDSMBaseEntity, SynologyDSMEntityDescription
|
||||||
|
from .models import SynologyDSMData
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -39,12 +39,9 @@ async def async_setup_entry(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Synology DSM update entities."""
|
"""Set up Synology DSM update entities."""
|
||||||
data = hass.data[DOMAIN][entry.unique_id]
|
data: SynologyDSMData = hass.data[DOMAIN][entry.unique_id]
|
||||||
api: SynoApi = data[SYNO_API]
|
|
||||||
coordinator = data[COORDINATOR_CENTRAL]
|
|
||||||
|
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
SynoDSMUpdateEntity(api, coordinator, description)
|
SynoDSMUpdateEntity(data.api, data.coordinator_central, description)
|
||||||
for description in UPDATE_ENTITIES
|
for description in UPDATE_ENTITIES
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -24,9 +24,9 @@ from tests.common import MockConfigEntry
|
||||||
@pytest.mark.no_bypass_setup
|
@pytest.mark.no_bypass_setup
|
||||||
async def test_services_registered(hass: HomeAssistant):
|
async def test_services_registered(hass: HomeAssistant):
|
||||||
"""Test if all services are registered."""
|
"""Test if all services are registered."""
|
||||||
with patch(
|
with patch("homeassistant.components.synology_dsm.common.SynologyDSM"), patch(
|
||||||
"homeassistant.components.synology_dsm.SynoApi.async_setup", return_value=True
|
"homeassistant.components.synology_dsm.PLATFORMS", return_value=[]
|
||||||
), patch("homeassistant.components.synology_dsm.PLATFORMS", return_value=[]):
|
):
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
data={
|
data={
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue