mirror of
https://github.com/home-assistant/core.git
synced 2025-07-11 07:17:12 +00:00
Allow ONVIF devices to resume a PullPoint subscription when the camera reboots (#37711)
This commit is contained in:
parent
4d1ef02802
commit
49b375ff94
@ -1,8 +1,13 @@
|
|||||||
"""ONVIF event abstraction."""
|
"""ONVIF event abstraction."""
|
||||||
|
import asyncio
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
from typing import Callable, Dict, List, Optional, Set
|
from typing import Callable, Dict, List, Optional, Set
|
||||||
|
|
||||||
from aiohttp.client_exceptions import ServerDisconnectedError
|
from aiohttp.client_exceptions import (
|
||||||
|
ClientConnectorError,
|
||||||
|
ClientOSError,
|
||||||
|
ServerDisconnectedError,
|
||||||
|
)
|
||||||
from onvif import ONVIFCamera, ONVIFService
|
from onvif import ONVIFCamera, ONVIFService
|
||||||
from zeep.exceptions import Fault
|
from zeep.exceptions import Fault
|
||||||
|
|
||||||
@ -15,6 +20,13 @@ from .models import Event
|
|||||||
from .parsers import PARSERS
|
from .parsers import PARSERS
|
||||||
|
|
||||||
UNHANDLED_TOPICS = set()
|
UNHANDLED_TOPICS = set()
|
||||||
|
SUBSCRIPTION_ERRORS = (
|
||||||
|
ClientConnectorError,
|
||||||
|
ClientOSError,
|
||||||
|
Fault,
|
||||||
|
ServerDisconnectedError,
|
||||||
|
asyncio.TimeoutError,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class EventManager:
|
class EventManager:
|
||||||
@ -44,11 +56,7 @@ class EventManager:
|
|||||||
"""Listen for data updates."""
|
"""Listen for data updates."""
|
||||||
# This is the first listener, set up polling.
|
# This is the first listener, set up polling.
|
||||||
if not self._listeners:
|
if not self._listeners:
|
||||||
self._unsub_refresh = async_track_point_in_utc_time(
|
self.schedule_pull()
|
||||||
self.hass,
|
|
||||||
self.async_pull_messages,
|
|
||||||
dt_util.utcnow() + dt.timedelta(seconds=1),
|
|
||||||
)
|
|
||||||
|
|
||||||
self._listeners.append(update_callback)
|
self._listeners.append(update_callback)
|
||||||
|
|
||||||
@ -89,12 +97,14 @@ class EventManager:
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.started = True
|
self.started = True
|
||||||
|
return True
|
||||||
|
|
||||||
return self.started
|
return False
|
||||||
|
|
||||||
async def async_stop(self) -> None:
|
async def async_stop(self) -> None:
|
||||||
"""Unsubscribe from events."""
|
"""Unsubscribe from events."""
|
||||||
self._listeners = []
|
self._listeners = []
|
||||||
|
self.started = False
|
||||||
|
|
||||||
if not self._subscription:
|
if not self._subscription:
|
||||||
return
|
return
|
||||||
@ -102,6 +112,40 @@ class EventManager:
|
|||||||
await self._subscription.Unsubscribe()
|
await self._subscription.Unsubscribe()
|
||||||
self._subscription = None
|
self._subscription = None
|
||||||
|
|
||||||
|
async def async_restart(self, _now: dt = None) -> None:
|
||||||
|
"""Restart the subscription assuming the camera rebooted."""
|
||||||
|
if not self.started:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self._subscription:
|
||||||
|
try:
|
||||||
|
await self._subscription.Unsubscribe()
|
||||||
|
except SUBSCRIPTION_ERRORS:
|
||||||
|
pass # Ignored. The subscription may no longer exist.
|
||||||
|
self._subscription = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
restarted = await self.async_start()
|
||||||
|
except SUBSCRIPTION_ERRORS:
|
||||||
|
restarted = False
|
||||||
|
|
||||||
|
if not restarted:
|
||||||
|
LOGGER.warning(
|
||||||
|
"Failed to restart ONVIF PullPoint subscription for '%s'. Retrying...",
|
||||||
|
self.unique_id,
|
||||||
|
)
|
||||||
|
# Try again in a minute
|
||||||
|
self._unsub_refresh = async_track_point_in_utc_time(
|
||||||
|
self.hass,
|
||||||
|
self.async_restart,
|
||||||
|
dt_util.utcnow() + dt.timedelta(seconds=60),
|
||||||
|
)
|
||||||
|
elif self._listeners:
|
||||||
|
LOGGER.info(
|
||||||
|
"Restarted ONVIF PullPoint subscription for '%s'", self.unique_id
|
||||||
|
)
|
||||||
|
self.schedule_pull()
|
||||||
|
|
||||||
async def async_renew(self) -> None:
|
async def async_renew(self) -> None:
|
||||||
"""Renew subscription."""
|
"""Renew subscription."""
|
||||||
if not self._subscription:
|
if not self._subscription:
|
||||||
@ -114,6 +158,14 @@ class EventManager:
|
|||||||
)
|
)
|
||||||
await self._subscription.Renew(termination_time)
|
await self._subscription.Renew(termination_time)
|
||||||
|
|
||||||
|
def schedule_pull(self) -> None:
|
||||||
|
"""Schedule async_pull_messages to run."""
|
||||||
|
self._unsub_refresh = async_track_point_in_utc_time(
|
||||||
|
self.hass,
|
||||||
|
self.async_pull_messages,
|
||||||
|
dt_util.utcnow() + dt.timedelta(seconds=1),
|
||||||
|
)
|
||||||
|
|
||||||
async def async_pull_messages(self, _now: dt = None) -> None:
|
async def async_pull_messages(self, _now: dt = None) -> None:
|
||||||
"""Pull messages from device."""
|
"""Pull messages from device."""
|
||||||
if self.hass.state == CoreState.running:
|
if self.hass.state == CoreState.running:
|
||||||
@ -129,14 +181,19 @@ class EventManager:
|
|||||||
dt_util.as_utc(response.TerminationTime) - dt_util.utcnow()
|
dt_util.as_utc(response.TerminationTime) - dt_util.utcnow()
|
||||||
).total_seconds() < 7200:
|
).total_seconds() < 7200:
|
||||||
await self.async_renew()
|
await self.async_renew()
|
||||||
|
except SUBSCRIPTION_ERRORS:
|
||||||
|
LOGGER.warning(
|
||||||
|
"Failed to fetch ONVIF PullPoint subscription messages for '%s'",
|
||||||
|
self.unique_id,
|
||||||
|
)
|
||||||
|
# Treat errors as if the camera restarted. Assume that the pullpoint
|
||||||
|
# subscription is no longer valid.
|
||||||
|
self._unsub_refresh = None
|
||||||
|
await self.async_restart()
|
||||||
|
return
|
||||||
|
|
||||||
# Parse response
|
# Parse response
|
||||||
await self.async_parse_messages(response.NotificationMessage)
|
await self.async_parse_messages(response.NotificationMessage)
|
||||||
|
|
||||||
except ServerDisconnectedError:
|
|
||||||
pass
|
|
||||||
except Fault:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Update entities
|
# Update entities
|
||||||
for update_callback in self._listeners:
|
for update_callback in self._listeners:
|
||||||
@ -144,11 +201,7 @@ class EventManager:
|
|||||||
|
|
||||||
# Reschedule another pull
|
# Reschedule another pull
|
||||||
if self._listeners:
|
if self._listeners:
|
||||||
self._unsub_refresh = async_track_point_in_utc_time(
|
self.schedule_pull()
|
||||||
self.hass,
|
|
||||||
self.async_pull_messages,
|
|
||||||
dt_util.utcnow() + dt.timedelta(seconds=1),
|
|
||||||
)
|
|
||||||
|
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
async def async_parse_messages(self, messages) -> None:
|
async def async_parse_messages(self, messages) -> None:
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "onvif",
|
"domain": "onvif",
|
||||||
"name": "ONVIF",
|
"name": "ONVIF",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/onvif",
|
"documentation": "https://www.home-assistant.io/integrations/onvif",
|
||||||
"requirements": ["onvif-zeep-async==0.4.0", "WSDiscovery==2.0.0"],
|
"requirements": ["onvif-zeep-async==0.5.0", "WSDiscovery==2.0.0"],
|
||||||
"dependencies": ["ffmpeg"],
|
"dependencies": ["ffmpeg"],
|
||||||
"codeowners": ["@hunterjm"],
|
"codeowners": ["@hunterjm"],
|
||||||
"config_flow": true
|
"config_flow": true
|
||||||
|
@ -1005,7 +1005,7 @@ oemthermostat==1.1
|
|||||||
onkyo-eiscp==1.2.7
|
onkyo-eiscp==1.2.7
|
||||||
|
|
||||||
# homeassistant.components.onvif
|
# homeassistant.components.onvif
|
||||||
onvif-zeep-async==0.4.0
|
onvif-zeep-async==0.5.0
|
||||||
|
|
||||||
# homeassistant.components.opengarage
|
# homeassistant.components.opengarage
|
||||||
open-garage==0.1.4
|
open-garage==0.1.4
|
||||||
|
@ -473,7 +473,7 @@ numpy==1.19.1
|
|||||||
oauth2client==4.0.0
|
oauth2client==4.0.0
|
||||||
|
|
||||||
# homeassistant.components.onvif
|
# homeassistant.components.onvif
|
||||||
onvif-zeep-async==0.4.0
|
onvif-zeep-async==0.5.0
|
||||||
|
|
||||||
# homeassistant.components.openerz
|
# homeassistant.components.openerz
|
||||||
openerz-api==0.1.0
|
openerz-api==0.1.0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user