mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
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:
parent
a25b94cd2d
commit
ee7ce47860
@ -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
|
||||
|
@ -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
|
||||
|
100
homeassistant/components/qvr_pro/__init__.py
Normal file
100
homeassistant/components/qvr_pro/__init__.py
Normal 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
|
102
homeassistant/components/qvr_pro/camera.py
Normal file
102
homeassistant/components/qvr_pro/camera.py
Normal 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
|
9
homeassistant/components/qvr_pro/const.py
Normal file
9
homeassistant/components/qvr_pro/const.py
Normal 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"
|
8
homeassistant/components/qvr_pro/manifest.json
Normal file
8
homeassistant/components/qvr_pro/manifest.json
Normal 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"]
|
||||
}
|
13
homeassistant/components/qvr_pro/services.yaml
Normal file
13
homeassistant/components/qvr_pro/services.yaml
Normal 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'
|
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user