Home Assistant Unofficial Reference 2024.12.1
camera.py
Go to the documentation of this file.
1 """Support for Synology DSM cameras."""
2 
3 from __future__ import annotations
4 
5 from dataclasses import dataclass
6 import logging
7 
8 from synology_dsm.api.surveillance_station import SynoCamera, SynoSurveillanceStation
9 from synology_dsm.exceptions import (
10  SynologyDSMAPIErrorException,
11  SynologyDSMRequestException,
12 )
13 
15  Camera,
16  CameraEntityDescription,
17  CameraEntityFeature,
18 )
19 from homeassistant.config_entries import ConfigEntry
20 from homeassistant.core import HomeAssistant, callback
21 from homeassistant.helpers.device_registry import DeviceInfo
22 from homeassistant.helpers.dispatcher import async_dispatcher_connect
23 from homeassistant.helpers.entity_platform import AddEntitiesCallback
24 
25 from . import SynoApi
26 from .const import (
27  CONF_SNAPSHOT_QUALITY,
28  DEFAULT_SNAPSHOT_QUALITY,
29  DOMAIN,
30  SIGNAL_CAMERA_SOURCE_CHANGED,
31 )
32 from .coordinator import SynologyDSMCameraUpdateCoordinator
33 from .entity import SynologyDSMBaseEntity, SynologyDSMEntityDescription
34 from .models import SynologyDSMData
35 
36 _LOGGER = logging.getLogger(__name__)
37 
38 
39 @dataclass(frozen=True, kw_only=True)
41  CameraEntityDescription, SynologyDSMEntityDescription
42 ):
43  """Describes Synology DSM camera entity."""
44 
45  camera_id: int
46 
47 
49  hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
50 ) -> None:
51  """Set up the Synology NAS cameras."""
52  data: SynologyDSMData = hass.data[DOMAIN][entry.unique_id]
53  if coordinator := data.coordinator_cameras:
55  SynoDSMCamera(data.api, coordinator, camera_id)
56  for camera_id in coordinator.data["cameras"]
57  )
58 
59 
60 class SynoDSMCamera(SynologyDSMBaseEntity[SynologyDSMCameraUpdateCoordinator], Camera):
61  """Representation a Synology camera."""
62 
63  _attr_supported_features = CameraEntityFeature.STREAM
64  entity_description: SynologyDSMCameraEntityDescription
65 
66  def __init__(
67  self,
68  api: SynoApi,
69  coordinator: SynologyDSMCameraUpdateCoordinator,
70  camera_id: int,
71  ) -> None:
72  """Initialize a Synology camera."""
74  api_key=SynoSurveillanceStation.CAMERA_API_KEY,
75  key=str(camera_id),
76  camera_id=camera_id,
77  name=None,
78  entity_registry_enabled_default=coordinator.data["cameras"][
79  camera_id
80  ].is_enabled,
81  )
82  self.snapshot_qualitysnapshot_quality = api._entry.options.get( # noqa: SLF001
83  CONF_SNAPSHOT_QUALITY, DEFAULT_SNAPSHOT_QUALITY
84  )
85  super().__init__(api, coordinator, description)
86  Camera.__init__(self)
87 
88  @property
89  def camera_data(self) -> SynoCamera:
90  """Camera data."""
91  return self.coordinator.data["cameras"][self.entity_description.camera_id]
92 
93  @property
94  def device_info(self) -> DeviceInfo:
95  """Return the device information."""
96  information = self._api.information
97  assert information is not None
98  return DeviceInfo(
99  identifiers={(DOMAIN, f"{information.serial}_{self.camera_data.id}")},
100  name=self.camera_datacamera_data.name,
101  model=self.camera_datacamera_data.model,
102  via_device=(
103  DOMAIN,
104  f"{information.serial}_{SynoSurveillanceStation.INFO_API_KEY}",
105  ),
106  )
107 
108  @property
109  def available(self) -> bool:
110  """Return the availability of the camera."""
111  return self.camera_datacamera_data.is_enabled and super().available
112 
113  @property
114  def is_recording(self) -> bool:
115  """Return true if the device is recording."""
116  return self.camera_datacamera_data.is_recording
117 
118  @property
119  def motion_detection_enabled(self) -> bool:
120  """Return the camera motion detection status."""
121  return bool(self.camera_datacamera_data.is_motion_detection_enabled)
122 
123  def _listen_source_updates(self) -> None:
124  """Listen for camera source changed events."""
125 
126  @callback
127  def _handle_signal(url: str) -> None:
128  if self.streamstream:
129  _LOGGER.debug("Update stream URL for camera %s", self.camera_datacamera_data.name)
130  self.streamstream.update_source(url)
131 
132  assert self.platformplatform.config_entry
133  self.async_on_removeasync_on_remove(
135  self.hasshasshass,
136  f"{SIGNAL_CAMERA_SOURCE_CHANGED}_{self.platform.config_entry.entry_id}_{self.camera_data.id}",
137  _handle_signal,
138  )
139  )
140 
141  async def async_added_to_hass(self) -> None:
142  """Subscribe to signal."""
143  self._listen_source_updates_listen_source_updates()
144  await super().async_added_to_hass()
145 
147  self, width: int | None = None, height: int | None = None
148  ) -> bytes | None:
149  """Return bytes of camera image."""
150  _LOGGER.debug(
151  "SynoDSMCamera.camera_image(%s)",
152  self.camera_datacamera_data.name,
153  )
154  if not self.availableavailableavailableavailable:
155  return None
156  assert self._api.surveillance_station is not None
157  try:
158  return await self._api.surveillance_station.get_camera_image(
159  self.entity_description.camera_id, self.snapshot_qualitysnapshot_quality
160  )
161  except (
162  SynologyDSMAPIErrorException,
163  SynologyDSMRequestException,
164  ConnectionRefusedError,
165  ) as err:
166  _LOGGER.debug(
167  "SynoDSMCamera.camera_image(%s) - Exception:%s",
168  self.camera_datacamera_data.name,
169  err,
170  )
171  return None
172 
173  async def stream_source(self) -> str | None:
174  """Return the source of the stream."""
175  _LOGGER.debug(
176  "SynoDSMCamera.stream_source(%s)",
177  self.camera_datacamera_data.name,
178  )
179  if not self.availableavailableavailableavailable:
180  return None
181 
182  return self.camera_datacamera_data.live_view.rtsp
183 
184  async def async_enable_motion_detection(self) -> None:
185  """Enable motion detection in the camera."""
186  _LOGGER.debug(
187  "SynoDSMCamera.enable_motion_detection(%s)",
188  self.camera_datacamera_data.name,
189  )
190  assert self._api.surveillance_station is not None
191  await self._api.surveillance_station.enable_motion_detection(
192  self.entity_description.camera_id
193  )
194 
195  async def async_disable_motion_detection(self) -> None:
196  """Disable motion detection in camera."""
197  _LOGGER.debug(
198  "SynoDSMCamera.disable_motion_detection(%s)",
199  self.camera_datacamera_data.name,
200  )
201  assert self._api.surveillance_station is not None
202  await self._api.surveillance_station.disable_motion_detection(
203  self.entity_description.camera_id
204  )
bytes|None async_camera_image(self, int|None width=None, int|None height=None)
Definition: camera.py:148
None __init__(self, SynoApi api, SynologyDSMCameraUpdateCoordinator coordinator, int camera_id)
Definition: camera.py:71
None async_on_remove(self, CALLBACK_TYPE func)
Definition: entity.py:1331
None async_setup_entry(HomeAssistant hass, ConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: camera.py:50
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103