diff --git a/.coveragerc b/.coveragerc index 162e0c65f06..3785240a387 100644 --- a/.coveragerc +++ b/.coveragerc @@ -829,6 +829,7 @@ omit = homeassistant/components/synology/camera.py homeassistant/components/synology_chat/notify.py homeassistant/components/synology_dsm/__init__.py + homeassistant/components/synology_dsm/camera.py homeassistant/components/synology_dsm/binary_sensor.py homeassistant/components/synology_dsm/sensor.py homeassistant/components/synology_srm/device_tracker.py diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index 7e229ef4a5b..223235a4121 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -10,6 +10,7 @@ from synology_dsm.api.core.utilization import SynoCoreUtilization from synology_dsm.api.dsm.information import SynoDSMInformation from synology_dsm.api.dsm.network import SynoDSMNetwork from synology_dsm.api.storage.storage import SynoStorage +from synology_dsm.api.surveillance_station import SynoSurveillanceStation import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry @@ -225,12 +226,14 @@ class SynoApi: self.security: SynoCoreSecurity = None self.storage: SynoStorage = None self.utilisation: SynoCoreUtilization = None + self.surveillance_station: SynoSurveillanceStation = None # Should we fetch them self._fetching_entities = {} self._with_security = True self._with_storage = True self._with_utilisation = True + self._with_surveillance_station = True self._unsub_dispatcher = None @@ -250,6 +253,11 @@ class SynoApi: device_token=self._entry.data.get("device_token"), ) + await self._hass.async_add_executor_job(self.dsm.discover_apis) + self._with_surveillance_station = bool( + self.dsm.apis.get(SynoSurveillanceStation.CAMERA_API_KEY) + ) + self._async_setup_api_requests() await self._hass.async_add_executor_job(self._fetch_device_configuration) @@ -294,6 +302,9 @@ class SynoApi: self._with_utilisation = bool( self._fetching_entities.get(SynoCoreUtilization.API_KEY) ) + self._with_surveillance_station = bool( + self._fetching_entities.get(SynoSurveillanceStation.CAMERA_API_KEY) + ) # Reset not used API if not self._with_security: @@ -308,6 +319,10 @@ class SynoApi: self.dsm.reset(self.utilisation) self.utilisation = None + if not self._with_surveillance_station: + self.dsm.reset(self.surveillance_station) + self.surveillance_station = None + def _fetch_device_configuration(self): """Fetch initial device config.""" self.information = self.dsm.information @@ -324,6 +339,9 @@ class SynoApi: if self._with_utilisation: self.utilisation = self.dsm.utilisation + if self._with_surveillance_station: + self.surveillance_station = self.dsm.surveillance_station + async def async_unload(self): """Stop interacting with the NAS and prepare for removal from hass.""" self._unsub_dispatcher() @@ -345,6 +363,8 @@ class SynologyDSMEntity(Entity): entity_info: Dict[str, str], ): """Initialize the Synology DSM entity.""" + super().__init__() + self._api = api self._api_key = entity_type.split(":")[0] self.entity_type = entity_type.split(":")[-1] diff --git a/homeassistant/components/synology_dsm/camera.py b/homeassistant/components/synology_dsm/camera.py new file mode 100644 index 00000000000..66137850a7c --- /dev/null +++ b/homeassistant/components/synology_dsm/camera.py @@ -0,0 +1,97 @@ +"""Support for Synology DSM cameras.""" +from typing import Dict + +from synology_dsm.api.surveillance_station import SynoSurveillanceStation + +from homeassistant.components.camera import SUPPORT_STREAM, Camera +from homeassistant.config_entries import ConfigEntry +from homeassistant.helpers.typing import HomeAssistantType + +from . import SynologyDSMEntity +from .const import ( + DOMAIN, + ENTITY_CLASS, + ENTITY_ENABLE, + ENTITY_ICON, + ENTITY_NAME, + ENTITY_UNIT, + SYNO_API, +) + + +async def async_setup_entry( + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities +) -> None: + """Set up the Synology NAS binary sensor.""" + + api = hass.data[DOMAIN][entry.unique_id][SYNO_API] + + if SynoSurveillanceStation.CAMERA_API_KEY not in api.dsm.apis: + return True + + surveillance_station = api.surveillance_station + await hass.async_add_executor_job(surveillance_station.update) + cameras = surveillance_station.get_all_cameras() + entities = [SynoDSMCamera(api, camera) for camera in cameras] + + async_add_entities(entities) + + +class SynoDSMCamera(SynologyDSMEntity, Camera): + """Representation a Synology camera.""" + + def __init__(self, api, camera): + """Initialize a Synology camera.""" + super().__init__( + api, + f"{SynoSurveillanceStation.CAMERA_API_KEY}:{camera.id}", + { + ENTITY_NAME: camera.name, + ENTITY_CLASS: None, + ENTITY_ICON: None, + ENTITY_ENABLE: True, + ENTITY_UNIT: None, + }, + ) + self._camera = camera + + @property + def device_info(self) -> Dict[str, any]: + """Return the device information.""" + return { + "identifiers": {(DOMAIN, self._api.information.serial, self._camera.id)}, + "name": self.name, + "model": self._camera.model, + "via_device": (DOMAIN, self._api.information.serial), + } + + @property + def supported_features(self) -> int: + """Return supported features of this camera.""" + return SUPPORT_STREAM + + @property + def is_recording(self): + """Return true if the device is recording.""" + return self._camera.is_recording + + @property + def motion_detection_enabled(self): + """Return the camera motion detection status.""" + return self._camera.is_motion_detection_enabled + + def camera_image(self) -> bytes: + """Return bytes of camera image.""" + return self._api.surveillance_station.get_camera_image(self._camera.id) + + async def stream_source(self) -> str: + """Return the source of the stream.""" + return self._camera.live_view.rtsp + + def enable_motion_detection(self): + """Enable motion detection in the camera.""" + self._api.surveillance_station.enable_motion_detection(self._camera.id) + + def disable_motion_detection(self): + """Disable motion detection in camera.""" + self._api.surveillance_station.disable_motion_detection(self._camera.id) diff --git a/homeassistant/components/synology_dsm/const.py b/homeassistant/components/synology_dsm/const.py index 2816ae681a1..693d8b2cd50 100644 --- a/homeassistant/components/synology_dsm/const.py +++ b/homeassistant/components/synology_dsm/const.py @@ -13,7 +13,7 @@ from homeassistant.const import ( ) DOMAIN = "synology_dsm" -PLATFORMS = ["binary_sensor", "sensor"] +PLATFORMS = ["binary_sensor", "camera", "sensor"] # Entry keys SYNO_API = "syno_api"