Bump py-synologydsm-api to 2.4.2 (#115499)
Co-authored-by: mib1185 <mail@mib85.de>
This commit is contained in:
parent
64a4d52b3c
commit
008c42e282
17 changed files with 170 additions and 91 deletions
|
@ -11,10 +11,10 @@ from synology_dsm.api.surveillance_station.camera import SynoCamera
|
|||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_MAC, CONF_VERIFY_SSL
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import config_validation as cv, device_registry as dr
|
||||
|
||||
from .common import SynoApi
|
||||
from .common import SynoApi, raise_config_entry_auth_error
|
||||
from .const import (
|
||||
DEFAULT_VERIFY_SSL,
|
||||
DOMAIN,
|
||||
|
@ -68,11 +68,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
try:
|
||||
await api.async_setup()
|
||||
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
|
||||
raise_config_entry_auth_error(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)
|
||||
|
@ -147,8 +143,10 @@ async def async_remove_config_entry_device(
|
|||
"""Remove synology_dsm config entry from a device."""
|
||||
data: SynologyDSMData = hass.data[DOMAIN][entry.unique_id]
|
||||
api = data.api
|
||||
assert api.information is not None
|
||||
serial = api.information.serial
|
||||
storage = api.storage
|
||||
assert storage is not None
|
||||
all_cameras: list[SynoCamera] = []
|
||||
if api.surveillance_station is not None:
|
||||
# get_all_cameras does not do I/O
|
||||
|
|
|
@ -69,6 +69,7 @@ async def async_setup_entry(
|
|||
data: SynologyDSMData = hass.data[DOMAIN][entry.unique_id]
|
||||
api = data.api
|
||||
coordinator = data.coordinator_central
|
||||
assert api.storage is not None
|
||||
|
||||
entities: list[SynoDSMSecurityBinarySensor | SynoDSMStorageBinarySensor] = [
|
||||
SynoDSMSecurityBinarySensor(api, coordinator, description)
|
||||
|
@ -121,7 +122,8 @@ class SynoDSMSecurityBinarySensor(SynoDSMBinarySensor):
|
|||
@property
|
||||
def extra_state_attributes(self) -> dict[str, str]:
|
||||
"""Return security checks details."""
|
||||
return self._api.security.status_by_check # type: ignore[no-any-return]
|
||||
assert self._api.security is not None
|
||||
return self._api.security.status_by_check
|
||||
|
||||
|
||||
class SynoDSMStorageBinarySensor(SynologyDSMDeviceEntity, SynoDSMBinarySensor):
|
||||
|
|
|
@ -73,7 +73,8 @@ class SynologyDSMButton(ButtonEntity):
|
|||
"""Initialize the Synology DSM binary_sensor entity."""
|
||||
self.entity_description = description
|
||||
self.syno_api = api
|
||||
|
||||
assert api.network is not None
|
||||
assert api.information is not None
|
||||
self._attr_name = f"{api.network.hostname} {description.name}"
|
||||
self._attr_unique_id = f"{api.information.serial}_{description.key}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
|
@ -82,6 +83,7 @@ class SynologyDSMButton(ButtonEntity):
|
|||
|
||||
async def async_press(self) -> None:
|
||||
"""Triggers the Synology DSM button press service."""
|
||||
assert self.syno_api.network is not None
|
||||
LOGGER.debug(
|
||||
"Trigger %s for %s",
|
||||
self.entity_description.key,
|
||||
|
|
|
@ -42,6 +42,8 @@ class SynologyDSMCameraEntityDescription(
|
|||
):
|
||||
"""Describes Synology DSM camera entity."""
|
||||
|
||||
camera_id: int
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
|
@ -65,12 +67,13 @@ class SynoDSMCamera(SynologyDSMBaseEntity[SynologyDSMCameraUpdateCoordinator], C
|
|||
self,
|
||||
api: SynoApi,
|
||||
coordinator: SynologyDSMCameraUpdateCoordinator,
|
||||
camera_id: str,
|
||||
camera_id: int,
|
||||
) -> None:
|
||||
"""Initialize a Synology camera."""
|
||||
description = SynologyDSMCameraEntityDescription(
|
||||
api_key=SynoSurveillanceStation.CAMERA_API_KEY,
|
||||
key=camera_id,
|
||||
key=str(camera_id),
|
||||
camera_id=camera_id,
|
||||
name=coordinator.data["cameras"][camera_id].name,
|
||||
entity_registry_enabled_default=coordinator.data["cameras"][
|
||||
camera_id
|
||||
|
@ -85,23 +88,20 @@ class SynoDSMCamera(SynologyDSMBaseEntity[SynologyDSMCameraUpdateCoordinator], C
|
|||
@property
|
||||
def camera_data(self) -> SynoCamera:
|
||||
"""Camera data."""
|
||||
return self.coordinator.data["cameras"][self.entity_description.key]
|
||||
return self.coordinator.data["cameras"][self.entity_description.camera_id]
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return the device information."""
|
||||
information = self._api.information
|
||||
assert information is not None
|
||||
return DeviceInfo(
|
||||
identifiers={
|
||||
(
|
||||
DOMAIN,
|
||||
f"{self._api.information.serial}_{self.camera_data.id}",
|
||||
)
|
||||
},
|
||||
identifiers={(DOMAIN, f"{information.serial}_{self.camera_data.id}")},
|
||||
name=self.camera_data.name,
|
||||
model=self.camera_data.model,
|
||||
via_device=(
|
||||
DOMAIN,
|
||||
f"{self._api.information.serial}_{SynoSurveillanceStation.INFO_API_KEY}",
|
||||
f"{information.serial}_{SynoSurveillanceStation.INFO_API_KEY}",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -113,12 +113,12 @@ class SynoDSMCamera(SynologyDSMBaseEntity[SynologyDSMCameraUpdateCoordinator], C
|
|||
@property
|
||||
def is_recording(self) -> bool:
|
||||
"""Return true if the device is recording."""
|
||||
return self.camera_data.is_recording # type: ignore[no-any-return]
|
||||
return self.camera_data.is_recording
|
||||
|
||||
@property
|
||||
def motion_detection_enabled(self) -> bool:
|
||||
"""Return the camera motion detection status."""
|
||||
return self.camera_data.is_motion_detection_enabled # type: ignore[no-any-return]
|
||||
return bool(self.camera_data.is_motion_detection_enabled)
|
||||
|
||||
def _listen_source_updates(self) -> None:
|
||||
"""Listen for camera source changed events."""
|
||||
|
@ -153,9 +153,10 @@ class SynoDSMCamera(SynologyDSMBaseEntity[SynologyDSMCameraUpdateCoordinator], C
|
|||
)
|
||||
if not self.available:
|
||||
return None
|
||||
assert self._api.surveillance_station is not None
|
||||
try:
|
||||
return await self._api.surveillance_station.get_camera_image( # type: ignore[no-any-return]
|
||||
self.entity_description.key, self.snapshot_quality
|
||||
return await self._api.surveillance_station.get_camera_image(
|
||||
self.entity_description.camera_id, self.snapshot_quality
|
||||
)
|
||||
except (
|
||||
SynologyDSMAPIErrorException,
|
||||
|
@ -178,7 +179,7 @@ class SynoDSMCamera(SynologyDSMBaseEntity[SynologyDSMCameraUpdateCoordinator], C
|
|||
if not self.available:
|
||||
return None
|
||||
|
||||
return self.camera_data.live_view.rtsp # type: ignore[no-any-return]
|
||||
return self.camera_data.live_view.rtsp
|
||||
|
||||
async def async_enable_motion_detection(self) -> None:
|
||||
"""Enable motion detection in the camera."""
|
||||
|
@ -186,8 +187,9 @@ class SynoDSMCamera(SynologyDSMBaseEntity[SynologyDSMCameraUpdateCoordinator], C
|
|||
"SynoDSMCamera.enable_motion_detection(%s)",
|
||||
self.camera_data.name,
|
||||
)
|
||||
assert self._api.surveillance_station is not None
|
||||
await self._api.surveillance_station.enable_motion_detection(
|
||||
self.entity_description.key
|
||||
self.entity_description.camera_id
|
||||
)
|
||||
|
||||
async def async_disable_motion_detection(self) -> None:
|
||||
|
@ -196,6 +198,7 @@ class SynoDSMCamera(SynologyDSMBaseEntity[SynologyDSMCameraUpdateCoordinator], C
|
|||
"SynoDSMCamera.disable_motion_detection(%s)",
|
||||
self.camera_data.name,
|
||||
)
|
||||
assert self._api.surveillance_station is not None
|
||||
await self._api.surveillance_station.disable_motion_detection(
|
||||
self.entity_description.key
|
||||
self.entity_description.camera_id
|
||||
)
|
||||
|
|
|
@ -33,9 +33,15 @@ from homeassistant.const import (
|
|||
CONF_VERIFY_SSL,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import CONF_DEVICE_TOKEN, SYNOLOGY_CONNECTION_EXCEPTIONS
|
||||
from .const import (
|
||||
CONF_DEVICE_TOKEN,
|
||||
EXCEPTION_DETAILS,
|
||||
EXCEPTION_UNKNOWN,
|
||||
SYNOLOGY_CONNECTION_EXCEPTIONS,
|
||||
)
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -43,6 +49,8 @@ LOGGER = logging.getLogger(__name__)
|
|||
class SynoApi:
|
||||
"""Class to interface with Synology DSM API."""
|
||||
|
||||
dsm: SynologyDSM
|
||||
|
||||
def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
"""Initialize the API wrapper class."""
|
||||
self._hass = hass
|
||||
|
@ -53,16 +61,15 @@ class SynoApi:
|
|||
self.config_url = f"http://{entry.data[CONF_HOST]}:{entry.data[CONF_PORT]}"
|
||||
|
||||
# DSM APIs
|
||||
self.dsm: SynologyDSM = None
|
||||
self.information: SynoDSMInformation = None
|
||||
self.network: SynoDSMNetwork = None
|
||||
self.security: SynoCoreSecurity = None
|
||||
self.storage: SynoStorage = None
|
||||
self.photos: SynoPhotos = None
|
||||
self.surveillance_station: SynoSurveillanceStation = None
|
||||
self.system: SynoCoreSystem = None
|
||||
self.upgrade: SynoCoreUpgrade = None
|
||||
self.utilisation: SynoCoreUtilization = None
|
||||
self.information: SynoDSMInformation | None = None
|
||||
self.network: SynoDSMNetwork | None = None
|
||||
self.security: SynoCoreSecurity | None = None
|
||||
self.storage: SynoStorage | None = None
|
||||
self.photos: SynoPhotos | None = None
|
||||
self.surveillance_station: SynoSurveillanceStation | None = None
|
||||
self.system: SynoCoreSystem | None = None
|
||||
self.upgrade: SynoCoreUpgrade | None = None
|
||||
self.utilisation: SynoCoreUtilization | None = None
|
||||
|
||||
# Should we fetch them
|
||||
self._fetching_entities: dict[str, set[str]] = {}
|
||||
|
@ -85,7 +92,7 @@ class SynoApi:
|
|||
self._entry.data[CONF_USERNAME],
|
||||
self._entry.data[CONF_PASSWORD],
|
||||
self._entry.data[CONF_SSL],
|
||||
timeout=self._entry.options.get(CONF_TIMEOUT),
|
||||
timeout=self._entry.options.get(CONF_TIMEOUT) or 10,
|
||||
device_token=self._entry.data.get(CONF_DEVICE_TOKEN),
|
||||
)
|
||||
await self.dsm.login()
|
||||
|
@ -159,6 +166,7 @@ class SynoApi:
|
|||
return
|
||||
|
||||
# surveillance_station is updated by own coordinator
|
||||
if self.surveillance_station:
|
||||
self.dsm.reset(self.surveillance_station)
|
||||
|
||||
# Determine if we should fetch an API
|
||||
|
@ -182,6 +190,7 @@ class SynoApi:
|
|||
"Disable security api from being updated for '%s'",
|
||||
self._entry.unique_id,
|
||||
)
|
||||
if self.security:
|
||||
self.dsm.reset(self.security)
|
||||
self.security = None
|
||||
|
||||
|
@ -189,6 +198,7 @@ class SynoApi:
|
|||
LOGGER.debug(
|
||||
"Disable photos api from being updated or '%s'", self._entry.unique_id
|
||||
)
|
||||
if self.photos:
|
||||
self.dsm.reset(self.photos)
|
||||
self.photos = None
|
||||
|
||||
|
@ -196,6 +206,7 @@ class SynoApi:
|
|||
LOGGER.debug(
|
||||
"Disable storage api from being updatedf or '%s'", self._entry.unique_id
|
||||
)
|
||||
if self.storage:
|
||||
self.dsm.reset(self.storage)
|
||||
self.storage = None
|
||||
|
||||
|
@ -203,6 +214,7 @@ class SynoApi:
|
|||
LOGGER.debug(
|
||||
"Disable system api from being updated for '%s'", self._entry.unique_id
|
||||
)
|
||||
if self.system:
|
||||
self.dsm.reset(self.system)
|
||||
self.system = None
|
||||
|
||||
|
@ -210,6 +222,7 @@ class SynoApi:
|
|||
LOGGER.debug(
|
||||
"Disable upgrade api from being updated for '%s'", self._entry.unique_id
|
||||
)
|
||||
if self.upgrade:
|
||||
self.dsm.reset(self.upgrade)
|
||||
self.upgrade = None
|
||||
|
||||
|
@ -218,6 +231,7 @@ class SynoApi:
|
|||
"Disable utilisation api from being updated for '%s'",
|
||||
self._entry.unique_id,
|
||||
)
|
||||
if self.utilisation:
|
||||
self.dsm.reset(self.utilisation)
|
||||
self.utilisation = None
|
||||
|
||||
|
@ -272,10 +286,12 @@ class SynoApi:
|
|||
|
||||
async def async_reboot(self) -> None:
|
||||
"""Reboot NAS."""
|
||||
if self.system:
|
||||
await self._syno_api_executer(self.system.reboot)
|
||||
|
||||
async def async_shutdown(self) -> None:
|
||||
"""Shutdown NAS."""
|
||||
if self.system:
|
||||
await self._syno_api_executer(self.system.shutdown)
|
||||
|
||||
async def async_unload(self) -> None:
|
||||
|
@ -293,3 +309,12 @@ class SynoApi:
|
|||
LOGGER.debug("Start data update for '%s'", self._entry.unique_id)
|
||||
self._setup_api_requests()
|
||||
await self.dsm.update(self._with_information)
|
||||
|
||||
|
||||
def raise_config_entry_auth_error(err: Exception) -> None:
|
||||
"""Raise ConfigEntryAuthFailed if error is related to authentication."""
|
||||
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
|
||||
|
|
|
@ -425,7 +425,7 @@ async def _login_and_fetch_syno_info(api: SynologyDSM, otp_code: str | None) ->
|
|||
):
|
||||
raise InvalidData
|
||||
|
||||
return api.information.serial # type: ignore[no-any-return]
|
||||
return api.information.serial
|
||||
|
||||
|
||||
class InvalidData(HomeAssistantError):
|
||||
|
|
|
@ -7,7 +7,10 @@ import logging
|
|||
from typing import Any, TypeVar
|
||||
|
||||
from synology_dsm.api.surveillance_station.camera import SynoCamera
|
||||
from synology_dsm.exceptions import SynologyDSMAPIErrorException
|
||||
from synology_dsm.exceptions import (
|
||||
SynologyDSMAPIErrorException,
|
||||
SynologyDSMNotLoggedInException,
|
||||
)
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_SCAN_INTERVAL
|
||||
|
@ -15,10 +18,11 @@ 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 .common import SynoApi, raise_config_entry_auth_error
|
||||
from .const import (
|
||||
DEFAULT_SCAN_INTERVAL,
|
||||
SIGNAL_CAMERA_SOURCE_CHANGED,
|
||||
SYNOLOGY_AUTH_FAILED_EXCEPTIONS,
|
||||
SYNOLOGY_CONNECTION_EXCEPTIONS,
|
||||
)
|
||||
|
||||
|
@ -65,13 +69,17 @@ class SynologyDSMSwitchUpdateCoordinator(
|
|||
async def async_setup(self) -> None:
|
||||
"""Set up the coordinator initial data."""
|
||||
info = await self.api.dsm.surveillance_station.get_info()
|
||||
assert info is not None
|
||||
self.version = info["data"]["CMSMinVersion"]
|
||||
|
||||
async def _async_update_data(self) -> dict[str, dict[str, Any]]:
|
||||
"""Fetch all data from api."""
|
||||
surveillance_station = self.api.surveillance_station
|
||||
assert surveillance_station is not None
|
||||
return {
|
||||
"switches": {"home_mode": await surveillance_station.get_home_mode_status()}
|
||||
"switches": {
|
||||
"home_mode": bool(await surveillance_station.get_home_mode_status())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -96,14 +104,23 @@ class SynologyDSMCentralUpdateCoordinator(SynologyDSMUpdateCoordinator[None]):
|
|||
|
||||
async def _async_update_data(self) -> None:
|
||||
"""Fetch all data from api."""
|
||||
for attempts in range(2):
|
||||
try:
|
||||
await self.api.async_update()
|
||||
except SynologyDSMNotLoggedInException:
|
||||
# If login is expired, try to login again
|
||||
try:
|
||||
await self.api.dsm.login()
|
||||
except SYNOLOGY_AUTH_FAILED_EXCEPTIONS as err:
|
||||
raise_config_entry_auth_error(err)
|
||||
if attempts == 0:
|
||||
continue
|
||||
except SYNOLOGY_CONNECTION_EXCEPTIONS as err:
|
||||
raise UpdateFailed(f"Error communicating with API: {err}") from err
|
||||
|
||||
|
||||
class SynologyDSMCameraUpdateCoordinator(
|
||||
SynologyDSMUpdateCoordinator[dict[str, dict[str, SynoCamera]]]
|
||||
SynologyDSMUpdateCoordinator[dict[str, dict[int, SynoCamera]]]
|
||||
):
|
||||
"""DataUpdateCoordinator to gather data for a synology_dsm cameras."""
|
||||
|
||||
|
@ -116,10 +133,11 @@ class SynologyDSMCameraUpdateCoordinator(
|
|||
"""Initialize DataUpdateCoordinator for cameras."""
|
||||
super().__init__(hass, entry, api, timedelta(seconds=30))
|
||||
|
||||
async def _async_update_data(self) -> dict[str, dict[str, SynoCamera]]:
|
||||
async def _async_update_data(self) -> dict[str, dict[int, SynoCamera]]:
|
||||
"""Fetch all camera data from api."""
|
||||
surveillance_station = self.api.surveillance_station
|
||||
current_data: dict[str, SynoCamera] = {
|
||||
assert surveillance_station is not None
|
||||
current_data: dict[int, SynoCamera] = {
|
||||
camera.id: camera for camera in surveillance_station.get_all_cameras()
|
||||
}
|
||||
|
||||
|
@ -128,7 +146,7 @@ class SynologyDSMCameraUpdateCoordinator(
|
|||
except SynologyDSMAPIErrorException as err:
|
||||
raise UpdateFailed(f"Error communicating with API: {err}") from err
|
||||
|
||||
new_data: dict[str, SynoCamera] = {
|
||||
new_data: dict[int, SynoCamera] = {
|
||||
camera.id: camera for camera in surveillance_station.get_all_cameras()
|
||||
}
|
||||
|
||||
|
|
|
@ -4,8 +4,6 @@ from __future__ import annotations
|
|||
|
||||
from typing import Any
|
||||
|
||||
from synology_dsm.api.surveillance_station.camera import SynoCamera
|
||||
|
||||
from homeassistant.components.camera import diagnostics as camera_diagnostics
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
|
@ -47,7 +45,6 @@ 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"]] = {
|
||||
"type": intf["type"],
|
||||
|
@ -55,7 +52,6 @@ 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"]] = {
|
||||
"name": disk["name"],
|
||||
|
@ -66,7 +62,6 @@ async def async_get_config_entry_diagnostics(
|
|||
"size_total": disk["size_total"],
|
||||
}
|
||||
|
||||
volume: dict
|
||||
for volume in syno_api.storage.volumes:
|
||||
diag_data["storage"]["volumes"][volume["id"]] = {
|
||||
"name": volume["fs_type"],
|
||||
|
@ -74,7 +69,6 @@ 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] = {
|
||||
"name": camera.name,
|
||||
|
|
|
@ -45,16 +45,21 @@ class SynologyDSMBaseEntity(CoordinatorEntity[_CoordinatorT]):
|
|||
self.entity_description = description
|
||||
|
||||
self._api = api
|
||||
information = api.information
|
||||
network = api.network
|
||||
assert information is not None
|
||||
assert network is not None
|
||||
|
||||
self._attr_unique_id: str = (
|
||||
f"{api.information.serial}_{description.api_key}:{description.key}"
|
||||
f"{information.serial}_{description.api_key}:{description.key}"
|
||||
)
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, self._api.information.serial)},
|
||||
name=self._api.network.hostname,
|
||||
identifiers={(DOMAIN, information.serial)},
|
||||
name=network.hostname,
|
||||
manufacturer="Synology",
|
||||
model=self._api.information.model,
|
||||
sw_version=self._api.information.version_string,
|
||||
configuration_url=self._api.config_url,
|
||||
model=information.model,
|
||||
sw_version=information.version_string,
|
||||
configuration_url=api.config_url,
|
||||
)
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
|
@ -85,14 +90,22 @@ class SynologyDSMDeviceEntity(
|
|||
self._device_model: str | None = None
|
||||
self._device_firmware: str | None = None
|
||||
self._device_type = None
|
||||
storage = api.storage
|
||||
information = api.information
|
||||
network = api.network
|
||||
assert information is not None
|
||||
assert storage is not None
|
||||
assert network is not None
|
||||
|
||||
if "volume" in description.key:
|
||||
volume = self._api.storage.get_volume(self._device_id)
|
||||
assert self._device_id is not None
|
||||
volume = storage.get_volume(self._device_id)
|
||||
assert volume is not None
|
||||
# Volume does not have a name
|
||||
self._device_name = volume["id"].replace("_", " ").capitalize()
|
||||
self._device_manufacturer = "Synology"
|
||||
self._device_model = self._api.information.model
|
||||
self._device_firmware = self._api.information.version_string
|
||||
self._device_model = information.model
|
||||
self._device_firmware = information.version_string
|
||||
self._device_type = (
|
||||
volume["device_type"]
|
||||
.replace("_", " ")
|
||||
|
@ -100,7 +113,9 @@ class SynologyDSMDeviceEntity(
|
|||
.replace("shr", "SHR")
|
||||
)
|
||||
elif "disk" in description.key:
|
||||
disk = self._api.storage.get_disk(self._device_id)
|
||||
assert self._device_id is not None
|
||||
disk = storage.get_disk(self._device_id)
|
||||
assert disk is not None
|
||||
self._device_name = disk["name"]
|
||||
self._device_manufacturer = disk["vendor"]
|
||||
self._device_model = disk["model"].strip()
|
||||
|
@ -109,11 +124,11 @@ class SynologyDSMDeviceEntity(
|
|||
|
||||
self._attr_unique_id += f"_{self._device_id}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, f"{self._api.information.serial}_{self._device_id}")},
|
||||
name=f"{self._api.network.hostname} ({self._device_name})",
|
||||
identifiers={(DOMAIN, f"{information.serial}_{self._device_id}")},
|
||||
name=f"{network.hostname} ({self._device_name})",
|
||||
manufacturer=self._device_manufacturer,
|
||||
model=self._device_model,
|
||||
sw_version=self._device_firmware,
|
||||
via_device=(DOMAIN, self._api.information.serial),
|
||||
via_device=(DOMAIN, information.serial),
|
||||
configuration_url=self._api.config_url,
|
||||
)
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
"documentation": "https://www.home-assistant.io/integrations/synology_dsm",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["synology_dsm"],
|
||||
"requirements": ["py-synologydsm-api==2.1.4"],
|
||||
"requirements": ["py-synologydsm-api==2.4.2"],
|
||||
"ssdp": [
|
||||
{
|
||||
"manufacturer": "Synology",
|
||||
|
|
|
@ -105,6 +105,7 @@ class SynologyPhotosMediaSource(MediaSource):
|
|||
]
|
||||
identifier = SynologyPhotosMediaSourceIdentifier(item.identifier)
|
||||
diskstation: SynologyDSMData = self.hass.data[DOMAIN][identifier.unique_id]
|
||||
assert diskstation.api.photos is not None
|
||||
|
||||
if identifier.album_id is None:
|
||||
# Get Albums
|
||||
|
@ -112,6 +113,7 @@ class SynologyPhotosMediaSource(MediaSource):
|
|||
albums = await diskstation.api.photos.get_albums()
|
||||
except SynologyDSMException:
|
||||
return []
|
||||
assert albums is not None
|
||||
|
||||
ret = [
|
||||
BrowseMediaSource(
|
||||
|
@ -148,6 +150,7 @@ class SynologyPhotosMediaSource(MediaSource):
|
|||
)
|
||||
except SynologyDSMException:
|
||||
return []
|
||||
assert album_items is not None
|
||||
|
||||
ret = []
|
||||
for album_item in album_items:
|
||||
|
@ -190,6 +193,8 @@ class SynologyPhotosMediaSource(MediaSource):
|
|||
self, item: SynoPhotosItem, diskstation: SynologyDSMData
|
||||
) -> str | None:
|
||||
"""Get thumbnail."""
|
||||
assert diskstation.api.photos is not None
|
||||
|
||||
try:
|
||||
thumbnail = await diskstation.api.photos.get_item_thumbnail_url(item)
|
||||
except SynologyDSMException:
|
||||
|
@ -215,13 +220,14 @@ class SynologyDsmMediaView(http.HomeAssistantView):
|
|||
raise web.HTTPNotFound
|
||||
# location: {cache_key}/{filename}
|
||||
cache_key, file_name = location.split("/")
|
||||
image_id = cache_key.split("_")[0]
|
||||
image_id = int(cache_key.split("_")[0])
|
||||
mime_type, _ = mimetypes.guess_type(file_name)
|
||||
if not isinstance(mime_type, str):
|
||||
raise web.HTTPNotFound
|
||||
diskstation: SynologyDSMData = self.hass.data[DOMAIN][source_dir_id]
|
||||
|
||||
item = SynoPhotosItem(image_id, "", "", "", cache_key, "")
|
||||
assert diskstation.api.photos is not None
|
||||
item = SynoPhotosItem(image_id, "", "", "", cache_key, "", False)
|
||||
try:
|
||||
image = await diskstation.api.photos.download_item(item)
|
||||
except SynologyDSMException as exc:
|
||||
|
|
|
@ -292,6 +292,8 @@ async def async_setup_entry(
|
|||
data: SynologyDSMData = hass.data[DOMAIN][entry.unique_id]
|
||||
api = data.api
|
||||
coordinator = data.coordinator_central
|
||||
storage = api.storage
|
||||
assert storage is not None
|
||||
|
||||
entities: list[SynoDSMUtilSensor | SynoDSMStorageSensor | SynoDSMInfoSensor] = [
|
||||
SynoDSMUtilSensor(api, coordinator, description)
|
||||
|
@ -299,21 +301,21 @@ async def async_setup_entry(
|
|||
]
|
||||
|
||||
# Handle all volumes
|
||||
if api.storage.volumes_ids:
|
||||
if storage.volumes_ids:
|
||||
entities.extend(
|
||||
[
|
||||
SynoDSMStorageSensor(api, coordinator, description, volume)
|
||||
for volume in entry.data.get(CONF_VOLUMES, api.storage.volumes_ids)
|
||||
for volume in entry.data.get(CONF_VOLUMES, storage.volumes_ids)
|
||||
for description in STORAGE_VOL_SENSORS
|
||||
]
|
||||
)
|
||||
|
||||
# Handle all disks
|
||||
if api.storage.disks_ids:
|
||||
if storage.disks_ids:
|
||||
entities.extend(
|
||||
[
|
||||
SynoDSMStorageSensor(api, coordinator, description, disk)
|
||||
for disk in entry.data.get(CONF_DISKS, api.storage.disks_ids)
|
||||
for disk in entry.data.get(CONF_DISKS, storage.disks_ids)
|
||||
for description in STORAGE_DISK_SENSORS
|
||||
]
|
||||
)
|
||||
|
|
|
@ -79,6 +79,8 @@ class SynoDSMSurveillanceHomeModeToggle(
|
|||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn on Home mode."""
|
||||
assert self._api.surveillance_station is not None
|
||||
assert self._api.information
|
||||
_LOGGER.debug(
|
||||
"SynoDSMSurveillanceHomeModeToggle.turn_on(%s)",
|
||||
self._api.information.serial,
|
||||
|
@ -88,6 +90,8 @@ class SynoDSMSurveillanceHomeModeToggle(
|
|||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn off Home mode."""
|
||||
assert self._api.surveillance_station is not None
|
||||
assert self._api.information
|
||||
_LOGGER.debug(
|
||||
"SynoDSMSurveillanceHomeModeToggle.turn_off(%s)",
|
||||
self._api.information.serial,
|
||||
|
@ -103,6 +107,9 @@ class SynoDSMSurveillanceHomeModeToggle(
|
|||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return the device information."""
|
||||
assert self._api.surveillance_station is not None
|
||||
assert self._api.information is not None
|
||||
assert self._api.network is not None
|
||||
return DeviceInfo(
|
||||
identifiers={
|
||||
(
|
||||
|
|
|
@ -64,24 +64,29 @@ class SynoDSMUpdateEntity(
|
|||
@property
|
||||
def installed_version(self) -> str | None:
|
||||
"""Version installed and in use."""
|
||||
return self._api.information.version_string # type: ignore[no-any-return]
|
||||
assert self._api.information is not None
|
||||
return self._api.information.version_string
|
||||
|
||||
@property
|
||||
def latest_version(self) -> str | None:
|
||||
"""Latest version available for install."""
|
||||
assert self._api.upgrade is not None
|
||||
if not self._api.upgrade.update_available:
|
||||
return self.installed_version
|
||||
return self._api.upgrade.available_version # type: ignore[no-any-return]
|
||||
return self._api.upgrade.available_version
|
||||
|
||||
@property
|
||||
def release_url(self) -> str | None:
|
||||
"""URL to the full release notes of the latest version available."""
|
||||
assert self._api.information is not None
|
||||
assert self._api.upgrade is not None
|
||||
|
||||
if (details := self._api.upgrade.available_version_details) is None:
|
||||
return None
|
||||
|
||||
url = URL("http://update.synology.com/autoupdate/whatsnew.php")
|
||||
query = {"model": self._api.information.model}
|
||||
if details.get("nano") > 0:
|
||||
if details["nano"] > 0:
|
||||
query["update_version"] = f"{details['buildnumber']}-{details['nano']}"
|
||||
else:
|
||||
query["update_version"] = details["buildnumber"]
|
||||
|
|
|
@ -1634,7 +1634,7 @@ py-schluter==0.1.7
|
|||
py-sucks==0.9.9
|
||||
|
||||
# homeassistant.components.synology_dsm
|
||||
py-synologydsm-api==2.1.4
|
||||
py-synologydsm-api==2.4.2
|
||||
|
||||
# homeassistant.components.zabbix
|
||||
py-zabbix==1.1.7
|
||||
|
|
|
@ -1293,7 +1293,7 @@ py-nightscout==1.2.2
|
|||
py-sucks==0.9.9
|
||||
|
||||
# homeassistant.components.synology_dsm
|
||||
py-synologydsm-api==2.1.4
|
||||
py-synologydsm-api==2.4.2
|
||||
|
||||
# homeassistant.components.seventeentrack
|
||||
py17track==2021.12.2
|
||||
|
|
|
@ -49,7 +49,9 @@ def dsm_with_photos() -> MagicMock:
|
|||
|
||||
dsm.photos.get_albums = AsyncMock(return_value=[SynoPhotosAlbum(1, "Album 1", 10)])
|
||||
dsm.photos.get_items_from_album = AsyncMock(
|
||||
return_value=[SynoPhotosItem(10, "", "filename.jpg", 12345, "10_1298753", "sm")]
|
||||
return_value=[
|
||||
SynoPhotosItem(10, "", "filename.jpg", 12345, "10_1298753", "sm", False)
|
||||
]
|
||||
)
|
||||
dsm.photos.get_item_thumbnail_url = AsyncMock(
|
||||
return_value="http://my.thumbnail.url"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue