mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Clean up camera component
This commit is contained in:
parent
703266312e
commit
aec25c88b4
@ -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
|
|
||||||
|
@ -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
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user