Home Assistant Unofficial Reference 2024.12.1
camera.py
Go to the documentation of this file.
1 """Component providing basic support for Foscam IP cameras."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 
7 import voluptuous as vol
8 
9 from homeassistant.components.camera import Camera, CameraEntityFeature
10 from homeassistant.config_entries import ConfigEntry
11 from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
12 from homeassistant.core import HomeAssistant
13 from homeassistant.helpers import config_validation as cv, entity_platform
14 from homeassistant.helpers.entity_platform import AddEntitiesCallback
15 
16 from .const import (
17  CONF_RTSP_PORT,
18  CONF_STREAM,
19  DOMAIN,
20  LOGGER,
21  SERVICE_PTZ,
22  SERVICE_PTZ_PRESET,
23 )
24 from .coordinator import FoscamCoordinator
25 from .entity import FoscamEntity
26 
27 DIR_UP = "up"
28 DIR_DOWN = "down"
29 DIR_LEFT = "left"
30 DIR_RIGHT = "right"
31 
32 DIR_TOPLEFT = "top_left"
33 DIR_TOPRIGHT = "top_right"
34 DIR_BOTTOMLEFT = "bottom_left"
35 DIR_BOTTOMRIGHT = "bottom_right"
36 
37 MOVEMENT_ATTRS = {
38  DIR_UP: "ptz_move_up",
39  DIR_DOWN: "ptz_move_down",
40  DIR_LEFT: "ptz_move_left",
41  DIR_RIGHT: "ptz_move_right",
42  DIR_TOPLEFT: "ptz_move_top_left",
43  DIR_TOPRIGHT: "ptz_move_top_right",
44  DIR_BOTTOMLEFT: "ptz_move_bottom_left",
45  DIR_BOTTOMRIGHT: "ptz_move_bottom_right",
46 }
47 
48 DEFAULT_TRAVELTIME = 0.125
49 
50 ATTR_MOVEMENT = "movement"
51 ATTR_TRAVELTIME = "travel_time"
52 ATTR_PRESET_NAME = "preset_name"
53 
54 PTZ_GOTO_PRESET_COMMAND = "ptz_goto_preset"
55 
56 
58  hass: HomeAssistant,
59  config_entry: ConfigEntry,
60  async_add_entities: AddEntitiesCallback,
61 ) -> None:
62  """Add a Foscam IP camera from a config entry."""
63  platform = entity_platform.async_get_current_platform()
64  platform.async_register_entity_service(
65  SERVICE_PTZ,
66  {
67  vol.Required(ATTR_MOVEMENT): vol.In(
68  [
69  DIR_UP,
70  DIR_DOWN,
71  DIR_LEFT,
72  DIR_RIGHT,
73  DIR_TOPLEFT,
74  DIR_TOPRIGHT,
75  DIR_BOTTOMLEFT,
76  DIR_BOTTOMRIGHT,
77  ]
78  ),
79  vol.Optional(ATTR_TRAVELTIME, default=DEFAULT_TRAVELTIME): cv.small_float,
80  },
81  "async_perform_ptz",
82  )
83 
84  platform.async_register_entity_service(
85  SERVICE_PTZ_PRESET,
86  {
87  vol.Required(ATTR_PRESET_NAME): cv.string,
88  },
89  "async_perform_ptz_preset",
90  )
91 
92  coordinator: FoscamCoordinator = hass.data[DOMAIN][config_entry.entry_id]
93 
94  async_add_entities([HassFoscamCamera(coordinator, config_entry)])
95 
96 
98  """An implementation of a Foscam IP camera."""
99 
100  _attr_has_entity_name = True
101  _attr_name = None
102 
103  def __init__(
104  self,
105  coordinator: FoscamCoordinator,
106  config_entry: ConfigEntry,
107  ) -> None:
108  """Initialize a Foscam camera."""
109  super().__init__(coordinator, config_entry.entry_id)
110  Camera.__init__(self)
111 
112  self._foscam_session_foscam_session = coordinator.session
113  self._username_username = config_entry.data[CONF_USERNAME]
114  self._password_password = config_entry.data[CONF_PASSWORD]
115  self._stream_stream = config_entry.data[CONF_STREAM]
116  self._attr_unique_id_attr_unique_id = config_entry.entry_id
117  self._rtsp_port_rtsp_port = config_entry.data[CONF_RTSP_PORT]
118  if self._rtsp_port_rtsp_port:
119  self._attr_supported_features_attr_supported_features = CameraEntityFeature.STREAM
120 
121  async def async_added_to_hass(self) -> None:
122  """Handle entity addition to hass."""
123  # Get motion detection status
124 
125  await super().async_added_to_hass()
126 
127  ret, response = await self.hasshasshass.async_add_executor_job(
128  self._foscam_session_foscam_session.get_motion_detect_config
129  )
130 
131  if ret == -3:
132  LOGGER.warning(
133  (
134  "Can't get motion detection status, camera %s configured with"
135  " non-admin user"
136  ),
137  self.namenamename,
138  )
139 
140  elif ret != 0:
141  LOGGER.error(
142  "Error getting motion detection status of %s: %s", self.namenamename, ret
143  )
144 
145  else:
146  self._attr_motion_detection_enabled_attr_motion_detection_enabled = response == 1
147 
149  self, width: int | None = None, height: int | None = None
150  ) -> bytes | None:
151  """Return a still image response from the camera."""
152  # Send the request to snap a picture and return raw jpg data
153  # Handle exception if host is not reachable or url failed
154  result, response = self._foscam_session_foscam_session.snap_picture_2()
155  if result != 0:
156  return None
157 
158  return response
159 
160  async def stream_source(self) -> str | None:
161  """Return the stream source."""
162  if self._rtsp_port_rtsp_port:
163  return f"rtsp://{self._username}:{self._password}@{self._foscam_session.host}:{self._rtsp_port}/video{self._stream}"
164 
165  return None
166 
167  def enable_motion_detection(self) -> None:
168  """Enable motion detection in camera."""
169  try:
170  ret = self._foscam_session_foscam_session.enable_motion_detection()
171 
172  if ret != 0:
173  if ret == -3:
174  LOGGER.warning(
175  (
176  "Can't set motion detection status, camera %s configured"
177  " with non-admin user"
178  ),
179  self.namenamename,
180  )
181  return
182 
183  self._attr_motion_detection_enabled_attr_motion_detection_enabled = True
184  except TypeError:
185  LOGGER.debug(
186  (
187  "Failed enabling motion detection on '%s'. Is it supported by the"
188  " device?"
189  ),
190  self.namenamename,
191  )
192 
193  def disable_motion_detection(self) -> None:
194  """Disable motion detection."""
195  try:
196  ret = self._foscam_session_foscam_session.disable_motion_detection()
197 
198  if ret != 0:
199  if ret == -3:
200  LOGGER.warning(
201  (
202  "Can't set motion detection status, camera %s configured"
203  " with non-admin user"
204  ),
205  self.namenamename,
206  )
207  return
208 
209  self._attr_motion_detection_enabled_attr_motion_detection_enabled = False
210  except TypeError:
211  LOGGER.debug(
212  (
213  "Failed disabling motion detection on '%s'. Is it supported by the"
214  " device?"
215  ),
216  self.namenamename,
217  )
218 
219  async def async_perform_ptz(self, movement, travel_time):
220  """Perform a PTZ action on the camera."""
221  LOGGER.debug("PTZ action '%s' on %s", movement, self.namenamename)
222 
223  movement_function = getattr(self._foscam_session_foscam_session, MOVEMENT_ATTRS[movement])
224 
225  ret, _ = await self.hasshasshass.async_add_executor_job(movement_function)
226 
227  if ret != 0:
228  LOGGER.error("Error moving %s '%s': %s", movement, self.namenamename, ret)
229  return
230 
231  await asyncio.sleep(travel_time)
232 
233  ret, _ = await self.hasshasshass.async_add_executor_job(
234  self._foscam_session_foscam_session.ptz_stop_run
235  )
236 
237  if ret != 0:
238  LOGGER.error("Error stopping movement on '%s': %s", self.namenamename, ret)
239  return
240 
241  async def async_perform_ptz_preset(self, preset_name):
242  """Perform a PTZ preset action on the camera."""
243  LOGGER.debug("PTZ preset '%s' on %s", preset_name, self.namenamename)
244 
245  preset_function = getattr(self._foscam_session_foscam_session, PTZ_GOTO_PRESET_COMMAND)
246 
247  ret, _ = await self.hasshasshass.async_add_executor_job(preset_function, preset_name)
248 
249  if ret != 0:
250  LOGGER.error(
251  "Error moving to preset %s on '%s': %s", preset_name, self.namenamename, ret
252  )
253  return
bytes|None camera_image(self, int|None width=None, int|None height=None)
Definition: camera.py:150
None __init__(self, FoscamCoordinator coordinator, ConfigEntry config_entry)
Definition: camera.py:107
def async_perform_ptz(self, movement, travel_time)
Definition: camera.py:219
str|UndefinedType|None name(self)
Definition: entity.py:738
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: camera.py:61