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
parent
3c1cdecb88
commit
d966e0cfce
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue