mirror of
https://github.com/home-assistant/core.git
synced 2025-08-12 09:05:15 +02:00
Address comments and add tests
This commit is contained in:
@@ -5,9 +5,9 @@ For more details about this platform, please refer to the documentation
|
||||
https://home-assistant.io/components/camera.push/
|
||||
"""
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
from collections import deque
|
||||
from datetime import timedelta
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA,\
|
||||
@@ -23,33 +23,35 @@ import homeassistant.util.dt as dt_util
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
API_URL = "/api/camera_push/{entity_id}"
|
||||
CONF_BUFFER_SIZE = 'cache'
|
||||
CONF_IMAGE_FIELD = 'image'
|
||||
|
||||
CONF_BUFFER_SIZE = "cache"
|
||||
|
||||
DEFAULT_NAME = 'Push Camera'
|
||||
DEFAULT_NAME = "Push Camera"
|
||||
|
||||
BLANK_IMAGE_SIZE = (640, 480)
|
||||
|
||||
ATTR_FILENAME = "filename"
|
||||
ATTR_FILENAME = 'filename'
|
||||
|
||||
REQUIREMENTS = ['pillow==5.0.0']
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_BUFFER_SIZE, default=1): cv.positive_int,
|
||||
vol.Optional(CONF_TIMEOUT, default=5): cv.positive_int,
|
||||
vol.Optional(CONF_TIMEOUT, default=timedelta(seconds=5)): vol.All(
|
||||
cv.time_period, cv.positive_timedelta),
|
||||
vol.Optional(CONF_IMAGE_FIELD, default='image'): cv.string,
|
||||
})
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_devices,
|
||||
discovery_info=None):
|
||||
"""Set up the Push Camera platform."""
|
||||
cameras = [PushCamera(config.get(CONF_NAME),
|
||||
config.get(CONF_BUFFER_SIZE),
|
||||
config.get(CONF_TIMEOUT))]
|
||||
cameras = [PushCamera(config[CONF_NAME],
|
||||
config[CONF_BUFFER_SIZE],
|
||||
config[CONF_TIMEOUT])]
|
||||
|
||||
hass.http.register_view(CameraPushReceiver(cameras))
|
||||
hass.http.register_view(CameraPushReceiver(cameras,
|
||||
config[CONF_IMAGE_FIELD]))
|
||||
|
||||
async_add_devices(cameras)
|
||||
|
||||
@@ -57,12 +59,13 @@ async def async_setup_platform(hass, config, async_add_devices,
|
||||
class CameraPushReceiver(HomeAssistantView):
|
||||
"""Handle pushes from remote camera."""
|
||||
|
||||
url = API_URL
|
||||
name = 'api:camera:push'
|
||||
url = "/api/camera_push/{entity_id}"
|
||||
name = 'api:camera_push:camera_entity'
|
||||
|
||||
def __init__(self, cameras):
|
||||
def __init__(self, cameras, image_field):
|
||||
"""Initialize CameraPushReceiver with camera entity."""
|
||||
self._cameras = cameras
|
||||
self._image = image_field
|
||||
|
||||
async def post(self, request, entity_id):
|
||||
"""Accept the POST from Camera."""
|
||||
@@ -70,16 +73,21 @@ class CameraPushReceiver(HomeAssistantView):
|
||||
(_camera,) = [camera for camera in self._cameras
|
||||
if camera.entity_id == entity_id]
|
||||
except ValueError:
|
||||
_LOGGER.error("Unknown push camera %s", entity_id)
|
||||
return self.json_message('Unknown Push Camera',
|
||||
HTTP_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
data = await request.post()
|
||||
_LOGGER.debug("Received Camera push: %s", data['image'])
|
||||
await _camera.update_image(data['image'].file.read(),
|
||||
data['image'].filename)
|
||||
except ValueError:
|
||||
_LOGGER.debug("Received Camera push: %s", data[self._image])
|
||||
await _camera.update_image(data[self._image].file.read(),
|
||||
data[self._image].filename)
|
||||
except ValueError as v:
|
||||
_LOGGER.error("Unknown value %s", v)
|
||||
return self.json_message('Invalid POST', HTTP_BAD_REQUEST)
|
||||
except KeyError as k:
|
||||
_LOGGER.error('In your POST message %s', k)
|
||||
return self.json_message('Parameter %s missing', HTTP_BAD_REQUEST)
|
||||
|
||||
|
||||
class PushCamera(Camera):
|
||||
@@ -89,12 +97,11 @@ class PushCamera(Camera):
|
||||
"""Initialize push camera component."""
|
||||
super().__init__()
|
||||
self._name = name
|
||||
self._motion_status = False
|
||||
self._last_trip = None
|
||||
self._filename = None
|
||||
self._expired = None
|
||||
self._state = STATE_IDLE
|
||||
self._timeout = datetime.timedelta(seconds=timeout)
|
||||
self._timeout = timeout
|
||||
self.queue = deque([], buffer_size)
|
||||
|
||||
from PIL import Image
|
||||
@@ -137,12 +144,7 @@ class PushCamera(Camera):
|
||||
self._expired = async_track_point_in_utc_time(
|
||||
self.hass, reset_state, dt_util.utcnow() + self._timeout)
|
||||
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def camera_image(self):
|
||||
"""Return a still image response."""
|
||||
return run_coroutine_threadsafe(
|
||||
self.async_camera_image(), self.hass.loop).result()
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
async def async_camera_image(self):
|
||||
"""Return a still image response."""
|
||||
@@ -161,7 +163,7 @@ class PushCamera(Camera):
|
||||
@property
|
||||
def motion_detection_enabled(self):
|
||||
"""Camera Motion Detection Status."""
|
||||
return self._motion_status
|
||||
return False
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
|
63
tests/components/camera/test_push.py
Normal file
63
tests/components/camera/test_push.py
Normal file
@@ -0,0 +1,63 @@
|
||||
"""The tests for generic camera component."""
|
||||
import io
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from homeassistant import core as ha
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import dt as dt_util
|
||||
from tests.components.auth import async_setup_auth
|
||||
|
||||
|
||||
async def test_bad_posting(aioclient_mock, hass, aiohttp_client):
|
||||
"""Test that posting to wrong api endpoint fails."""
|
||||
await async_setup_component(hass, 'camera', {
|
||||
'camera': {
|
||||
'platform': 'push',
|
||||
'name': 'config_test',
|
||||
}})
|
||||
|
||||
client = await async_setup_auth(hass, aiohttp_client)
|
||||
|
||||
# missing file
|
||||
resp = await client.post('/api/camera_push/camera.config_test')
|
||||
assert resp.status == 400
|
||||
|
||||
files = {'image': io.BytesIO(b'fake')}
|
||||
|
||||
# wrong entity
|
||||
resp = await client.post('/api/camera_push/camera.wrong', data=files)
|
||||
assert resp.status == 400
|
||||
|
||||
|
||||
async def test_posting_url(aioclient_mock, hass, aiohttp_client):
|
||||
"""Test that posting to api endpoint works."""
|
||||
await async_setup_component(hass, 'camera', {
|
||||
'camera': {
|
||||
'platform': 'push',
|
||||
'name': 'config_test',
|
||||
}})
|
||||
|
||||
client = await async_setup_auth(hass, aiohttp_client)
|
||||
files = {'image': io.BytesIO(b'fake')}
|
||||
|
||||
# initial state
|
||||
camera_state = hass.states.get('camera.config_test')
|
||||
assert camera_state.state == 'idle'
|
||||
|
||||
# post image
|
||||
resp = await client.post('/api/camera_push/camera.config_test', data=files)
|
||||
assert resp.status == 200
|
||||
|
||||
# state recording
|
||||
camera_state = hass.states.get('camera.config_test')
|
||||
assert camera_state.state == 'recording'
|
||||
|
||||
# await timeout
|
||||
shifted_time = dt_util.utcnow() + timedelta(seconds=15)
|
||||
hass.bus.async_fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: shifted_time})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# back to initial state
|
||||
camera_state = hass.states.get('camera.config_test')
|
||||
assert camera_state.state == 'idle'
|
Reference in New Issue
Block a user