1 """The motionEye integration."""
3 from __future__
import annotations
5 from contextlib
import suppress
6 from types
import MappingProxyType
10 from jinja2
import Template
11 from motioneye_client.client
import MotionEyeClient, MotionEyeClientURLParseError
12 from motioneye_client.const
import (
13 DEFAULT_SURVEILLANCE_USERNAME,
16 KEY_STREAMING_AUTH_MODE,
17 KEY_TEXT_OVERLAY_CAMERA_NAME,
18 KEY_TEXT_OVERLAY_CUSTOM_TEXT,
19 KEY_TEXT_OVERLAY_CUSTOM_TEXT_LEFT,
20 KEY_TEXT_OVERLAY_CUSTOM_TEXT_RIGHT,
21 KEY_TEXT_OVERLAY_DISABLED,
22 KEY_TEXT_OVERLAY_LEFT,
23 KEY_TEXT_OVERLAY_RIGHT,
24 KEY_TEXT_OVERLAY_TIMESTAMP,
26 import voluptuous
as vol
39 HTTP_BASIC_AUTHENTICATION,
40 HTTP_DIGEST_AUTHENTICATION,
48 from .
import get_camera_from_cameras, is_acceptable_camera, listen_for_new_cameras
53 CONF_STREAM_URL_TEMPLATE,
54 CONF_SURVEILLANCE_PASSWORD,
55 CONF_SURVEILLANCE_USERNAME,
57 MOTIONEYE_MANUFACTURER,
59 SERVICE_SET_TEXT_OVERLAY,
61 TYPE_MOTIONEYE_MJPEG_CAMERA,
63 from .entity
import MotionEyeEntity
65 PLATFORMS = [Platform.CAMERA]
67 SCHEMA_TEXT_OVERLAY = vol.In(
69 KEY_TEXT_OVERLAY_DISABLED,
70 KEY_TEXT_OVERLAY_TIMESTAMP,
71 KEY_TEXT_OVERLAY_CUSTOM_TEXT,
72 KEY_TEXT_OVERLAY_CAMERA_NAME,
75 SCHEMA_SERVICE_SET_TEXT = vol.Schema(
77 cv.make_entity_service_schema(
79 vol.Optional(KEY_TEXT_OVERLAY_LEFT): SCHEMA_TEXT_OVERLAY,
80 vol.Optional(KEY_TEXT_OVERLAY_CUSTOM_TEXT_LEFT): cv.string,
81 vol.Optional(KEY_TEXT_OVERLAY_RIGHT): SCHEMA_TEXT_OVERLAY,
82 vol.Optional(KEY_TEXT_OVERLAY_CUSTOM_TEXT_RIGHT): cv.string,
85 cv.has_at_least_one_key(
86 KEY_TEXT_OVERLAY_LEFT,
87 KEY_TEXT_OVERLAY_CUSTOM_TEXT_LEFT,
88 KEY_TEXT_OVERLAY_RIGHT,
89 KEY_TEXT_OVERLAY_CUSTOM_TEXT_RIGHT,
96 hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
98 """Set up motionEye from a config entry."""
99 entry_data = hass.data[DOMAIN][entry.entry_id]
102 def camera_add(camera: dict[str, Any]) ->
None:
103 """Add a new motionEye camera."""
109 CONF_SURVEILLANCE_USERNAME, DEFAULT_SURVEILLANCE_USERNAME
111 entry.data.get(CONF_SURVEILLANCE_PASSWORD,
""),
113 entry_data[CONF_CLIENT],
114 entry_data[CONF_COORDINATOR],
122 platform = entity_platform.async_get_current_platform()
123 platform.async_register_entity_service(
124 SERVICE_SET_TEXT_OVERLAY,
125 SCHEMA_SERVICE_SET_TEXT,
126 "async_set_text_overlay",
128 platform.async_register_entity_service(
130 {vol.Required(CONF_ACTION): cv.string},
131 "async_request_action",
133 platform.async_register_entity_service(
136 "async_request_snapshot",
141 """motionEye mjpeg camera."""
143 _attr_brand = MOTIONEYE_MANUFACTURER
145 _attr_is_streaming =
True
149 config_entry_id: str,
152 camera: dict[str, Any],
153 client: MotionEyeClient,
154 coordinator: DataUpdateCoordinator,
155 options: MappingProxyType[str, str],
157 """Initialize a MJPEG camera."""
162 MotionEyeEntity.__init__(
165 TYPE_MOTIONEYE_MJPEG_CAMERA,
171 MjpegCamera.__init__(
179 self, camera: dict[str, Any]
181 """Convert a motionEye camera to MjpegCamera internal properties."""
183 if camera.get(KEY_STREAMING_AUTH_MODE)
in (
184 HTTP_BASIC_AUTHENTICATION,
185 HTTP_DIGEST_AUTHENTICATION,
187 auth = camera[KEY_STREAMING_AUTH_MODE]
189 streaming_template = self.
_options_options.
get(CONF_STREAM_URL_TEMPLATE,
"").strip()
192 if streaming_template:
195 streaming_url = Template(streaming_template).render(**camera)
197 with suppress(MotionEyeClientURLParseError):
198 streaming_url = self.
_client_client.get_camera_stream_url(camera)
204 CONF_MJPEG_URL: streaming_url
or "",
205 CONF_STILL_IMAGE_URL: self.
_client_client.get_camera_snapshot_url(camera),
206 CONF_AUTHENTICATION: auth,
211 """Set the internal state to match the given camera."""
229 """Determine if a camera is streaming/usable."""
232 )
and MotionEyeClient.is_camera_streaming(self.
_camera_camera)
236 """Return if entity is available."""
241 """Handle updated data from the coordinator."""
246 KEY_MOTION_DETECTION,
False
252 """Return the camera motion detection status."""
257 left_text: str |
None =
None,
258 right_text: str |
None =
None,
259 custom_left_text: str |
None =
None,
260 custom_right_text: str |
None =
None,
262 """Set text overlay for a camera."""
268 if left_text
is not None:
269 camera[KEY_TEXT_OVERLAY_LEFT] = left_text
270 if right_text
is not None:
271 camera[KEY_TEXT_OVERLAY_RIGHT] = right_text
272 if custom_left_text
is not None:
273 camera[KEY_TEXT_OVERLAY_CUSTOM_TEXT_LEFT] = custom_left_text.encode(
276 if custom_right_text
is not None:
277 camera[KEY_TEXT_OVERLAY_CUSTOM_TEXT_RIGHT] = custom_right_text.encode(
283 """Call a motionEye action on a camera."""
287 """Request a motionEye snapshot be saved."""
None async_request_action(self, str action)
None _handle_coordinator_update(self)
None async_request_snapshot(self)
_motion_detection_enabled
bool _is_acceptable_streaming_camera(self)
None __init__(self, str config_entry_id, str username, str password, dict[str, Any] camera, MotionEyeClient client, DataUpdateCoordinator coordinator, MappingProxyType[str, str] options)
dict[str, Any] _get_mjpeg_camera_properties_for_camera(self, dict[str, Any] camera)
None async_set_text_overlay(self, str|None left_text=None, str|None right_text=None, str|None custom_left_text=None, str|None custom_right_text=None)
bool motion_detection_enabled(self)
None _set_mjpeg_camera_state_for_camera(self, dict[str, Any] camera)
web.Response get(self, web.Request request, str config_key)
None async_setup_entry(HomeAssistant hass, ConfigEntry entry, AddEntitiesCallback async_add_entities)
bool is_acceptable_camera(dict[str, Any]|None camera)
None listen_for_new_cameras(HomeAssistant hass, ConfigEntry entry, Callable add_func)
dict[str, Any]|None get_camera_from_cameras(int camera_id, dict[str, Any]|None data)