Remove onvif from mypy ignore list (#75162)

This commit is contained in:
epenet 2022-07-14 11:55:43 +02:00 committed by GitHub
parent b60f6c7cdd
commit debd475a6d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 56 additions and 59 deletions

View File

@ -1,42 +1,50 @@
"""Base classes for ONVIF entities.""" """Base classes for ONVIF entities."""
from __future__ import annotations
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.entity import DeviceInfo, Entity from homeassistant.helpers.entity import DeviceInfo, Entity
from .const import DOMAIN from .const import DOMAIN
from .device import ONVIFDevice from .device import ONVIFDevice
from .models import Profile
class ONVIFBaseEntity(Entity): class ONVIFBaseEntity(Entity):
"""Base class common to all ONVIF entities.""" """Base class common to all ONVIF entities."""
def __init__(self, device: ONVIFDevice, profile: Profile = None) -> None: def __init__(self, device: ONVIFDevice) -> None:
"""Initialize the ONVIF entity.""" """Initialize the ONVIF entity."""
self.device: ONVIFDevice = device self.device: ONVIFDevice = device
self.profile: Profile = profile
@property @property
def available(self): def available(self):
"""Return True if device is available.""" """Return True if device is available."""
return self.device.available return self.device.available
@property
def mac_or_serial(self) -> str:
"""Return MAC or serial, for unique_id generation.
MAC address is not always available, and given the number
of non-conformant ONVIF devices we have historically supported,
we can not guarantee serial number either. Due to this, we have
adopted an either/or approach in the config entry setup, and can
guarantee that one or the other will be populated.
See: https://github.com/home-assistant/core/issues/35883
"""
return (
self.device.info.mac
or self.device.info.serial_number # type:ignore[return-value]
)
@property @property
def device_info(self) -> DeviceInfo: def device_info(self) -> DeviceInfo:
"""Return a device description for device registry.""" """Return a device description for device registry."""
connections = None connections: set[tuple[str, str]] = set()
if self.device.info.mac: if self.device.info.mac:
connections = {(CONNECTION_NETWORK_MAC, self.device.info.mac)} connections = {(CONNECTION_NETWORK_MAC, self.device.info.mac)}
return DeviceInfo( return DeviceInfo(
connections=connections, connections=connections,
identifiers={ identifiers={(DOMAIN, self.mac_or_serial)},
# MAC address is not always available, and given the number
# of non-conformant ONVIF devices we have historically supported,
# we can not guarantee serial number either. Due to this, we have
# adopted an either/or approach in the config entry setup, and can
# guarantee that one or the other will be populated.
# See: https://github.com/home-assistant/core/issues/35883
(DOMAIN, self.device.info.mac or self.device.info.serial_number)
},
manufacturer=self.device.info.manufacturer, manufacturer=self.device.info.manufacturer,
model=self.device.info.model, model=self.device.info.model,
name=self.device.name, name=self.device.name,

View File

@ -48,15 +48,16 @@ async def async_setup_entry(
device.events.async_add_listener(async_check_entities) device.events.async_add_listener(async_check_entities)
return True
class ONVIFBinarySensor(ONVIFBaseEntity, RestoreEntity, BinarySensorEntity): class ONVIFBinarySensor(ONVIFBaseEntity, RestoreEntity, BinarySensorEntity):
"""Representation of a binary ONVIF event.""" """Representation of a binary ONVIF event."""
_attr_should_poll = False _attr_should_poll = False
_attr_unique_id: str
def __init__(self, uid, device: ONVIFDevice, entry: er.RegistryEntry | None = None): def __init__(
self, uid: str, device: ONVIFDevice, entry: er.RegistryEntry | None = None
) -> None:
"""Initialize the ONVIF binary sensor.""" """Initialize the ONVIF binary sensor."""
self._attr_unique_id = uid self._attr_unique_id = uid
if entry is not None: if entry is not None:
@ -65,6 +66,7 @@ class ONVIFBinarySensor(ONVIFBaseEntity, RestoreEntity, BinarySensorEntity):
self._attr_name = entry.name self._attr_name = entry.name
else: else:
event = device.events.get_uid(uid) event = device.events.get_uid(uid)
assert event
self._attr_device_class = event.device_class self._attr_device_class = event.device_class
self._attr_entity_category = event.entity_category self._attr_entity_category = event.entity_category
self._attr_entity_registry_enabled_default = event.entity_enabled self._attr_entity_registry_enabled_default = event.entity_enabled

View File

@ -30,9 +30,7 @@ class RebootButton(ONVIFBaseEntity, ButtonEntity):
"""Initialize the button entity.""" """Initialize the button entity."""
super().__init__(device) super().__init__(device)
self._attr_name = f"{self.device.name} Reboot" self._attr_name = f"{self.device.name} Reboot"
self._attr_unique_id = ( self._attr_unique_id = f"{self.mac_or_serial}_reboot"
f"{self.device.info.mac or self.device.info.serial_number}_reboot"
)
async def async_press(self) -> None: async def async_press(self) -> None:
"""Send out a SystemReboot command.""" """Send out a SystemReboot command."""
@ -49,7 +47,7 @@ class SetSystemDateAndTimeButton(ONVIFBaseEntity, ButtonEntity):
"""Initialize the button entity.""" """Initialize the button entity."""
super().__init__(device) super().__init__(device)
self._attr_name = f"{self.device.name} Set System Date and Time" self._attr_name = f"{self.device.name} Set System Date and Time"
self._attr_unique_id = f"{self.device.info.mac or self.device.info.serial_number}_setsystemdatetime" self._attr_unique_id = f"{self.mac_or_serial}_setsystemdatetime"
async def async_press(self) -> None: async def async_press(self) -> None:
"""Send out a SetSystemDateAndTime command.""" """Send out a SetSystemDateAndTime command."""

View File

@ -13,6 +13,7 @@ from homeassistant.components.stream import (
CONF_RTSP_TRANSPORT, CONF_RTSP_TRANSPORT,
CONF_USE_WALLCLOCK_AS_TIMESTAMPS, CONF_USE_WALLCLOCK_AS_TIMESTAMPS,
) )
from homeassistant.components.stream.const import RTSP_TRANSPORTS
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import HTTP_BASIC_AUTHENTICATION from homeassistant.const import HTTP_BASIC_AUTHENTICATION
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -46,6 +47,8 @@ from .const import (
ZOOM_IN, ZOOM_IN,
ZOOM_OUT, ZOOM_OUT,
) )
from .device import ONVIFDevice
from .models import Profile
async def async_setup_entry( async def async_setup_entry(
@ -85,20 +88,19 @@ async def async_setup_entry(
[ONVIFCameraEntity(device, profile) for profile in device.profiles] [ONVIFCameraEntity(device, profile) for profile in device.profiles]
) )
return True
class ONVIFCameraEntity(ONVIFBaseEntity, Camera): class ONVIFCameraEntity(ONVIFBaseEntity, Camera):
"""Representation of an ONVIF camera.""" """Representation of an ONVIF camera."""
_attr_supported_features = CameraEntityFeature.STREAM _attr_supported_features = CameraEntityFeature.STREAM
def __init__(self, device, profile): def __init__(self, device: ONVIFDevice, profile: Profile) -> None:
"""Initialize ONVIF camera entity.""" """Initialize ONVIF camera entity."""
ONVIFBaseEntity.__init__(self, device, profile) ONVIFBaseEntity.__init__(self, device)
Camera.__init__(self) Camera.__init__(self)
self.profile = profile
self.stream_options[CONF_RTSP_TRANSPORT] = device.config_entry.options.get( self.stream_options[CONF_RTSP_TRANSPORT] = device.config_entry.options.get(
CONF_RTSP_TRANSPORT CONF_RTSP_TRANSPORT, next(iter(RTSP_TRANSPORTS))
) )
self.stream_options[ self.stream_options[
CONF_USE_WALLCLOCK_AS_TIMESTAMPS CONF_USE_WALLCLOCK_AS_TIMESTAMPS
@ -118,8 +120,8 @@ class ONVIFCameraEntity(ONVIFBaseEntity, Camera):
def unique_id(self) -> str: def unique_id(self) -> str:
"""Return a unique ID.""" """Return a unique ID."""
if self.profile.index: if self.profile.index:
return f"{self.device.info.mac or self.device.info.serial_number}_{self.profile.index}" return f"{self.mac_or_serial}_{self.profile.index}"
return self.device.info.mac or self.device.info.serial_number return self.mac_or_serial
@property @property
def entity_registry_enabled_default(self) -> bool: def entity_registry_enabled_default(self) -> bool:
@ -149,6 +151,7 @@ class ONVIFCameraEntity(ONVIFBaseEntity, Camera):
) )
if image is None: if image is None:
assert self._stream_uri
return await ffmpeg.async_get_image( return await ffmpeg.async_get_image(
self.hass, self.hass,
self._stream_uri, self._stream_uri,

View File

@ -42,21 +42,21 @@ from .models import PTZ, Capabilities, DeviceInfo, Profile, Resolution, Video
class ONVIFDevice: class ONVIFDevice:
"""Manages an ONVIF device.""" """Manages an ONVIF device."""
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry = None) -> None: device: ONVIFCamera
events: EventManager
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
"""Initialize the device.""" """Initialize the device."""
self.hass: HomeAssistant = hass self.hass: HomeAssistant = hass
self.config_entry: ConfigEntry = config_entry self.config_entry: ConfigEntry = config_entry
self.available: bool = True self.available: bool = True
self.device: ONVIFCamera = None
self.events: EventManager = None
self.info: DeviceInfo = DeviceInfo() self.info: DeviceInfo = DeviceInfo()
self.capabilities: Capabilities = Capabilities() self.capabilities: Capabilities = Capabilities()
self.profiles: list[Profile] = [] self.profiles: list[Profile] = []
self.max_resolution: int = 0 self.max_resolution: int = 0
self._dt_diff_seconds: int = 0 self._dt_diff_seconds: float = 0
@property @property
def name(self) -> str: def name(self) -> str:
@ -99,6 +99,7 @@ class ONVIFDevice:
await self.async_check_date_and_time() await self.async_check_date_and_time()
# Create event manager # Create event manager
assert self.config_entry.unique_id
self.events = EventManager( self.events = EventManager(
self.hass, self.device, self.config_entry.unique_id self.hass, self.device, self.config_entry.unique_id
) )
@ -297,7 +298,7 @@ class ONVIFDevice:
"""Obtain media profiles for this device.""" """Obtain media profiles for this device."""
media_service = self.device.create_media_service() media_service = self.device.create_media_service()
result = await media_service.GetProfiles() result = await media_service.GetProfiles()
profiles = [] profiles: list[Profile] = []
if not isinstance(result, list): if not isinstance(result, list):
return profiles return profiles
@ -396,7 +397,7 @@ class ONVIFDevice:
req.ProfileToken = profile.token req.ProfileToken = profile.token
if move_mode == CONTINUOUS_MOVE: if move_mode == CONTINUOUS_MOVE:
# Guard against unsupported operation # Guard against unsupported operation
if not profile.ptz.continuous: if not profile.ptz or not profile.ptz.continuous:
LOGGER.warning( LOGGER.warning(
"ContinuousMove not supported on device '%s'", self.name "ContinuousMove not supported on device '%s'", self.name
) )
@ -419,7 +420,7 @@ class ONVIFDevice:
) )
elif move_mode == RELATIVE_MOVE: elif move_mode == RELATIVE_MOVE:
# Guard against unsupported operation # Guard against unsupported operation
if not profile.ptz.relative: if not profile.ptz or not profile.ptz.relative:
LOGGER.warning( LOGGER.warning(
"RelativeMove not supported on device '%s'", self.name "RelativeMove not supported on device '%s'", self.name
) )
@ -436,7 +437,7 @@ class ONVIFDevice:
await ptz_service.RelativeMove(req) await ptz_service.RelativeMove(req)
elif move_mode == ABSOLUTE_MOVE: elif move_mode == ABSOLUTE_MOVE:
# Guard against unsupported operation # Guard against unsupported operation
if not profile.ptz.absolute: if not profile.ptz or not profile.ptz.absolute:
LOGGER.warning( LOGGER.warning(
"AbsoluteMove not supported on device '%s'", self.name "AbsoluteMove not supported on device '%s'", self.name
) )
@ -453,6 +454,11 @@ class ONVIFDevice:
await ptz_service.AbsoluteMove(req) await ptz_service.AbsoluteMove(req)
elif move_mode == GOTOPRESET_MOVE: elif move_mode == GOTOPRESET_MOVE:
# Guard against unsupported operation # Guard against unsupported operation
if not profile.ptz or not profile.ptz.presets:
LOGGER.warning(
"Absolute Presets not supported on device '%s'", self.name
)
return
if preset_val not in profile.ptz.presets: if preset_val not in profile.ptz.presets:
LOGGER.warning( LOGGER.warning(
"PTZ preset '%s' does not exist on device '%s'. Available Presets: %s", "PTZ preset '%s' does not exist on device '%s'. Available Presets: %s",

View File

@ -2,6 +2,7 @@
from __future__ import annotations from __future__ import annotations
from datetime import date, datetime from datetime import date, datetime
from decimal import Decimal
from homeassistant.components.sensor import RestoreSensor from homeassistant.components.sensor import RestoreSensor
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@ -47,8 +48,6 @@ async def async_setup_entry(
device.events.async_add_listener(async_check_entities) device.events.async_add_listener(async_check_entities)
return True
class ONVIFSensor(ONVIFBaseEntity, RestoreSensor): class ONVIFSensor(ONVIFBaseEntity, RestoreSensor):
"""Representation of a ONVIF sensor event.""" """Representation of a ONVIF sensor event."""
@ -65,6 +64,7 @@ class ONVIFSensor(ONVIFBaseEntity, RestoreSensor):
self._attr_native_unit_of_measurement = entry.unit_of_measurement self._attr_native_unit_of_measurement = entry.unit_of_measurement
else: else:
event = device.events.get_uid(uid) event = device.events.get_uid(uid)
assert event
self._attr_device_class = event.device_class self._attr_device_class = event.device_class
self._attr_entity_category = event.entity_category self._attr_entity_category = event.entity_category
self._attr_entity_registry_enabled_default = event.entity_enabled self._attr_entity_registry_enabled_default = event.entity_enabled
@ -75,7 +75,7 @@ class ONVIFSensor(ONVIFBaseEntity, RestoreSensor):
super().__init__(device) super().__init__(device)
@property @property
def native_value(self) -> StateType | date | datetime: def native_value(self) -> StateType | date | datetime | Decimal:
"""Return the value reported by the sensor.""" """Return the value reported by the sensor."""
if (event := self.device.events.get_uid(self._attr_unique_id)) is not None: if (event := self.device.events.get_uid(self._attr_unique_id)) is not None:
return event.value return event.value

View File

@ -2689,21 +2689,6 @@ ignore_errors = true
[mypy-homeassistant.components.minecraft_server.sensor] [mypy-homeassistant.components.minecraft_server.sensor]
ignore_errors = true ignore_errors = true
[mypy-homeassistant.components.onvif.base]
ignore_errors = true
[mypy-homeassistant.components.onvif.binary_sensor]
ignore_errors = true
[mypy-homeassistant.components.onvif.camera]
ignore_errors = true
[mypy-homeassistant.components.onvif.device]
ignore_errors = true
[mypy-homeassistant.components.onvif.sensor]
ignore_errors = true
[mypy-homeassistant.components.sonos] [mypy-homeassistant.components.sonos]
ignore_errors = true ignore_errors = true

View File

@ -27,11 +27,6 @@ IGNORED_MODULES: Final[list[str]] = [
"homeassistant.components.minecraft_server", "homeassistant.components.minecraft_server",
"homeassistant.components.minecraft_server.helpers", "homeassistant.components.minecraft_server.helpers",
"homeassistant.components.minecraft_server.sensor", "homeassistant.components.minecraft_server.sensor",
"homeassistant.components.onvif.base",
"homeassistant.components.onvif.binary_sensor",
"homeassistant.components.onvif.camera",
"homeassistant.components.onvif.device",
"homeassistant.components.onvif.sensor",
"homeassistant.components.sonos", "homeassistant.components.sonos",
"homeassistant.components.sonos.alarms", "homeassistant.components.sonos.alarms",
"homeassistant.components.sonos.binary_sensor", "homeassistant.components.sonos.binary_sensor",