core/homeassistant/components/camera/__init__.py

192 lines
5.0 KiB
Python
Raw Normal View History

2015-06-05 12:51:29 +00:00
# pylint: disable=too-many-lines
"""
2016-02-17 07:52:05 +00:00
Component to interface with cameras.
2015-06-05 12:51:29 +00:00
2015-11-09 12:12:18 +00:00
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/camera/
2015-06-05 12:51:29 +00:00
"""
import logging
import time
2015-11-29 21:49:05 +00:00
2015-06-05 12:51:29 +00:00
from homeassistant.helpers.entity import Entity
2015-11-29 21:49:05 +00:00
from homeassistant.helpers.entity_component import EntityComponent
2016-03-28 01:48:51 +00:00
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
2016-05-14 07:58:36 +00:00
from homeassistant.components.http import HomeAssistantView
2015-06-05 12:51:29 +00:00
DOMAIN = 'camera'
DEPENDENCIES = ['http']
SCAN_INTERVAL = 30
ENTITY_ID_FORMAT = DOMAIN + '.{}'
STATE_RECORDING = 'recording'
2015-06-05 12:51:29 +00:00
STATE_STREAMING = 'streaming'
STATE_IDLE = 'idle'
2016-05-27 08:29:48 +00:00
ENTITY_IMAGE_URL = '/api/camera_proxy/{0}?token={1}'
2015-07-10 08:03:46 +00:00
2015-06-05 12:51:29 +00:00
# pylint: disable=too-many-branches
def setup(hass, config):
2016-03-07 19:29:54 +00:00
"""Setup the camera component."""
2015-06-05 12:51:29 +00:00
component = EntityComponent(
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL)
2015-06-05 12:51:29 +00:00
2016-05-14 07:58:36 +00:00
hass.wsgi.register_view(CameraImageView(hass, component.entities))
hass.wsgi.register_view(CameraMjpegStream(hass, component.entities))
2015-06-05 12:51:29 +00:00
2016-05-14 07:58:36 +00:00
component.setup(config)
2015-06-05 12:51:29 +00:00
2015-07-10 09:42:22 +00:00
return True
2015-06-05 12:51:29 +00:00
class Camera(Entity):
2016-02-17 07:52:05 +00:00
"""The base class for camera entities."""
2016-03-07 19:29:54 +00:00
2015-07-11 06:17:12 +00:00
def __init__(self):
2016-02-17 07:52:05 +00:00
"""Initialize a camera."""
2015-07-11 06:17:12 +00:00
self.is_streaming = False
@property
def access_token(self):
"""Access token for this camera."""
return str(id(self))
2016-02-17 07:52:05 +00:00
@property
def should_poll(self):
"""No need to poll cameras."""
return False
2016-02-24 06:41:24 +00:00
@property
def entity_picture(self):
"""Return a link to the camera feed as entity picture."""
2016-05-27 08:29:48 +00:00
return ENTITY_IMAGE_URL.format(self.entity_id, self.access_token)
2016-02-24 06:41:24 +00:00
2015-06-05 12:51:29 +00:00
@property
def is_recording(self):
2016-02-17 07:52:05 +00:00
"""Return true if the device is recording."""
2015-06-05 12:51:29 +00:00
return False
@property
def brand(self):
2016-02-17 07:52:05 +00:00
"""Camera brand."""
2015-06-05 12:51:29 +00:00
return None
@property
def model(self):
2016-02-17 07:52:05 +00:00
"""Camera model."""
2015-06-05 12:51:29 +00:00
return None
2015-07-11 06:17:12 +00:00
def camera_image(self):
2016-02-17 07:52:05 +00:00
"""Return bytes of camera image."""
2015-06-05 12:51:29 +00:00
raise NotImplementedError()
2016-05-14 07:58:36 +00:00
def mjpeg_stream(self, response):
2016-02-17 07:52:05 +00:00
"""Generate an HTTP MJPEG stream from camera images."""
2016-05-14 07:58:36 +00:00
def stream():
"""Stream images as mjpeg stream."""
try:
last_image = None
while True:
img_bytes = self.camera_image()
2016-05-15 03:35:58 +00:00
if img_bytes is not None and img_bytes != last_image:
yield bytes(
'--jpegboundary\r\n'
'Content-Type: image/jpeg\r\n'
'Content-Length: {}\r\n\r\n'.format(
len(img_bytes)), 'utf-8') + img_bytes + b'\r\n'
2016-05-15 03:35:58 +00:00
last_image = img_bytes
time.sleep(0.5)
2016-05-14 07:58:36 +00:00
except GeneratorExit:
pass
return response(
stream(),
content_type=('multipart/x-mixed-replace; '
'boundary=--jpegboundary')
)
2015-06-05 12:51:29 +00:00
@property
def state(self):
2016-02-17 07:52:05 +00:00
"""Camera state."""
2015-06-05 12:51:29 +00:00
if self.is_recording:
return STATE_RECORDING
elif self.is_streaming:
return STATE_STREAMING
else:
return STATE_IDLE
@property
def state_attributes(self):
2016-02-17 07:52:05 +00:00
"""Camera state attributes."""
attr = {
'access_token': self.access_token,
}
2015-07-11 18:55:25 +00:00
if self.model:
attr['model_name'] = self.model
if self.brand:
attr['brand'] = self.brand
return attr
2016-05-14 07:58:36 +00:00
class CameraView(HomeAssistantView):
"""Base CameraView."""
requires_auth = False
2016-05-14 07:58:36 +00:00
def __init__(self, hass, entities):
"""Initialize a basic camera view."""
super().__init__(hass)
self.entities = entities
def get(self, request, entity_id):
"""Start a get request."""
camera = self.entities.get(entity_id)
if camera is None:
return self.Response(status=404)
authenticated = (request.authenticated or
request.args.get('token') == camera.access_token)
if not authenticated:
return self.Response(status=401)
return self.handle(camera)
def handle(self, camera):
"""Hanlde the camera request."""
raise NotImplementedError()
2016-05-14 07:58:36 +00:00
class CameraImageView(CameraView):
"""Camera view to serve an image."""
2016-05-15 04:18:46 +00:00
url = "/api/camera_proxy/<entity(domain=camera):entity_id>"
2016-05-14 07:58:36 +00:00
name = "api:camera:image"
def handle(self, camera):
2016-05-14 07:58:36 +00:00
"""Serve camera image."""
response = camera.camera_image()
if response is None:
return self.Response(status=500)
return self.Response(response)
class CameraMjpegStream(CameraView):
"""Camera View to serve an MJPEG stream."""
2016-05-15 04:18:46 +00:00
url = "/api/camera_proxy_stream/<entity(domain=camera):entity_id>"
2016-05-14 07:58:36 +00:00
name = "api:camera:stream"
def handle(self, camera):
2016-05-14 07:58:36 +00:00
"""Serve camera image."""
return camera.mjpeg_stream(self.Response)