diff --git a/homeassistant/components/reolink/host.py b/homeassistant/components/reolink/host.py index 8dad658d7db..5ff79029bd1 100644 --- a/homeassistant/components/reolink/host.py +++ b/homeassistant/components/reolink/host.py @@ -14,10 +14,11 @@ from reolink_aio.exceptions import ReolinkError, SubscriptionError from homeassistant.components import webhook from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME -from homeassistant.core import HomeAssistant +from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant from homeassistant.helpers import issue_registry as ir from homeassistant.helpers.device_registry import format_mac from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.event import async_call_later from homeassistant.helpers.network import NoURLAvailableError, get_url from .const import CONF_PROTOCOL, CONF_USE_HTTPS, DOMAIN @@ -26,6 +27,7 @@ from .exceptions import ReolinkSetupException, ReolinkWebhookException, UserNotA DEFAULT_TIMEOUT = 60 FIRST_ONVIF_TIMEOUT = 15 SUBSCRIPTION_RENEW_THRESHOLD = 300 +POLL_INTERVAL_NO_PUSH = 5 _LOGGER = logging.getLogger(__name__) @@ -59,6 +61,8 @@ class ReolinkHost: self._base_url: str = "" self._webhook_url: str = "" self._webhook_reachable: asyncio.Event = asyncio.Event() + self._cancel_poll: CALLBACK_TYPE | None = None + self._poll_job = HassJob(self._async_poll_all_motion, cancel_on_shutdown=True) self._lost_subscription: bool = False @property @@ -169,6 +173,7 @@ class ReolinkHost: "network_link": "https://my.home-assistant.io/redirect/network/", }, ) + await self._async_poll_all_motion() else: ir.async_delete_issue(self._hass, DOMAIN, "webhook_url") @@ -228,6 +233,9 @@ class ReolinkHost: async def stop(self, event=None): """Disconnect the API.""" + if self._cancel_poll is not None: + self._cancel_poll() + self._cancel_poll = None self.unregister_webhook() await self.disconnect() @@ -359,6 +367,43 @@ class ReolinkHost: webhook.async_unregister(self._hass, self.webhook_id) self.webhook_id = None + async def _async_poll_all_motion(self, *_) -> None: + """Poll motion and AI states until the first ONVIF push is received.""" + if self._webhook_reachable.is_set(): + # ONVIF push is working, stop polling + self._cancel_poll = None + return + + try: + await self._api.get_motion_state_all_ch() + except ( + aiohttp.ClientConnectorError, + ReolinkError, + ) as err: + _LOGGER.error( + "Reolink error while polling motion state for host %s:%s: %s", + self._api.host, + self._api.port, + str(err), + ) + except asyncio.TimeoutError: + _LOGGER.error( + "Reolink timeout error while polling motion state for host %s:%s", + self._api.host, + self._api.port, + ) + finally: + # schedule next poll + if not self._hass.is_stopping: + self._cancel_poll = async_call_later( + self._hass, POLL_INTERVAL_NO_PUSH, self._poll_job + ) + + # After receiving the new motion states in the upstream lib, + # update the binary sensors with async_write_ha_state + # The same dispatch as for the webhook can be used + async_dispatcher_send(self._hass, f"{self.webhook_id}_all", {}) + async def handle_webhook( self, hass: HomeAssistant, webhook_id: str, request: Request ) -> None: diff --git a/homeassistant/components/reolink/strings.json b/homeassistant/components/reolink/strings.json index d02dbb8ab4d..f208e3e4035 100644 --- a/homeassistant/components/reolink/strings.json +++ b/homeassistant/components/reolink/strings.json @@ -45,7 +45,7 @@ }, "webhook_url": { "title": "Reolink webhook URL unreachable", - "description": "Did not receive initial ONVIF state from {name}. Most likely, the Reolink camera can not reach the current (local) Home Assistant URL `{base_url}`, please configure a (local) HTTP address under \"Home Assistant URL\" in the [network settings]({network_link}) that points to Home Assistant. For example `http://192.168.1.10:8123` where `192.168.1.10` is the IP of the Home Assistant device. Also, make sure the Reolink camera can reach that URL." + "description": "Did not receive initial ONVIF state from {name}. Most likely, the Reolink camera can not reach the current (local) Home Assistant URL `{base_url}`, please configure a (local) HTTP address under \"Home Assistant URL\" in the [network settings]({network_link}) that points to Home Assistant. For example `http://192.168.1.10:8123` where `192.168.1.10` is the IP of the Home Assistant device. Also, make sure the Reolink camera can reach that URL. Using fast motion/AI state polling until the first ONVIF push is received." }, "enable_port": { "title": "Reolink port not enabled",