mirror of
https://github.com/home-assistant/core.git
synced 2025-07-20 11:47:06 +00:00
Support for PTZ in Onvif cameras (#11630)
* Service PTZ added * Removed description loading during setup * Fixed hound issues * Changed attribute names * Fixed pylint error * Cleaning up the code * Changed access to protected member to dict * Removed new line added by mistake * Fixed pylint error * Fixed minors * Fixed pylint caused by usage of create_type function * Code made more concise * Fixed string intendation problem * Service name changed * Update code to fit with the new version * Set ptz to None if PTZ setup failed * more precise exception used
This commit is contained in:
parent
0d0e0b8ba3
commit
2280dc2a34
@ -10,13 +10,15 @@ import logging
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_NAME, CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_PORT)
|
CONF_NAME, CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_PORT,
|
||||||
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
|
ATTR_ENTITY_ID)
|
||||||
|
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA, DOMAIN
|
||||||
from homeassistant.components.ffmpeg import (
|
from homeassistant.components.ffmpeg import (
|
||||||
DATA_FFMPEG, CONF_EXTRA_ARGUMENTS)
|
DATA_FFMPEG, CONF_EXTRA_ARGUMENTS)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.aiohttp_client import (
|
from homeassistant.helpers.aiohttp_client import (
|
||||||
async_aiohttp_proxy_stream)
|
async_aiohttp_proxy_stream)
|
||||||
|
from homeassistant.helpers.service import extract_entity_ids
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -32,6 +34,22 @@ DEFAULT_USERNAME = 'admin'
|
|||||||
DEFAULT_PASSWORD = '888888'
|
DEFAULT_PASSWORD = '888888'
|
||||||
DEFAULT_ARGUMENTS = '-q:v 2'
|
DEFAULT_ARGUMENTS = '-q:v 2'
|
||||||
|
|
||||||
|
ATTR_PAN = "pan"
|
||||||
|
ATTR_TILT = "tilt"
|
||||||
|
ATTR_ZOOM = "zoom"
|
||||||
|
|
||||||
|
DIR_UP = "UP"
|
||||||
|
DIR_DOWN = "DOWN"
|
||||||
|
DIR_LEFT = "LEFT"
|
||||||
|
DIR_RIGHT = "RIGHT"
|
||||||
|
ZOOM_OUT = "ZOOM_OUT"
|
||||||
|
ZOOM_IN = "ZOOM_IN"
|
||||||
|
|
||||||
|
SERVICE_PTZ = "onvif_ptz"
|
||||||
|
|
||||||
|
ONVIF_DATA = "onvif"
|
||||||
|
ENTITIES = "entities"
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
vol.Required(CONF_HOST): cv.string,
|
vol.Required(CONF_HOST): cv.string,
|
||||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||||
@ -41,12 +59,38 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
vol.Optional(CONF_EXTRA_ARGUMENTS, default=DEFAULT_ARGUMENTS): cv.string,
|
vol.Optional(CONF_EXTRA_ARGUMENTS, default=DEFAULT_ARGUMENTS): cv.string,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
SERVICE_PTZ_SCHEMA = vol.Schema({
|
||||||
|
ATTR_ENTITY_ID: cv.entity_ids,
|
||||||
|
ATTR_PAN: vol.In([DIR_LEFT, DIR_RIGHT]),
|
||||||
|
ATTR_TILT: vol.In([DIR_UP, DIR_DOWN]),
|
||||||
|
ATTR_ZOOM: vol.In([ZOOM_OUT, ZOOM_IN])
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||||
"""Set up a ONVIF camera."""
|
"""Set up a ONVIF camera."""
|
||||||
if not hass.data[DATA_FFMPEG].async_run_test(config.get(CONF_HOST)):
|
if not hass.data[DATA_FFMPEG].async_run_test(config.get(CONF_HOST)):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def handle_ptz(service):
|
||||||
|
"""Handle PTZ service call."""
|
||||||
|
pan = service.data.get(ATTR_PAN, None)
|
||||||
|
tilt = service.data.get(ATTR_TILT, None)
|
||||||
|
zoom = service.data.get(ATTR_ZOOM, None)
|
||||||
|
all_cameras = hass.data[ONVIF_DATA][ENTITIES]
|
||||||
|
entity_ids = extract_entity_ids(hass, service)
|
||||||
|
target_cameras = []
|
||||||
|
if not entity_ids:
|
||||||
|
target_cameras = all_cameras
|
||||||
|
else:
|
||||||
|
target_cameras = [camera for camera in all_cameras
|
||||||
|
if camera.entity_id in entity_ids]
|
||||||
|
for camera in target_cameras:
|
||||||
|
camera.perform_ptz(pan, tilt, zoom)
|
||||||
|
|
||||||
|
hass.services.async_register(DOMAIN, SERVICE_PTZ, handle_ptz,
|
||||||
|
schema=SERVICE_PTZ_SCHEMA)
|
||||||
async_add_devices([ONVIFHassCamera(hass, config)])
|
async_add_devices([ONVIFHassCamera(hass, config)])
|
||||||
|
|
||||||
|
|
||||||
@ -55,19 +99,21 @@ class ONVIFHassCamera(Camera):
|
|||||||
|
|
||||||
def __init__(self, hass, config):
|
def __init__(self, hass, config):
|
||||||
"""Initialize a ONVIF camera."""
|
"""Initialize a ONVIF camera."""
|
||||||
from onvif import ONVIFCamera
|
from onvif import ONVIFCamera, exceptions
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self._name = config.get(CONF_NAME)
|
self._name = config.get(CONF_NAME)
|
||||||
self._ffmpeg_arguments = config.get(CONF_EXTRA_ARGUMENTS)
|
self._ffmpeg_arguments = config.get(CONF_EXTRA_ARGUMENTS)
|
||||||
self._input = None
|
self._input = None
|
||||||
|
camera = None
|
||||||
try:
|
try:
|
||||||
_LOGGER.debug("Connecting with ONVIF Camera: %s on port %s",
|
_LOGGER.debug("Connecting with ONVIF Camera: %s on port %s",
|
||||||
config.get(CONF_HOST), config.get(CONF_PORT))
|
config.get(CONF_HOST), config.get(CONF_PORT))
|
||||||
media_service = ONVIFCamera(
|
camera = ONVIFCamera(
|
||||||
config.get(CONF_HOST), config.get(CONF_PORT),
|
config.get(CONF_HOST), config.get(CONF_PORT),
|
||||||
config.get(CONF_USERNAME), config.get(CONF_PASSWORD)
|
config.get(CONF_USERNAME), config.get(CONF_PASSWORD)
|
||||||
).create_media_service()
|
)
|
||||||
|
media_service = camera.create_media_service()
|
||||||
stream_uri = media_service.GetStreamUri(
|
stream_uri = media_service.GetStreamUri(
|
||||||
{'StreamSetup': {'Stream': 'RTP-Unicast', 'Transport': 'RTSP'}}
|
{'StreamSetup': {'Stream': 'RTP-Unicast', 'Transport': 'RTSP'}}
|
||||||
)
|
)
|
||||||
@ -81,6 +127,30 @@ class ONVIFHassCamera(Camera):
|
|||||||
except Exception as err:
|
except Exception as err:
|
||||||
_LOGGER.error("Unable to communicate with ONVIF Camera: %s", err)
|
_LOGGER.error("Unable to communicate with ONVIF Camera: %s", err)
|
||||||
raise
|
raise
|
||||||
|
try:
|
||||||
|
self._ptz = camera.create_ptz_service()
|
||||||
|
except exceptions.ONVIFError as err:
|
||||||
|
self._ptz = None
|
||||||
|
_LOGGER.warning("Unable to setup PTZ for ONVIF Camera: %s", err)
|
||||||
|
|
||||||
|
def perform_ptz(self, pan, tilt, zoom):
|
||||||
|
"""Perform a PTZ action on the camera."""
|
||||||
|
if self._ptz:
|
||||||
|
pan_val = 1 if pan == DIR_RIGHT else -1 if pan == DIR_LEFT else 0
|
||||||
|
tilt_val = 1 if tilt == DIR_UP else -1 if tilt == DIR_DOWN else 0
|
||||||
|
zoom_val = 1 if zoom == ZOOM_IN else -1 if zoom == ZOOM_OUT else 0
|
||||||
|
req = {"Velocity": {
|
||||||
|
"PanTilt": {"_x": pan_val, "_y": tilt_val},
|
||||||
|
"Zoom": {"_x": zoom_val}}}
|
||||||
|
self._ptz.ContinuousMove(req)
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def async_added_to_hass(self):
|
||||||
|
"""Callback when entity is added to hass."""
|
||||||
|
if ONVIF_DATA not in self.hass.data:
|
||||||
|
self.hass.data[ONVIF_DATA] = {}
|
||||||
|
self.hass.data[ONVIF_DATA][ENTITIES] = []
|
||||||
|
self.hass.data[ONVIF_DATA][ENTITIES].append(self)
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_camera_image(self):
|
def async_camera_image(self):
|
||||||
|
@ -23,3 +23,20 @@ snapshot:
|
|||||||
filename:
|
filename:
|
||||||
description: Template of a Filename. Variable is entity_id.
|
description: Template of a Filename. Variable is entity_id.
|
||||||
example: '/tmp/snapshot_{{ entity_id }}'
|
example: '/tmp/snapshot_{{ entity_id }}'
|
||||||
|
|
||||||
|
onvif_ptz:
|
||||||
|
description: Pan/Tilt/Zoom service for ONVIF camera.
|
||||||
|
fields:
|
||||||
|
entity_id:
|
||||||
|
description: Name(s) of entities to pan, tilt or zoom.
|
||||||
|
example: 'camera.living_room_camera'
|
||||||
|
pan:
|
||||||
|
description: "Direction of pan. Allowed values: LEFT, RIGHT."
|
||||||
|
example: 'LEFT'
|
||||||
|
tilt:
|
||||||
|
description: "Direction of tilt. Allowed values: DOWN, UP."
|
||||||
|
example: 'DOWN'
|
||||||
|
zoom:
|
||||||
|
description: "Zoom. Allowed values: ZOOM_IN, ZOOM_OUT"
|
||||||
|
example: "ZOOM_IN"
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user