diff --git a/homeassistant/components/onvif/__init__.py b/homeassistant/components/onvif/__init__.py index 36b4a28dffd..a834a8f2df6 100644 --- a/homeassistant/components/onvif/__init__.py +++ b/homeassistant/components/onvif/__init__.py @@ -20,7 +20,13 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady -from .const import CONF_SNAPSHOT_AUTH, DEFAULT_ARGUMENTS, DOMAIN +from .const import ( + CONF_ENABLE_WEBHOOKS, + CONF_SNAPSHOT_AUTH, + DEFAULT_ARGUMENTS, + DEFAULT_ENABLE_WEBHOOKS, + DOMAIN, +) from .device import ONVIFDevice LOGGER = logging.getLogger(__name__) @@ -143,6 +149,7 @@ async def async_populate_options(hass, entry): options = { CONF_EXTRA_ARGUMENTS: DEFAULT_ARGUMENTS, CONF_RTSP_TRANSPORT: next(iter(RTSP_TRANSPORTS)), + CONF_ENABLE_WEBHOOKS: DEFAULT_ENABLE_WEBHOOKS, } hass.config_entries.async_update_entry(entry, options=options) diff --git a/homeassistant/components/onvif/config_flow.py b/homeassistant/components/onvif/config_flow.py index da948787e49..020649db87e 100644 --- a/homeassistant/components/onvif/config_flow.py +++ b/homeassistant/components/onvif/config_flow.py @@ -34,7 +34,9 @@ from homeassistant.helpers import device_registry as dr from .const import ( CONF_DEVICE_ID, + CONF_ENABLE_WEBHOOKS, DEFAULT_ARGUMENTS, + DEFAULT_ENABLE_WEBHOOKS, DEFAULT_PORT, DOMAIN, GET_CAPABILITIES_EXCEPTIONS, @@ -387,6 +389,12 @@ class OnvifOptionsFlowHandler(config_entries.OptionsFlow): CONF_USE_WALLCLOCK_AS_TIMESTAMPS, self.config_entry.options.get(CONF_USE_WALLCLOCK_AS_TIMESTAMPS, False), ) + self.options[CONF_ENABLE_WEBHOOKS] = user_input.get( + CONF_ENABLE_WEBHOOKS, + self.config_entry.options.get( + CONF_ENABLE_WEBHOOKS, DEFAULT_ENABLE_WEBHOOKS + ), + ) return self.async_create_entry(title="", data=self.options) advanced_options = {} @@ -415,6 +423,12 @@ class OnvifOptionsFlowHandler(config_entries.OptionsFlow): CONF_RTSP_TRANSPORT, next(iter(RTSP_TRANSPORTS)) ), ): vol.In(RTSP_TRANSPORTS), + vol.Optional( + CONF_ENABLE_WEBHOOKS, + default=self.config_entry.options.get( + CONF_ENABLE_WEBHOOKS, DEFAULT_ENABLE_WEBHOOKS + ), + ): bool, **advanced_options, } ), diff --git a/homeassistant/components/onvif/const.py b/homeassistant/components/onvif/const.py index bfe22eacbd5..8d95ef484bf 100644 --- a/homeassistant/components/onvif/const.py +++ b/homeassistant/components/onvif/const.py @@ -14,6 +14,8 @@ DEFAULT_ARGUMENTS = "-pred 1" CONF_DEVICE_ID = "deviceid" CONF_SNAPSHOT_AUTH = "snapshot_auth" +CONF_ENABLE_WEBHOOKS = "enable_webhooks" +DEFAULT_ENABLE_WEBHOOKS = True ATTR_PAN = "pan" ATTR_TILT = "tilt" diff --git a/homeassistant/components/onvif/device.py b/homeassistant/components/onvif/device.py index 1152503a718..e470b3c700b 100644 --- a/homeassistant/components/onvif/device.py +++ b/homeassistant/components/onvif/device.py @@ -28,7 +28,9 @@ import homeassistant.util.dt as dt_util from .const import ( ABSOLUTE_MOVE, + CONF_ENABLE_WEBHOOKS, CONTINUOUS_MOVE, + DEFAULT_ENABLE_WEBHOOKS, GET_CAPABILITIES_EXCEPTIONS, GOTOPRESET_MOVE, LOGGER, @@ -52,6 +54,7 @@ class ONVIFDevice: """Initialize the device.""" self.hass: HomeAssistant = hass self.config_entry: ConfigEntry = config_entry + self._original_options = dict(config_entry.options) self.available: bool = True self.info: DeviceInfo = DeviceInfo() @@ -63,6 +66,13 @@ class ONVIFDevice: self._dt_diff_seconds: float = 0 + async def _async_update_listener( + self, hass: HomeAssistant, entry: ConfigEntry + ) -> None: + """Handle options update.""" + if self._original_options != entry.options: + hass.async_create_task(hass.config_entries.async_reload(entry.entry_id)) + @property def name(self) -> str: """Return the name of this device.""" @@ -151,6 +161,14 @@ class ONVIFDevice: self.capabilities.events = await self.async_start_events() LOGGER.debug("Camera %s capabilities = %s", self.name, self.capabilities) + # Bind the listener to the ONVIFDevice instance since + # async_update_listener only creates a weak reference to the listener + # and we need to make sure it doesn't get garbage collected since only + # the ONVIFDevice instance is stored in hass.data + self.config_entry.async_on_unload( + self.config_entry.add_update_listener(self._async_update_listener) + ) + async def async_stop(self, event=None): """Shut it all down.""" if self.events: @@ -357,7 +375,12 @@ class ONVIFDevice: "WSPullPointSupport" ) LOGGER.debug("%s: WSPullPointSupport: %s", self.name, pull_point_support) - return await self.events.async_start(pull_point_support is not False, True) + return await self.events.async_start( + pull_point_support is not False, + self.config_entry.options.get( + CONF_ENABLE_WEBHOOKS, DEFAULT_ENABLE_WEBHOOKS + ), + ) return False diff --git a/homeassistant/components/onvif/strings.json b/homeassistant/components/onvif/strings.json index 3e9db0b3c7e..8e989f1dfa0 100644 --- a/homeassistant/components/onvif/strings.json +++ b/homeassistant/components/onvif/strings.json @@ -61,7 +61,8 @@ "data": { "extra_arguments": "Extra FFMPEG arguments", "rtsp_transport": "RTSP transport mechanism", - "use_wallclock_as_timestamps": "Use wall clock as timestamps" + "use_wallclock_as_timestamps": "Use wall clock as timestamps", + "enable_webhooks": "Enable Webhooks" }, "title": "ONVIF Device Options" } diff --git a/tests/components/onvif/test_config_flow.py b/tests/components/onvif/test_config_flow.py index 8187a427be9..981620e7ae0 100644 --- a/tests/components/onvif/test_config_flow.py +++ b/tests/components/onvif/test_config_flow.py @@ -1,6 +1,8 @@ """Test ONVIF config flow.""" from unittest.mock import MagicMock, patch +import pytest + from homeassistant import config_entries, data_entry_flow from homeassistant.components import dhcp from homeassistant.components.onvif import DOMAIN, config_flow @@ -597,7 +599,8 @@ async def test_flow_manual_entry_wrong_password(hass: HomeAssistant) -> None: } -async def test_option_flow(hass: HomeAssistant) -> None: +@pytest.mark.parametrize("option_value", [True, False]) +async def test_option_flow(hass: HomeAssistant, option_value: bool) -> None: """Test config flow options.""" entry, _, _ = await setup_onvif_integration(hass) @@ -613,7 +616,8 @@ async def test_option_flow(hass: HomeAssistant) -> None: user_input={ config_flow.CONF_EXTRA_ARGUMENTS: "", config_flow.CONF_RTSP_TRANSPORT: list(config_flow.RTSP_TRANSPORTS)[1], - config_flow.CONF_USE_WALLCLOCK_AS_TIMESTAMPS: True, + config_flow.CONF_USE_WALLCLOCK_AS_TIMESTAMPS: option_value, + config_flow.CONF_ENABLE_WEBHOOKS: option_value, }, ) @@ -621,7 +625,8 @@ async def test_option_flow(hass: HomeAssistant) -> None: assert result["data"] == { config_flow.CONF_EXTRA_ARGUMENTS: "", config_flow.CONF_RTSP_TRANSPORT: list(config_flow.RTSP_TRANSPORTS)[1], - config_flow.CONF_USE_WALLCLOCK_AS_TIMESTAMPS: True, + config_flow.CONF_USE_WALLCLOCK_AS_TIMESTAMPS: option_value, + config_flow.CONF_ENABLE_WEBHOOKS: option_value, } diff --git a/tests/components/onvif/test_diagnostics.py b/tests/components/onvif/test_diagnostics.py index 2ab2deb6884..eb7d058a755 100644 --- a/tests/components/onvif/test_diagnostics.py +++ b/tests/components/onvif/test_diagnostics.py @@ -39,7 +39,11 @@ async def test_diagnostics( "password": "**REDACTED**", "snapshot_auth": "digest", }, - "options": {"extra_arguments": "-pred 1", "rtsp_transport": "tcp"}, + "options": { + "extra_arguments": "-pred 1", + "rtsp_transport": "tcp", + "enable_webhooks": True, + }, "pref_disable_new_entities": False, "pref_disable_polling": False, "source": "user",