diff --git a/custom_components/reolink_dev/__init__.py b/custom_components/reolink_dev/__init__.py index b2601a9..3a2c858 100644 --- a/custom_components/reolink_dev/__init__.py +++ b/custom_components/reolink_dev/__init__.py @@ -33,6 +33,7 @@ CONF_THUMBNAIL_PATH, CONF_STREAM_FORMAT, CONF_MOTION_STATES_UPDATE_FALLBACK_DELAY, + CONF_ONVIF_SUBSCRIPTION_DISABLED, DEFAULT_SMTP_PORT, COORDINATOR, MOTION_UPDATE_COORDINATOR, @@ -94,7 +95,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD], ) - await push.subscribe(base.event_id) + if not base.onvif_subscription_disabled: + await push.subscribe(base.event_id) + await push.set_smtp_port(entry.options.get(CONF_SMTP_PORT, DEFAULT_SMTP_PORT)) hass.data[DOMAIN][base.push_manager] = push @@ -102,7 +105,8 @@ async def async_update_data(): """Perform the actual updates.""" async with async_timeout.timeout(base.timeout): - await push.renew() + if not base.onvif_subscription_disabled: + await push.renew() await base.update_states() coordinator = DataUpdateCoordinator( @@ -124,7 +128,7 @@ async def async_update_motion_states(): # Force a refresh of motion sensors (in case Webhook is broken) if base.sensor_motion_detection is not None: # hass.bus.async_fire(base.event_id, {"motion": False}) - await base.sensor_motion_detection.handle_event(Event(base.event_id, {"motion": True})) + await base.sensor_motion_detection.handle_event(Event(base.event_id, {"motion": True, "available": True})) coordinator_motion_update = DataUpdateCoordinator( hass, @@ -167,6 +171,7 @@ async def update_listener(hass: HomeAssistant, entry: ConfigEntry): motion_state_coordinator: DataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][MOTION_UPDATE_COORDINATOR] base.motion_states_update_fallback_delay = entry.options[CONF_MOTION_STATES_UPDATE_FALLBACK_DELAY] + base.onvif_subscription_disabled = entry.options[CONF_ONVIF_SUBSCRIPTION_DISABLED] if motion_state_coordinator.update_interval != base.motion_states_update_fallback_delay: if base.motion_states_update_fallback_delay is None or base.motion_states_update_fallback_delay <= 0: diff --git a/custom_components/reolink_dev/base.py b/custom_components/reolink_dev/base.py index 92d6075..6a94193 100644 --- a/custom_components/reolink_dev/base.py +++ b/custom_components/reolink_dev/base.py @@ -44,6 +44,7 @@ CONF_STREAM, CONF_STREAM_FORMAT, CONF_MOTION_STATES_UPDATE_FALLBACK_DELAY, + CONF_ONVIF_SUBSCRIPTION_DISABLED, DEFAULT_USE_HTTPS, DEFAULT_CHANNEL, DEFAULT_MOTION_OFF_DELAY, @@ -52,6 +53,7 @@ DEFAULT_STREAM_FORMAT, DEFAULT_TIMEOUT, DEFAULT_MOTION_STATES_UPDATE_FALLBACK_DELAY, + DEFAULT_ONVIF_SUBSCRIPTION_DISABLED, DOMAIN, PUSH_MANAGER, SESSION_RENEW_THRESHOLD, @@ -156,6 +158,10 @@ def __init__( else: self.motion_states_update_fallback_delay = options[CONF_MOTION_STATES_UPDATE_FALLBACK_DELAY] + self.onvif_subscription_disabled = DEFAULT_ONVIF_SUBSCRIPTION_DISABLED + if CONF_ONVIF_SUBSCRIPTION_DISABLED in options: + self.onvif_subscription_disabled = options[CONF_ONVIF_SUBSCRIPTION_DISABLED] + from .binary_sensor import MotionSensor, ObjectDetectedSensor self.sensor_motion_detection: Optional[MotionSensor] = None diff --git a/custom_components/reolink_dev/binary_sensor.py b/custom_components/reolink_dev/binary_sensor.py index d2e602c..83d0fc2 100644 --- a/custom_components/reolink_dev/binary_sensor.py +++ b/custom_components/reolink_dev/binary_sensor.py @@ -105,11 +105,19 @@ async def handle_event(self, event): try: self._available = event.data["available"] - return except KeyError: pass if not self._available: + if self._base.sensor_person_detection is not None and self._base.sensor_person_detection.available: + await self._base.sensor_person_detection.handle_event( + Event(self._base.event_id, {"available": False})) + if self._base.sensor_vehicle_detection is not None and self._base.sensor_vehicle_detection.available: + await self._base.sensor_vehicle_detection.handle_event( + Event(self._base.event_id, {"available": False})) + if self._base.sensor_pet_detection is not None and self._base.sensor_pet_detection.available: + await self._base.sensor_pet_detection.handle_event( + Event(self._base.event_id, {"available": False})) return try: @@ -151,13 +159,13 @@ async def handle_event(self, event): # send an event to AI based motion sensor entities if self._base.sensor_person_detection is not None: await self._base.sensor_person_detection.handle_event( - Event(self._base.event_id, {"ai_refreshed": True})) + Event(self._base.event_id, {"ai_refreshed": True, "available": True})) if self._base.sensor_vehicle_detection is not None: await self._base.sensor_vehicle_detection.handle_event( - Event(self._base.event_id, {"ai_refreshed": True})) + Event(self._base.event_id, {"ai_refreshed": True, "available": True})) if self._base.sensor_pet_detection is not None: await self._base.sensor_pet_detection.handle_event( - Event(self._base.event_id, {"ai_refreshed": True})) + Event(self._base.event_id, {"ai_refreshed": True, "available": True})) if self.enabled: self.async_schedule_update_ha_state() @@ -271,7 +279,6 @@ def available(self): return self._base.api.ai_state and self._base.api.session_active return self._available - @property def device_class(self): """Return the class of this device.""" @@ -290,7 +297,6 @@ async def handle_event(self, event): if new_availability != self._available: self._available = new_availability self.async_schedule_update_ha_state() - return except KeyError: pass diff --git a/custom_components/reolink_dev/config_flow.py b/custom_components/reolink_dev/config_flow.py index 2842f17..ee3e466 100644 --- a/custom_components/reolink_dev/config_flow.py +++ b/custom_components/reolink_dev/config_flow.py @@ -29,6 +29,7 @@ CONF_STREAM_FORMAT, CONF_THUMBNAIL_PATH, CONF_MOTION_STATES_UPDATE_FALLBACK_DELAY, + CONF_ONVIF_SUBSCRIPTION_DISABLED, DEFAULT_SMTP_PORT, DEFAULT_MOTION_OFF_DELAY, DEFAULT_USE_HTTPS, @@ -39,6 +40,7 @@ DEFAULT_THUMBNAIL_PATH, DEFAULT_TIMEOUT, DEFAULT_MOTION_STATES_UPDATE_FALLBACK_DELAY, + DEFAULT_ONVIF_SUBSCRIPTION_DISABLED, DOMAIN, ) @@ -200,6 +202,12 @@ async def async_step_init(self, user_input=None): # pylint: disable=unused-argu CONF_MOTION_OFF_DELAY, DEFAULT_MOTION_OFF_DELAY ), ): vol.All(vol.Coerce(int), vol.Range(min=0)), + vol.Required( + CONF_ONVIF_SUBSCRIPTION_DISABLED, + default=self.config_entry.options.get( + CONF_ONVIF_SUBSCRIPTION_DISABLED, DEFAULT_ONVIF_SUBSCRIPTION_DISABLED + ), + ): vol.All(vol.Coerce(bool)), vol.Required( CONF_MOTION_STATES_UPDATE_FALLBACK_DELAY, default=self.config_entry.options.get( diff --git a/custom_components/reolink_dev/const.py b/custom_components/reolink_dev/const.py index e313466..f1e58c8 100644 --- a/custom_components/reolink_dev/const.py +++ b/custom_components/reolink_dev/const.py @@ -24,6 +24,7 @@ CONF_PLAYBACK_MONTHS = "playback_months" CONF_THUMBNAIL_PATH = "playback_thumbnail_path" CONF_MOTION_STATES_UPDATE_FALLBACK_DELAY = "motion_states_update_fallback_delay" +CONF_ONVIF_SUBSCRIPTION_DISABLED = "onvif_subscription_disabled" DEFAULT_USE_HTTPS = True DEFAULT_CHANNEL = 1 @@ -33,6 +34,7 @@ DEFAULT_STREAM = "main" DEFAULT_STREAM_FORMAT = "h264" DEFAULT_MOTION_STATES_UPDATE_FALLBACK_DELAY = 30 +DEFAULT_ONVIF_SUBSCRIPTION_DISABLED = False DEFAULT_TIMEOUT = 30 DEFAULT_PLAYBACK_MONTHS = 2 diff --git a/custom_components/reolink_dev/translations/en.json b/custom_components/reolink_dev/translations/en.json index c446489..1dcb21e 100644 --- a/custom_components/reolink_dev/translations/en.json +++ b/custom_components/reolink_dev/translations/en.json @@ -33,6 +33,7 @@ "stream": "Stream", "timeout": "Timeout (seconds)", "smtp_port": "SMTP port for AI detection events (0 to disable)", + "onvif_subscription_disabled": "Disable ONVIF event subscription (use polling instead)", "motion_states_update_fallback_delay": "Motion states update fallback delay (seconds, 0 or less to disable): Reolink's event subscription is not always reliable, this will help to avoid event misses", "motion_off_delay": "Motion sensor off delay (seconds)", "playback_months": "Playback range (months)",