From d96249e39c8dbfa6568bb05688bd49110abfcb9b Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Tue, 23 Feb 2021 23:23:50 +0100 Subject: [PATCH] Implement additional DataUpdateCoordinator to harmonize the data update handling of Synology DSM (#46113) --- .../components/synology_dsm/__init__.py | 237 +++++++++--------- .../components/synology_dsm/binary_sensor.py | 21 +- .../components/synology_dsm/camera.py | 19 +- .../components/synology_dsm/config_flow.py | 3 +- .../components/synology_dsm/const.py | 6 +- .../components/synology_dsm/sensor.py | 42 +++- .../components/synology_dsm/switch.py | 58 ++--- 7 files changed, 210 insertions(+), 176 deletions(-) diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index b0d78ca6716..6f0476b403c 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -39,12 +39,6 @@ from homeassistant.core import ServiceCall, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import entity_registry import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.dispatcher import ( - async_dispatcher_connect, - async_dispatcher_send, -) -from homeassistant.helpers.entity import Entity -from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, @@ -53,9 +47,12 @@ from homeassistant.helpers.update_coordinator import ( ) from .const import ( + CONF_DEVICE_TOKEN, CONF_SERIAL, CONF_VOLUMES, - COORDINATOR_SURVEILLANCE, + COORDINATOR_CAMERAS, + COORDINATOR_CENTRAL, + COORDINATOR_SWITCHES, DEFAULT_SCAN_INTERVAL, DEFAULT_USE_SSL, DEFAULT_VERIFY_SSL, @@ -73,6 +70,7 @@ from .const import ( STORAGE_DISK_SENSORS, STORAGE_VOL_SENSORS, SYNO_API, + SYSTEM_LOADED, TEMP_SENSORS_KEYS, UNDO_UPDATE_LISTENER, UTILISATION_SENSORS, @@ -196,12 +194,11 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): _LOGGER.debug("async_setup_entry() - Unable to connect to DSM: %s", err) raise ConfigEntryNotReady from err - undo_listener = entry.add_update_listener(_async_update_listener) - hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.unique_id] = { + UNDO_UPDATE_LISTENER: entry.add_update_listener(_async_update_listener), SYNO_API: api, - UNDO_UPDATE_LISTENER: undo_listener, + SYSTEM_LOADED: True, } # Services @@ -214,32 +211,82 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): entry, data={**entry.data, CONF_MAC: network.macs} ) - # setup DataUpdateCoordinator - async def async_coordinator_update_data_surveillance_station(): - """Fetch all surveillance station data from api.""" + async def async_coordinator_update_data_cameras(): + """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: + return None + surveillance_station = api.surveillance_station + try: async with async_timeout.timeout(10): await hass.async_add_executor_job(surveillance_station.update) except SynologyDSMAPIErrorException as err: + _LOGGER.debug( + "async_coordinator_update_data_cameras() - exception: %s", err + ) raise UpdateFailed(f"Error communicating with API: {err}") from err - if SynoSurveillanceStation.CAMERA_API_KEY not in api.dsm.apis: - return - return { "cameras": { camera.id: camera for camera in surveillance_station.get_all_cameras() } } - hass.data[DOMAIN][entry.unique_id][ - COORDINATOR_SURVEILLANCE - ] = DataUpdateCoordinator( + async def async_coordinator_update_data_central(): + """Fetch all device and sensor data from api.""" + try: + await api.async_update() + except Exception as err: + _LOGGER.debug( + "async_coordinator_update_data_central() - exception: %s", err + ) + raise UpdateFailed(f"Error communicating with API: {err}") from err + return None + + async def async_coordinator_update_data_switches(): + """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 + ) + } + } + + hass.data[DOMAIN][entry.unique_id][COORDINATOR_CAMERAS] = DataUpdateCoordinator( hass, _LOGGER, - name=f"{entry.unique_id}_surveillance_station", - update_method=async_coordinator_update_data_surveillance_station, + 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), ) @@ -304,10 +351,11 @@ async def _async_setup_services(hass: HomeAssistantType): _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.system.shutdown() + await dsm_api.async_shutdown() for service in SERVICES: hass.services.async_register(DOMAIN, service, service_handler) @@ -342,16 +390,8 @@ class SynoApi: self._with_upgrade = True self._with_utilisation = True - self._unsub_dispatcher = None - - @property - def signal_sensor_update(self) -> str: - """Event specific per Synology DSM entry to signal updates in sensors.""" - return f"{DOMAIN}-{self.information.serial}-sensor-update" - async def async_setup(self): """Start interacting with the NAS.""" - # init SynologyDSM object and login self.dsm = SynologyDSM( self._entry.data[CONF_HOST], self._entry.data[CONF_PORT], @@ -360,7 +400,7 @@ class SynoApi: self._entry.data[CONF_SSL], self._entry.data[CONF_VERIFY_SSL], timeout=self._entry.options.get(CONF_TIMEOUT), - device_token=self._entry.data.get("device_token"), + device_token=self._entry.data.get(CONF_DEVICE_TOKEN), ) await self._hass.async_add_executor_job(self.dsm.login) @@ -373,24 +413,14 @@ class SynoApi: self._with_surveillance_station, ) - self._async_setup_api_requests() + self._setup_api_requests() await self._hass.async_add_executor_job(self._fetch_device_configuration) await self.async_update() - self._unsub_dispatcher = async_track_time_interval( - self._hass, - self.async_update, - timedelta( - minutes=self._entry.options.get( - CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL - ) - ), - ) - @callback def subscribe(self, api_key, unique_id): - """Subscribe an entity from API fetches.""" + """Subscribe an entity to API fetches.""" _LOGGER.debug( "SynoAPI.subscribe() - api_key:%s, unique_id:%s", api_key, unique_id ) @@ -401,31 +431,35 @@ class SynoApi: @callback def unsubscribe() -> None: """Unsubscribe an entity from API fetches (when disable).""" + _LOGGER.debug( + "SynoAPI.unsubscribe() - api_key:%s, unique_id:%s", api_key, unique_id + ) self._fetching_entities[api_key].remove(unique_id) + if len(self._fetching_entities[api_key]) == 0: + self._fetching_entities.pop(api_key) return unsubscribe @callback - def _async_setup_api_requests(self): + def _setup_api_requests(self): """Determine if we should fetch each API, if one entity needs it.""" - _LOGGER.debug( - "SynoAPI._async_setup_api_requests() - self._fetching_entities:%s", - self._fetching_entities, - ) - # Entities not added yet, fetch all if not self._fetching_entities: _LOGGER.debug( - "SynoAPI._async_setup_api_requests() - Entities not added yet, fetch all" + "SynoAPI._setup_api_requests() - Entities not added yet, fetch all" ) return # Determine if we should fetch an API + self._with_system = bool(self.dsm.apis.get(SynoCoreSystem.API_KEY)) + self._with_surveillance_station = bool( + self.dsm.apis.get(SynoSurveillanceStation.CAMERA_API_KEY) + ) or bool(self.dsm.apis.get(SynoSurveillanceStation.HOME_MODE_API_KEY)) + self._with_security = bool( self._fetching_entities.get(SynoCoreSecurity.API_KEY) ) self._with_storage = bool(self._fetching_entities.get(SynoStorage.API_KEY)) - self._with_system = bool(self._fetching_entities.get(SynoCoreSystem.API_KEY)) self._with_upgrade = bool(self._fetching_entities.get(SynoCoreUpgrade.API_KEY)) self._with_utilisation = bool( self._fetching_entities.get(SynoCoreUtilization.API_KEY) @@ -433,39 +467,36 @@ class SynoApi: self._with_information = bool( self._fetching_entities.get(SynoDSMInformation.API_KEY) ) - self._with_surveillance_station = bool( - self.dsm.apis.get(SynoSurveillanceStation.CAMERA_API_KEY) - ) # Reset not used API, information is not reset since it's used in device_info if not self._with_security: - _LOGGER.debug("SynoAPI._async_setup_api_requests() - disable security") + _LOGGER.debug("SynoAPI._setup_api_requests() - disable security") self.dsm.reset(self.security) self.security = None if not self._with_storage: - _LOGGER.debug("SynoAPI._async_setup_api_requests() - disable storage") + _LOGGER.debug("SynoAPI._setup_api_requests() - disable storage") self.dsm.reset(self.storage) self.storage = None if not self._with_system: - _LOGGER.debug("SynoAPI._async_setup_api_requests() - disable system") + _LOGGER.debug("SynoAPI._setup_api_requests() - disable system") self.dsm.reset(self.system) self.system = None if not self._with_upgrade: - _LOGGER.debug("SynoAPI._async_setup_api_requests() - disable upgrade") + _LOGGER.debug("SynoAPI._setup_api_requests() - disable upgrade") self.dsm.reset(self.upgrade) self.upgrade = None if not self._with_utilisation: - _LOGGER.debug("SynoAPI._async_setup_api_requests() - disable utilisation") + _LOGGER.debug("SynoAPI._setup_api_requests() - disable utilisation") self.dsm.reset(self.utilisation) self.utilisation = None if not self._with_surveillance_station: _LOGGER.debug( - "SynoAPI._async_setup_api_requests() - disable surveillance_station" + "SynoAPI._setup_api_requests() - disable surveillance_station" ) self.dsm.reset(self.surveillance_station) self.surveillance_station = None @@ -504,26 +535,31 @@ class SynoApi: async def async_reboot(self): """Reboot NAS.""" - if not self.system: - _LOGGER.debug("SynoAPI.async_reboot() - System API not ready: %s", self) - return - await self._hass.async_add_executor_job(self.system.reboot) + try: + await self._hass.async_add_executor_job(self.system.reboot) + except (SynologyDSMLoginFailedException, SynologyDSMRequestException) as err: + _LOGGER.error("Reboot not possible, please try again later") + _LOGGER.debug("Exception:%s", err) async def async_shutdown(self): """Shutdown NAS.""" - if not self.system: - _LOGGER.debug("SynoAPI.async_shutdown() - System API not ready: %s", self) - return - await self._hass.async_add_executor_job(self.system.shutdown) + try: + await self._hass.async_add_executor_job(self.system.shutdown) + except (SynologyDSMLoginFailedException, SynologyDSMRequestException) as err: + _LOGGER.error("Shutdown not possible, please try again later") + _LOGGER.debug("Exception:%s", err) async def async_unload(self): """Stop interacting with the NAS and prepare for removal from hass.""" - self._unsub_dispatcher() + try: + await self._hass.async_add_executor_job(self.dsm.logout) + except (SynologyDSMAPIErrorException, SynologyDSMRequestException) as err: + _LOGGER.debug("Logout not possible:%s", err) async def async_update(self, now=None): """Update function for updating API information.""" _LOGGER.debug("SynoAPI.async_update()") - self._async_setup_api_requests() + self._setup_api_requests() try: await self._hass.async_add_executor_job( self.dsm.update, self._with_information @@ -535,10 +571,9 @@ class SynoApi: _LOGGER.debug("SynoAPI.async_update() - exception: %s", err) await self._hass.config_entries.async_reload(self._entry.entry_id) return - async_dispatcher_send(self._hass, self.signal_sensor_update) -class SynologyDSMBaseEntity(Entity): +class SynologyDSMBaseEntity(CoordinatorEntity): """Representation of a Synology NAS entry.""" def __init__( @@ -546,8 +581,11 @@ class SynologyDSMBaseEntity(Entity): api: SynoApi, entity_type: str, entity_info: Dict[str, str], + coordinator: DataUpdateCoordinator, ): """Initialize the Synology DSM entity.""" + super().__init__(coordinator) + self._api = api self._api_key = entity_type.split(":")[0] self.entity_type = entity_type.split(":")[-1] @@ -606,59 +644,13 @@ class SynologyDSMBaseEntity(Entity): """Return if the entity should be enabled when first added to the entity registry.""" return self._enable_default - -class SynologyDSMDispatcherEntity(SynologyDSMBaseEntity, Entity): - """Representation of a Synology NAS entry.""" - - def __init__( - self, - api: SynoApi, - entity_type: str, - entity_info: Dict[str, str], - ): - """Initialize the Synology DSM entity.""" - super().__init__(api, entity_type, entity_info) - Entity.__init__(self) - - @property - def should_poll(self) -> bool: - """No polling needed.""" - return False - - async def async_update(self): - """Only used by the generic entity update service.""" - if not self.enabled: - return - - await self._api.async_update() - async def async_added_to_hass(self): - """Register state update callback.""" - self.async_on_remove( - async_dispatcher_connect( - self.hass, self._api.signal_sensor_update, self.async_write_ha_state - ) - ) - + """Register entity for updates from API.""" self.async_on_remove(self._api.subscribe(self._api_key, self.unique_id)) + await super().async_added_to_hass() -class SynologyDSMCoordinatorEntity(SynologyDSMBaseEntity, CoordinatorEntity): - """Representation of a Synology NAS entry.""" - - def __init__( - self, - api: SynoApi, - entity_type: str, - entity_info: Dict[str, str], - coordinator: DataUpdateCoordinator, - ): - """Initialize the Synology DSM entity.""" - super().__init__(api, entity_type, entity_info) - CoordinatorEntity.__init__(self, coordinator) - - -class SynologyDSMDeviceEntity(SynologyDSMDispatcherEntity): +class SynologyDSMDeviceEntity(SynologyDSMBaseEntity): """Representation of a Synology NAS disk or volume entry.""" def __init__( @@ -666,10 +658,11 @@ class SynologyDSMDeviceEntity(SynologyDSMDispatcherEntity): api: SynoApi, entity_type: str, entity_info: Dict[str, str], + coordinator: DataUpdateCoordinator, device_id: str = None, ): """Initialize the Synology DSM disk or volume entity.""" - super().__init__(api, entity_type, entity_info) + super().__init__(api, entity_type, entity_info, coordinator) self._device_id = device_id self._device_name = None self._device_manufacturer = None diff --git a/homeassistant/components/synology_dsm/binary_sensor.py b/homeassistant/components/synology_dsm/binary_sensor.py index 2bbfb8f4641..6e89f3d7a84 100644 --- a/homeassistant/components/synology_dsm/binary_sensor.py +++ b/homeassistant/components/synology_dsm/binary_sensor.py @@ -6,8 +6,9 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_DISKS from homeassistant.helpers.typing import HomeAssistantType -from . import SynologyDSMDeviceEntity, SynologyDSMDispatcherEntity +from . import SynologyDSMBaseEntity, SynologyDSMDeviceEntity from .const import ( + COORDINATOR_CENTRAL, DOMAIN, SECURITY_BINARY_SENSORS, STORAGE_DISK_BINARY_SENSORS, @@ -21,18 +22,20 @@ async def async_setup_entry( ) -> None: """Set up the Synology NAS binary sensor.""" - api = hass.data[DOMAIN][entry.unique_id][SYNO_API] + data = hass.data[DOMAIN][entry.unique_id] + api = data[SYNO_API] + coordinator = data[COORDINATOR_CENTRAL] entities = [ SynoDSMSecurityBinarySensor( - api, sensor_type, SECURITY_BINARY_SENSORS[sensor_type] + api, sensor_type, SECURITY_BINARY_SENSORS[sensor_type], coordinator ) for sensor_type in SECURITY_BINARY_SENSORS ] entities += [ SynoDSMUpgradeBinarySensor( - api, sensor_type, UPGRADE_BINARY_SENSORS[sensor_type] + api, sensor_type, UPGRADE_BINARY_SENSORS[sensor_type], coordinator ) for sensor_type in UPGRADE_BINARY_SENSORS ] @@ -42,7 +45,11 @@ async def async_setup_entry( for disk in entry.data.get(CONF_DISKS, api.storage.disks_ids): entities += [ SynoDSMStorageBinarySensor( - api, sensor_type, STORAGE_DISK_BINARY_SENSORS[sensor_type], disk + api, + sensor_type, + STORAGE_DISK_BINARY_SENSORS[sensor_type], + coordinator, + disk, ) for sensor_type in STORAGE_DISK_BINARY_SENSORS ] @@ -50,7 +57,7 @@ async def async_setup_entry( async_add_entities(entities) -class SynoDSMSecurityBinarySensor(SynologyDSMDispatcherEntity, BinarySensorEntity): +class SynoDSMSecurityBinarySensor(SynologyDSMBaseEntity, BinarySensorEntity): """Representation a Synology Security binary sensor.""" @property @@ -78,7 +85,7 @@ class SynoDSMStorageBinarySensor(SynologyDSMDeviceEntity, BinarySensorEntity): return getattr(self._api.storage, self.entity_type)(self._device_id) -class SynoDSMUpgradeBinarySensor(SynologyDSMDispatcherEntity, BinarySensorEntity): +class SynoDSMUpgradeBinarySensor(SynologyDSMBaseEntity, BinarySensorEntity): """Representation a Synology Upgrade binary sensor.""" @property diff --git a/homeassistant/components/synology_dsm/camera.py b/homeassistant/components/synology_dsm/camera.py index f24615bd28e..c0e0ded72ed 100644 --- a/homeassistant/components/synology_dsm/camera.py +++ b/homeassistant/components/synology_dsm/camera.py @@ -3,16 +3,19 @@ import logging from typing import Dict from synology_dsm.api.surveillance_station import SynoSurveillanceStation -from synology_dsm.exceptions import SynologyDSMAPIErrorException +from synology_dsm.exceptions import ( + SynologyDSMAPIErrorException, + SynologyDSMRequestException, +) from homeassistant.components.camera import SUPPORT_STREAM, Camera from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from . import SynoApi, SynologyDSMCoordinatorEntity +from . import SynoApi, SynologyDSMBaseEntity from .const import ( - COORDINATOR_SURVEILLANCE, + COORDINATOR_CAMERAS, DOMAIN, ENTITY_CLASS, ENTITY_ENABLE, @@ -37,7 +40,7 @@ async def async_setup_entry( return # initial data fetch - coordinator = data[COORDINATOR_SURVEILLANCE] + coordinator = data[COORDINATOR_CAMERAS] await coordinator.async_refresh() async_add_entities( @@ -46,7 +49,7 @@ async def async_setup_entry( ) -class SynoDSMCamera(SynologyDSMCoordinatorEntity, Camera): +class SynoDSMCamera(SynologyDSMBaseEntity, Camera): """Representation a Synology camera.""" def __init__( @@ -125,7 +128,11 @@ class SynoDSMCamera(SynologyDSMCoordinatorEntity, Camera): return None try: return self._api.surveillance_station.get_camera_image(self._camera_id) - except (SynologyDSMAPIErrorException) as err: + except ( + SynologyDSMAPIErrorException, + SynologyDSMRequestException, + ConnectionRefusedError, + ) as err: _LOGGER.debug( "SynoDSMCamera.camera_image(%s) - Exception:%s", self.camera_data.name, diff --git a/homeassistant/components/synology_dsm/config_flow.py b/homeassistant/components/synology_dsm/config_flow.py index f4638a5ec73..e7b510bb399 100644 --- a/homeassistant/components/synology_dsm/config_flow.py +++ b/homeassistant/components/synology_dsm/config_flow.py @@ -31,6 +31,7 @@ from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from .const import ( + CONF_DEVICE_TOKEN, CONF_VOLUMES, DEFAULT_PORT, DEFAULT_PORT_SSL, @@ -180,7 +181,7 @@ class SynologyDSMFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): CONF_MAC: api.network.macs, } if otp_code: - config_data["device_token"] = api.device_token + config_data[CONF_DEVICE_TOKEN] = api.device_token if user_input.get(CONF_DISKS): config_data[CONF_DISKS] = user_input[CONF_DISKS] if user_input.get(CONF_VOLUMES): diff --git a/homeassistant/components/synology_dsm/const.py b/homeassistant/components/synology_dsm/const.py index 1c4e004f749..97f378c8e76 100644 --- a/homeassistant/components/synology_dsm/const.py +++ b/homeassistant/components/synology_dsm/const.py @@ -19,7 +19,10 @@ from homeassistant.const import ( DOMAIN = "synology_dsm" PLATFORMS = ["binary_sensor", "camera", "sensor", "switch"] -COORDINATOR_SURVEILLANCE = "coordinator_surveillance_station" +COORDINATOR_CAMERAS = "coordinator_cameras" +COORDINATOR_CENTRAL = "coordinator_central" +COORDINATOR_SWITCHES = "coordinator_switches" +SYSTEM_LOADED = "system_loaded" # Entry keys SYNO_API = "syno_api" @@ -28,6 +31,7 @@ UNDO_UPDATE_LISTENER = "undo_update_listener" # Configuration CONF_SERIAL = "serial" CONF_VOLUMES = "volumes" +CONF_DEVICE_TOKEN = "device_token" DEFAULT_USE_SSL = True DEFAULT_VERIFY_SSL = False diff --git a/homeassistant/components/synology_dsm/sensor.py b/homeassistant/components/synology_dsm/sensor.py index 7dd4e5e9870..79350ce89d3 100644 --- a/homeassistant/components/synology_dsm/sensor.py +++ b/homeassistant/components/synology_dsm/sensor.py @@ -13,11 +13,13 @@ from homeassistant.const import ( ) from homeassistant.helpers.temperature import display_temp from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.util.dt import utcnow -from . import SynoApi, SynologyDSMDeviceEntity, SynologyDSMDispatcherEntity +from . import SynoApi, SynologyDSMBaseEntity, SynologyDSMDeviceEntity from .const import ( CONF_VOLUMES, + COORDINATOR_CENTRAL, DOMAIN, ENTITY_UNIT_LOAD, INFORMATION_SENSORS, @@ -34,10 +36,14 @@ async def async_setup_entry( ) -> None: """Set up the Synology NAS Sensor.""" - api = hass.data[DOMAIN][entry.unique_id][SYNO_API] + data = hass.data[DOMAIN][entry.unique_id] + api = data[SYNO_API] + coordinator = data[COORDINATOR_CENTRAL] entities = [ - SynoDSMUtilSensor(api, sensor_type, UTILISATION_SENSORS[sensor_type]) + SynoDSMUtilSensor( + api, sensor_type, UTILISATION_SENSORS[sensor_type], coordinator + ) for sensor_type in UTILISATION_SENSORS ] @@ -46,7 +52,11 @@ async def async_setup_entry( for volume in entry.data.get(CONF_VOLUMES, api.storage.volumes_ids): entities += [ SynoDSMStorageSensor( - api, sensor_type, STORAGE_VOL_SENSORS[sensor_type], volume + api, + sensor_type, + STORAGE_VOL_SENSORS[sensor_type], + coordinator, + volume, ) for sensor_type in STORAGE_VOL_SENSORS ] @@ -56,20 +66,26 @@ async def async_setup_entry( for disk in entry.data.get(CONF_DISKS, api.storage.disks_ids): entities += [ SynoDSMStorageSensor( - api, sensor_type, STORAGE_DISK_SENSORS[sensor_type], disk + api, + sensor_type, + STORAGE_DISK_SENSORS[sensor_type], + coordinator, + disk, ) for sensor_type in STORAGE_DISK_SENSORS ] entities += [ - SynoDSMInfoSensor(api, sensor_type, INFORMATION_SENSORS[sensor_type]) + SynoDSMInfoSensor( + api, sensor_type, INFORMATION_SENSORS[sensor_type], coordinator + ) for sensor_type in INFORMATION_SENSORS ] async_add_entities(entities) -class SynoDSMUtilSensor(SynologyDSMDispatcherEntity): +class SynoDSMUtilSensor(SynologyDSMBaseEntity): """Representation a Synology Utilisation sensor.""" @property @@ -122,12 +138,18 @@ class SynoDSMStorageSensor(SynologyDSMDeviceEntity): return attr -class SynoDSMInfoSensor(SynologyDSMDispatcherEntity): +class SynoDSMInfoSensor(SynologyDSMBaseEntity): """Representation a Synology information sensor.""" - def __init__(self, api: SynoApi, entity_type: str, entity_info: Dict[str, str]): + def __init__( + self, + api: SynoApi, + entity_type: str, + entity_info: Dict[str, str], + coordinator: DataUpdateCoordinator, + ): """Initialize the Synology SynoDSMInfoSensor entity.""" - super().__init__(api, entity_type, entity_info) + super().__init__(api, entity_type, entity_info, coordinator) self._previous_uptime = None self._last_boot = None diff --git a/homeassistant/components/synology_dsm/switch.py b/homeassistant/components/synology_dsm/switch.py index 21511757cf3..998f74adf2a 100644 --- a/homeassistant/components/synology_dsm/switch.py +++ b/homeassistant/components/synology_dsm/switch.py @@ -7,9 +7,10 @@ from synology_dsm.api.surveillance_station import SynoSurveillanceStation from homeassistant.components.switch import ToggleEntity from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from . import SynoApi, SynologyDSMDispatcherEntity -from .const import DOMAIN, SURVEILLANCE_SWITCH, SYNO_API +from . import SynoApi, SynologyDSMBaseEntity +from .const import COORDINATOR_SWITCHES, DOMAIN, SURVEILLANCE_SWITCH, SYNO_API _LOGGER = logging.getLogger(__name__) @@ -19,16 +20,21 @@ async def async_setup_entry( ) -> None: """Set up the Synology NAS switch.""" - api = hass.data[DOMAIN][entry.unique_id][SYNO_API] + data = hass.data[DOMAIN][entry.unique_id] + api = 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 = data[COORDINATOR_SWITCHES] + await coordinator.async_refresh() entities += [ SynoDSMSurveillanceHomeModeToggle( - api, sensor_type, SURVEILLANCE_SWITCH[sensor_type], version + api, sensor_type, SURVEILLANCE_SWITCH[sensor_type], version, coordinator ) for sensor_type in SURVEILLANCE_SWITCH ] @@ -36,58 +42,52 @@ async def async_setup_entry( async_add_entities(entities, True) -class SynoDSMSurveillanceHomeModeToggle(SynologyDSMDispatcherEntity, ToggleEntity): +class SynoDSMSurveillanceHomeModeToggle(SynologyDSMBaseEntity, ToggleEntity): """Representation a Synology Surveillance Station Home Mode toggle.""" def __init__( - self, api: SynoApi, entity_type: str, entity_info: Dict[str, str], version: str + self, + api: SynoApi, + entity_type: str, + entity_info: Dict[str, str], + version: str, + coordinator: DataUpdateCoordinator, ): """Initialize a Synology Surveillance Station Home Mode.""" super().__init__( api, entity_type, entity_info, + coordinator, ) self._version = version - self._state = None @property def is_on(self) -> bool: """Return the state.""" - if self.entity_type == "home_mode": - return self._state - return None + return self.coordinator.data["switches"][self.entity_type] - @property - def should_poll(self) -> bool: - """No polling needed.""" - return True - - async def async_update(self): - """Update the toggle state.""" - _LOGGER.debug( - "SynoDSMSurveillanceHomeModeToggle.async_update(%s)", - self._api.information.serial, - ) - self._state = await self.hass.async_add_executor_job( - self._api.surveillance_station.get_home_mode_status - ) - - def turn_on(self, **kwargs) -> None: + async def async_turn_on(self, **kwargs) -> None: """Turn on Home mode.""" _LOGGER.debug( "SynoDSMSurveillanceHomeModeToggle.turn_on(%s)", self._api.information.serial, ) - self._api.surveillance_station.set_home_mode(True) + await self.hass.async_add_executor_job( + self._api.dsm.surveillance_station.set_home_mode, True + ) + await self.coordinator.async_request_refresh() - def turn_off(self, **kwargs) -> None: + async def async_turn_off(self, **kwargs) -> None: """Turn off Home mode.""" _LOGGER.debug( "SynoDSMSurveillanceHomeModeToggle.turn_off(%s)", self._api.information.serial, ) - self._api.surveillance_station.set_home_mode(False) + await self.hass.async_add_executor_job( + self._api.dsm.surveillance_station.set_home_mode, False + ) + await self.coordinator.async_request_refresh() @property def available(self) -> bool: