Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Component to interface with cameras."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 import collections
7 from collections.abc import Awaitable, Callable, Coroutine
8 from contextlib import suppress
9 from dataclasses import asdict, dataclass
10 from datetime import datetime, timedelta
11 from enum import IntFlag
12 from functools import partial
13 import logging
14 import os
15 from random import SystemRandom
16 import time
17 from typing import Any, Final, final
18 
19 from aiohttp import hdrs, web
20 import attr
21 from propcache import cached_property, under_cached_property
22 import voluptuous as vol
23 from webrtc_models import RTCIceCandidateInit, RTCIceServer
24 
25 from homeassistant.components import websocket_api
26 from homeassistant.components.http import KEY_AUTHENTICATED, HomeAssistantView
28  ATTR_MEDIA_CONTENT_ID,
29  ATTR_MEDIA_CONTENT_TYPE,
30  DOMAIN as DOMAIN_MP,
31  SERVICE_PLAY_MEDIA,
32 )
34  FORMAT_CONTENT_TYPE,
35  OUTPUT_FORMATS,
36  Orientation,
37  Stream,
38  create_stream,
39 )
40 from homeassistant.components.websocket_api import ActiveConnection
41 from homeassistant.config_entries import ConfigEntry
42 from homeassistant.const import (
43  ATTR_ENTITY_ID,
44  CONF_FILENAME,
45  CONTENT_TYPE_MULTIPART,
46  EVENT_HOMEASSISTANT_STARTED,
47  EVENT_HOMEASSISTANT_STOP,
48  SERVICE_TURN_OFF,
49  SERVICE_TURN_ON,
50 )
51 from homeassistant.core import Event, HomeAssistant, ServiceCall, callback
52 from homeassistant.exceptions import HomeAssistantError
53 from homeassistant.helpers import config_validation as cv, issue_registry as ir
55  DeprecatedConstantEnum,
56  all_with_deprecated_constants,
57  check_if_deprecated_constant,
58  deprecated_function,
59  dir_with_deprecated_constants,
60 )
61 from homeassistant.helpers.entity import Entity, EntityDescription
62 from homeassistant.helpers.entity_component import EntityComponent
63 from homeassistant.helpers.event import async_track_time_interval
64 from homeassistant.helpers.frame import ReportBehavior, report_usage
65 from homeassistant.helpers.network import get_url
66 from homeassistant.helpers.template import Template
67 from homeassistant.helpers.typing import ConfigType, VolDictType
68 from homeassistant.loader import bind_hass
69 
70 from .const import ( # noqa: F401
71  _DEPRECATED_STREAM_TYPE_HLS,
72  _DEPRECATED_STREAM_TYPE_WEB_RTC,
73  CAMERA_IMAGE_TIMEOUT,
74  CAMERA_STREAM_SOURCE_TIMEOUT,
75  CONF_DURATION,
76  CONF_LOOKBACK,
77  DATA_CAMERA_PREFS,
78  DATA_COMPONENT,
79  DOMAIN,
80  PREF_ORIENTATION,
81  PREF_PRELOAD_STREAM,
82  SERVICE_RECORD,
83  CameraState,
84  StreamType,
85 )
86 from .helper import get_camera_from_entity_id
87 from .img_util import scale_jpeg_camera_image
88 from .prefs import CameraPreferences, DynamicStreamSettings # noqa: F401
89 from .webrtc import (
90  DATA_ICE_SERVERS,
91  CameraWebRTCLegacyProvider,
92  CameraWebRTCProvider,
93  WebRTCAnswer,
94  WebRTCCandidate, # noqa: F401
95  WebRTCClientConfiguration,
96  WebRTCError,
97  WebRTCMessage, # noqa: F401
98  WebRTCSendMessage,
99  async_get_supported_legacy_provider,
100  async_get_supported_provider,
101  async_register_ice_servers,
102  async_register_rtsp_to_web_rtc_provider, # noqa: F401
103  async_register_webrtc_provider, # noqa: F401
104  async_register_ws,
105 )
106 
107 _LOGGER = logging.getLogger(__name__)
108 
109 
110 ENTITY_ID_FORMAT: Final = DOMAIN + ".{}"
111 PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA
112 PLATFORM_SCHEMA_BASE = cv.PLATFORM_SCHEMA_BASE
113 SCAN_INTERVAL: Final = timedelta(seconds=30)
114 
115 SERVICE_ENABLE_MOTION: Final = "enable_motion_detection"
116 SERVICE_DISABLE_MOTION: Final = "disable_motion_detection"
117 SERVICE_SNAPSHOT: Final = "snapshot"
118 SERVICE_PLAY_STREAM: Final = "play_stream"
119 
120 ATTR_FILENAME: Final = "filename"
121 ATTR_MEDIA_PLAYER: Final = "media_player"
122 ATTR_FORMAT: Final = "format"
123 
124 # These constants are deprecated as of Home Assistant 2024.10
125 # Please use the StreamType enum instead.
126 _DEPRECATED_STATE_RECORDING = DeprecatedConstantEnum(CameraState.RECORDING, "2025.10")
127 _DEPRECATED_STATE_STREAMING = DeprecatedConstantEnum(CameraState.STREAMING, "2025.10")
128 _DEPRECATED_STATE_IDLE = DeprecatedConstantEnum(CameraState.IDLE, "2025.10")
129 
130 
131 class CameraEntityFeature(IntFlag):
132  """Supported features of the camera entity."""
133 
134  ON_OFF = 1
135  STREAM = 2
136 
137 
138 # These SUPPORT_* constants are deprecated as of Home Assistant 2022.5.
139 # Pleease use the CameraEntityFeature enum instead.
140 _DEPRECATED_SUPPORT_ON_OFF: Final = DeprecatedConstantEnum(
141  CameraEntityFeature.ON_OFF, "2025.1"
142 )
143 _DEPRECATED_SUPPORT_STREAM: Final = DeprecatedConstantEnum(
144  CameraEntityFeature.STREAM, "2025.1"
145 )
146 
147 
148 DEFAULT_CONTENT_TYPE: Final = "image/jpeg"
149 ENTITY_IMAGE_URL: Final = "/api/camera_proxy/{0}?token={1}"
150 
151 TOKEN_CHANGE_INTERVAL: Final = timedelta(minutes=5)
152 _RND: Final = SystemRandom()
153 
154 MIN_STREAM_INTERVAL: Final = 0.5 # seconds
155 
156 CAMERA_SERVICE_SNAPSHOT: VolDictType = {vol.Required(ATTR_FILENAME): cv.template}
157 
158 CAMERA_SERVICE_PLAY_STREAM: VolDictType = {
159  vol.Required(ATTR_MEDIA_PLAYER): cv.entities_domain(DOMAIN_MP),
160  vol.Optional(ATTR_FORMAT, default="hls"): vol.In(OUTPUT_FORMATS),
161 }
162 
163 CAMERA_SERVICE_RECORD: VolDictType = {
164  vol.Required(CONF_FILENAME): cv.template,
165  vol.Optional(CONF_DURATION, default=30): vol.Coerce(int),
166  vol.Optional(CONF_LOOKBACK, default=0): vol.Coerce(int),
167 }
168 
169 
170 class CameraEntityDescription(EntityDescription, frozen_or_thawed=True):
171  """A class that describes camera entities."""
172 
173 
174 @attr.s
175 class Image:
176  """Represent an image."""
177 
178  content_type: str = attr.ib()
179  content: bytes = attr.ib()
180 
181 
182 @dataclass(frozen=True)
184  """Camera capabilities."""
185 
186  frontend_stream_types: set[StreamType]
187 
188 
189 @bind_hass
190 async def async_request_stream(hass: HomeAssistant, entity_id: str, fmt: str) -> str:
191  """Request a stream for a camera entity."""
192  camera = get_camera_from_entity_id(hass, entity_id)
193  return await _async_stream_endpoint_url(hass, camera, fmt)
194 
195 
197  camera: Camera,
198  timeout: int = 10,
199  width: int | None = None,
200  height: int | None = None,
201 ) -> Image:
202  """Fetch a snapshot image from a camera.
203 
204  If width and height are passed, an attempt to scale
205  the image will be made on a best effort basis.
206  Not all cameras can scale images or return jpegs
207  that we can scale, however the majority of cases
208  are handled.
209  """
210  with suppress(asyncio.CancelledError, TimeoutError):
211  async with asyncio.timeout(timeout):
212  image_bytes = (
214  camera, width=width, height=height, wait_for_next_keyframe=False
215  )
216  if camera.use_stream_for_stills
217  else await camera.async_camera_image(width=width, height=height)
218  )
219  if image_bytes:
220  content_type = camera.content_type
221  image = Image(content_type, image_bytes)
222  if (
223  width is not None
224  and height is not None
225  and ("jpeg" in content_type or "jpg" in content_type)
226  ):
227  assert width is not None
228  assert height is not None
229  return Image(
230  content_type, scale_jpeg_camera_image(image, width, height)
231  )
232 
233  return image
234 
235  raise HomeAssistantError("Unable to get image")
236 
237 
238 @bind_hass
239 async def async_get_image(
240  hass: HomeAssistant,
241  entity_id: str,
242  timeout: int = 10,
243  width: int | None = None,
244  height: int | None = None,
245 ) -> Image:
246  """Fetch an image from a camera entity.
247 
248  width and height will be passed to the underlying camera.
249  """
250  camera = get_camera_from_entity_id(hass, entity_id)
251  return await _async_get_image(camera, timeout, width, height)
252 
253 
255  camera: Camera,
256  width: int | None = None,
257  height: int | None = None,
258  wait_for_next_keyframe: bool = False,
259 ) -> bytes | None:
260  if not camera.stream and CameraEntityFeature.STREAM in camera.supported_features:
261  camera.stream = await camera.async_create_stream()
262  if camera.stream:
263  return await camera.stream.async_get_image(
264  width=width, height=height, wait_for_next_keyframe=wait_for_next_keyframe
265  )
266  return None
267 
268 
269 @bind_hass
270 async def async_get_stream_source(hass: HomeAssistant, entity_id: str) -> str | None:
271  """Fetch the stream source for a camera entity."""
272  camera = get_camera_from_entity_id(hass, entity_id)
273  return await camera.stream_source()
274 
275 
276 @bind_hass
278  hass: HomeAssistant, request: web.Request, entity_id: str
279 ) -> web.StreamResponse | None:
280  """Fetch an mjpeg stream from a camera entity."""
281  camera = get_camera_from_entity_id(hass, entity_id)
282 
283  try:
284  stream = await camera.handle_async_mjpeg_stream(request)
285  except ConnectionResetError:
286  stream = None
287  _LOGGER.debug("Error while writing MJPEG stream to transport")
288  return stream
289 
290 
292  request: web.Request,
293  image_cb: Callable[[], Awaitable[bytes | None]],
294  content_type: str,
295  interval: float,
296 ) -> web.StreamResponse:
297  """Generate an HTTP MJPEG stream from camera images.
298 
299  This method must be run in the event loop.
300  """
301  response = web.StreamResponse()
302  response.content_type = CONTENT_TYPE_MULTIPART.format("--frameboundary")
303  await response.prepare(request)
304 
305  async def write_to_mjpeg_stream(img_bytes: bytes) -> None:
306  """Write image to stream."""
307  await response.write(
308  bytes(
309  "--frameboundary\r\n"
310  f"Content-Type: {content_type}\r\n"
311  f"Content-Length: {len(img_bytes)}\r\n\r\n",
312  "utf-8",
313  )
314  + img_bytes
315  + b"\r\n"
316  )
317 
318  last_image = None
319 
320  while True:
321  last_fetch = time.monotonic()
322  img_bytes = await image_cb()
323  if not img_bytes:
324  break
325 
326  if img_bytes != last_image:
327  await write_to_mjpeg_stream(img_bytes)
328 
329  # Chrome always shows the n-1 frame:
330  # https://issues.chromium.org/issues/41199053
331  # https://issues.chromium.org/issues/40791855
332  # We send the first frame twice to ensure it shows
333  # Subsequent frames are not a concern at reasonable frame rates
334  # (even 1/10 FPS is about the latency of HLS)
335  if last_image is None:
336  await write_to_mjpeg_stream(img_bytes)
337  last_image = img_bytes
338 
339  next_fetch = last_fetch + interval
340  now = time.monotonic()
341  if next_fetch > now:
342  sleep_time = next_fetch - now
343  await asyncio.sleep(sleep_time)
344 
345  return response
346 
347 
348 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
349  """Set up the camera component."""
350  component = hass.data[DATA_COMPONENT] = EntityComponent[Camera](
351  _LOGGER, DOMAIN, hass, SCAN_INTERVAL
352  )
353 
354  prefs = CameraPreferences(hass)
355  await prefs.async_load()
356  hass.data[DATA_CAMERA_PREFS] = prefs
357 
358  hass.http.register_view(CameraImageView(component))
359  hass.http.register_view(CameraMjpegStream(component))
360 
361  websocket_api.async_register_command(hass, ws_camera_stream)
362  websocket_api.async_register_command(hass, websocket_get_prefs)
363  websocket_api.async_register_command(hass, websocket_update_prefs)
364  websocket_api.async_register_command(hass, ws_camera_capabilities)
365  async_register_ws(hass)
366 
367  await component.async_setup(config)
368 
369  async def preload_stream(_event: Event) -> None:
370  """Load stream prefs and start stream if preload_stream is True."""
371  for camera in list(component.entities):
372  stream_prefs = await prefs.get_dynamic_stream_settings(camera.entity_id)
373  if not stream_prefs.preload_stream:
374  continue
375  stream = await camera.async_create_stream()
376  if not stream:
377  continue
378  stream.add_provider("hls")
379  await stream.start()
380 
381  hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, preload_stream)
382 
383  @callback
384  def update_tokens(t: datetime) -> None:
385  """Update tokens of the entities."""
386  for entity in component.entities:
387  entity.async_update_token()
388  entity.async_write_ha_state()
389 
391  hass, update_tokens, TOKEN_CHANGE_INTERVAL, name="Camera update tokens"
392  )
393 
394  @callback
395  def unsub_track_time_interval(_event: Event) -> None:
396  """Unsubscribe track time interval timer."""
397  unsub()
398 
399  hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, unsub_track_time_interval)
400 
401  component.async_register_entity_service(
402  SERVICE_ENABLE_MOTION, None, "async_enable_motion_detection"
403  )
404  component.async_register_entity_service(
405  SERVICE_DISABLE_MOTION, None, "async_disable_motion_detection"
406  )
407  component.async_register_entity_service(SERVICE_TURN_OFF, None, "async_turn_off")
408  component.async_register_entity_service(SERVICE_TURN_ON, None, "async_turn_on")
409  component.async_register_entity_service(
410  SERVICE_SNAPSHOT, CAMERA_SERVICE_SNAPSHOT, async_handle_snapshot_service
411  )
412  component.async_register_entity_service(
413  SERVICE_PLAY_STREAM,
414  CAMERA_SERVICE_PLAY_STREAM,
415  async_handle_play_stream_service,
416  )
417  component.async_register_entity_service(
418  SERVICE_RECORD, CAMERA_SERVICE_RECORD, async_handle_record_service
419  )
420 
421  @callback
422  def get_ice_servers() -> list[RTCIceServer]:
423  if hass.config.webrtc.ice_servers:
424  return hass.config.webrtc.ice_servers
425  return [
426  RTCIceServer(
427  urls=[
428  "stun:stun.home-assistant.io:80",
429  "stun:stun.home-assistant.io:3478",
430  ]
431  ),
432  ]
433 
434  async_register_ice_servers(hass, get_ice_servers)
435  return True
436 
437 
438 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
439  """Set up a config entry."""
440  return await hass.data[DATA_COMPONENT].async_setup_entry(entry)
441 
442 
443 async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
444  """Unload a config entry."""
445  return await hass.data[DATA_COMPONENT].async_unload_entry(entry)
446 
447 
448 CACHED_PROPERTIES_WITH_ATTR_ = {
449  "brand",
450  "frame_interval",
451  "frontend_stream_type",
452  "is_on",
453  "is_recording",
454  "is_streaming",
455  "model",
456  "motion_detection_enabled",
457  "supported_features",
458 }
459 
460 
461 class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
462  """The base class for camera entities."""
463 
464  _entity_component_unrecorded_attributes = frozenset(
465  {"access_token", "entity_picture"}
466  )
467 
468  # Entity Properties
469  _attr_brand: str | None = None
470  _attr_frame_interval: float = MIN_STREAM_INTERVAL
471  # Deprecated in 2024.12. Remove in 2025.6
472  _attr_frontend_stream_type: StreamType | None
473  _attr_is_on: bool = True
474  _attr_is_recording: bool = False
475  _attr_is_streaming: bool = False
476  _attr_model: str | None = None
477  _attr_motion_detection_enabled: bool = False
478  _attr_should_poll: bool = False # No need to poll cameras
479  _attr_state: None = None # State is determined by is_on
480  _attr_supported_features: CameraEntityFeature = CameraEntityFeature(0)
481 
482  __supports_stream: CameraEntityFeature | None = None
483 
484  def __init__(self) -> None:
485  """Initialize a camera."""
486  self._cache: dict[str, Any] = {}
487  self.streamstream: Stream | None = None
488  self.stream_options: dict[str, str | bool | float] = {}
489  self.content_type: str = DEFAULT_CONTENT_TYPE
490  self.access_tokens: collections.deque = collections.deque([], 2)
491  self._warned_old_signature_warned_old_signature = False
492  self.async_update_tokenasync_update_token()
493  self._create_stream_lock_create_stream_lock: asyncio.Lock | None = None
494  self._webrtc_provider_webrtc_provider: CameraWebRTCProvider | None = None
495  self._legacy_webrtc_provider_legacy_webrtc_provider: CameraWebRTCLegacyProvider | None = None
496  self._supports_native_sync_webrtc_supports_native_sync_webrtc = (
497  type(self).async_handle_web_rtc_offer != Camera.async_handle_web_rtc_offer
498  )
499  self._supports_native_async_webrtc_supports_native_async_webrtc = (
500  type(self).async_handle_async_webrtc_offer
501  != Camera.async_handle_async_webrtc_offer
502  )
503  self._deprecate_attr_frontend_stream_type_logged_deprecate_attr_frontend_stream_type_logged = False
504  if type(self).frontend_stream_type != Camera.frontend_stream_type:
505  report_usage(
506  (
507  f"is overwriting the 'frontend_stream_type' property in the {type(self).__name__} class,"
508  " which is deprecated and will be removed in Home Assistant 2025.6, "
509  ),
510  core_integration_behavior=ReportBehavior.ERROR,
511  exclude_integrations={DOMAIN},
512  )
513 
514  @cached_property
515  def entity_picture(self) -> str:
516  """Return a link to the camera feed as entity picture."""
517  if self._attr_entity_picture is not None:
518  return self._attr_entity_picture
519  return ENTITY_IMAGE_URL.format(self.entity_identity_id, self.access_tokens[-1])
520 
521  @cached_property
522  def use_stream_for_stills(self) -> bool:
523  """Whether or not to use stream to generate stills."""
524  return False
525 
526  @cached_property
527  def supported_features(self) -> CameraEntityFeature:
528  """Flag supported features."""
529  return self._attr_supported_features
530 
531  @property
532  def supported_features_compat(self) -> CameraEntityFeature:
533  """Return the supported features as CameraEntityFeature.
534 
535  Remove this compatibility shim in 2025.1 or later.
536  """
537  features = self.supported_featuressupported_featuressupported_features
538  if type(features) is int: # noqa: E721
539  new_features = CameraEntityFeature(features)
540  self._report_deprecated_supported_features_values_report_deprecated_supported_features_values(new_features)
541  return new_features
542  return features
543 
544  @cached_property
545  def is_recording(self) -> bool:
546  """Return true if the device is recording."""
547  return self._attr_is_recording
548 
549  @cached_property
550  def is_streaming(self) -> bool:
551  """Return true if the device is streaming."""
552  return self._attr_is_streaming
553 
554  @cached_property
555  def brand(self) -> str | None:
556  """Return the camera brand."""
557  return self._attr_brand
558 
559  @cached_property
560  def motion_detection_enabled(self) -> bool:
561  """Return the camera motion detection status."""
562  return self._attr_motion_detection_enabled
563 
564  @cached_property
565  def model(self) -> str | None:
566  """Return the camera model."""
567  return self._attr_model
568 
569  @cached_property
570  def frame_interval(self) -> float:
571  """Return the interval between frames of the mjpeg stream."""
572  return self._attr_frame_interval
573 
574  @property
575  def frontend_stream_type(self) -> StreamType | None:
576  """Return the type of stream supported by this camera.
577 
578  A camera may have a single stream type which is used to inform the
579  frontend which camera attributes and player to use. The default type
580  is to use HLS, and components can override to change the type.
581  """
582  # Deprecated in 2024.12. Remove in 2025.6
583  # Use the camera_capabilities instead
584  if hasattr(self, "_attr_frontend_stream_type"):
585  if not self._deprecate_attr_frontend_stream_type_logged_deprecate_attr_frontend_stream_type_logged:
586  report_usage(
587  (
588  f"is setting the '_attr_frontend_stream_type' attribute in the {type(self).__name__} class,"
589  " which is deprecated and will be removed in Home Assistant 2025.6, "
590  ),
591  core_integration_behavior=ReportBehavior.ERROR,
592  exclude_integrations={DOMAIN},
593  )
594 
595  self._deprecate_attr_frontend_stream_type_logged_deprecate_attr_frontend_stream_type_logged = True
596  return self._attr_frontend_stream_type
597  if CameraEntityFeature.STREAM not in self.supported_features_compatsupported_features_compat:
598  return None
599  if (
600  self._webrtc_provider_webrtc_provider
601  or self._legacy_webrtc_provider_legacy_webrtc_provider
602  or self._supports_native_sync_webrtc_supports_native_sync_webrtc
603  or self._supports_native_async_webrtc_supports_native_async_webrtc
604  ):
605  return StreamType.WEB_RTC
606  return StreamType.HLS
607 
608  @property
609  def available(self) -> bool:
610  """Return True if entity is available."""
611  if (stream := self.streamstream) and not stream.available:
612  return False
613  return super().available
614 
615  async def async_create_stream(self) -> Stream | None:
616  """Create a Stream for stream_source."""
617  # There is at most one stream (a decode worker) per camera
618  if not self._create_stream_lock_create_stream_lock:
619  self._create_stream_lock_create_stream_lock = asyncio.Lock()
620  async with self._create_stream_lock_create_stream_lock:
621  if not self.streamstream:
622  async with asyncio.timeout(CAMERA_STREAM_SOURCE_TIMEOUT):
623  source = await self.stream_sourcestream_source()
624  if not source:
625  return None
626  self.streamstream = create_stream(
627  self.hasshass,
628  source,
629  options=self.stream_options,
630  dynamic_stream_settings=await self.hasshass.data[
631  DATA_CAMERA_PREFS
632  ].get_dynamic_stream_settings(self.entity_identity_id),
633  stream_label=self.entity_identity_id,
634  )
635  self.streamstream.set_update_callback(self.async_write_ha_stateasync_write_ha_stateasync_write_ha_state)
636  return self.streamstream
637 
638  async def stream_source(self) -> str | None:
639  """Return the source of the stream.
640 
641  This is used by cameras with CameraEntityFeature.STREAM
642  and StreamType.HLS.
643  """
644  return None
645 
646  async def async_handle_web_rtc_offer(self, offer_sdp: str) -> str | None:
647  """Handle the WebRTC offer and return an answer.
648 
649  This is used by cameras with CameraEntityFeature.STREAM
650  and StreamType.WEB_RTC.
651 
652  Integrations can override with a native WebRTC implementation.
653  """
654 
656  self, offer_sdp: str, session_id: str, send_message: WebRTCSendMessage
657  ) -> None:
658  """Handle the async WebRTC offer.
659 
660  Async means that it could take some time to process the offer and responses/message
661  will be sent with the send_message callback.
662  This method is used by cameras with CameraEntityFeature.STREAM.
663  An integration overriding this method must also implement async_on_webrtc_candidate.
664 
665  Integrations can override with a native WebRTC implementation.
666  """
667  if self._supports_native_sync_webrtc_supports_native_sync_webrtc:
668  try:
669  answer = await deprecated_function(
670  "async_handle_async_webrtc_offer",
671  breaks_in_ha_version="2025.6",
672  )(self.async_handle_web_rtc_offerasync_handle_web_rtc_offer)(offer_sdp)
673  except ValueError as ex:
674  _LOGGER.error("Error handling WebRTC offer: %s", ex)
675  send_message(
676  WebRTCError(
677  "webrtc_offer_failed",
678  str(ex),
679  )
680  )
681  except TimeoutError:
682  # This catch was already here and should stay through the deprecation
683  _LOGGER.error("Timeout handling WebRTC offer")
684  send_message(
685  WebRTCError(
686  "webrtc_offer_failed",
687  "Timeout handling WebRTC offer",
688  )
689  )
690  else:
691  if answer:
692  send_message(WebRTCAnswer(answer))
693  else:
694  _LOGGER.error("Error handling WebRTC offer: No answer")
695  send_message(
696  WebRTCError(
697  "webrtc_offer_failed",
698  "No answer on WebRTC offer",
699  )
700  )
701  return
702 
703  if self._webrtc_provider_webrtc_provider:
704  await self._webrtc_provider_webrtc_provider.async_handle_async_webrtc_offer(
705  self, offer_sdp, session_id, send_message
706  )
707  return
708 
709  if self._legacy_webrtc_provider_legacy_webrtc_provider and (
710  answer := await self._legacy_webrtc_provider_legacy_webrtc_provider.async_handle_web_rtc_offer(
711  self, offer_sdp
712  )
713  ):
714  send_message(WebRTCAnswer(answer))
715  else:
716  raise HomeAssistantError("Camera does not support WebRTC")
717 
719  self, width: int | None = None, height: int | None = None
720  ) -> bytes | None:
721  """Return bytes of camera image."""
722  raise NotImplementedError
723 
725  self, width: int | None = None, height: int | None = None
726  ) -> bytes | None:
727  """Return bytes of camera image."""
728  return await self.hasshass.async_add_executor_job(
729  partial(self.camera_imagecamera_image, width=width, height=height)
730  )
731 
733  self, request: web.Request, interval: float
734  ) -> web.StreamResponse:
735  """Generate an HTTP MJPEG stream from camera images."""
736  return await async_get_still_stream(
737  request, self.async_camera_imageasync_camera_image, self.content_type, interval
738  )
739 
741  self, request: web.Request
742  ) -> web.StreamResponse | None:
743  """Serve an HTTP MJPEG stream from the camera.
744 
745  This method can be overridden by camera platforms to proxy
746  a direct stream from the camera.
747  """
748  return await self.handle_async_still_streamhandle_async_still_stream(request, self.frame_intervalframe_interval)
749 
750  @property
751  @final
752  def state(self) -> str:
753  """Return the camera state."""
754  if self.is_recordingis_recording:
755  return CameraState.RECORDING
756  if self.is_streamingis_streaming:
757  return CameraState.STREAMING
758  return CameraState.IDLE
759 
760  @cached_property
761  def is_on(self) -> bool:
762  """Return true if on."""
763  return self._attr_is_on
764 
765  def turn_off(self) -> None:
766  """Turn off camera."""
767  raise NotImplementedError
768 
769  async def async_turn_off(self) -> None:
770  """Turn off camera."""
771  await self.hasshass.async_add_executor_job(self.turn_offturn_off)
772 
773  def turn_on(self) -> None:
774  """Turn on camera."""
775  raise NotImplementedError
776 
777  async def async_turn_on(self) -> None:
778  """Turn on camera."""
779  await self.hasshass.async_add_executor_job(self.turn_onturn_on)
780 
781  def enable_motion_detection(self) -> None:
782  """Enable motion detection in the camera."""
783  raise NotImplementedError
784 
785  async def async_enable_motion_detection(self) -> None:
786  """Call the job and enable motion detection."""
787  await self.hasshass.async_add_executor_job(self.enable_motion_detectionenable_motion_detection)
788 
789  def disable_motion_detection(self) -> None:
790  """Disable motion detection in camera."""
791  raise NotImplementedError
792 
793  async def async_disable_motion_detection(self) -> None:
794  """Call the job and disable motion detection."""
795  await self.hasshass.async_add_executor_job(self.disable_motion_detectiondisable_motion_detection)
796 
797  @final
798  @property
799  def state_attributes(self) -> dict[str, str | None]:
800  """Return the camera state attributes."""
801  attrs = {"access_token": self.access_tokens[-1]}
802 
803  if model := self.modelmodel:
804  attrs["model_name"] = model
805 
806  if brand := self.brandbrand:
807  attrs["brand"] = brand
808 
809  if motion_detection_enabled := self.motion_detection_enabledmotion_detection_enabled:
810  attrs["motion_detection"] = motion_detection_enabled
811 
812  if frontend_stream_type := self.frontend_stream_typefrontend_stream_type:
813  attrs["frontend_stream_type"] = frontend_stream_type
814 
815  return attrs
816 
817  @callback
818  def async_update_token(self) -> None:
819  """Update the used token."""
820  self.access_tokens.append(hex(_RND.getrandbits(256))[2:])
821  self.__dict__.pop("entity_picture", None)
822 
823  async def async_internal_added_to_hass(self) -> None:
824  """Run when entity about to be added to hass."""
825  await super().async_internal_added_to_hass()
826  self.__supports_stream__supports_stream = (
827  self.supported_features_compatsupported_features_compat & CameraEntityFeature.STREAM
828  )
829  await self.async_refresh_providersasync_refresh_providers(write_state=False)
830 
831  async def async_refresh_providers(self, *, write_state: bool = True) -> None:
832  """Determine if any of the registered providers are suitable for this entity.
833 
834  This affects state attributes, so it should be invoked any time the registered
835  providers or inputs to the state attributes change.
836  """
837  old_provider = self._webrtc_provider_webrtc_provider
838  old_legacy_provider = self._legacy_webrtc_provider_legacy_webrtc_provider
839  new_provider = None
840  new_legacy_provider = None
841 
842  # Skip all providers if the camera has a native WebRTC implementation
843  if not (
844  self._supports_native_sync_webrtc_supports_native_sync_webrtc or self._supports_native_async_webrtc_supports_native_async_webrtc
845  ):
846  # Camera doesn't have a native WebRTC implementation
847  new_provider = await self._async_get_supported_webrtc_provider(
848  async_get_supported_provider
849  )
850 
851  if new_provider is None:
852  # Only add the legacy provider if the new provider is not available
853  new_legacy_provider = await self._async_get_supported_webrtc_provider(
854  async_get_supported_legacy_provider
855  )
856 
857  if old_provider != new_provider or old_legacy_provider != new_legacy_provider:
858  self._webrtc_provider_webrtc_provider = new_provider
859  self._legacy_webrtc_provider_legacy_webrtc_provider = new_legacy_provider
860  self._invalidate_camera_capabilities_cache_invalidate_camera_capabilities_cache()
861  if write_state:
862  self.async_write_ha_stateasync_write_ha_stateasync_write_ha_state()
863 
864  async def _async_get_supported_webrtc_provider[_T](
865  self, fn: Callable[[HomeAssistant, Camera], Coroutine[None, None, _T | None]]
866  ) -> _T | None:
867  """Get first provider that supports this camera."""
868  if CameraEntityFeature.STREAM not in self.supported_features_compatsupported_features_compat:
869  return None
870 
871  return await fn(self.hasshass, self)
872 
873  @callback
874  def _async_get_webrtc_client_configuration(self) -> WebRTCClientConfiguration:
875  """Return the WebRTC client configuration adjustable per integration."""
876  return WebRTCClientConfiguration()
877 
878  @final
879  @callback
880  def async_get_webrtc_client_configuration(self) -> WebRTCClientConfiguration:
881  """Return the WebRTC client configuration and extend it with the registered ice servers."""
882  config = self._async_get_webrtc_client_configuration_async_get_webrtc_client_configuration()
883 
884  if not self._supports_native_sync_webrtc_supports_native_sync_webrtc:
885  # Until 2024.11, the frontend was not resolving any ice servers
886  # The async approach was added 2024.11 and new integrations need to use it
887  ice_servers = [
888  server
889  for servers in self.hasshass.data.get(DATA_ICE_SERVERS, [])
890  for server in servers()
891  ]
892  config.configuration.ice_servers.extend(ice_servers)
893 
894  config.get_candidates_upfront = (
895  self._supports_native_sync_webrtc_supports_native_sync_webrtc
896  or self._legacy_webrtc_provider_legacy_webrtc_provider is not None
897  )
898 
899  return config
900 
902  self, session_id: str, candidate: RTCIceCandidateInit
903  ) -> None:
904  """Handle a WebRTC candidate."""
905  if self._webrtc_provider_webrtc_provider:
906  await self._webrtc_provider_webrtc_provider.async_on_webrtc_candidate(session_id, candidate)
907  else:
908  raise HomeAssistantError("Cannot handle WebRTC candidate")
909 
910  @callback
911  def close_webrtc_session(self, session_id: str) -> None:
912  """Close a WebRTC session."""
913  if self._webrtc_provider_webrtc_provider:
914  self._webrtc_provider_webrtc_provider.async_close_session(session_id)
915 
916  @callback
918  """Invalidate the camera capabilities cache."""
919  self._cache.pop("camera_capabilities", None)
920 
921  @final
922  @under_cached_property
923  def camera_capabilities(self) -> CameraCapabilities:
924  """Return the camera capabilities."""
925  frontend_stream_types = set()
926  if CameraEntityFeature.STREAM in self.supported_features_compatsupported_features_compat:
927  if self._supports_native_sync_webrtc_supports_native_sync_webrtc or self._supports_native_async_webrtc_supports_native_async_webrtc:
928  # The camera has a native WebRTC implementation
929  frontend_stream_types.add(StreamType.WEB_RTC)
930  else:
931  frontend_stream_types.add(StreamType.HLS)
932 
933  if self._webrtc_provider_webrtc_provider or self._legacy_webrtc_provider_legacy_webrtc_provider:
934  frontend_stream_types.add(StreamType.WEB_RTC)
935 
936  return CameraCapabilities(frontend_stream_types)
937 
938  @callback
939  def async_write_ha_state(self) -> None:
940  """Write the state to the state machine.
941 
942  Schedules async_refresh_providers if support of streams have changed.
943  """
944  super().async_write_ha_state()
945  if self.__supports_stream__supports_stream != (
946  supports_stream := self.supported_features_compatsupported_features_compat
947  & CameraEntityFeature.STREAM
948  ):
949  self.__supports_stream__supports_stream = supports_stream
950  self._invalidate_camera_capabilities_cache_invalidate_camera_capabilities_cache()
951  self.hasshass.async_create_task(self.async_refresh_providersasync_refresh_providers())
952 
953 
954 class CameraView(HomeAssistantView):
955  """Base CameraView."""
956 
957  requires_auth = False
958 
959  def __init__(self, component: EntityComponent[Camera]) -> None:
960  """Initialize a basic camera view."""
961  self.componentcomponent = component
962 
963  async def get(self, request: web.Request, entity_id: str) -> web.StreamResponse:
964  """Start a GET request."""
965  if (camera := self.componentcomponent.get_entity(entity_id)) is None:
966  raise web.HTTPNotFound
967 
968  authenticated = (
969  request[KEY_AUTHENTICATED]
970  or request.query.get("token") in camera.access_tokens
971  )
972 
973  if not authenticated:
974  # Attempt with invalid bearer token, raise unauthorized
975  # so ban middleware can handle it.
976  if hdrs.AUTHORIZATION in request.headers:
977  raise web.HTTPUnauthorized
978  # Invalid sigAuth or camera access token
979  raise web.HTTPForbidden
980 
981  if not camera.is_on:
982  _LOGGER.debug("Camera is off")
983  raise web.HTTPServiceUnavailable
984 
985  return await self.handlehandle(request, camera)
986 
987  async def handle(self, request: web.Request, camera: Camera) -> web.StreamResponse:
988  """Handle the camera request."""
989  raise NotImplementedError
990 
991 
993  """Camera view to serve an image."""
994 
995  url = "/api/camera_proxy/{entity_id}"
996  name = "api:camera:image"
997 
998  async def handle(self, request: web.Request, camera: Camera) -> web.Response:
999  """Serve camera image."""
1000  width = request.query.get("width")
1001  height = request.query.get("height")
1002  try:
1003  image = await _async_get_image(
1004  camera,
1005  CAMERA_IMAGE_TIMEOUT,
1006  int(width) if width else None,
1007  int(height) if height else None,
1008  )
1009  except (HomeAssistantError, ValueError) as ex:
1010  raise web.HTTPInternalServerError from ex
1011 
1012  return web.Response(body=image.content, content_type=image.content_type)
1013 
1014 
1016  """Camera View to serve an MJPEG stream."""
1017 
1018  url = "/api/camera_proxy_stream/{entity_id}"
1019  name = "api:camera:stream"
1020 
1021  async def handle(self, request: web.Request, camera: Camera) -> web.StreamResponse:
1022  """Serve camera stream, possibly with interval."""
1023  if (interval_str := request.query.get("interval")) is None:
1024  try:
1025  stream = await camera.handle_async_mjpeg_stream(request)
1026  except ConnectionResetError:
1027  stream = None
1028  _LOGGER.debug("Error while writing MJPEG stream to transport")
1029  if stream is None:
1030  raise web.HTTPBadGateway
1031  return stream
1032 
1033  try:
1034  # Compose camera stream from stills
1035  interval = float(interval_str)
1036  if interval < MIN_STREAM_INTERVAL:
1037  raise ValueError(f"Stream interval must be > {MIN_STREAM_INTERVAL}") # noqa: TRY301
1038  return await camera.handle_async_still_stream(request, interval)
1039  except ValueError as err:
1040  raise web.HTTPBadRequest from err
1041 
1042 
1043 @websocket_api.websocket_command( { vol.Required("type"): "camera/capabilities",
1044  vol.Required("entity_id"): cv.entity_id,
1045  }
1046 )
1047 @websocket_api.async_response
1048 async def ws_camera_capabilities(
1049  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
1050 ) -> None:
1051  """Handle get camera capabilities websocket command.
1052 
1053  Async friendly.
1054  """
1055  camera = get_camera_from_entity_id(hass, msg["entity_id"])
1056  connection.send_result(msg["id"], asdict(camera.camera_capabilities))
1057 
1058 
1059 @websocket_api.websocket_command( { vol.Required("type"): "camera/stream",
1060  vol.Required("entity_id"): cv.entity_id,
1061  vol.Optional("format", default="hls"): vol.In(OUTPUT_FORMATS),
1062  }
1063 )
1064 @websocket_api.async_response
1065 async def ws_camera_stream(
1066  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
1067 ) -> None:
1068  """Handle get camera stream websocket command.
1070  Async friendly.
1071  """
1072  try:
1073  entity_id = msg["entity_id"]
1074  camera = get_camera_from_entity_id(hass, entity_id)
1075  url = await _async_stream_endpoint_url(hass, camera, fmt=msg["format"])
1076  connection.send_result(msg["id"], {"url": url})
1077  except HomeAssistantError as ex:
1078  _LOGGER.error("Error requesting stream: %s", ex)
1079  connection.send_error(msg["id"], "start_stream_failed", str(ex))
1080  except TimeoutError:
1081  _LOGGER.error("Timeout getting stream source")
1082  connection.send_error(
1083  msg["id"], "start_stream_failed", "Timeout getting stream source"
1084  )
1085 
1086 
1087 @websocket_api.websocket_command( {vol.Required("type"): "camera/get_prefs", vol.Required("entity_id"): cv.entity_id}
1088 )
1089 @websocket_api.async_response
1090 async def websocket_get_prefs(
1091  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
1092 ) -> None:
1093  """Handle request for account info."""
1094  stream_prefs = await hass.data[DATA_CAMERA_PREFS].get_dynamic_stream_settings(
1095  msg["entity_id"]
1096  )
1097  connection.send_result(msg["id"], asdict(stream_prefs))
1098 
1099 
1100 @websocket_api.websocket_command( { vol.Required("type"): "camera/update_prefs",
1101  vol.Required("entity_id"): cv.entity_id,
1102  vol.Optional(PREF_PRELOAD_STREAM): bool,
1103  vol.Optional(PREF_ORIENTATION): vol.Coerce(Orientation),
1104  }
1105 )
1106 @websocket_api.async_response
1107 async def websocket_update_prefs(
1108  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
1109 ) -> None:
1110  """Handle request for account info."""
1111  changes = dict(msg)
1112  changes.pop("id")
1113  changes.pop("type")
1114  entity_id = changes.pop("entity_id")
1115  try:
1116  entity_prefs = await hass.data[DATA_CAMERA_PREFS].async_update(
1117  entity_id, **changes
1118  )
1119  except HomeAssistantError as ex:
1120  _LOGGER.error("Error setting camera preferences: %s", ex)
1121  connection.send_error(msg["id"], "update_failed", str(ex))
1122  else:
1123  connection.send_result(msg["id"], entity_prefs)
1124 
1125 
1126 class _TemplateCameraEntity:
1127  """Class to warn when the `entity_id` template variable is accessed.
1128 
1129  Can be removed in HA Core 2025.6.
1130  """
1131 
1132  def __init__(self, camera: Camera, service: str) -> None:
1133  """Initialize."""
1134  self._camera_camera = camera
1135  self._entity_id_entity_id = camera.entity_id
1136  self._hass_hass = camera.hass
1137  self._service_service = service
1138 
1139  def _report_issue(self) -> None:
1140  """Create a repair issue."""
1141  ir.async_create_issue(
1142  self._hass_hass,
1143  DOMAIN,
1144  f"deprecated_filename_template_{self._entity_id}_{self._service}",
1145  breaks_in_ha_version="2025.6.0",
1146  is_fixable=True,
1147  severity=ir.IssueSeverity.WARNING,
1148  translation_key="deprecated_filename_template",
1149  translation_placeholders={
1150  "entity_id": self._entity_id_entity_id,
1151  "service": f"{DOMAIN}.{self._service}",
1152  },
1153  )
1154 
1155  def __getattr__(self, name: str) -> Any:
1156  """Forward to the camera entity."""
1157  self._report_issue_report_issue()
1158  return getattr(self._camera_camera, name)
1159 
1160  def __str__(self) -> str:
1161  """Forward to the camera entity."""
1162  self._report_issue_report_issue()
1163  return str(self._camera_camera)
1164 
1165 
1167  camera: Camera, service_call: ServiceCall
1168 ) -> None:
1169  """Handle snapshot services calls."""
1170  hass = camera.hass
1171  filename: Template = service_call.data[ATTR_FILENAME]
1172 
1173  snapshot_file = filename.async_render(
1174  variables={ATTR_ENTITY_ID: _TemplateCameraEntity(camera, SERVICE_SNAPSHOT)}
1175  )
1176 
1177  # check if we allow to access to that file
1178  if not hass.config.is_allowed_path(snapshot_file):
1179  raise HomeAssistantError(
1180  f"Cannot write `{snapshot_file}`, no access to path; `allowlist_external_dirs` may need to be adjusted in `configuration.yaml`"
1181  )
1182 
1183  async with asyncio.timeout(CAMERA_IMAGE_TIMEOUT):
1184  image = (
1185  await _async_get_stream_image(camera, wait_for_next_keyframe=True)
1186  if camera.use_stream_for_stills
1187  else await camera.async_camera_image()
1188  )
1189 
1190  if image is None:
1191  return
1192 
1193  def _write_image(to_file: str, image_data: bytes) -> None:
1194  """Executor helper to write image."""
1195  os.makedirs(os.path.dirname(to_file), exist_ok=True)
1196  with open(to_file, "wb") as img_file:
1197  img_file.write(image_data)
1198 
1199  try:
1200  await hass.async_add_executor_job(_write_image, snapshot_file, image)
1201  except OSError as err:
1202  _LOGGER.error("Can't write image to file: %s", err)
1203 
1204 
1206  camera: Camera, service_call: ServiceCall
1207 ) -> None:
1208  """Handle play stream services calls."""
1209  hass = camera.hass
1210  fmt = service_call.data[ATTR_FORMAT]
1211  url = await _async_stream_endpoint_url(camera.hass, camera, fmt)
1212  url = f"{get_url(hass)}{url}"
1213 
1214  await hass.services.async_call(
1215  DOMAIN_MP,
1216  SERVICE_PLAY_MEDIA,
1217  {
1218  ATTR_ENTITY_ID: service_call.data[ATTR_MEDIA_PLAYER],
1219  ATTR_MEDIA_CONTENT_ID: url,
1220  ATTR_MEDIA_CONTENT_TYPE: FORMAT_CONTENT_TYPE[fmt],
1221  },
1222  blocking=True,
1223  context=service_call.context,
1224  )
1225 
1226 
1227 async def _async_stream_endpoint_url(
1228  hass: HomeAssistant, camera: Camera, fmt: str
1229 ) -> str:
1230  stream = await camera.async_create_stream()
1231  if not stream:
1232  raise HomeAssistantError(
1233  f"{camera.entity_id} does not support play stream service"
1234  )
1235 
1236  stream.add_provider(fmt)
1237  await stream.start()
1238  return stream.endpoint_url(fmt)
1239 
1240 
1241 async def async_handle_record_service(
1242  camera: Camera, service_call: ServiceCall
1243 ) -> None:
1244  """Handle stream recording service calls."""
1245  stream = await camera.async_create_stream()
1246 
1247  if not stream:
1248  raise HomeAssistantError(f"{camera.entity_id} does not support record service")
1249 
1250  filename = service_call.data[CONF_FILENAME]
1251  video_path = filename.async_render(
1252  variables={ATTR_ENTITY_ID: _TemplateCameraEntity(camera, SERVICE_RECORD)}
1253  )
1254 
1255  await stream.async_record(
1256  video_path,
1257  duration=service_call.data[CONF_DURATION],
1258  lookback=service_call.data[CONF_LOOKBACK],
1259  )
1260 
1261 
1262 # These can be removed if no deprecated constant are in this module anymore
1263 __getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
1264 __dir__ = partial(
1265  dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
1266 )
1267 __all__ = all_with_deprecated_constants(globals())
1268 
web.Response handle(self, web.Request request, Camera camera)
Definition: __init__.py:998
web.StreamResponse handle(self, web.Request request, Camera camera)
Definition: __init__.py:1021
web.StreamResponse handle(self, web.Request request, Camera camera)
Definition: __init__.py:987
web.StreamResponse get(self, web.Request request, str entity_id)
Definition: __init__.py:963
None __init__(self, EntityComponent[Camera] component)
Definition: __init__.py:959
str|None async_handle_web_rtc_offer(self, str offer_sdp)
Definition: __init__.py:646
None async_refresh_providers(self, *bool write_state=True)
Definition: __init__.py:831
WebRTCClientConfiguration _async_get_webrtc_client_configuration(self)
Definition: __init__.py:874
None async_handle_async_webrtc_offer(self, str offer_sdp, str session_id, WebRTCSendMessage send_message)
Definition: __init__.py:657
bytes|None camera_image(self, int|None width=None, int|None height=None)
Definition: __init__.py:720
StreamType|None frontend_stream_type(self)
Definition: __init__.py:575
CameraEntityFeature supported_features(self)
Definition: __init__.py:527
web.StreamResponse|None handle_async_mjpeg_stream(self, web.Request request)
Definition: __init__.py:742
None async_on_webrtc_candidate(self, str session_id, RTCIceCandidateInit candidate)
Definition: __init__.py:903
CameraCapabilities camera_capabilities(self)
Definition: __init__.py:923
bytes|None async_camera_image(self, int|None width=None, int|None height=None)
Definition: __init__.py:726
web.StreamResponse handle_async_still_stream(self, web.Request request, float interval)
Definition: __init__.py:734
None close_webrtc_session(self, str session_id)
Definition: __init__.py:911
WebRTCClientConfiguration async_get_webrtc_client_configuration(self)
Definition: __init__.py:880
CameraEntityFeature supported_features_compat(self)
Definition: __init__.py:532
Stream|None async_create_stream(self)
Definition: __init__.py:615
dict[str, str|None] state_attributes(self)
Definition: __init__.py:799
None _report_deprecated_supported_features_values(self, IntFlag replacement)
Definition: entity.py:1645
int|None supported_features(self)
Definition: entity.py:861
CalendarEntity get_entity(HomeAssistant hass, str entity_id)
Definition: trigger.py:96
Camera get_camera_from_entity_id(HomeAssistant hass, str entity_id)
Definition: helper.py:16
bytes scale_jpeg_camera_image(Image cam_image, int width, int height)
Definition: img_util.py:48
Callable[[], None] async_register_ice_servers(HomeAssistant hass, Callable[[], Iterable[RTCIceServer]] get_ice_server_fn)
Definition: webrtc.py:402
None async_register_ws(HomeAssistant hass)
Definition: webrtc.py:360
None ws_camera_capabilities(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
Definition: __init__.py:1052
None async_handle_play_stream_service(Camera camera, ServiceCall service_call)
Definition: __init__.py:1214
Image async_get_image(HomeAssistant hass, str entity_id, int timeout=10, int|None width=None, int|None height=None)
Definition: __init__.py:245
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:443
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:438
str|None async_get_stream_source(HomeAssistant hass, str entity_id)
Definition: __init__.py:270
None async_handle_snapshot_service(Camera camera, ServiceCall service_call)
Definition: __init__.py:1175
None websocket_update_prefs(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
Definition: __init__.py:1116
None async_handle_record_service(Camera camera, ServiceCall service_call)
Definition: __init__.py:1250
Image _async_get_image(Camera camera, int timeout=10, int|None width=None, int|None height=None)
Definition: __init__.py:201
bytes|None _async_get_stream_image(Camera camera, int|None width=None, int|None height=None, bool wait_for_next_keyframe=False)
Definition: __init__.py:259
str _async_stream_endpoint_url(HomeAssistant hass, Camera camera, str fmt)
Definition: __init__.py:1236
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:348
str async_request_stream(HomeAssistant hass, str entity_id, str fmt)
Definition: __init__.py:190
web.StreamResponse|None async_get_mjpeg_stream(HomeAssistant hass, web.Request request, str entity_id)
Definition: __init__.py:279
None ws_camera_stream(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
Definition: __init__.py:1071
web.StreamResponse async_get_still_stream(web.Request request, Callable[[], Awaitable[bytes|None]] image_cb, str content_type, float interval)
Definition: __init__.py:296
None websocket_get_prefs(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
Definition: __init__.py:1097
None __init__(self, _HAFFmpegT ffmpeg, bool initial_state=True)
Definition: __init__.py:184
None open(self, **Any kwargs)
Definition: lock.py:86
Stream create_stream(HomeAssistant hass, str stream_source, Mapping[str, str|bool|float] options, DynamicStreamSettings dynamic_stream_settings, str|None stream_label=None)
Definition: __init__.py:117
list[str] all_with_deprecated_constants(dict[str, Any] module_globals)
Definition: deprecation.py:356
CALLBACK_TYPE async_track_time_interval(HomeAssistant hass, Callable[[datetime], Coroutine[Any, Any, None]|None] action, timedelta interval, *str|None name=None, bool|None cancel_on_shutdown=None)
Definition: event.py:1679
None report_usage(str what, *str|None breaks_in_ha_version=None, ReportBehavior core_behavior=ReportBehavior.ERROR, ReportBehavior core_integration_behavior=ReportBehavior.LOG, ReportBehavior custom_integration_behavior=ReportBehavior.LOG, set[str]|None exclude_integrations=None, str|None integration_domain=None, int level=logging.WARNING)
Definition: frame.py:195