Migrate onvif to use onvif-zeep-async 4.0.1 with aiohttp (#146297)

This commit is contained in:
J. Nick Koston 2025-06-07 13:39:59 -05:00 committed by GitHub
parent a979f884f9
commit 636b484d9d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 43 additions and 18 deletions

View File

@ -5,7 +5,7 @@ from contextlib import suppress
from http import HTTPStatus from http import HTTPStatus
import logging import logging
from httpx import RequestError import aiohttp
from onvif.exceptions import ONVIFError from onvif.exceptions import ONVIFError
from onvif.util import is_auth_error, stringify_onvif_error from onvif.util import is_auth_error, stringify_onvif_error
from zeep.exceptions import Fault, TransportError from zeep.exceptions import Fault, TransportError
@ -49,7 +49,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await device.async_setup() await device.async_setup()
if not entry.data.get(CONF_SNAPSHOT_AUTH): if not entry.data.get(CONF_SNAPSHOT_AUTH):
await async_populate_snapshot_auth(hass, device, entry) await async_populate_snapshot_auth(hass, device, entry)
except RequestError as err: except (TimeoutError, aiohttp.ClientError) as err:
await device.device.close() await device.device.close()
raise ConfigEntryNotReady( raise ConfigEntryNotReady(
f"Could not connect to camera {device.device.host}:{device.device.port}: {err}" f"Could not connect to camera {device.device.host}:{device.device.port}: {err}"
@ -119,7 +119,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
if device.capabilities.events and device.events.started: if device.capabilities.events and device.events.started:
try: try:
await device.events.async_stop() await device.events.async_stop()
except (ONVIFError, Fault, RequestError, TransportError): except (TimeoutError, ONVIFError, Fault, aiohttp.ClientError, TransportError):
LOGGER.warning("Error while stopping events: %s", device.name) LOGGER.warning("Error while stopping events: %s", device.name)
return await hass.config_entries.async_unload_platforms(entry, device.platforms) return await hass.config_entries.async_unload_platforms(entry, device.platforms)

View File

@ -1,8 +1,9 @@
"""Constants for the onvif component.""" """Constants for the onvif component."""
import asyncio
import logging import logging
from httpx import RequestError import aiohttp
from onvif.exceptions import ONVIFError from onvif.exceptions import ONVIFError
from zeep.exceptions import Fault, TransportError from zeep.exceptions import Fault, TransportError
@ -48,4 +49,10 @@ SERVICE_PTZ = "ptz"
# Some cameras don't support the GetServiceCapabilities call # Some cameras don't support the GetServiceCapabilities call
# and will return a 404 error which is caught by TransportError # and will return a 404 error which is caught by TransportError
GET_CAPABILITIES_EXCEPTIONS = (ONVIFError, Fault, RequestError, TransportError) GET_CAPABILITIES_EXCEPTIONS = (
ONVIFError,
Fault,
aiohttp.ClientError,
asyncio.TimeoutError,
TransportError,
)

View File

@ -9,7 +9,7 @@ import os
import time import time
from typing import Any from typing import Any
from httpx import RequestError import aiohttp
import onvif import onvif
from onvif import ONVIFCamera from onvif import ONVIFCamera
from onvif.exceptions import ONVIFError from onvif.exceptions import ONVIFError
@ -235,7 +235,7 @@ class ONVIFDevice:
LOGGER.debug("%s: Retrieving current device date/time", self.name) LOGGER.debug("%s: Retrieving current device date/time", self.name)
try: try:
device_time = await device_mgmt.GetSystemDateAndTime() device_time = await device_mgmt.GetSystemDateAndTime()
except (RequestError, Fault) as err: except (TimeoutError, aiohttp.ClientError, Fault) as err:
LOGGER.warning( LOGGER.warning(
"Couldn't get device '%s' date/time. Error: %s", self.name, err "Couldn't get device '%s' date/time. Error: %s", self.name, err
) )
@ -303,7 +303,7 @@ class ONVIFDevice:
# Set Date and Time ourselves if Date and Time is set manually in the camera. # Set Date and Time ourselves if Date and Time is set manually in the camera.
try: try:
await self.async_manually_set_date_and_time() await self.async_manually_set_date_and_time()
except (RequestError, TransportError, IndexError, Fault): except (TimeoutError, aiohttp.ClientError, TransportError, IndexError, Fault):
LOGGER.warning("%s: Could not sync date/time on this camera", self.name) LOGGER.warning("%s: Could not sync date/time on this camera", self.name)
self._async_log_time_out_of_sync(cam_date_utc, system_date) self._async_log_time_out_of_sync(cam_date_utc, system_date)

View File

@ -6,8 +6,8 @@ import asyncio
from collections.abc import Callable from collections.abc import Callable
import datetime as dt import datetime as dt
import aiohttp
from aiohttp.web import Request from aiohttp.web import Request
from httpx import RemoteProtocolError, RequestError, TransportError
from onvif import ONVIFCamera from onvif import ONVIFCamera
from onvif.client import ( from onvif.client import (
NotificationManager, NotificationManager,
@ -16,7 +16,7 @@ from onvif.client import (
) )
from onvif.exceptions import ONVIFError from onvif.exceptions import ONVIFError
from onvif.util import stringify_onvif_error from onvif.util import stringify_onvif_error
from zeep.exceptions import Fault, ValidationError, XMLParseError from zeep.exceptions import Fault, TransportError, ValidationError, XMLParseError
from homeassistant.components import webhook from homeassistant.components import webhook
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@ -34,10 +34,23 @@ from .parsers import PARSERS
UNHANDLED_TOPICS: set[str] = {"tns1:MediaControl/VideoEncoderConfiguration"} UNHANDLED_TOPICS: set[str] = {"tns1:MediaControl/VideoEncoderConfiguration"}
SUBSCRIPTION_ERRORS = (Fault, TimeoutError, TransportError) SUBSCRIPTION_ERRORS = (Fault, TimeoutError, TransportError)
CREATE_ERRORS = (ONVIFError, Fault, RequestError, XMLParseError, ValidationError) CREATE_ERRORS = (
ONVIFError,
Fault,
aiohttp.ClientError,
asyncio.TimeoutError,
XMLParseError,
ValidationError,
)
SET_SYNCHRONIZATION_POINT_ERRORS = (*SUBSCRIPTION_ERRORS, TypeError) SET_SYNCHRONIZATION_POINT_ERRORS = (*SUBSCRIPTION_ERRORS, TypeError)
UNSUBSCRIBE_ERRORS = (XMLParseError, *SUBSCRIPTION_ERRORS) UNSUBSCRIBE_ERRORS = (XMLParseError, *SUBSCRIPTION_ERRORS)
RENEW_ERRORS = (ONVIFError, RequestError, XMLParseError, *SUBSCRIPTION_ERRORS) RENEW_ERRORS = (
ONVIFError,
aiohttp.ClientError,
asyncio.TimeoutError,
XMLParseError,
*SUBSCRIPTION_ERRORS,
)
# #
# We only keep the subscription alive for 10 minutes, and will keep # We only keep the subscription alive for 10 minutes, and will keep
# renewing it every 8 minutes. This is to avoid the camera # renewing it every 8 minutes. This is to avoid the camera
@ -372,13 +385,13 @@ class PullPointManager:
"%s: PullPoint skipped because Home Assistant is not running yet", "%s: PullPoint skipped because Home Assistant is not running yet",
self._name, self._name,
) )
except RemoteProtocolError as err: except aiohttp.ServerDisconnectedError as err:
# Either a shutdown event or the camera closed the connection. Because # Either a shutdown event or the camera closed the connection. Because
# http://datatracker.ietf.org/doc/html/rfc2616#section-8.1.4 allows the server # http://datatracker.ietf.org/doc/html/rfc2616#section-8.1.4 allows the server
# to close the connection at any time, we treat this as a normal. Some # to close the connection at any time, we treat this as a normal. Some
# cameras may close the connection if there are no messages to pull. # cameras may close the connection if there are no messages to pull.
LOGGER.debug( LOGGER.debug(
"%s: PullPoint subscription encountered a remote protocol error " "%s: PullPoint subscription encountered a server disconnected error "
"(this is normal for some cameras): %s", "(this is normal for some cameras): %s",
self._name, self._name,
stringify_onvif_error(err), stringify_onvif_error(err),
@ -394,7 +407,12 @@ class PullPointManager:
# Treat errors as if the camera restarted. Assume that the pullpoint # Treat errors as if the camera restarted. Assume that the pullpoint
# subscription is no longer valid. # subscription is no longer valid.
self._pullpoint_manager.resume() self._pullpoint_manager.resume()
except (XMLParseError, RequestError, TimeoutError, TransportError) as err: except (
XMLParseError,
aiohttp.ClientError,
TimeoutError,
TransportError,
) as err:
LOGGER.debug( LOGGER.debug(
"%s: PullPoint subscription encountered an unexpected error and will be retried " "%s: PullPoint subscription encountered an unexpected error and will be retried "
"(this is normal for some cameras): %s", "(this is normal for some cameras): %s",

View File

@ -8,5 +8,5 @@
"documentation": "https://www.home-assistant.io/integrations/onvif", "documentation": "https://www.home-assistant.io/integrations/onvif",
"iot_class": "local_push", "iot_class": "local_push",
"loggers": ["onvif", "wsdiscovery", "zeep"], "loggers": ["onvif", "wsdiscovery", "zeep"],
"requirements": ["onvif-zeep-async==3.2.5", "WSDiscovery==2.1.2"] "requirements": ["onvif-zeep-async==4.0.1", "WSDiscovery==2.1.2"]
} }

2
requirements_all.txt generated
View File

@ -1584,7 +1584,7 @@ ondilo==0.5.0
onedrive-personal-sdk==0.0.14 onedrive-personal-sdk==0.0.14
# homeassistant.components.onvif # homeassistant.components.onvif
onvif-zeep-async==3.2.5 onvif-zeep-async==4.0.1
# homeassistant.components.opengarage # homeassistant.components.opengarage
open-garage==0.2.0 open-garage==0.2.0

View File

@ -1349,7 +1349,7 @@ ondilo==0.5.0
onedrive-personal-sdk==0.0.14 onedrive-personal-sdk==0.0.14
# homeassistant.components.onvif # homeassistant.components.onvif
onvif-zeep-async==3.2.5 onvif-zeep-async==4.0.1
# homeassistant.components.opengarage # homeassistant.components.opengarage
open-garage==0.2.0 open-garage==0.2.0