mirror of
https://github.com/home-assistant/core.git
synced 2025-04-25 17:57:55 +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/qnap/sensor.py
|
||||||
homeassistant/components/qrcode/image_processing.py
|
homeassistant/components/qrcode/image_processing.py
|
||||||
homeassistant/components/quantum_gateway/device_tracker.py
|
homeassistant/components/quantum_gateway/device_tracker.py
|
||||||
|
homeassistant/components/qvr_pro/*
|
||||||
homeassistant/components/qwikswitch/*
|
homeassistant/components/qwikswitch/*
|
||||||
homeassistant/components/rachio/*
|
homeassistant/components/rachio/*
|
||||||
homeassistant/components/radarr/sensor.py
|
homeassistant/components/radarr/sensor.py
|
||||||
|
@ -278,6 +278,7 @@ homeassistant/components/pvoutput/* @fabaff
|
|||||||
homeassistant/components/qld_bushfire/* @exxamalte
|
homeassistant/components/qld_bushfire/* @exxamalte
|
||||||
homeassistant/components/qnap/* @colinodell
|
homeassistant/components/qnap/* @colinodell
|
||||||
homeassistant/components/quantum_gateway/* @cisasteelersfan
|
homeassistant/components/quantum_gateway/* @cisasteelersfan
|
||||||
|
homeassistant/components/qvr_pro/* @oblogic7
|
||||||
homeassistant/components/qwikswitch/* @kellerza
|
homeassistant/components/qwikswitch/* @kellerza
|
||||||
homeassistant/components/rainbird/* @konikvranik
|
homeassistant/components/rainbird/* @konikvranik
|
||||||
homeassistant/components/raincloud/* @vanstinator
|
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
|
# homeassistant.components.ps4
|
||||||
pyps4-2ndscreen==1.0.7
|
pyps4-2ndscreen==1.0.7
|
||||||
|
|
||||||
|
# homeassistant.components.qvr_pro
|
||||||
|
pyqvrpro==0.51
|
||||||
|
|
||||||
# homeassistant.components.qwikswitch
|
# homeassistant.components.qwikswitch
|
||||||
pyqwikswitch==0.93
|
pyqwikswitch==0.93
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user