Add QVR Pro integration (#31173)

* Initial working commit

* Create const file.  Load camera from component.

* Handle failed authentication.  Bump library version.

* Remove line break

* Camera attributes and recording services

* Add services, manifest, constant update, and exclude_channels.  Prefix channel name.  Update service argument.

* Update codeowners

* Update coveragerc

* Remove codeowners line

* Update codeowners again from python3 -m script.hassfest

* Update homeassistant/components/qvrpro/__init__.py

Co-Authored-By: springstan <46536646+springstan@users.noreply.github.com>

* Requested changes

* Fix typo

* Update to use exception. Bump library version.

* Support stream component

* Update module header

* Missing property wrapper

* Partial requested changes

* Update coveragerc and codeowners

* Move constants to const file.  Add SHORT_NAME

* Add conf variable

* Use camera domain

* More requested changes

* Requested changes

* Requested changes

* Update prefix

* Handle error condition when camera is not configured to support live streaming

* Move method to camera setup.  Disable stream component support.

* Move auth string to library to prevent private member access

Co-authored-by: springstan <46536646+springstan@users.noreply.github.com>
This commit is contained in:
Matt Snyder 2020-03-02 18:10:02 -06:00 committed by GitHub
parent a25b94cd2d
commit ee7ce47860
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 237 additions and 0 deletions

View File

@ -567,6 +567,7 @@ omit =
homeassistant/components/qnap/sensor.py
homeassistant/components/qrcode/image_processing.py
homeassistant/components/quantum_gateway/device_tracker.py
homeassistant/components/qvr_pro/*
homeassistant/components/qwikswitch/*
homeassistant/components/rachio/*
homeassistant/components/radarr/sensor.py

View File

@ -278,6 +278,7 @@ homeassistant/components/pvoutput/* @fabaff
homeassistant/components/qld_bushfire/* @exxamalte
homeassistant/components/qnap/* @colinodell
homeassistant/components/quantum_gateway/* @cisasteelersfan
homeassistant/components/qvr_pro/* @oblogic7
homeassistant/components/qwikswitch/* @kellerza
homeassistant/components/rainbird/* @konikvranik
homeassistant/components/raincloud/* @vanstinator

View File

@ -0,0 +1,100 @@
"""Support for QVR Pro NVR software by QNAP."""
import logging
from pyqvrpro import Client
from pyqvrpro.client import AuthenticationError, InsufficientPermissionsError
import voluptuous as vol
from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import load_platform
from .const import (
CONF_EXCLUDE_CHANNELS,
DOMAIN,
SERVICE_START_RECORD,
SERVICE_STOP_RECORD,
)
SERVICE_CHANNEL_GUID = "guid"
_LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_EXCLUDE_CHANNELS, default=[]): vol.All(
cv.ensure_list_csv, [cv.positive_int]
),
}
)
},
extra=vol.ALLOW_EXTRA,
)
SERVICE_CHANNEL_RECORD_SCHEMA = vol.Schema(
{vol.Required(SERVICE_CHANNEL_GUID): cv.string}
)
def setup(hass, config):
"""Set up the QVR Pro component."""
conf = config[DOMAIN]
user = conf[CONF_USERNAME]
password = conf[CONF_PASSWORD]
host = conf[CONF_HOST]
excluded_channels = conf[CONF_EXCLUDE_CHANNELS]
try:
qvrpro = Client(user, password, host)
channel_resp = qvrpro.get_channel_list()
except InsufficientPermissionsError:
_LOGGER.error("User must have Surveillance Management permission")
return False
except AuthenticationError:
_LOGGER.error("Authentication failed")
return False
channels = []
for channel in channel_resp["channels"]:
if channel["channel_index"] + 1 in excluded_channels:
continue
channels.append(channel)
hass.data[DOMAIN] = {"channels": channels, "client": qvrpro}
load_platform(hass, CAMERA_DOMAIN, DOMAIN, {}, config)
# Register services
def handle_start_record(call):
guid = call.data[SERVICE_CHANNEL_GUID]
qvrpro.start_recording(guid)
def handle_stop_record(call):
guid = call.data[SERVICE_CHANNEL_GUID]
qvrpro.stop_recording(guid)
hass.services.register(
DOMAIN,
SERVICE_START_RECORD,
handle_start_record,
schema=SERVICE_CHANNEL_RECORD_SCHEMA,
)
hass.services.register(
DOMAIN,
SERVICE_STOP_RECORD,
handle_stop_record,
schema=SERVICE_CHANNEL_RECORD_SCHEMA,
)
return True

View File

@ -0,0 +1,102 @@
"""Support for QVR Pro streams."""
import logging
from pyqvrpro.client import QVRResponseError
from homeassistant.components.camera import Camera
from .const import DOMAIN, SHORT_NAME
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the QVR Pro camera platform."""
if discovery_info is None:
return
client = hass.data[DOMAIN]["client"]
entities = []
for channel in hass.data[DOMAIN]["channels"]:
stream_source = get_stream_source(channel["guid"], client)
entities.append(
QVRProCamera(**channel, stream_source=stream_source, client=client)
)
add_entities(entities)
def get_stream_source(guid, client):
"""Get channel stream source."""
try:
resp = client.get_channel_live_stream(guid, protocol="rtsp")
full_url = resp["resourceUris"]
protocol = full_url[:7]
auth = f"{client.get_auth_string()}@"
url = full_url[7:]
return f"{protocol}{auth}{url}"
except QVRResponseError as ex:
_LOGGER.error(ex)
return None
class QVRProCamera(Camera):
"""Representation of a QVR Pro camera."""
def __init__(self, name, model, brand, channel_index, guid, stream_source, client):
"""Init QVR Pro camera."""
self._name = f"{SHORT_NAME} {name}"
self._model = model
self._brand = brand
self.index = channel_index
self.guid = guid
self._client = client
self._stream_source = stream_source
self._supported_features = 0
super().__init__()
@property
def name(self):
"""Return the name of the entity."""
return self._name
@property
def model(self):
"""Return the model of the entity."""
return self._model
@property
def brand(self):
"""Return the brand of the entity."""
return self._brand
@property
def device_state_attributes(self):
"""Get the state attributes."""
attrs = {"qvr_guid": self.guid}
return attrs
def camera_image(self):
"""Get image bytes from camera."""
return self._client.get_snapshot(self.guid)
async def stream_source(self):
"""Get stream source."""
return self._stream_source
@property
def supported_features(self):
"""Get supported features."""
return self._supported_features

View File

@ -0,0 +1,9 @@
"""Constants for QVR Pro component."""
DOMAIN = "qvr_pro"
SHORT_NAME = "QVR"
CONF_EXCLUDE_CHANNELS = "exclude_channels"
SERVICE_STOP_RECORD = "stop_record"
SERVICE_START_RECORD = "start_record"

View File

@ -0,0 +1,8 @@
{
"domain": "qvr_pro",
"name": "QVR Pro",
"documentation": "https://www.home-assistant.io/integrations/qvr_pro",
"requirements": ["pyqvrpro==0.51"],
"dependencies": [],
"codeowners": ["@oblogic7"]
}

View File

@ -0,0 +1,13 @@
start_record:
description: Start QVR Pro recording on specified channel.
fields:
guid:
description: GUID of the channel to start recording.
example: '245EBE933C0A597EBE865C0A245E0002'
stop_record:
description: Stop QVR Pro recording on specified channel.
fields:
guid:
description: GUID of the channel to stop recording.
example: '245EBE933C0A597EBE865C0A245E0002'

View File

@ -1472,6 +1472,9 @@ pypoint==1.1.2
# homeassistant.components.ps4
pyps4-2ndscreen==1.0.7
# homeassistant.components.qvr_pro
pyqvrpro==0.51
# homeassistant.components.qwikswitch
pyqwikswitch==0.93