From e1647fb6acccec5eaa13b8e3aaa9cadc8fc17f88 Mon Sep 17 00:00:00 2001 From: jgriff2 Date: Thu, 13 Oct 2016 08:49:58 -0700 Subject: [PATCH] Add synology ss cameras (#3626) * Add files via upload * Update .coveragerc * test * Update synology camera * Use voluptuous for synology * Use voluptuous for synology * Use voluptuous for synology * Use voluptuous for synology * Conform synology to flake8 * Added Whitelist to synology * Sync to dev branch * Added helper function to synology --- .coveragerc | 1 + homeassistant/components/camera/synology.py | 223 ++++++++++++++++++++ 2 files changed, 224 insertions(+) create mode 100644 homeassistant/components/camera/synology.py diff --git a/.coveragerc b/.coveragerc index 045e8f77588..a9b9d4e6df5 100644 --- a/.coveragerc +++ b/.coveragerc @@ -114,6 +114,7 @@ omit = homeassistant/components/camera/foscam.py homeassistant/components/camera/mjpeg.py homeassistant/components/camera/rpi_camera.py + homeassistant/components/camera/synology.py homeassistant/components/climate/eq3btsmart.py homeassistant/components/climate/heatmiser.py homeassistant/components/climate/homematic.py diff --git a/homeassistant/components/camera/synology.py b/homeassistant/components/camera/synology.py new file mode 100644 index 00000000000..dedf91a0031 --- /dev/null +++ b/homeassistant/components/camera/synology.py @@ -0,0 +1,223 @@ +""" +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