mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 18:57:06 +00:00
Camera Preferences + Preload Stream (#22339)
* initial commit for camera preferences and preload stream * cleanup and add tests * respect camera preferences on each request stream call * return the new prefs after update
This commit is contained in:
parent
baa4945944
commit
bad0a8b342
@ -20,7 +20,7 @@ import voluptuous as vol
|
|||||||
|
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, \
|
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, \
|
||||||
SERVICE_TURN_ON
|
SERVICE_TURN_ON, EVENT_HOMEASSISTANT_START
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.loader import bind_hass
|
from homeassistant.loader import bind_hass
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
@ -37,7 +37,9 @@ from homeassistant.components.stream.const import (
|
|||||||
from homeassistant.components import websocket_api
|
from homeassistant.components import websocket_api
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
DOMAIN = 'camera'
|
from .const import DOMAIN, DATA_CAMERA_PREFS
|
||||||
|
from .prefs import CameraPreferences
|
||||||
|
|
||||||
DEPENDENCIES = ['http']
|
DEPENDENCIES = ['http']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -68,7 +70,6 @@ ENTITY_IMAGE_URL = '/api/camera_proxy/{0}?token={1}'
|
|||||||
TOKEN_CHANGE_INTERVAL = timedelta(minutes=5)
|
TOKEN_CHANGE_INTERVAL = timedelta(minutes=5)
|
||||||
_RND = SystemRandom()
|
_RND = SystemRandom()
|
||||||
|
|
||||||
FALLBACK_STREAM_INTERVAL = 1 # seconds
|
|
||||||
MIN_STREAM_INTERVAL = 0.5 # seconds
|
MIN_STREAM_INTERVAL = 0.5 # seconds
|
||||||
|
|
||||||
CAMERA_SERVICE_SCHEMA = vol.Schema({
|
CAMERA_SERVICE_SCHEMA = vol.Schema({
|
||||||
@ -103,12 +104,14 @@ class Image:
|
|||||||
async def async_request_stream(hass, entity_id, fmt):
|
async def async_request_stream(hass, entity_id, fmt):
|
||||||
"""Request a stream for a camera entity."""
|
"""Request a stream for a camera entity."""
|
||||||
camera = _get_camera_from_entity_id(hass, entity_id)
|
camera = _get_camera_from_entity_id(hass, entity_id)
|
||||||
|
camera_prefs = hass.data[DATA_CAMERA_PREFS].get(entity_id)
|
||||||
|
|
||||||
if not camera.stream_source:
|
if not camera.stream_source:
|
||||||
raise HomeAssistantError("{} does not support play stream service"
|
raise HomeAssistantError("{} does not support play stream service"
|
||||||
.format(camera.entity_id))
|
.format(camera.entity_id))
|
||||||
|
|
||||||
return request_stream(hass, camera.stream_source, fmt=fmt)
|
return request_stream(hass, camera.stream_source, fmt=fmt,
|
||||||
|
keepalive=camera_prefs.preload_stream)
|
||||||
|
|
||||||
|
|
||||||
@bind_hass
|
@bind_hass
|
||||||
@ -197,6 +200,10 @@ async def async_setup(hass, config):
|
|||||||
component = hass.data[DOMAIN] = \
|
component = hass.data[DOMAIN] = \
|
||||||
EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
|
EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
|
||||||
|
|
||||||
|
prefs = CameraPreferences(hass)
|
||||||
|
await prefs.async_initialize()
|
||||||
|
hass.data[DATA_CAMERA_PREFS] = prefs
|
||||||
|
|
||||||
hass.http.register_view(CameraImageView(component))
|
hass.http.register_view(CameraImageView(component))
|
||||||
hass.http.register_view(CameraMjpegStream(component))
|
hass.http.register_view(CameraMjpegStream(component))
|
||||||
hass.components.websocket_api.async_register_command(
|
hass.components.websocket_api.async_register_command(
|
||||||
@ -204,9 +211,21 @@ async def async_setup(hass, config):
|
|||||||
SCHEMA_WS_CAMERA_THUMBNAIL
|
SCHEMA_WS_CAMERA_THUMBNAIL
|
||||||
)
|
)
|
||||||
hass.components.websocket_api.async_register_command(ws_camera_stream)
|
hass.components.websocket_api.async_register_command(ws_camera_stream)
|
||||||
|
hass.components.websocket_api.async_register_command(websocket_get_prefs)
|
||||||
|
hass.components.websocket_api.async_register_command(
|
||||||
|
websocket_update_prefs)
|
||||||
|
|
||||||
await component.async_setup(config)
|
await component.async_setup(config)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def preload_stream(event):
|
||||||
|
for camera in component.entities:
|
||||||
|
camera_prefs = prefs.get(camera.entity_id)
|
||||||
|
if camera.stream_source and camera_prefs.preload_stream:
|
||||||
|
request_stream(hass, camera.stream_source, keepalive=True)
|
||||||
|
|
||||||
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, preload_stream)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def update_tokens(time):
|
def update_tokens(time):
|
||||||
"""Update tokens of the entities."""
|
"""Update tokens of the entities."""
|
||||||
@ -522,14 +541,17 @@ async def ws_camera_stream(hass, connection, msg):
|
|||||||
Async friendly.
|
Async friendly.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
camera = _get_camera_from_entity_id(hass, msg['entity_id'])
|
entity_id = msg['entity_id']
|
||||||
|
camera = _get_camera_from_entity_id(hass, entity_id)
|
||||||
|
camera_prefs = hass.data[DATA_CAMERA_PREFS].get(entity_id)
|
||||||
|
|
||||||
if not camera.stream_source:
|
if not camera.stream_source:
|
||||||
raise HomeAssistantError("{} does not support play stream service"
|
raise HomeAssistantError("{} does not support play stream service"
|
||||||
.format(camera.entity_id))
|
.format(camera.entity_id))
|
||||||
|
|
||||||
fmt = msg['format']
|
fmt = msg['format']
|
||||||
url = request_stream(hass, camera.stream_source, fmt=fmt)
|
url = request_stream(hass, camera.stream_source, fmt=fmt,
|
||||||
|
keepalive=camera_prefs.preload_stream)
|
||||||
connection.send_result(msg['id'], {'url': url})
|
connection.send_result(msg['id'], {'url': url})
|
||||||
except HomeAssistantError as ex:
|
except HomeAssistantError as ex:
|
||||||
_LOGGER.error(ex)
|
_LOGGER.error(ex)
|
||||||
@ -537,6 +559,36 @@ async def ws_camera_stream(hass, connection, msg):
|
|||||||
msg['id'], 'start_stream_failed', str(ex))
|
msg['id'], 'start_stream_failed', str(ex))
|
||||||
|
|
||||||
|
|
||||||
|
@websocket_api.async_response
|
||||||
|
@websocket_api.websocket_command({
|
||||||
|
vol.Required('type'): 'camera/get_prefs',
|
||||||
|
vol.Required('entity_id'): cv.entity_id,
|
||||||
|
})
|
||||||
|
async def websocket_get_prefs(hass, connection, msg):
|
||||||
|
"""Handle request for account info."""
|
||||||
|
prefs = hass.data[DATA_CAMERA_PREFS].get(msg['entity_id'])
|
||||||
|
connection.send_result(msg['id'], prefs.as_dict())
|
||||||
|
|
||||||
|
|
||||||
|
@websocket_api.async_response
|
||||||
|
@websocket_api.websocket_command({
|
||||||
|
vol.Required('type'): 'camera/update_prefs',
|
||||||
|
vol.Required('entity_id'): cv.entity_id,
|
||||||
|
vol.Optional('preload_stream'): bool,
|
||||||
|
})
|
||||||
|
async def websocket_update_prefs(hass, connection, msg):
|
||||||
|
"""Handle request for account info."""
|
||||||
|
prefs = hass.data[DATA_CAMERA_PREFS]
|
||||||
|
|
||||||
|
changes = dict(msg)
|
||||||
|
changes.pop('id')
|
||||||
|
changes.pop('type')
|
||||||
|
entity_id = changes.pop('entity_id')
|
||||||
|
await prefs.async_update(entity_id, **changes)
|
||||||
|
|
||||||
|
connection.send_result(msg['id'], prefs.get(entity_id).as_dict())
|
||||||
|
|
||||||
|
|
||||||
async def async_handle_snapshot_service(camera, service):
|
async def async_handle_snapshot_service(camera, service):
|
||||||
"""Handle snapshot services calls."""
|
"""Handle snapshot services calls."""
|
||||||
hass = camera.hass
|
hass = camera.hass
|
||||||
@ -573,10 +625,12 @@ async def async_handle_play_stream_service(camera, service_call):
|
|||||||
.format(camera.entity_id))
|
.format(camera.entity_id))
|
||||||
|
|
||||||
hass = camera.hass
|
hass = camera.hass
|
||||||
|
camera_prefs = hass.data[DATA_CAMERA_PREFS].get(camera.entity_id)
|
||||||
fmt = service_call.data[ATTR_FORMAT]
|
fmt = service_call.data[ATTR_FORMAT]
|
||||||
entity_ids = service_call.data[ATTR_MEDIA_PLAYER]
|
entity_ids = service_call.data[ATTR_MEDIA_PLAYER]
|
||||||
|
|
||||||
url = request_stream(hass, camera.stream_source, fmt=fmt)
|
url = request_stream(hass, camera.stream_source, fmt=fmt,
|
||||||
|
keepalive=camera_prefs.preload_stream)
|
||||||
data = {
|
data = {
|
||||||
ATTR_ENTITY_ID: entity_ids,
|
ATTR_ENTITY_ID: entity_ids,
|
||||||
ATTR_MEDIA_CONTENT_ID: "{}{}".format(hass.config.api.base_url, url),
|
ATTR_MEDIA_CONTENT_ID: "{}{}".format(hass.config.api.base_url, url),
|
||||||
|
6
homeassistant/components/camera/const.py
Normal file
6
homeassistant/components/camera/const.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
"""Constants for Camera component."""
|
||||||
|
DOMAIN = 'camera'
|
||||||
|
|
||||||
|
DATA_CAMERA_PREFS = 'camera_prefs'
|
||||||
|
|
||||||
|
PREF_PRELOAD_STREAM = 'preload_stream'
|
60
homeassistant/components/camera/prefs.py
Normal file
60
homeassistant/components/camera/prefs.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
"""Preference management for camera component."""
|
||||||
|
from .const import DOMAIN, PREF_PRELOAD_STREAM
|
||||||
|
|
||||||
|
STORAGE_KEY = DOMAIN
|
||||||
|
STORAGE_VERSION = 1
|
||||||
|
_UNDEF = object()
|
||||||
|
|
||||||
|
|
||||||
|
class CameraEntityPreferences:
|
||||||
|
"""Handle preferences for camera entity."""
|
||||||
|
|
||||||
|
def __init__(self, prefs):
|
||||||
|
"""Initialize prefs."""
|
||||||
|
self._prefs = prefs
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
"""Return dictionary version."""
|
||||||
|
return self._prefs
|
||||||
|
|
||||||
|
@property
|
||||||
|
def preload_stream(self):
|
||||||
|
"""Return if stream is loaded on hass start."""
|
||||||
|
return self._prefs.get(PREF_PRELOAD_STREAM, False)
|
||||||
|
|
||||||
|
|
||||||
|
class CameraPreferences:
|
||||||
|
"""Handle camera preferences."""
|
||||||
|
|
||||||
|
def __init__(self, hass):
|
||||||
|
"""Initialize camera prefs."""
|
||||||
|
self._hass = hass
|
||||||
|
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
|
||||||
|
self._prefs = None
|
||||||
|
|
||||||
|
async def async_initialize(self):
|
||||||
|
"""Finish initializing the preferences."""
|
||||||
|
prefs = await self._store.async_load()
|
||||||
|
|
||||||
|
if prefs is None:
|
||||||
|
prefs = {}
|
||||||
|
|
||||||
|
self._prefs = prefs
|
||||||
|
|
||||||
|
async def async_update(self, entity_id, *, preload_stream=_UNDEF,
|
||||||
|
stream_options=_UNDEF):
|
||||||
|
"""Update camera preferences."""
|
||||||
|
if not self._prefs.get(entity_id):
|
||||||
|
self._prefs[entity_id] = {}
|
||||||
|
|
||||||
|
for key, value in (
|
||||||
|
(PREF_PRELOAD_STREAM, preload_stream),
|
||||||
|
):
|
||||||
|
if value is not _UNDEF:
|
||||||
|
self._prefs[entity_id][key] = value
|
||||||
|
|
||||||
|
await self._store.async_save(self._prefs)
|
||||||
|
|
||||||
|
def get(self, entity_id):
|
||||||
|
"""Get preferences for an entity."""
|
||||||
|
return CameraEntityPreferences(self._prefs.get(entity_id, {}))
|
@ -12,7 +12,8 @@ import voluptuous as vol
|
|||||||
|
|
||||||
from homeassistant.const import CONF_NAME
|
from homeassistant.const import CONF_NAME
|
||||||
from homeassistant.components.camera import (
|
from homeassistant.components.camera import (
|
||||||
Camera, CAMERA_SERVICE_SCHEMA, DOMAIN, PLATFORM_SCHEMA)
|
Camera, CAMERA_SERVICE_SCHEMA, PLATFORM_SCHEMA)
|
||||||
|
from homeassistant.components.camera.const import DOMAIN
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
@ -6,8 +6,9 @@ import logging
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.camera import (
|
from homeassistant.components.camera import (
|
||||||
ATTR_ENTITY_ID, ATTR_FILENAME, CAMERA_SERVICE_SCHEMA, DOMAIN,
|
ATTR_ENTITY_ID, ATTR_FILENAME, CAMERA_SERVICE_SCHEMA,
|
||||||
PLATFORM_SCHEMA, SUPPORT_ON_OFF, Camera)
|
PLATFORM_SCHEMA, SUPPORT_ON_OFF, Camera)
|
||||||
|
from homeassistant.components.camera.const import DOMAIN
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ATTRIBUTION, ATTR_BATTERY_CHARGING, ATTR_BATTERY_LEVEL,
|
ATTR_ATTRIBUTION, ATTR_BATTERY_CHARGING, ATTR_BATTERY_LEVEL,
|
||||||
CONF_SCAN_INTERVAL, STATE_OFF, STATE_ON)
|
CONF_SCAN_INTERVAL, STATE_OFF, STATE_ON)
|
||||||
|
@ -13,7 +13,8 @@ import voluptuous as vol
|
|||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_NAME, CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_PORT,
|
CONF_NAME, CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_PORT,
|
||||||
ATTR_ENTITY_ID)
|
ATTR_ENTITY_ID)
|
||||||
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA, DOMAIN
|
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
|
||||||
|
from homeassistant.components.camera.const import DOMAIN
|
||||||
from homeassistant.components.ffmpeg import (
|
from homeassistant.components.ffmpeg import (
|
||||||
DATA_FFMPEG, CONF_EXTRA_ARGUMENTS)
|
DATA_FFMPEG, CONF_EXTRA_ARGUMENTS)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
@ -14,7 +14,8 @@ import aiohttp
|
|||||||
import async_timeout
|
import async_timeout
|
||||||
|
|
||||||
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA,\
|
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA,\
|
||||||
STATE_IDLE, STATE_RECORDING, DOMAIN
|
STATE_IDLE, STATE_RECORDING
|
||||||
|
from homeassistant.components.camera.const import DOMAIN
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.const import CONF_NAME, CONF_TIMEOUT, CONF_WEBHOOK_ID
|
from homeassistant.const import CONF_NAME, CONF_TIMEOUT, CONF_WEBHOOK_ID
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
@ -56,6 +56,9 @@ def request_stream(hass, stream_source, *, fmt='hls',
|
|||||||
stream = Stream(hass, stream_source,
|
stream = Stream(hass, stream_source,
|
||||||
options=options, keepalive=keepalive)
|
options=options, keepalive=keepalive)
|
||||||
streams[stream_source] = stream
|
streams[stream_source] = stream
|
||||||
|
else:
|
||||||
|
# Update keepalive option on existing stream
|
||||||
|
stream.keepalive = keepalive
|
||||||
|
|
||||||
# Add provider
|
# Add provider
|
||||||
stream.add_provider(fmt)
|
stream.add_provider(fmt)
|
||||||
|
@ -4,7 +4,9 @@ All containing methods are legacy helpers that should not be used by new
|
|||||||
components. Instead call the service directly.
|
components. Instead call the service directly.
|
||||||
"""
|
"""
|
||||||
from homeassistant.components.camera import (
|
from homeassistant.components.camera import (
|
||||||
ATTR_FILENAME, DOMAIN, SERVICE_ENABLE_MOTION, SERVICE_SNAPSHOT)
|
ATTR_FILENAME, SERVICE_ENABLE_MOTION, SERVICE_SNAPSHOT)
|
||||||
|
from homeassistant.components.camera.const import (
|
||||||
|
DOMAIN, DATA_CAMERA_PREFS, PREF_PRELOAD_STREAM)
|
||||||
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, \
|
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, \
|
||||||
SERVICE_TURN_ON
|
SERVICE_TURN_ON
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
@ -45,3 +47,13 @@ def async_snapshot(hass, filename, entity_id=None):
|
|||||||
|
|
||||||
hass.async_add_job(hass.services.async_call(
|
hass.async_add_job(hass.services.async_call(
|
||||||
DOMAIN, SERVICE_SNAPSHOT, data))
|
DOMAIN, SERVICE_SNAPSHOT, data))
|
||||||
|
|
||||||
|
|
||||||
|
def mock_camera_prefs(hass, entity_id, prefs={}):
|
||||||
|
"""Fixture for cloud component."""
|
||||||
|
prefs_to_set = {
|
||||||
|
PREF_PRELOAD_STREAM: True,
|
||||||
|
}
|
||||||
|
prefs_to_set.update(prefs)
|
||||||
|
hass.data[DATA_CAMERA_PREFS]._prefs[entity_id] = prefs_to_set
|
||||||
|
return prefs_to_set
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
"""The tests for the camera component."""
|
"""The tests for the camera component."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import base64
|
import base64
|
||||||
|
import io
|
||||||
from unittest.mock import patch, mock_open, PropertyMock
|
from unittest.mock import patch, mock_open, PropertyMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.setup import setup_component, async_setup_component
|
from homeassistant.setup import setup_component, async_setup_component
|
||||||
from homeassistant.const import (ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE)
|
from homeassistant.const import (
|
||||||
|
ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE, EVENT_HOMEASSISTANT_START)
|
||||||
from homeassistant.components import camera, http
|
from homeassistant.components import camera, http
|
||||||
|
from homeassistant.components.camera.const import DOMAIN, PREF_PRELOAD_STREAM
|
||||||
|
from homeassistant.components.camera.prefs import CameraEntityPreferences
|
||||||
from homeassistant.components.websocket_api.const import TYPE_RESULT
|
from homeassistant.components.websocket_api.const import TYPE_RESULT
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.util.async_ import run_coroutine_threadsafe
|
from homeassistant.util.async_ import run_coroutine_threadsafe
|
||||||
@ -16,7 +20,6 @@ from tests.common import (
|
|||||||
get_test_home_assistant, get_test_instance_port, assert_setup_component,
|
get_test_home_assistant, get_test_instance_port, assert_setup_component,
|
||||||
mock_coro)
|
mock_coro)
|
||||||
from tests.components.camera import common
|
from tests.components.camera import common
|
||||||
from tests.components.stream.common import generate_h264_video
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@ -41,6 +44,12 @@ def mock_stream(hass):
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def setup_camera_prefs(hass):
|
||||||
|
"""Initialize HTTP API."""
|
||||||
|
return common.mock_camera_prefs(hass, 'camera.demo_camera')
|
||||||
|
|
||||||
|
|
||||||
class TestSetupCamera:
|
class TestSetupCamera:
|
||||||
"""Test class for setup camera."""
|
"""Test class for setup camera."""
|
||||||
|
|
||||||
@ -146,7 +155,7 @@ def test_snapshot_service(hass, mock_camera):
|
|||||||
assert mock_write.mock_calls[0][1][0] == b'Test'
|
assert mock_write.mock_calls[0][1][0] == b'Test'
|
||||||
|
|
||||||
|
|
||||||
async def test_webocket_camera_thumbnail(hass, hass_ws_client, mock_camera):
|
async def test_websocket_camera_thumbnail(hass, hass_ws_client, mock_camera):
|
||||||
"""Test camera_thumbnail websocket command."""
|
"""Test camera_thumbnail websocket command."""
|
||||||
await async_setup_component(hass, 'camera')
|
await async_setup_component(hass, 'camera')
|
||||||
|
|
||||||
@ -167,7 +176,7 @@ async def test_webocket_camera_thumbnail(hass, hass_ws_client, mock_camera):
|
|||||||
base64.b64encode(b'Test').decode('utf-8')
|
base64.b64encode(b'Test').decode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
async def test_webocket_stream_no_source(hass, hass_ws_client,
|
async def test_websocket_stream_no_source(hass, hass_ws_client,
|
||||||
mock_camera, mock_stream):
|
mock_camera, mock_stream):
|
||||||
"""Test camera/stream websocket command."""
|
"""Test camera/stream websocket command."""
|
||||||
await async_setup_component(hass, 'camera')
|
await async_setup_component(hass, 'camera')
|
||||||
@ -191,7 +200,7 @@ async def test_webocket_stream_no_source(hass, hass_ws_client,
|
|||||||
assert not msg['success']
|
assert not msg['success']
|
||||||
|
|
||||||
|
|
||||||
async def test_webocket_camera_stream(hass, hass_ws_client, hass_client,
|
async def test_websocket_camera_stream(hass, hass_ws_client,
|
||||||
mock_camera, mock_stream):
|
mock_camera, mock_stream):
|
||||||
"""Test camera/stream websocket command."""
|
"""Test camera/stream websocket command."""
|
||||||
await async_setup_component(hass, 'camera')
|
await async_setup_component(hass, 'camera')
|
||||||
@ -201,7 +210,7 @@ async def test_webocket_camera_stream(hass, hass_ws_client, hass_client,
|
|||||||
) as mock_request_stream, \
|
) as mock_request_stream, \
|
||||||
patch('homeassistant.components.demo.camera.DemoCamera.stream_source',
|
patch('homeassistant.components.demo.camera.DemoCamera.stream_source',
|
||||||
new_callable=PropertyMock) as mock_stream_source:
|
new_callable=PropertyMock) as mock_stream_source:
|
||||||
mock_stream_source.return_value = generate_h264_video()
|
mock_stream_source.return_value = io.BytesIO()
|
||||||
# Request playlist through WebSocket
|
# Request playlist through WebSocket
|
||||||
client = await hass_ws_client(hass)
|
client = await hass_ws_client(hass)
|
||||||
await client.send_json({
|
await client.send_json({
|
||||||
@ -219,6 +228,44 @@ async def test_webocket_camera_stream(hass, hass_ws_client, hass_client,
|
|||||||
assert msg['result']['url'][-13:] == 'playlist.m3u8'
|
assert msg['result']['url'][-13:] == 'playlist.m3u8'
|
||||||
|
|
||||||
|
|
||||||
|
async def test_websocket_get_prefs(hass, hass_ws_client,
|
||||||
|
mock_camera):
|
||||||
|
"""Test get camera preferences websocket command."""
|
||||||
|
await async_setup_component(hass, 'camera')
|
||||||
|
|
||||||
|
# Request preferences through websocket
|
||||||
|
client = await hass_ws_client(hass)
|
||||||
|
await client.send_json({
|
||||||
|
'id': 7,
|
||||||
|
'type': 'camera/get_prefs',
|
||||||
|
'entity_id': 'camera.demo_camera',
|
||||||
|
})
|
||||||
|
msg = await client.receive_json()
|
||||||
|
|
||||||
|
# Assert WebSocket response
|
||||||
|
assert msg['success']
|
||||||
|
|
||||||
|
|
||||||
|
async def test_websocket_update_prefs(hass, hass_ws_client,
|
||||||
|
mock_camera, setup_camera_prefs):
|
||||||
|
"""Test updating preference."""
|
||||||
|
await async_setup_component(hass, 'camera')
|
||||||
|
assert setup_camera_prefs[PREF_PRELOAD_STREAM]
|
||||||
|
client = await hass_ws_client(hass)
|
||||||
|
await client.send_json({
|
||||||
|
'id': 8,
|
||||||
|
'type': 'camera/update_prefs',
|
||||||
|
'entity_id': 'camera.demo_camera',
|
||||||
|
'preload_stream': False,
|
||||||
|
})
|
||||||
|
response = await client.receive_json()
|
||||||
|
|
||||||
|
assert response['success']
|
||||||
|
assert not setup_camera_prefs[PREF_PRELOAD_STREAM]
|
||||||
|
assert response['result'][PREF_PRELOAD_STREAM] == \
|
||||||
|
setup_camera_prefs[PREF_PRELOAD_STREAM]
|
||||||
|
|
||||||
|
|
||||||
async def test_play_stream_service_no_source(hass, mock_camera, mock_stream):
|
async def test_play_stream_service_no_source(hass, mock_camera, mock_stream):
|
||||||
"""Test camera play_stream service."""
|
"""Test camera play_stream service."""
|
||||||
data = {
|
data = {
|
||||||
@ -243,10 +290,54 @@ async def test_handle_play_stream_service(hass, mock_camera, mock_stream):
|
|||||||
) as mock_request_stream, \
|
) as mock_request_stream, \
|
||||||
patch('homeassistant.components.demo.camera.DemoCamera.stream_source',
|
patch('homeassistant.components.demo.camera.DemoCamera.stream_source',
|
||||||
new_callable=PropertyMock) as mock_stream_source:
|
new_callable=PropertyMock) as mock_stream_source:
|
||||||
mock_stream_source.return_value = generate_h264_video()
|
mock_stream_source.return_value = io.BytesIO()
|
||||||
# Call service
|
# Call service
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
camera.DOMAIN, camera.SERVICE_PLAY_STREAM, data, blocking=True)
|
camera.DOMAIN, camera.SERVICE_PLAY_STREAM, data, blocking=True)
|
||||||
# So long as we request the stream, the rest should be covered
|
# So long as we request the stream, the rest should be covered
|
||||||
# by the play_media service tests.
|
# by the play_media service tests.
|
||||||
assert mock_request_stream.called
|
assert mock_request_stream.called
|
||||||
|
|
||||||
|
|
||||||
|
async def test_no_preload_stream(hass, mock_stream):
|
||||||
|
"""Test camera preload preference."""
|
||||||
|
demo_prefs = CameraEntityPreferences({
|
||||||
|
PREF_PRELOAD_STREAM: False,
|
||||||
|
})
|
||||||
|
with patch('homeassistant.components.camera.request_stream'
|
||||||
|
) as mock_request_stream, \
|
||||||
|
patch('homeassistant.components.camera.prefs.CameraPreferences.get',
|
||||||
|
return_value=demo_prefs), \
|
||||||
|
patch('homeassistant.components.demo.camera.DemoCamera.stream_source',
|
||||||
|
new_callable=PropertyMock) as mock_stream_source:
|
||||||
|
mock_stream_source.return_value = io.BytesIO()
|
||||||
|
await async_setup_component(hass, 'camera', {
|
||||||
|
DOMAIN: {
|
||||||
|
'platform': 'demo'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert not mock_request_stream.called
|
||||||
|
|
||||||
|
|
||||||
|
async def test_preload_stream(hass, mock_stream):
|
||||||
|
"""Test camera preload preference."""
|
||||||
|
demo_prefs = CameraEntityPreferences({
|
||||||
|
PREF_PRELOAD_STREAM: True,
|
||||||
|
})
|
||||||
|
with patch('homeassistant.components.camera.request_stream'
|
||||||
|
) as mock_request_stream, \
|
||||||
|
patch('homeassistant.components.camera.prefs.CameraPreferences.get',
|
||||||
|
return_value=demo_prefs), \
|
||||||
|
patch('homeassistant.components.demo.camera.DemoCamera.stream_source',
|
||||||
|
new_callable=PropertyMock) as mock_stream_source:
|
||||||
|
mock_stream_source.return_value = io.BytesIO()
|
||||||
|
await async_setup_component(hass, 'camera', {
|
||||||
|
DOMAIN: {
|
||||||
|
'platform': 'demo'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert mock_request_stream.called
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from homeassistant.components.camera import DOMAIN
|
from homeassistant.components.camera.const import DOMAIN
|
||||||
from homeassistant.components.local_file.camera import (
|
from homeassistant.components.local_file.camera import (
|
||||||
SERVICE_UPDATE_FILE_PATH)
|
SERVICE_UPDATE_FILE_PATH)
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
Loading…
x
Reference in New Issue
Block a user