1 """Component providing support to the Ring Door Bell camera."""
3 from __future__
import annotations
5 from collections.abc
import Callable
6 from dataclasses
import dataclass
7 from datetime
import timedelta
9 from typing
import TYPE_CHECKING, Any, Generic
11 from aiohttp
import web
12 from haffmpeg.camera
import CameraMjpeg
13 from ring_doorbell
import RingDoorBell
14 from ring_doorbell.webrtcstream
import RingWebRtcMessage
19 CameraEntityDescription,
33 from .
import RingConfigEntry
34 from .coordinator
import RingDataCoordinator
35 from .entity
import RingDeviceT, RingEntity, exception_wrap
38 MOTION_DETECTION_CAPABILITY =
"motion_detection"
40 _LOGGER = logging.getLogger(__name__)
43 @dataclass(frozen=True, kw_only=True)
45 """Base class for event entity description."""
47 exists_fn: Callable[[RingDoorBell], bool]
49 motion_detection: bool
52 CAMERA_DESCRIPTIONS: tuple[RingCameraEntityDescription, ...] = (
55 translation_key=
"live_view",
56 exists_fn=
lambda _:
True,
58 motion_detection=
False,
62 translation_key=
"last_recording",
63 entity_registry_enabled_default=
False,
64 exists_fn=
lambda camera: camera.has_subscription,
66 motion_detection=
True,
73 entry: RingConfigEntry,
74 async_add_entities: AddEntitiesCallback,
76 """Set up a Ring Door Bell and StickUp Camera."""
77 ring_data = entry.runtime_data
78 devices_coordinator = ring_data.devices_coordinator
79 ffmpeg_manager = ffmpeg.get_ffmpeg_manager(hass)
82 RingCam(camera, devices_coordinator, description, ffmpeg_manager=ffmpeg_manager)
83 for description
in CAMERA_DESCRIPTIONS
84 for camera
in ring_data.devices.video_devices
85 if description.exists_fn(camera)
92 """An implementation of a Ring Door Bell camera."""
97 coordinator: RingDataCoordinator,
98 description: RingCameraEntityDescription,
102 """Initialize a Ring Door Bell camera."""
103 super().
__init__(device, coordinator)
105 Camera.__init__(self)
107 self.
_last_event_last_event: dict[str, Any] |
None =
None
110 self.
_images_images: dict[tuple[int |
None, int |
None], bytes] = {}
111 self.
_expires_at_expires_at = dt_util.utcnow() - FORCE_REFRESH_INTERVAL
113 if description.motion_detection
and device.has_capability(
114 MOTION_DETECTION_CAPABILITY
117 if description.live_stream:
118 self._attr_supported_features |= CameraEntityFeature.STREAM
122 """Call update method."""
142 """Return the state attributes."""
149 self, width: int |
None =
None, height: int |
None =
None
151 """Return a still image response from the camera."""
152 key = (width, height)
154 image = await ffmpeg.async_get_image(
162 self.
_images_images[key] = image
167 self, request: web.Request
168 ) -> web.StreamResponse |
None:
169 """Generate an HTTP MJPEG stream from the camera."""
174 await stream.open_camera(self.
_video_url_video_url)
177 stream_reader = await stream.get_reader()
188 self, offer_sdp: str, session_id: str, send_message: WebRTCSendMessage
190 """Return the source of the stream."""
192 def message_wrapper(ring_message: RingWebRtcMessage) ->
None:
193 if ring_message.error_code:
194 msg = ring_message.error_message
or ""
195 send_message(WebRTCError(ring_message.error_code, msg))
196 elif ring_message.answer:
198 elif ring_message.candidate:
202 ring_message.candidate,
203 sdp_m_line_index=ring_message.sdp_m_line_index
or 0,
209 offer_sdp, session_id, message_wrapper, keep_alive_timeout=
None
213 self, session_id: str, candidate: RTCIceCandidateInit
215 """Handle a WebRTC candidate."""
216 if candidate.sdp_m_line_index
is None:
217 msg =
"The sdp_m_line_index is required for ring webrtc streaming"
220 session_id, candidate.candidate, candidate.sdp_m_line_index
225 """Close a WebRTC session."""
229 """Update camera entity and refresh attributes."""
241 if self.
_last_event_last_event[
"recording"][
"status"] !=
"ready":
244 utcnow = dt_util.utcnow()
254 self.
_expires_at_expires_at = FORCE_REFRESH_INTERVAL + utcnow
262 assert event_id
and isinstance(event_id, int)
269 "Entity %s does not have motion detection capability", self.
entity_identity_id
278 """Enable motion detection in the camera."""
282 """Disable motion detection in camera."""
None async_write_ha_state(self)
None _handle_coordinator_update(self)
None async_on_webrtc_candidate(self, str session_id, RTCIceCandidateInit candidate)
None close_webrtc_session(self, str session_id)
web.StreamResponse|None handle_async_mjpeg_stream(self, web.Request request)
None async_enable_motion_detection(self)
None async_handle_async_webrtc_offer(self, str offer_sdp, str session_id, WebRTCSendMessage send_message)
_attr_motion_detection_enabled
None async_disable_motion_detection(self)
bytes|None async_camera_image(self, int|None width=None, int|None height=None)
None _async_set_motion_detection_enabled(self, bool new_state)
dict[str, Any] extra_state_attributes(self)
str|None _async_get_video(self)
None __init__(self, RingDoorBell device, RingDataCoordinator coordinator, RingCameraEntityDescription description, *ffmpeg.FFmpegManager ffmpeg_manager)
RingDevices _get_coordinator_data(self)
None async_schedule_update_ha_state(self, bool force_refresh=False)
None async_write_ha_state(self)
web.Response get(self, web.Request request, str config_key)
None async_setup_entry(HomeAssistant hass, RingConfigEntry entry, AddEntitiesCallback async_add_entities)
web.StreamResponse async_aiohttp_proxy_stream(HomeAssistant hass, web.BaseRequest request, aiohttp.StreamReader stream, str|None content_type, int buffer_size=102400, int timeout=10)