mirror of
https://github.com/home-assistant/core.git
synced 2025-07-13 16:27:08 +00:00
Add amcrest binary_sensors (#22703)
* Add amcrest binary_sensors Add binary_sensors with option motion_detected. Deprecate motion_detector sensor. * Update per review * Update per review Add custom validators to make sure camera names are unique, and to issue warning if deprecated sensors option motion_detector is used. async_setup_platform should not return a value. * Another review update Since there is only one type of binary_sensor, remove type test in update method.
This commit is contained in:
parent
a48c0f2991
commit
34bb31f4ec
@ -7,11 +7,11 @@ import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (
|
||||
CONF_NAME, CONF_HOST, CONF_PORT, CONF_USERNAME, CONF_PASSWORD,
|
||||
CONF_SENSORS, CONF_SWITCHES, CONF_SCAN_INTERVAL, HTTP_BASIC_AUTHENTICATION)
|
||||
CONF_BINARY_SENSORS, CONF_SENSORS, CONF_SWITCHES, CONF_SCAN_INTERVAL,
|
||||
HTTP_BASIC_AUTHENTICATION)
|
||||
from homeassistant.helpers import discovery
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
|
||||
REQUIREMENTS = ['amcrest==1.3.0']
|
||||
DEPENDENCIES = ['ffmpeg']
|
||||
|
||||
@ -52,9 +52,14 @@ STREAM_SOURCE_LIST = {
|
||||
'rtsp': 2,
|
||||
}
|
||||
|
||||
BINARY_SENSORS = {
|
||||
'motion_detected': 'Motion Detected'
|
||||
}
|
||||
|
||||
# Sensor types are defined like: Name, units, icon
|
||||
SENSOR_MOTION_DETECTOR = 'motion_detector'
|
||||
SENSORS = {
|
||||
'motion_detector': ['Motion Detected', None, 'mdi:run'],
|
||||
SENSOR_MOTION_DETECTOR: ['Motion Detected', None, 'mdi:run'],
|
||||
'sdcard': ['SD Used', '%', 'mdi:sd'],
|
||||
'ptz_preset': ['PTZ Preset', None, 'mdi:camera-iris'],
|
||||
}
|
||||
@ -65,28 +70,49 @@ SWITCHES = {
|
||||
'motion_recording': ['Motion Recording', 'mdi:record-rec']
|
||||
}
|
||||
|
||||
|
||||
def _deprecated_sensors(value):
|
||||
if SENSOR_MOTION_DETECTOR in value:
|
||||
_LOGGER.warning(
|
||||
'sensors option %s is deprecated. '
|
||||
'Please remove from your configuration and '
|
||||
'use binary_sensors option motion_detected instead.',
|
||||
SENSOR_MOTION_DETECTOR)
|
||||
return value
|
||||
|
||||
|
||||
def _has_unique_names(value):
|
||||
names = [camera[CONF_NAME] for camera in value]
|
||||
vol.Schema(vol.Unique())(names)
|
||||
return value
|
||||
|
||||
|
||||
AMCREST_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
||||
vol.Optional(CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION):
|
||||
vol.All(vol.In(AUTHENTICATION_LIST)),
|
||||
vol.Optional(CONF_RESOLUTION, default=DEFAULT_RESOLUTION):
|
||||
vol.All(vol.In(RESOLUTION_LIST)),
|
||||
vol.Optional(CONF_STREAM_SOURCE, default=DEFAULT_STREAM_SOURCE):
|
||||
vol.All(vol.In(STREAM_SOURCE_LIST)),
|
||||
vol.Optional(CONF_FFMPEG_ARGUMENTS, default=DEFAULT_ARGUMENTS):
|
||||
cv.string,
|
||||
vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL):
|
||||
cv.time_period,
|
||||
vol.Optional(CONF_BINARY_SENSORS):
|
||||
vol.All(cv.ensure_list, [vol.In(BINARY_SENSORS)]),
|
||||
vol.Optional(CONF_SENSORS):
|
||||
vol.All(cv.ensure_list, [vol.In(SENSORS)], _deprecated_sensors),
|
||||
vol.Optional(CONF_SWITCHES):
|
||||
vol.All(cv.ensure_list, [vol.In(SWITCHES)]),
|
||||
})
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.All(cv.ensure_list, [vol.Schema({
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
||||
vol.Optional(CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION):
|
||||
vol.All(vol.In(AUTHENTICATION_LIST)),
|
||||
vol.Optional(CONF_RESOLUTION, default=DEFAULT_RESOLUTION):
|
||||
vol.All(vol.In(RESOLUTION_LIST)),
|
||||
vol.Optional(CONF_STREAM_SOURCE, default=DEFAULT_STREAM_SOURCE):
|
||||
vol.All(vol.In(STREAM_SOURCE_LIST)),
|
||||
vol.Optional(CONF_FFMPEG_ARGUMENTS, default=DEFAULT_ARGUMENTS):
|
||||
cv.string,
|
||||
vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL):
|
||||
cv.time_period,
|
||||
vol.Optional(CONF_SENSORS):
|
||||
vol.All(cv.ensure_list, [vol.In(SENSORS)]),
|
||||
vol.Optional(CONF_SWITCHES):
|
||||
vol.All(cv.ensure_list, [vol.In(SWITCHES)]),
|
||||
})])
|
||||
DOMAIN: vol.All(cv.ensure_list, [AMCREST_SCHEMA], _has_unique_names)
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
@ -94,20 +120,24 @@ def setup(hass, config):
|
||||
"""Set up the Amcrest IP Camera component."""
|
||||
from amcrest import AmcrestCamera, AmcrestError
|
||||
|
||||
hass.data[DATA_AMCREST] = {}
|
||||
hass.data.setdefault(DATA_AMCREST, {})
|
||||
amcrest_cams = config[DOMAIN]
|
||||
|
||||
for device in amcrest_cams:
|
||||
name = device[CONF_NAME]
|
||||
username = device[CONF_USERNAME]
|
||||
password = device[CONF_PASSWORD]
|
||||
|
||||
try:
|
||||
camera = AmcrestCamera(device.get(CONF_HOST),
|
||||
device.get(CONF_PORT),
|
||||
device.get(CONF_USERNAME),
|
||||
device.get(CONF_PASSWORD)).camera
|
||||
camera = AmcrestCamera(device[CONF_HOST],
|
||||
device[CONF_PORT],
|
||||
username,
|
||||
password).camera
|
||||
# pylint: disable=pointless-statement
|
||||
camera.current_time
|
||||
|
||||
except AmcrestError as ex:
|
||||
_LOGGER.error("Unable to connect to Amcrest camera: %s", str(ex))
|
||||
_LOGGER.error("Unable to connect to %s camera: %s", name, str(ex))
|
||||
hass.components.persistent_notification.create(
|
||||
'Error: {}<br />'
|
||||
'You will need to restart hass after fixing.'
|
||||
@ -116,23 +146,19 @@ def setup(hass, config):
|
||||
notification_id=NOTIFICATION_ID)
|
||||
continue
|
||||
|
||||
ffmpeg_arguments = device.get(CONF_FFMPEG_ARGUMENTS)
|
||||
name = device.get(CONF_NAME)
|
||||
resolution = RESOLUTION_LIST[device.get(CONF_RESOLUTION)]
|
||||
ffmpeg_arguments = device[CONF_FFMPEG_ARGUMENTS]
|
||||
resolution = RESOLUTION_LIST[device[CONF_RESOLUTION]]
|
||||
binary_sensors = device.get(CONF_BINARY_SENSORS)
|
||||
sensors = device.get(CONF_SENSORS)
|
||||
switches = device.get(CONF_SWITCHES)
|
||||
stream_source = STREAM_SOURCE_LIST[device.get(CONF_STREAM_SOURCE)]
|
||||
|
||||
username = device.get(CONF_USERNAME)
|
||||
password = device.get(CONF_PASSWORD)
|
||||
stream_source = STREAM_SOURCE_LIST[device[CONF_STREAM_SOURCE]]
|
||||
|
||||
# currently aiohttp only works with basic authentication
|
||||
# only valid for mjpeg streaming
|
||||
if username is not None and password is not None:
|
||||
if device.get(CONF_AUTHENTICATION) == HTTP_BASIC_AUTHENTICATION:
|
||||
authentication = aiohttp.BasicAuth(username, password)
|
||||
else:
|
||||
authentication = None
|
||||
if device[CONF_AUTHENTICATION] == HTTP_BASIC_AUTHENTICATION:
|
||||
authentication = aiohttp.BasicAuth(username, password)
|
||||
else:
|
||||
authentication = None
|
||||
|
||||
hass.data[DATA_AMCREST][name] = AmcrestDevice(
|
||||
camera, name, authentication, ffmpeg_arguments, stream_source,
|
||||
@ -143,6 +169,13 @@ def setup(hass, config):
|
||||
CONF_NAME: name,
|
||||
}, config)
|
||||
|
||||
if binary_sensors:
|
||||
discovery.load_platform(
|
||||
hass, 'binary_sensor', DOMAIN, {
|
||||
CONF_NAME: name,
|
||||
CONF_BINARY_SENSORS: binary_sensors
|
||||
}, config)
|
||||
|
||||
if sensors:
|
||||
discovery.load_platform(
|
||||
hass, 'sensor', DOMAIN, {
|
||||
@ -157,7 +190,7 @@ def setup(hass, config):
|
||||
CONF_SWITCHES: switches
|
||||
}, config)
|
||||
|
||||
return True
|
||||
return len(hass.data[DATA_AMCREST]) >= 1
|
||||
|
||||
|
||||
class AmcrestDevice:
|
||||
|
71
homeassistant/components/amcrest/binary_sensor.py
Normal file
71
homeassistant/components/amcrest/binary_sensor.py
Normal file
@ -0,0 +1,71 @@
|
||||
"""Suppoort for Amcrest IP camera binary sensors."""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice, DEVICE_CLASS_MOTION)
|
||||
from homeassistant.const import CONF_NAME, CONF_BINARY_SENSORS
|
||||
from . import DATA_AMCREST, BINARY_SENSORS
|
||||
|
||||
DEPENDENCIES = ['amcrest']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=5)
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_devices,
|
||||
discovery_info=None):
|
||||
"""Set up a binary sensor for an Amcrest IP Camera."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
device_name = discovery_info[CONF_NAME]
|
||||
binary_sensors = discovery_info[CONF_BINARY_SENSORS]
|
||||
amcrest = hass.data[DATA_AMCREST][device_name]
|
||||
|
||||
amcrest_binary_sensors = []
|
||||
for sensor_type in binary_sensors:
|
||||
amcrest_binary_sensors.append(
|
||||
AmcrestBinarySensor(amcrest.name, amcrest.device, sensor_type))
|
||||
|
||||
async_add_devices(amcrest_binary_sensors, True)
|
||||
|
||||
|
||||
class AmcrestBinarySensor(BinarySensorDevice):
|
||||
"""Binary sensor for Amcrest camera."""
|
||||
|
||||
def __init__(self, name, camera, sensor_type):
|
||||
"""Initialize entity."""
|
||||
self._name = '{} {}'.format(name, BINARY_SENSORS[sensor_type])
|
||||
self._camera = camera
|
||||
self._sensor_type = sensor_type
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return entity name."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return if entity is on."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return device class."""
|
||||
return DEVICE_CLASS_MOTION
|
||||
|
||||
def update(self):
|
||||
"""Update entity."""
|
||||
from amcrest import AmcrestError
|
||||
|
||||
_LOGGER.debug('Pulling data from %s binary sensor', self._name)
|
||||
|
||||
try:
|
||||
self._state = self._camera.is_motion_detected
|
||||
except AmcrestError as error:
|
||||
_LOGGER.error(
|
||||
'Could not update %s binary sensor due to error: %s',
|
||||
self.name, error)
|
@ -28,8 +28,6 @@ async def async_setup_platform(hass, config, async_add_entities,
|
||||
|
||||
async_add_entities([AmcrestCam(hass, amcrest)], True)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class AmcrestCam(Camera):
|
||||
"""An implementation of an Amcrest IP camera."""
|
||||
|
@ -30,7 +30,6 @@ async def async_setup_platform(
|
||||
AmcrestSensor(amcrest.name, amcrest.device, sensor_type))
|
||||
|
||||
async_add_entities(amcrest_sensors, True)
|
||||
return True
|
||||
|
||||
|
||||
class AmcrestSensor(Entity):
|
||||
|
Loading…
x
Reference in New Issue
Block a user