Skip to content

Commit

Permalink
auto add all supported profiles to simplify config flow
Browse files Browse the repository at this point in the history
  • Loading branch information
hunterjm committed Apr 23, 2020
1 parent 72e0f10 commit 322beba
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 117 deletions.
83 changes: 11 additions & 72 deletions homeassistant/components/onvif/camera.py
Expand Up @@ -11,11 +11,10 @@
from onvif import ONVIFCamera, exceptions
import requests
from requests.auth import HTTPDigestAuth
import voluptuous as vol
from zeep.asyncio import AsyncTransport
from zeep.exceptions import Fault

from homeassistant.components.camera import PLATFORM_SCHEMA, SUPPORT_STREAM, Camera
from homeassistant.components.camera import SUPPORT_STREAM, Camera
from homeassistant.components.ffmpeg import CONF_EXTRA_ARGUMENTS, DATA_FFMPEG
from homeassistant.const import (
CONF_HOST,
Expand All @@ -29,47 +28,22 @@
async_aiohttp_proxy_stream,
async_get_clientsession,
)
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util

from .const import (
ABSOLUTE_MOVE,
CONF_PROFILE,
CONF_RTSP_TRANSPORT,
CONTINUOUS_MOVE,
DEFAULT_ARGUMENTS,
DEFAULT_NAME,
DEFAULT_PASSWORD,
DEFAULT_PORT,
DEFAULT_PROFILE,
DEFAULT_USERNAME,
DOMAIN,
ENTITIES,
LOGGER,
PAN_FACTOR,
RELATIVE_MOVE,
RTSP_TRANS_PROTOCOLS,
TILT_FACTOR,
ZOOM_FACTOR,
)

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string,
vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_EXTRA_ARGUMENTS, default=DEFAULT_ARGUMENTS): cv.string,
vol.Optional(CONF_RTSP_TRANSPORT, default=RTSP_TRANS_PROTOCOLS[0]): vol.In(
RTSP_TRANS_PROTOCOLS
),
vol.Optional(CONF_PROFILE, default=DEFAULT_PROFILE): vol.All(
vol.Coerce(int), vol.Range(min=0)
),
}
)


