diff --git a/homeassistant/components/amcrest/__init__.py b/homeassistant/components/amcrest/__init__.py index be2a6b78f30..3baad1ac88e 100644 --- a/homeassistant/components/amcrest/__init__.py +++ b/homeassistant/components/amcrest/__init__.py @@ -33,7 +33,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send, dispatcher_s from homeassistant.helpers.event import track_time_interval from homeassistant.helpers.service import async_extract_entity_ids -from .binary_sensor import BINARY_SENSORS +from .binary_sensor import BINARY_POLLED_SENSORS, BINARY_SENSORS, check_binary_sensors from .camera import CAMERA_SERVICES, STREAM_SOURCE_LIST from .const import ( CAMERAS, @@ -98,7 +98,7 @@ AMCREST_SCHEMA = vol.Schema( 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.Unique() + cv.ensure_list, [vol.In(BINARY_SENSORS)], vol.Unique(), check_binary_sensors ), vol.Optional(CONF_SENSORS): vol.All( cv.ensure_list, [vol.In(SENSORS)], vol.Unique() @@ -271,7 +271,7 @@ def setup(hass, config): event_codes = [ BINARY_SENSORS[sensor_type][SENSOR_EVENT_CODE] for sensor_type in binary_sensors - if BINARY_SENSORS[sensor_type][SENSOR_EVENT_CODE] is not None + if sensor_type not in BINARY_POLLED_SENSORS ] if event_codes: _start_event_monitor(hass, name, api, event_codes) diff --git a/homeassistant/components/amcrest/binary_sensor.py b/homeassistant/components/amcrest/binary_sensor.py index a3057211f2a..cfb61799be8 100644 --- a/homeassistant/components/amcrest/binary_sensor.py +++ b/homeassistant/components/amcrest/binary_sensor.py @@ -3,6 +3,7 @@ from datetime import timedelta import logging from amcrest import AmcrestError +import voluptuous as vol from homeassistant.components.binary_sensor import ( DEVICE_CLASS_CONNECTIVITY, @@ -12,6 +13,7 @@ from homeassistant.components.binary_sensor import ( from homeassistant.const import CONF_BINARY_SENSORS, CONF_NAME from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.util import Throttle from .const import ( BINARY_SENSOR_SCAN_INTERVAL_SECS, @@ -28,25 +30,42 @@ from .helpers import log_update_error, service_signal _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = timedelta(seconds=BINARY_SENSOR_SCAN_INTERVAL_SECS) +_ONLINE_SCAN_INTERVAL = timedelta(seconds=60 - BINARY_SENSOR_SCAN_INTERVAL_SECS) BINARY_SENSOR_MOTION_DETECTED = "motion_detected" +BINARY_SENSOR_MOTION_DETECTED_POLLED = "motion_detected_polled" BINARY_SENSOR_ONLINE = "online" +BINARY_POLLED_SENSORS = [ + BINARY_SENSOR_MOTION_DETECTED_POLLED, + BINARY_SENSOR_ONLINE, +] +_MOTION_DETECTED_PARAMS = ("Motion Detected", DEVICE_CLASS_MOTION, "VideoMotion") BINARY_SENSORS = { - BINARY_SENSOR_MOTION_DETECTED: ( - "Motion Detected", - DEVICE_CLASS_MOTION, - "VideoMotion", - ), + BINARY_SENSOR_MOTION_DETECTED: _MOTION_DETECTED_PARAMS, + BINARY_SENSOR_MOTION_DETECTED_POLLED: _MOTION_DETECTED_PARAMS, BINARY_SENSOR_ONLINE: ("Online", DEVICE_CLASS_CONNECTIVITY, None), } BINARY_SENSORS = { k: dict(zip((SENSOR_NAME, SENSOR_DEVICE_CLASS, SENSOR_EVENT_CODE), v)) for k, v in BINARY_SENSORS.items() } +_EXCLUSIVE_OPTIONS = [ + {BINARY_SENSOR_MOTION_DETECTED, BINARY_SENSOR_MOTION_DETECTED_POLLED}, +] _UPDATE_MSG = "Updating %s binary sensor" +def check_binary_sensors(value): + """Validate binary sensor configurations.""" + for exclusive_options in _EXCLUSIVE_OPTIONS: + if len(set(value) & exclusive_options) > 1: + raise vol.Invalid( + f"must contain at most one of {', '.join(exclusive_options)}." + ) + return value + + async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up a binary sensor for an Amcrest IP Camera.""" if discovery_info is None: @@ -80,7 +99,7 @@ class AmcrestBinarySensor(BinarySensorEntity): @property def should_poll(self): """Return True if entity has to be polled for state.""" - return self._sensor_type == BINARY_SENSOR_ONLINE + return self._sensor_type in BINARY_POLLED_SENSORS @property def name(self): @@ -109,6 +128,7 @@ class AmcrestBinarySensor(BinarySensorEntity): else: self._update_others() + @Throttle(_ONLINE_SCAN_INTERVAL) def _update_online(self): if not (self._api.available or self.is_on): return @@ -137,6 +157,11 @@ class AmcrestBinarySensor(BinarySensorEntity): async def async_on_demand_update(self): """Update state.""" + if self._sensor_type == BINARY_SENSOR_ONLINE: + _LOGGER.debug(_UPDATE_MSG, self._name) + self._state = self._api.available + self.async_write_ha_state() + return self.async_schedule_update_ha_state(True) @callback @@ -155,7 +180,7 @@ class AmcrestBinarySensor(BinarySensorEntity): self.async_on_demand_update, ) ) - if self._event_code: + if self._event_code and self._sensor_type not in BINARY_POLLED_SENSORS: self._unsub_dispatcher.append( async_dispatcher_connect( self.hass, diff --git a/homeassistant/components/amcrest/const.py b/homeassistant/components/amcrest/const.py index da7e5456786..ba7597d61af 100644 --- a/homeassistant/components/amcrest/const.py +++ b/homeassistant/components/amcrest/const.py @@ -4,7 +4,7 @@ DATA_AMCREST = DOMAIN CAMERAS = "cameras" DEVICES = "devices" -BINARY_SENSOR_SCAN_INTERVAL_SECS = 60 +BINARY_SENSOR_SCAN_INTERVAL_SECS = 5 CAMERA_WEB_SESSION_TIMEOUT = 10 COMM_RETRIES = 1 COMM_TIMEOUT = 6.05