mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 21:57:51 +00:00
Allow generic camera conf without still_image_url (#62611)
* Allow generic config with no CONF_STILL_IMAGE_URL * Use Stream.async_get_image when no CONF_STILL_IMAGE_URL * Remove GenericCamera.camera_image
This commit is contained in:
parent
3e3fb52dfa
commit
08a3140e6c
@ -1,7 +1,6 @@
|
|||||||
"""Support for IP Cameras."""
|
"""Support for IP Cameras."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
@ -45,8 +44,8 @@ GET_IMAGE_TIMEOUT = 10
|
|||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_STILL_IMAGE_URL): cv.template,
|
vol.Required(vol.Any(CONF_STILL_IMAGE_URL, CONF_STREAM_SOURCE)): cv.template,
|
||||||
vol.Optional(CONF_STREAM_SOURCE): cv.template,
|
vol.Optional(vol.Any(CONF_STILL_IMAGE_URL, CONF_STREAM_SOURCE)): cv.template,
|
||||||
vol.Optional(CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION): vol.In(
|
vol.Optional(CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION): vol.In(
|
||||||
[HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION]
|
[HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION]
|
||||||
),
|
),
|
||||||
@ -81,9 +80,10 @@ class GenericCamera(Camera):
|
|||||||
self.hass = hass
|
self.hass = hass
|
||||||
self._authentication = device_info.get(CONF_AUTHENTICATION)
|
self._authentication = device_info.get(CONF_AUTHENTICATION)
|
||||||
self._name = device_info.get(CONF_NAME)
|
self._name = device_info.get(CONF_NAME)
|
||||||
self._still_image_url = device_info[CONF_STILL_IMAGE_URL]
|
self._still_image_url = device_info.get(CONF_STILL_IMAGE_URL)
|
||||||
|
if self._still_image_url:
|
||||||
|
self._still_image_url.hass = hass
|
||||||
self._stream_source = device_info.get(CONF_STREAM_SOURCE)
|
self._stream_source = device_info.get(CONF_STREAM_SOURCE)
|
||||||
self._still_image_url.hass = hass
|
|
||||||
if self._stream_source is not None:
|
if self._stream_source is not None:
|
||||||
self._stream_source.hass = hass
|
self._stream_source.hass = hass
|
||||||
self._limit_refetch = device_info[CONF_LIMIT_REFETCH_TO_URL_CHANGE]
|
self._limit_refetch = device_info[CONF_LIMIT_REFETCH_TO_URL_CHANGE]
|
||||||
@ -120,18 +120,16 @@ class GenericCamera(Camera):
|
|||||||
"""Return the interval between frames of the mjpeg stream."""
|
"""Return the interval between frames of the mjpeg stream."""
|
||||||
return self._frame_interval
|
return self._frame_interval
|
||||||
|
|
||||||
def camera_image(
|
|
||||||
self, width: int | None = None, height: int | None = None
|
|
||||||
) -> bytes | None:
|
|
||||||
"""Return bytes of camera image."""
|
|
||||||
return asyncio.run_coroutine_threadsafe(
|
|
||||||
self.async_camera_image(), self.hass.loop
|
|
||||||
).result()
|
|
||||||
|
|
||||||
async def async_camera_image(
|
async def async_camera_image(
|
||||||
self, width: int | None = None, height: int | None = None
|
self, width: int | None = None, height: int | None = None
|
||||||
) -> bytes | None:
|
) -> bytes | None:
|
||||||
"""Return a still image response from the camera."""
|
"""Return a still image response from the camera."""
|
||||||
|
if not self._still_image_url:
|
||||||
|
if not self.stream:
|
||||||
|
await self.async_create_stream()
|
||||||
|
if self.stream:
|
||||||
|
return await self.stream.async_get_image(width, height)
|
||||||
|
return None
|
||||||
try:
|
try:
|
||||||
url = self._still_image_url.async_render(parse_result=False)
|
url = self._still_image_url.async_render(parse_result=False)
|
||||||
except TemplateError as err:
|
except TemplateError as err:
|
||||||
|
@ -437,6 +437,8 @@ class Stream:
|
|||||||
hass.add_executor_job underneath the hood.
|
hass.add_executor_job underneath the hood.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
self.add_provider(HLS_PROVIDER)
|
||||||
|
self.start()
|
||||||
return await self._keyframe_converter.async_get_image(
|
return await self._keyframe_converter.async_get_image(
|
||||||
width=width, height=height
|
width=width, height=height
|
||||||
)
|
)
|
||||||
|
@ -14,7 +14,7 @@ from homeassistant.components.websocket_api.const import TYPE_RESULT
|
|||||||
from homeassistant.const import SERVICE_RELOAD
|
from homeassistant.const import SERVICE_RELOAD
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
from tests.common import get_fixture_path
|
from tests.common import AsyncMock, Mock, get_fixture_path
|
||||||
|
|
||||||
|
|
||||||
@respx.mock
|
@respx.mock
|
||||||
@ -459,3 +459,48 @@ async def test_timeout_cancelled(hass, hass_client):
|
|||||||
assert respx.calls.call_count == total_calls
|
assert respx.calls.call_count == total_calls
|
||||||
assert resp.status == HTTPStatus.OK
|
assert resp.status == HTTPStatus.OK
|
||||||
assert await resp.text() == "hello world"
|
assert await resp.text() == "hello world"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_no_still_image_url(hass, hass_client):
|
||||||
|
"""Test that the component can grab images from stream with no still_image_url."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
"camera",
|
||||||
|
{
|
||||||
|
"camera": {
|
||||||
|
"name": "config_test",
|
||||||
|
"platform": "generic",
|
||||||
|
"stream_source": "rtsp://example.com:554/rtsp/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
client = await hass_client()
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.generic.camera.GenericCamera.stream_source",
|
||||||
|
return_value=None,
|
||||||
|
) as mock_stream_source:
|
||||||
|
|
||||||
|
# First test when there is no stream_source should fail
|
||||||
|
resp = await client.get("/api/camera_proxy/camera.config_test")
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
mock_stream_source.assert_called_once()
|
||||||
|
assert resp.status == HTTPStatus.INTERNAL_SERVER_ERROR
|
||||||
|
|
||||||
|
with patch("homeassistant.components.camera.create_stream") as mock_create_stream:
|
||||||
|
|
||||||
|
# Now test when creating the stream succeeds
|
||||||
|
mock_stream = Mock()
|
||||||
|
mock_stream.async_get_image = AsyncMock()
|
||||||
|
mock_stream.async_get_image.return_value = b"stream_keyframe_image"
|
||||||
|
mock_create_stream.return_value = mock_stream
|
||||||
|
|
||||||
|
# should start the stream and get the image
|
||||||
|
resp = await client.get("/api/camera_proxy/camera.config_test")
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
mock_create_stream.assert_called_once()
|
||||||
|
mock_stream.async_get_image.assert_called_once()
|
||||||
|
assert resp.status == HTTPStatus.OK
|
||||||
|
assert await resp.read() == b"stream_keyframe_image"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user