224 lines
8.9 KiB
Python
224 lines
8.9 KiB
Python
|
"""
|
||
|
Support for Synology Surveillance Station Cameras.
|
||
|
|
||
|
For more details about this platform, please refer to the documentation at
|
||
|
https://home-assistant.io/components/camera.synology/
|
||
|
"""
|
||
|
import logging
|
||
|
|
||
|
import voluptuous as vol
|
||
|
|
||
|
import requests
|
||
|
|
||
|
from homeassistant.const import (
|
||
|
CONF_NAME, CONF_USERNAME, CONF_PASSWORD,
|
||
|
CONF_URL, CONF_WHITELIST)
|
||
|
from homeassistant.components.camera import (
|
||
|
Camera, PLATFORM_SCHEMA)
|
||
|
import homeassistant.helpers.config_validation as cv
|
||
|
|
||
|
_LOGGER = logging.getLogger(__name__)
|
||
|
|
||
|
# pylint: disable=too-many-locals
|
||
|
DEFAULT_NAME = 'Synology Camera'
|
||
|
DEFAULT_STREAM_ID = '0'
|
||
|
TIMEOUT = 5
|
||
|
CONF_CAMERA_NAME = 'camera_name'
|
||
|
CONF_STREAM_ID = 'stream_id'
|
||
|
CONF_VALID_CERT = 'valid_cert'
|
||
|
|
||
|
QUERY_CGI = 'query.cgi'
|
||
|
QUERY_API = 'SYNO.API.Info'
|
||
|
AUTH_API = 'SYNO.API.Auth'
|
||
|
CAMERA_API = 'SYNO.SurveillanceStation.Camera'
|
||
|
STREAMING_API = 'SYNO.SurveillanceStation.VideoStream'
|
||
|
SESSION_ID = '0'
|
||
|
|
||
|
WEBAPI_PATH = '/webapi/'
|
||
|
AUTH_PATH = 'auth.cgi'
|
||
|
CAMERA_PATH = 'camera.cgi'
|
||
|
STREAMING_PATH = 'SurveillanceStation/videoStreaming.cgi'
|
||
|
|
||
|
SYNO_API_URL = '{0}{1}{2}'
|
||
|
|
||
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||
|
vol.Required(CONF_USERNAME): cv.string,
|
||
|
vol.Required(CONF_PASSWORD): cv.string,
|
||
|
vol.Required(CONF_URL): cv.string,
|
||
|
vol.Optional(CONF_WHITELIST, default=[]): cv.ensure_list,
|
||
|
vol.Optional(CONF_VALID_CERT, default=True): cv.boolean,
|
||
|
})
|
||
|
|
||
|
|
||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||
|
"""Setup a Synology IP Camera."""
|
||
|
# Determine API to use for authentication
|
||
|
syno_api_url = SYNO_API_URL.format(config.get(CONF_URL),
|
||
|
WEBAPI_PATH,
|
||
|
QUERY_CGI)
|
||
|
query_payload = {'api': QUERY_API,
|
||
|
'method': 'Query',
|
||
|
'version': '1',
|
||
|
'query': 'SYNO.'}
|
||
|
query_req = requests.get(syno_api_url,
|
||
|
params=query_payload,
|
||
|
verify=config.get(CONF_VALID_CERT),
|
||
|
timeout=TIMEOUT)
|
||
|
query_resp = query_req.json()
|
||
|
auth_path = query_resp['data'][AUTH_API]['path']
|
||
|
camera_api = query_resp['data'][CAMERA_API]['path']
|
||
|
camera_path = query_resp['data'][CAMERA_API]['path']
|
||
|
streaming_path = query_resp['data'][STREAMING_API]['path']
|
||
|
|
||
|
# Authticate to NAS to get a session id
|
||
|
syno_auth_url = SYNO_API_URL.format(config.get(CONF_URL),
|
||
|
WEBAPI_PATH,
|
||
|
auth_path)
|
||
|
session_id = get_session_id(config.get(CONF_USERNAME),
|
||
|
config.get(CONF_PASSWORD),
|
||
|
syno_auth_url,
|
||
|
config.get(CONF_VALID_CERT))
|
||
|
|
||
|
# Use SessionID to get cameras in system
|
||
|
syno_camera_url = SYNO_API_URL.format(config.get(CONF_URL),
|
||
|
WEBAPI_PATH,
|
||
|
camera_api)
|
||
|
camera_payload = {'api': CAMERA_API,
|
||
|
'method': 'List',
|
||
|
'version': '1'}
|
||
|
camera_req = requests.get(syno_camera_url,
|
||
|
params=camera_payload,
|
||
|
verify=config.get(CONF_VALID_CERT),
|
||
|
timeout=TIMEOUT,
|
||
|
cookies={'id': session_id})
|
||
|
camera_resp = camera_req.json()
|
||
|
cameras = camera_resp['data']['cameras']
|
||
|
for camera in cameras:
|
||
|
if not config.get(CONF_WHITELIST):
|
||
|
camera_id = camera['id']
|
||
|
snapshot_path = camera['snapshot_path']
|
||
|
|
||
|
add_devices([SynologyCamera(config,
|
||
|
camera_id,
|
||
|
camera['name'],
|
||
|
snapshot_path,
|
||
|
streaming_path,
|
||
|
camera_path,
|
||
|
auth_path)])
|
||
|
|
||
|
|
||
|
def get_session_id(username, password, login_url, valid_cert):
|
||
|
"""Get a session id."""
|
||
|
auth_payload = {'api': AUTH_API,
|
||
|
'method': 'Login',
|
||
|
'version': '2',
|
||
|
'account': username,
|
||
|
'passwd': password,
|
||
|
'session': 'SurveillanceStation',
|
||
|
'format': 'sid'}
|
||
|
auth_req = requests.get(login_url,
|
||
|
params=auth_payload,
|
||
|
verify=valid_cert,
|
||
|
timeout=TIMEOUT)
|
||
|
auth_resp = auth_req.json()
|
||
|
return auth_resp['data']['sid']
|
||
|
|
||
|
|
||
|
# pylint: disable=too-many-instance-attributes
|
||
|
class SynologyCamera(Camera):
|
||
|
"""An implementation of a Synology NAS based IP camera."""
|
||
|
|
||
|
# pylint: disable=too-many-arguments
|
||
|
def __init__(self, config, camera_id, camera_name,
|
||
|
snapshot_path, streaming_path, camera_path, auth_path):
|
||
|
"""Initialize a Synology Surveillance Station camera."""
|
||
|
super().__init__()
|
||
|
self._name = camera_name
|
||
|
self._username = config.get(CONF_USERNAME)
|
||
|
self._password = config.get(CONF_PASSWORD)
|
||
|
self._synology_url = config.get(CONF_URL)
|
||
|
self._api_url = config.get(CONF_URL) + 'webapi/'
|
||
|
self._login_url = config.get(CONF_URL) + '/webapi/' + 'auth.cgi'
|
||
|
self._camera_name = config.get(CONF_CAMERA_NAME)
|
||
|
self._stream_id = config.get(CONF_STREAM_ID)
|
||
|
self._valid_cert = config.get(CONF_VALID_CERT)
|
||
|
self._camera_id = camera_id
|
||
|
self._snapshot_path = snapshot_path
|
||
|
self._streaming_path = streaming_path
|
||
|
self._camera_path = camera_path
|
||
|
self._auth_path = auth_path
|
||
|
|
||
|
self._session_id = get_session_id(self._username,
|
||
|
self._password,
|
||
|
self._login_url,
|
||
|
self._valid_cert)
|
||
|
|
||
|
def get_sid(self):
|
||
|
"""Get a session id."""
|
||
|
auth_payload = {'api': AUTH_API,
|
||
|
'method': 'Login',
|
||
|
'version': '2',
|
||
|
'account': self._username,
|
||
|
'passwd': self._password,
|
||
|
'session': 'SurveillanceStation',
|
||
|
'format': 'sid'}
|
||
|
auth_req = requests.get(self._login_url,
|
||
|
params=auth_payload,
|
||
|
verify=self._valid_cert,
|
||
|
timeout=TIMEOUT)
|
||
|
auth_resp = auth_req.json()
|
||
|
self._session_id = auth_resp['data']['sid']
|
||
|
|
||
|
def camera_image(self):
|
||
|
"""Return a still image response from the camera."""
|
||
|
image_url = SYNO_API_URL.format(self._synology_url,
|
||
|
WEBAPI_PATH,
|
||
|
self._camera_path)
|
||
|
image_payload = {'api': CAMERA_API,
|
||
|
'method': 'GetSnapshot',
|
||
|
'version': '1',
|
||
|
'cameraId': self._camera_id}
|
||
|
try:
|
||
|
response = requests.get(image_url,
|
||
|
params=image_payload,
|
||
|
timeout=TIMEOUT,
|
||
|
verify=self._valid_cert,
|
||
|
cookies={'id': self._session_id})
|
||
|
except requests.exceptions.RequestException as error:
|
||
|
_LOGGER.error('Error getting camera image: %s', error)
|
||
|
return None
|
||
|
|
||
|
return response.content
|
||
|
|
||
|
def camera_stream(self):
|
||
|
"""Return a MJPEG stream image response directly from the camera."""
|
||
|
streaming_url = SYNO_API_URL.format(self._synology_url,
|
||
|
WEBAPI_PATH,
|
||
|
self._streaming_path)
|
||
|
streaming_payload = {'api': STREAMING_API,
|
||
|
'method': 'Stream',
|
||
|
'version': '1',
|
||
|
'cameraId': self._camera_id,
|
||
|
'format': 'mjpeg'}
|
||
|
response = requests.get(streaming_url,
|
||
|
payload=streaming_payload,
|
||
|
stream=True,
|
||
|
timeout=TIMEOUT,
|
||
|
cookies={'id': self._session_id})
|
||
|
return response
|
||
|
|
||
|
def mjpeg_steam(self, response):
|
||
|
"""Generate an HTTP MJPEG Stream from the Synology NAS."""
|
||
|
stream = self.camera_stream()
|
||
|
return response(
|
||
|
stream.iter_content(chunk_size=1024),
|
||
|
mimetype=stream.headers['CONTENT_TYPE_HEADER'],
|
||
|
direct_passthrough=True
|
||
|
)
|
||
|
|
||
|
@property
|
||
|
def name(self):
|
||
|
"""Return the name of this device."""
|
||
|
return self._name
|