Add control of Amcrest indicator light (#23986)

Enable feature by default but allow it to be disabled by "control_light: false" in config.

Get brand from camera instead of assuming Amcrest (since this works with other cameras, too.)

Retrieve RTSP URL in update method instead of in stream_source property and in handle_async_mjpeg_stream method.

Move amcrest imports from methods to global.
pull/24230/head
Phil Bruckner 2019-05-31 15:41:48 -05:00 committed by Paulus Schoutsen
parent 3c1cdecb88
commit d966e0cfce
3 changed files with 45 additions and 30 deletions

View File

@ -3,6 +3,7 @@ import logging
from datetime import timedelta
import aiohttp
from amcrest import AmcrestCamera, AmcrestError
import voluptuous as vol
from homeassistant.auth.permissions.const import POLICY_CONTROL
@ -32,6 +33,7 @@ _LOGGER = logging.getLogger(__name__)
CONF_RESOLUTION = 'resolution'
CONF_STREAM_SOURCE = 'stream_source'
CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments'
CONF_CONTROL_LIGHT = 'control_light'
DEFAULT_NAME = 'Amcrest Camera'
DEFAULT_PORT = 80
@ -103,6 +105,7 @@ AMCREST_SCHEMA = vol.All(
_deprecated_sensor_values),
vol.Optional(CONF_SWITCHES):
vol.All(cv.ensure_list, [vol.In(SWITCHES)]),
vol.Optional(CONF_CONTROL_LIGHT, default=True): cv.boolean,
}),
_deprecated_switches
)
@ -114,8 +117,6 @@ CONFIG_SCHEMA = vol.Schema({
def setup(hass, config):
"""Set up the Amcrest IP Camera component."""
from amcrest import AmcrestCamera, AmcrestError
hass.data.setdefault(DATA_AMCREST, {'devices': {}, 'cameras': []})
devices = config[DOMAIN]
@ -149,6 +150,7 @@ def setup(hass, config):
sensors = device.get(CONF_SENSORS)
switches = device.get(CONF_SWITCHES)
stream_source = device[CONF_STREAM_SOURCE]
control_light = device.get(CONF_CONTROL_LIGHT)
# currently aiohttp only works with basic authentication
# only valid for mjpeg streaming
@ -159,7 +161,7 @@ def setup(hass, config):
hass.data[DATA_AMCREST]['devices'][name] = AmcrestDevice(
api, authentication, ffmpeg_arguments, stream_source,
resolution)
resolution, control_light)
discovery.load_platform(
hass, CAMERA, DOMAIN, {
@ -245,10 +247,11 @@ class AmcrestDevice:
"""Representation of a base Amcrest discovery device."""
def __init__(self, api, authentication, ffmpeg_arguments,
stream_source, resolution):
stream_source, resolution, control_light):
"""Initialize the entity."""
self.api = api
self.authentication = authentication
self.ffmpeg_arguments = ffmpeg_arguments
self.stream_source = stream_source
self.resolution = resolution
self.control_light = control_light

View File

@ -2,6 +2,8 @@
from datetime import timedelta
import logging
from amcrest import AmcrestError
from homeassistant.components.binary_sensor import (
BinarySensorDevice, DEVICE_CLASS_MOTION)
from homeassistant.const import CONF_NAME, CONF_BINARY_SENSORS
@ -58,8 +60,6 @@ class AmcrestBinarySensor(BinarySensorDevice):
def update(self):
"""Update entity."""
from amcrest import AmcrestError
_LOGGER.debug('Pulling data from %s binary sensor', self._name)
try:

View File

@ -2,6 +2,7 @@
import asyncio
import logging
from amcrest import AmcrestError
import voluptuous as vol
from homeassistant.components.camera import (
@ -94,19 +95,20 @@ class AmcrestCam(Camera):
self._stream_source = device.stream_source
self._resolution = device.resolution
self._token = self._auth = device.authentication
self._control_light = device.control_light
self._is_recording = False
self._motion_detection_enabled = None
self._brand = None
self._model = None
self._audio_enabled = None
self._motion_recording_enabled = None
self._color_bw = None
self._rtsp_url = None
self._snapshot_lock = asyncio.Lock()
self._unsub_dispatcher = []
async def async_camera_image(self):
"""Return a still image response from the camera."""
from amcrest import AmcrestError
if not self.is_on:
_LOGGER.error(
'Attempt to take snaphot when %s camera is off', self.name)
@ -143,7 +145,7 @@ class AmcrestCam(Camera):
# streaming via ffmpeg
from haffmpeg.camera import CameraMjpeg
streaming_url = self._api.rtsp_url(typeno=self._resolution)
streaming_url = self._rtsp_url
stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop)
await stream.open_camera(
streaming_url, extra_cmd=self._ffmpeg_arguments)
@ -191,7 +193,7 @@ class AmcrestCam(Camera):
@property
def brand(self):
"""Return the camera brand."""
return 'Amcrest'
return self._brand
@property
def motion_detection_enabled(self):
@ -205,7 +207,7 @@ class AmcrestCam(Camera):
async def stream_source(self):
"""Return the source of the stream."""
return self._api.rtsp_url(typeno=self._resolution)
return self._rtsp_url
@property
def is_on(self):
@ -231,9 +233,19 @@ class AmcrestCam(Camera):
def update(self):
"""Update entity status."""
from amcrest import AmcrestError
_LOGGER.debug('Pulling data from %s camera', self.name)
if self._brand is None:
try:
resp = self._api.vendor_information.strip()
if resp.startswith('vendor='):
self._brand = resp.split('=')[-1]
else:
self._brand = 'unknown'
except AmcrestError as error:
_LOGGER.error(
'Could not get %s camera brand due to error: %s',
self.name, error)
self._brand = 'unknwown'
if self._model is None:
try:
self._model = self._api.device_type.split('=')[-1].strip()
@ -241,7 +253,7 @@ class AmcrestCam(Camera):
_LOGGER.error(
'Could not get %s camera model due to error: %s',
self.name, error)
self._model = ''
self._model = 'unknown'
try:
self.is_streaming = self._api.video_enabled
self._is_recording = self._api.record_mode == 'Manual'
@ -251,6 +263,7 @@ class AmcrestCam(Camera):
self._motion_recording_enabled = (
self._api.is_record_on_motion_detection())
self._color_bw = _CBW[self._api.day_night_color]
self._rtsp_url = self._api.rtsp_url(typeno=self._resolution)
except AmcrestError as error:
_LOGGER.error(
'Could not get %s camera attributes due to error: %s',
@ -322,8 +335,6 @@ class AmcrestCam(Camera):
def _enable_video_stream(self, enable):
"""Enable or disable camera video stream."""
from amcrest import AmcrestError
# Given the way the camera's state is determined by
# is_streaming and is_recording, we can't leave
# recording on if video stream is being turned off.
@ -338,11 +349,11 @@ class AmcrestCam(Camera):
else:
self.is_streaming = enable
self.schedule_update_ha_state()
if self._control_light:
self._enable_light(self._audio_enabled or self.is_streaming)
def _enable_recording(self, enable):
"""Turn recording on or off."""
from amcrest import AmcrestError
# Given the way the camera's state is determined by
# is_streaming and is_recording, we can't leave
# video stream off if recording is being turned on.
@ -362,8 +373,6 @@ class AmcrestCam(Camera):
def _enable_motion_detection(self, enable):
"""Enable or disable motion detection."""
from amcrest import AmcrestError
try:
self._api.motion_detection = str(enable).lower()
except AmcrestError as error:
@ -376,8 +385,6 @@ class AmcrestCam(Camera):
def _enable_audio(self, enable):
"""Enable or disable audio stream."""
from amcrest import AmcrestError
try:
self._api.audio_enabled = enable
except AmcrestError as error:
@ -387,11 +394,22 @@ class AmcrestCam(Camera):
else:
self._audio_enabled = enable
self.schedule_update_ha_state()
if self._control_light:
self._enable_light(self._audio_enabled or self.is_streaming)
def _enable_light(self, enable):
"""Enable or disable indicator light."""
try:
self._api.command(
'configManager.cgi?action=setConfig&LightGlobal[0].Enable={}'
.format(str(enable).lower()))
except AmcrestError as error:
_LOGGER.error(
'Could not %s %s camera indicator light due to error: %s',
'enable' if enable else 'disable', self.name, error)
def _enable_motion_recording(self, enable):
"""Enable or disable motion recording."""
from amcrest import AmcrestError
try:
self._api.motion_recording = str(enable).lower()
except AmcrestError as error:
@ -404,8 +422,6 @@ class AmcrestCam(Camera):
def _goto_preset(self, preset):
"""Move camera position and zoom to preset."""
from amcrest import AmcrestError
try:
self._api.go_to_preset(
action='start', preset_point_number=preset)
@ -416,8 +432,6 @@ class AmcrestCam(Camera):
def _set_color_bw(self, cbw):
"""Set camera color mode."""
from amcrest import AmcrestError
try:
self._api.day_night_color = _CBW.index(cbw)
except AmcrestError as error:
@ -430,8 +444,6 @@ class AmcrestCam(Camera):
def _start_tour(self, start):
"""Start camera tour."""
from amcrest import AmcrestError
try:
self._api.tour(start=start)
except AmcrestError as error: