1 """Support for IP Cameras."""
3 from __future__
import annotations
6 from collections.abc
import Mapping
7 from datetime
import datetime, timedelta
12 import voluptuous
as vol
18 CONF_USE_WALLCLOCK_AS_TIMESTAMPS,
27 HTTP_DIGEST_AUTHENTICATION,
40 CONF_LIMIT_REFETCH_TO_URL_CHANGE,
46 _LOGGER = logging.getLogger(__name__)
50 hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
52 """Set up a generic IP Camera."""
55 [
GenericCamera(hass, entry.options, entry.entry_id, entry.title)]
60 """Generate httpx.Auth object from credentials."""
61 username: str |
None = device_info.get(CONF_USERNAME)
62 password: str |
None = device_info.get(CONF_PASSWORD)
63 authentication = device_info.get(CONF_AUTHENTICATION)
64 if username
and password:
65 if authentication == HTTP_DIGEST_AUTHENTICATION:
66 return httpx.DigestAuth(username=username, password=password)
67 return httpx.BasicAuth(username=username, password=password)
72 """A generic implementation of an IP camera."""
74 _last_image: bytes |
None
75 _last_update: datetime
76 _update_lock: asyncio.Lock
81 device_info: Mapping[str, Any],
85 """Initialize a generic camera."""
90 self.
_username_username = device_info.get(CONF_USERNAME)
91 self.
_password_password = device_info.get(CONF_PASSWORD)
92 self.
_name_name = device_info.get(CONF_NAME, title)
99 self.
_limit_refetch_limit_refetch = device_info[CONF_LIMIT_REFETCH_TO_URL_CHANGE]
105 if device_info.get(CONF_RTSP_TRANSPORT):
106 self.stream_options[CONF_RTSP_TRANSPORT] = device_info[CONF_RTSP_TRANSPORT]
108 if device_info.get(CONF_USE_WALLCLOCK_AS_TIMESTAMPS):
109 self.stream_options[CONF_USE_WALLCLOCK_AS_TIMESTAMPS] =
True
117 identifiers={(DOMAIN, identifier)},
118 manufacturer=
"Generic",
123 """Whether or not to use stream to generate stills."""
127 self, width: int |
None =
None, height: int |
None =
None
129 """Return a still image response from the camera."""
133 url = self.
_still_image_url_still_image_url.async_render(parse_result=
False)
134 except TemplateError
as err:
135 _LOGGER.error(
"Error parsing template %s: %s", self.
_still_image_url_still_image_url, err)
139 vol.Schema(vol.Url())(url)
140 except vol.Invalid
as err:
141 _LOGGER.warning(
"Invalid URL '%s': %s, returning last image", url, err)
157 update_time = datetime.now()
159 response = await async_client.get(
161 auth=self.
_auth_auth,
162 follow_redirects=
True,
163 timeout=GET_IMAGE_TIMEOUT,
165 response.raise_for_status()
169 except httpx.TimeoutException:
170 _LOGGER.error(
"Timeout getting camera image from %s", self.
_name_name)
172 except (httpx.RequestError, httpx.HTTPStatusError)
as err:
174 "Error getting new camera image from %s: %s", self.
_name_name, err
183 """Return the name of this device."""
184 return self.
_name_name
187 """Return the source of the stream."""
192 stream_url = self.
_stream_source_stream_source.async_render(parse_result=
False)
193 url = yarl.URL(stream_url)
199 and url.is_absolute()
203 except TemplateError
as err:
204 _LOGGER.error(
"Error parsing template %s: %s", self.
_stream_source_stream_source, err)
None __init__(self, HomeAssistant hass, Mapping[str, Any] device_info, str identifier, str title)
bytes|None async_camera_image(self, int|None width=None, int|None height=None)
bool use_stream_for_stills(self)
str|None stream_source(self)
httpx.Auth|None generate_auth(Mapping[str, Any] device_info)
None async_setup_entry(HomeAssistant hass, ConfigEntry entry, AddEntitiesCallback async_add_entities)
httpx.AsyncClient get_async_client(HomeAssistant hass, bool verify_ssl=True)