Clean up camera component

This commit is contained in:
Paulus Schoutsen 2015-07-10 23:17:12 -07:00
parent 703266312e
commit aec25c88b4
2 changed files with 54 additions and 167 deletions

View File

@ -5,24 +5,23 @@ homeassistant.components.camera
Component to interface with various cameras. Component to interface with various cameras.
The following features are supported: The following features are supported:
-Recording - Returning recorded camera images and streams
-Snapshot - Proxying image requests via HA for external access
-Motion Detection Recording(for supported cameras) - Converting a still image url into a live video stream
-Automatic Configuration(for supported cameras)
-Creation of child entities for supported functions
-Collating motion event images passed via FTP into time based events
-Returning recorded camera images and streams
-Proxying image requests via HA for external access
-Converting a still image url into a live video stream
-A service for calling camera functions
Upcoming features Upcoming features
-Camera movement(panning) - Recording
-Zoom - Snapshot
-Light/Nightvision toggling - Motion Detection Recording(for supported cameras)
-Support for more devices - Automatic Configuration(for supported cameras)
-A demo entity - Creation of child entities for supported functions
-Expanded documentation - Collating motion event images passed via FTP into time based events
- A service for calling camera functions
- Camera movement(panning)
- Zoom
- Light/Nightvision toggling
- Support for more devices
- Expanded documentation
""" """
import requests import requests
import logging import logging
@ -103,7 +102,7 @@ def setup(hass, config):
camera = component.entities[entity_id] camera = component.entities[entity_id]
if camera: if camera:
response = camera.get_camera_image() response = camera.camera_image()
handler.wfile.write(response) handler.wfile.write(response)
else: else:
handler.send_response(HTTP_NOT_FOUND) handler.send_response(HTTP_NOT_FOUND)
@ -146,7 +145,7 @@ def setup(hass, config):
while True: while True:
img_bytes = camera.get_camera_image() img_bytes = camera.camera_image()
headers_str = '\r\n'.join(( headers_str = '\r\n'.join((
'Content-length: {}'.format(len(img_bytes)), 'Content-length: {}'.format(len(img_bytes)),
@ -180,23 +179,15 @@ def setup(hass, config):
class Camera(Entity): class Camera(Entity):
""" The base class for camera components """ """ The base class for camera components """
def __init__(self):
self.is_streaming = False
@property @property
# pylint: disable=no-self-use # pylint: disable=no-self-use
def is_recording(self): def is_recording(self):
""" Returns true if the device is recording """ """ Returns true if the device is recording """
return False return False
@property
# pylint: disable=no-self-use
def is_streaming(self):
""" Returns true if the device is streaming """
return False
@is_streaming.setter
def is_streaming(self, value):
""" Set this to true when streaming begins """
pass
@property @property
# pylint: disable=no-self-use # pylint: disable=no-self-use
def brand(self): def brand(self):
@ -209,19 +200,7 @@ class Camera(Entity):
""" Returns string of camera model """ """ Returns string of camera model """
return None return None
@property def camera_image(self):
# pylint: disable=no-self-use
def image_url(self):
""" Return the still image segment of the URL """
return None
@property
# pylint: disable=no-self-use
def still_image_url(self):
""" Get the URL of a camera still image """
return None
def get_camera_image(self):
""" Return bytes of camera image """ """ Return bytes of camera image """
raise NotImplementedError() raise NotImplementedError()
@ -238,14 +217,11 @@ class Camera(Entity):
@property @property
def state_attributes(self): def state_attributes(self):
""" Returns optional state attributes. """ """ Returns optional state attributes. """
attr = super().state_attributes return {
attr['model_name'] = self.model 'model_name': self.model,
attr['brand'] = self.brand 'brand': self.brand,
CAMERA_PROXY_URL.format(self.entity_id) 'still_image_url': CAMERA_STILL_URL.format(self.entity_id),
attr['still_image_url'] = CAMERA_STILL_URL.format(self.entity_id) ATTR_ENTITY_PICTURE: ENTITY_IMAGE_URL.format(
attr[ATTR_ENTITY_PICTURE] = ENTITY_IMAGE_URL.format( self.entity_id, str(time.time())),
self.entity_id, 'stream_url': CAMERA_PROXY_URL.format(self.entity_id)
str(time.time())) }
attr['stream_url'] = CAMERA_PROXY_URL.format(self.entity_id)
return attr

View File

@ -1,76 +1,45 @@
""" """
Support for IP Cameras. Support for IP Cameras.
This component provides basic support for IP camera models that do not have This component provides basic support for IP cameras. For the basic support to
a speicifc HA component. work you camera must support accessing a JPEG snapshot via a URL and you will
need to specify the "still_image_url" parameter which should be the location of
the JPEG image.
As part of the basic support the following features will be provided: As part of the basic support the following features will be provided:
-MJPEG video streaming -MJPEG video streaming
-Saving a snapshot -Saving a snapshot
-Recording(JPEG frame capture) -Recording(JPEG frame capture)
NOTE: for the basic support to work you camera must support accessing a JPEG To use this component, add the following to your config/configuration.yaml:
snapshot via a URL and you will need to specify the "still_image_url" parameter
which should be the location of the JPEG snapshot relative to you specified
base_url. For example "snapshot.cgi" or "image.jpg".
To use this component you will need to add something like the following to your
config/configuration.yaml
camera: camera:
platform: generic platform: generic
base_url: http://YOUR_CAMERA_IP_AND_PORT/
name: Door Camera name: Door Camera
brand: dlink
family: DCS
model: DCS-930L
username: YOUR_USERNAME username: YOUR_USERNAME
password: YOUR_PASSWORD password: YOUR_PASSWORD
still_image_url: image.jpg still_image_url: http://YOUR_CAMERA_IP_AND_PORT/image.jpg
VARIABLES: VARIABLES:
These are the variables for the device_data array: These are the variables for the device_data array:
base_url still_image_url
*Required *Required
The base URL for accessing you camera The URL your camera serves the image on.
Example: http://192.168.1.21:2112/ Example: http://192.168.1.21:2112/
name name
*Optional *Optional
This parameter allows you to override the name of your camera in homeassistant This parameter allows you to override the name of your camera in homeassistant
brand
*Optional
The manufacturer of your device, used to help load the specific camera
functionality.
family
*Optional
The family of devices by the specified brand, useful when many models
support the same settings. This used when attempting load up specific
device functionality.
model
*Optional
The specific model number of your device.
still_image_url
*Optional
Useful if using an unsupported camera model. This should point to the location
of the still image on your particular camera and should be relative to your
specified base_url.
Example: cam/image.jpg
username username
*Required *Optional
THe username for acessing your camera THe username for acessing your camera
password password
*Required *Optional
the password for accessing your camera the password for accessing your camera
@ -88,97 +57,39 @@ _LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None): def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Find and return Vera lights. """ """ Adds a generic IP Camera. """
if not validate_config( if not validate_config({DOMAIN: config}, {DOMAIN: ['still_image_url']}, _LOGGER):
{DOMAIN: config},
{DOMAIN: ['base_url', CONF_USERNAME, CONF_PASSWORD]},
_LOGGER):
return None return None
camera = GenericCamera(hass, config) add_devices_callback([GenericCamera(config)])
cameras = [camera]
add_devices_callback(cameras)
# pylint: disable=too-many-instance-attributes # pylint: disable=too-many-instance-attributes
class GenericCamera(Camera): class GenericCamera(Camera):
""" """
Base class for cameras. A generic implementation of an IP camera that is reachable over a URL.
This is quite a large class but the camera component encompasses a lot of
functionality. It should take care of most of the heavy lifting and
plumbing associated with adding support for additional models of camera.
If you are adding support for a new camera your entity class should inherit
from this.
""" """
def __init__(self, hass, device_info): def __init__(self, device_info):
self.hass = hass super().__init__()
self._device_info = device_info self._name = device_info.get('name', 'Generic Camera')
self._base_url = device_info.get('base_url')
if not self._base_url.endswith('/'):
self._base_url = self._base_url + '/'
self._username = device_info.get('username') self._username = device_info.get('username')
self._password = device_info.get('password') self._password = device_info.get('password')
self._is_streaming = False
self._still_image_url = device_info.get('still_image_url', 'image.jpg')
self._logger = logging.getLogger(__name__)
def get_camera_image(self): self._still_image_url += device_info.get('still_image_url', 'image.jpg')
def camera_image(self):
""" Return a still image reponse from the camera """ """ Return a still image reponse from the camera """
if self.username and self.password: if self._username and self._password:
response = requests.get( response = requests.get(
self.still_image_url, self._still_image_url,
auth=HTTPBasicAuth( auth=HTTPBasicAuth(self._username,self._password))
self.username,
self.password))
else: else:
response = requests.get(self.still_image_url) response = requests.get(self._still_image_url)
return response.content return response.content
@property
def device_info(self):
""" Return the config data for this device """
return self._device_info
@property @property
def name(self): def name(self):
""" Return the name of this device """ """ Return the name of this device """
return self._device_info.get('name') or super().name return self._name
@property
def state_attributes(self):
""" Returns optional state attributes. """
attr = super().state_attributes
return attr
@property
def base_url(self):
""" Return the URL of the IP Camera """
return self._base_url
@property
def username(self):
""" Return the configured username """
return self._username
@property
def password(self):
""" Return the configured password """
return self._password
@property
def is_streaming(self):
return self._is_streaming
@is_streaming.setter
# pylint: disable=arguments-differ
def is_streaming(self, value):
self._is_streaming = value
@property
def still_image_url(self):
""" This should be implemented by different camera models. """
return self.base_url + self._still_image_url