From 0983367abe4e8efda0b82be61d8917ae04d9c7f6 Mon Sep 17 00:00:00 2001 From: Kevin McCormack Date: Sun, 8 Sep 2019 15:36:48 -0400 Subject: [PATCH] Add vivotek camera component (#26071) * Add vivotek camera component * Update vivotek camera compontent Use async request to enable/disable motion detection * Update Vivotek camera - Use HTTPS - Use IP address for still and stream * Update vivotek component - Add brand and model properties - Add state property - Use attribute to save motion detection status * Add vivotek camera to .coveragerc * Update vivotek camera Fix lint errors * Update vivotek camera Remove unused method * Update Vivotek integration to use libpyvivotek Use libpyvivotek instead of directly making HTTPS API calls. * Update Vivotek component Address code review. - Remove unused code - Replace async methods with synchronous methods - Update docstrings * Linter fixes for Vivotek component * Update Vivotek camera component - Add SSL option - Remove authentication options as only basic authentication is currently working * Update Vivotek camera component - Make frame rate configurable - Require username and password * Remove unused constants in Vivotek component * Update Vivotek camera integration - Use libpyvivotek v0.2.1 with better response parsing - Use add_entities instead of async_add_entities * Update Vivotek camera component - Build camera and stream source ouside VivotekCam - Remove unnecessary _stream_source attribute * Update Vivotek camera component - Move brand to constant - Move _supported_features to property * Update Vivotek camera compontent to remove unused property --- .coveragerc | 1 + homeassistant/components/vivotek/__init__.py | 1 + homeassistant/components/vivotek/camera.py | 120 ++++++++++++++++++ .../components/vivotek/manifest.json | 10 ++ requirements_all.txt | 3 + 5 files changed, 135 insertions(+) create mode 100644 homeassistant/components/vivotek/__init__.py create mode 100644 homeassistant/components/vivotek/camera.py create mode 100644 homeassistant/components/vivotek/manifest.json diff --git a/.coveragerc b/.coveragerc index 03ee2e4038c..e96340dde1c 100644 --- a/.coveragerc +++ b/.coveragerc @@ -694,6 +694,7 @@ omit = homeassistant/components/vesync/switch.py homeassistant/components/viaggiatreno/sensor.py homeassistant/components/vicare/* + homeassistant/components/vivotek/camera.py homeassistant/components/vizio/media_player.py homeassistant/components/vlc/media_player.py homeassistant/components/vlc_telnet/media_player.py diff --git a/homeassistant/components/vivotek/__init__.py b/homeassistant/components/vivotek/__init__.py new file mode 100644 index 00000000000..b5220b12a9b --- /dev/null +++ b/homeassistant/components/vivotek/__init__.py @@ -0,0 +1 @@ +"""The Vivotek camera component.""" diff --git a/homeassistant/components/vivotek/camera.py b/homeassistant/components/vivotek/camera.py new file mode 100644 index 00000000000..bf136731cb6 --- /dev/null +++ b/homeassistant/components/vivotek/camera.py @@ -0,0 +1,120 @@ +"""Support for Vivotek IP Cameras.""" + +import logging + +import voluptuous as vol +from libpyvivotek import VivotekCamera + +from homeassistant.const import ( + CONF_IP_ADDRESS, + CONF_NAME, + CONF_PASSWORD, + CONF_SSL, + CONF_USERNAME, + CONF_VERIFY_SSL, +) +from homeassistant.components.camera import PLATFORM_SCHEMA, SUPPORT_STREAM, Camera +from homeassistant.helpers import config_validation as cv + +_LOGGER = logging.getLogger(__name__) + +CONF_FRAMERATE = "framerate" + +DEFAULT_CAMERA_BRAND = "Vivotek" +DEFAULT_NAME = "Vivotek Camera" +DEFAULT_EVENT_0_KEY = "event_i0_enable" + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_IP_ADDRESS): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Optional(CONF_SSL, default=False): cv.boolean, + vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean, + vol.Optional(CONF_FRAMERATE, default=2): cv.positive_int, + } +) + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up a Vivotek IP Camera.""" + args = dict( + config=config, + cam=VivotekCamera( + host=config[CONF_IP_ADDRESS], + port=(443 if config[CONF_SSL] else 80), + verify_ssl=config[CONF_VERIFY_SSL], + usr=config[CONF_USERNAME], + pwd=config[CONF_PASSWORD], + ), + stream_source=( + "rtsp://%s:%s@%s:554/live.sdp", + config[CONF_USERNAME], + config[CONF_PASSWORD], + config[CONF_IP_ADDRESS], + ), + ) + add_entities([VivotekCam(**args)]) + + +class VivotekCam(Camera): + """A Vivotek IP camera.""" + + def __init__(self, config, cam, stream_source): + """Initialize a Vivotek camera.""" + super().__init__() + + self._cam = cam + self._frame_interval = 1 / config[CONF_FRAMERATE] + self._motion_detection_enabled = False + self._name = config[CONF_NAME] + self._stream_source = stream_source + + @property + def supported_features(self): + """Return supported features for this camera.""" + return SUPPORT_STREAM + + @property + def frame_interval(self): + """Return the interval between frames of the mjpeg stream.""" + return self._frame_interval + + def camera_image(self): + """Return bytes of camera image.""" + return self._cam.snapshot() + + @property + def name(self): + """Return the name of this device.""" + return self._name + + async def stream_source(self): + """Return the source of the stream.""" + return self._stream_source + + @property + def motion_detection_enabled(self): + """Return the camera motion detection status.""" + return self._motion_detection_enabled + + def disable_motion_detection(self): + """Disable motion detection in camera.""" + response = self._cam.set_param(DEFAULT_EVENT_0_KEY, 0) + self._motion_detection_enabled = int(response) == 1 + + def enable_motion_detection(self): + """Enable motion detection in camera.""" + response = self._cam.set_param(DEFAULT_EVENT_0_KEY, 1) + self._motion_detection_enabled = int(response) == 1 + + @property + def brand(self): + """Return the camera brand.""" + return DEFAULT_CAMERA_BRAND + + @property + def model(self): + """Return the camera model.""" + return self._cam.model_name diff --git a/homeassistant/components/vivotek/manifest.json b/homeassistant/components/vivotek/manifest.json new file mode 100644 index 00000000000..8a6a37762d4 --- /dev/null +++ b/homeassistant/components/vivotek/manifest.json @@ -0,0 +1,10 @@ +{ + "domain": "vivotek", + "name": "Vivotek", + "documentation": "https://www.home-assistant.io/components/vivotek", + "requirements": [ + "libpyvivotek==0.2.1" + ], + "dependencies": [], + "codeowners": [] +} diff --git a/requirements_all.txt b/requirements_all.txt index 225227cc5b5..e467f5ab2ba 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -726,6 +726,9 @@ libpurecool==0.5.0 # homeassistant.components.foscam libpyfoscam==1.0 +# homeassistant.components.vivotek +libpyvivotek==0.2.1 + # homeassistant.components.mikrotik librouteros==2.3.0