async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Axis camera video stream."""
Expand Down Expand Up @@ -112,6 +86,8 @@ def __init__(self, hass, config):
self._name = config.get(CONF_NAME)
self._ffmpeg_arguments = config.get(CONF_EXTRA_ARGUMENTS)
self._profile_index = config.get(CONF_PROFILE)
self._profile_token = None
self._profile_name = None
self._ptz_service = None
self._input = None
self._snapshot = None
Expand Down Expand Up @@ -150,6 +126,7 @@ async def async_initialize(self):
await self.async_obtain_device_info()
await self.async_obtain_mac_address()
await self.async_check_date_and_time()
await self.async_obtain_profile_token()
await self.async_obtain_input_uri()
await self.async_obtain_snapshot_uri()
self.setup_ptz()
Expand Down Expand Up @@ -271,14 +248,14 @@ async def async_obtain_profile_token(self):

LOGGER.debug("Using profile index '%d'", self._profile_index)

return profiles[self._profile_index].token
self._profile_token = profiles[self._profile_index].token
self._profile_name = profiles[self._profile_index].Name
except exceptions.ONVIFError as err:
LOGGER.error(
"Couldn't retrieve profile token of camera '%s'. Error: %s",
self._name,
err,
)
return None

async def async_obtain_input_uri(self):
"""Set the input uri for the camera."""
Expand All @@ -287,33 +264,14 @@ async def async_obtain_input_uri(self):
)

try:
LOGGER.debug("Retrieving profiles")

media_service = self._camera.create_media_service()

profiles = await media_service.GetProfiles()

LOGGER.debug("Retrieved '%d' profiles", len(profiles))

if self._profile_index >= len(profiles):
LOGGER.warning(
"ONVIF Camera '%s' doesn't provide profile %d."
" Using the last profile.",
self._name,
self._profile_index,
)
self._profile_index = -1

LOGGER.debug("Using profile index '%d'", self._profile_index)

LOGGER.debug("Retrieving stream uri")

# Fix Onvif setup error on Goke GK7102 based IP camera
# where we need to recreate media_service #26781
media_service = self._camera.create_media_service()

req = media_service.create_type("GetStreamUri")
req.ProfileToken = profiles[self._profile_index].token
req.ProfileToken = self._profile_token
req.StreamSetup = {
"Stream": "RTP-Unicast",
"Transport": {"Protocol": "RTSP"},
Expand Down Expand Up @@ -341,33 +299,14 @@ async def async_obtain_snapshot_uri(self):
)

try:
LOGGER.debug("Retrieving profiles")

media_service = self._camera.create_media_service()

profiles = await media_service.GetProfiles()

LOGGER.debug("Retrieved '%d' profiles", len(profiles))

if self._profile_index >= len(profiles):
LOGGER.warning(
"ONVIF Camera '%s' doesn't provide profile %d."
" Using the last profile.",
self._name,
self._profile_index,
)
self._profile_index = -1

LOGGER.debug("Using profile index '%d'", self._profile_index)

LOGGER.debug("Retrieving snapshot uri")

# Fix Onvif setup error on Goke GK7102 based IP camera
# where we need to recreate media_service #26781
media_service = self._camera.create_media_service()

req = media_service.create_type("GetSnapshotUri")
req.ProfileToken = profiles[self._profile_index].token
req.ProfileToken = self._profile_token

try:
snapshot_uri = await media_service.GetSnapshotUri(req)
Expand Down Expand Up @@ -415,7 +354,7 @@ async def async_perform_ptz(
)
try:
req = self._ptz_service.create_type(move_mode)
req.ProfileToken = await self.async_obtain_profile_token()
req.ProfileToken = self._profile_token
if move_mode == CONTINUOUS_MOVE:
req.Velocity = {
"PanTilt": {"x": pan_val, "y": tilt_val},
Expand All @@ -425,7 +364,7 @@ async def async_perform_ptz(
await self._ptz_service.ContinuousMove(req)
await asyncio.sleep(continuous_duration)
req = self._ptz_service.create_type("Stop")
req.ProfileToken = await self.async_obtain_profile_token()
req.ProfileToken = self._profile_token
await self._ptz_service.Stop({"ProfileToken": req.ProfileToken})
elif move_mode == RELATIVE_MOVE:
req.Translation = {
Expand Down Expand Up @@ -539,7 +478,7 @@ async def stream_source(self):
@property
def name(self):
"""Return the name of this camera."""
return self._name
return f"{self._name} - {self._profile_name}"

@property
def unique_id(self) -> Optional[str]:
Expand Down
58 changes: 13 additions & 45 deletions homeassistant/components/onvif/config_flow.py
Expand Up @@ -22,7 +22,6 @@
)
from homeassistant.core import callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv

# pylint: disable=unused-import
from .const import (
Expand Down Expand Up @@ -232,29 +231,21 @@ async def async_step_profiles(self, user_input=None):
}
)

# Shortcut if profiles already configured
if self.onvif_config.get(CONF_PROFILE):
return await self.async_step_configure_profile()

media_service = camera.create_media_service()
profiles = await media_service.GetProfiles()
LOGGER.debug("Media Profiles %s", pformat(profiles))
for i, profile in enumerate(profiles):
if profile.VideoEncoderConfiguration.Encoding != "H264":
continue
self.profiles[i] = {
"name": profile.Name,
"resolution": "%dx%d"
% (
profile.VideoEncoderConfiguration.Resolution.Width,
profile.VideoEncoderConfiguration.Resolution.Height,
),
}

if not self.profiles:
if not self.onvif_config.get(CONF_PROFILE):
self.onvif_config[CONF_PROFILE] = []
media_service = camera.create_media_service()
profiles = await media_service.GetProfiles()
LOGGER.debug("Media Profiles %s", pformat(profiles))
for key, profile in enumerate(profiles):
if profile.VideoEncoderConfiguration.Encoding != "H264":
continue
self.onvif_config[CONF_PROFILE].append(key)

if not self.onvif_config[CONF_PROFILE]:
return self.async_abort(reason="no_h264")

return await self.async_step_configure_profile()
title = f"{self.onvif_config[CONF_NAME]} - {self.device_id}"
return self.async_create_entry(title=title, data=self.onvif_config)

except exceptions.ONVIFError as err:
LOGGER.error(
Expand All @@ -269,29 +260,6 @@ async def async_step_profiles(self, user_input=None):

return self.async_show_form(step_id="auth", errors=errors)

async def async_step_configure_profile(self, user_input=None):
"""Allow the user to configure ONVIF profiles."""
if user_input is not None:
self.onvif_config[CONF_PROFILE] = [
int(val) for val in user_input[CONF_PROFILE]
]

selected_profiles = self.onvif_config.get(CONF_PROFILE)
if selected_profiles and len(selected_profiles) > 0:
title = f"{self.onvif_config[CONF_NAME]} - {self.device_id}"
return self.async_create_entry(title=title, data=self.onvif_config)

profiles = {}
for key, profile in self.profiles.items():
profiles[str(key)] = f"{profile['name']} ({profile['resolution']})"

return self.async_show_form(
step_id="configure_profile",
data_schema=vol.Schema(
{vol.Required(CONF_PROFILE): cv.multi_select(profiles)}
),
)

async def async_step_import(self, user_input):
"""Handle import."""
self.onvif_config = user_input
Expand Down

0 comments on commit 322beba

Please sign in to comment.