diff --git a/homeassistant/components/reolink/__init__.py b/homeassistant/components/reolink/__init__.py index 94d4c1561ab..c3d8df61f5a 100644 --- a/homeassistant/components/reolink/__init__.py +++ b/homeassistant/components/reolink/__init__.py @@ -6,6 +6,7 @@ import asyncio from dataclasses import dataclass from datetime import timedelta import logging +from typing import Literal from aiohttp import ClientConnectorError import async_timeout @@ -43,8 +44,8 @@ class ReolinkData: """Data for the Reolink integration.""" host: ReolinkHost - device_coordinator: DataUpdateCoordinator - firmware_coordinator: DataUpdateCoordinator + device_coordinator: DataUpdateCoordinator[None] + firmware_coordinator: DataUpdateCoordinator[str | Literal[False]] async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: @@ -74,7 +75,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, host.stop) ) - async def async_device_config_update(): + async def async_device_config_update() -> None: """Update the host state cache and renew the ONVIF-subscription.""" async with async_timeout.timeout(host.api.timeout): try: @@ -87,7 +88,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b async with async_timeout.timeout(host.api.timeout): await host.renew() - async def async_check_firmware_update(): + async def async_check_firmware_update() -> str | Literal[False]: """Check for firmware updates.""" if not host.api.supported(None, "update"): return False diff --git a/homeassistant/components/reolink/binary_sensor.py b/homeassistant/components/reolink/binary_sensor.py index 3c97087c89d..1a7649f367a 100644 --- a/homeassistant/components/reolink/binary_sensor.py +++ b/homeassistant/components/reolink/binary_sensor.py @@ -24,7 +24,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import ReolinkData from .const import DOMAIN -from .entity import ReolinkCoordinatorEntity +from .entity import ReolinkChannelCoordinatorEntity @dataclass @@ -113,7 +113,7 @@ async def async_setup_entry( async_add_entities(entities) -class ReolinkBinarySensorEntity(ReolinkCoordinatorEntity, BinarySensorEntity): +class ReolinkBinarySensorEntity(ReolinkChannelCoordinatorEntity, BinarySensorEntity): """Base binary-sensor class for Reolink IP camera motion sensors.""" entity_description: ReolinkBinarySensorEntityDescription diff --git a/homeassistant/components/reolink/button.py b/homeassistant/components/reolink/button.py index 528eb8c7405..65bb8036c0b 100644 --- a/homeassistant/components/reolink/button.py +++ b/homeassistant/components/reolink/button.py @@ -15,7 +15,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import ReolinkData from .const import DOMAIN -from .entity import ReolinkCoordinatorEntity +from .entity import ReolinkChannelCoordinatorEntity @dataclass @@ -112,7 +112,7 @@ async def async_setup_entry( ) -class ReolinkButtonEntity(ReolinkCoordinatorEntity, ButtonEntity): +class ReolinkButtonEntity(ReolinkChannelCoordinatorEntity, ButtonEntity): """Base button entity class for Reolink IP cameras.""" entity_description: ReolinkButtonEntityDescription diff --git a/homeassistant/components/reolink/camera.py b/homeassistant/components/reolink/camera.py index 4a270d6f5a6..13471df3392 100644 --- a/homeassistant/components/reolink/camera.py +++ b/homeassistant/components/reolink/camera.py @@ -10,7 +10,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import ReolinkData from .const import DOMAIN -from .entity import ReolinkCoordinatorEntity +from .entity import ReolinkChannelCoordinatorEntity _LOGGER = logging.getLogger(__name__) @@ -39,7 +39,7 @@ async def async_setup_entry( async_add_entities(cameras) -class ReolinkCamera(ReolinkCoordinatorEntity, Camera): +class ReolinkCamera(ReolinkChannelCoordinatorEntity, Camera): """An implementation of a Reolink IP camera.""" _attr_supported_features: CameraEntityFeature = CameraEntityFeature.STREAM @@ -51,7 +51,7 @@ class ReolinkCamera(ReolinkCoordinatorEntity, Camera): stream: str, ) -> None: """Initialize Reolink camera stream.""" - ReolinkCoordinatorEntity.__init__(self, reolink_data, channel) + ReolinkChannelCoordinatorEntity.__init__(self, reolink_data, channel) Camera.__init__(self) self._stream = stream diff --git a/homeassistant/components/reolink/entity.py b/homeassistant/components/reolink/entity.py index 5f983ab3494..3a962d099df 100644 --- a/homeassistant/components/reolink/entity.py +++ b/homeassistant/components/reolink/entity.py @@ -1,6 +1,8 @@ """Reolink parent entity class.""" from __future__ import annotations +from typing import TypeVar + from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.update_coordinator import ( @@ -11,24 +13,20 @@ from homeassistant.helpers.update_coordinator import ( from . import ReolinkData from .const import DOMAIN +_T = TypeVar("_T") -class ReolinkBaseCoordinatorEntity(CoordinatorEntity): - """Parent class for entities that control the Reolink NVR itself, without a channel. - A camera connected directly to HomeAssistant without using a NVR is in the reolink API - basically a NVR with a single channel that has the camera connected to that channel. - """ +class ReolinkBaseCoordinatorEntity(CoordinatorEntity[DataUpdateCoordinator[_T]]): + """Parent class fo Reolink entities.""" _attr_has_entity_name = True def __init__( self, reolink_data: ReolinkData, - coordinator: DataUpdateCoordinator | None = None, + coordinator: DataUpdateCoordinator[_T], ) -> None: - """Initialize ReolinkBaseCoordinatorEntity for a NVR entity without a channel.""" - if coordinator is None: - coordinator = reolink_data.device_coordinator + """Initialize ReolinkBaseCoordinatorEntity.""" super().__init__(coordinator) self._host = reolink_data.host @@ -52,17 +50,28 @@ class ReolinkBaseCoordinatorEntity(CoordinatorEntity): return self._host.api.session_active and super().available -class ReolinkCoordinatorEntity(ReolinkBaseCoordinatorEntity): +class ReolinkHostCoordinatorEntity(ReolinkBaseCoordinatorEntity[None]): + """Parent class for entities that control the Reolink NVR itself, without a channel. + + A camera connected directly to HomeAssistant without using a NVR is in the reolink API + basically a NVR with a single channel that has the camera connected to that channel. + """ + + def __init__(self, reolink_data: ReolinkData) -> None: + """Initialize ReolinkHostCoordinatorEntity.""" + super().__init__(reolink_data, reolink_data.device_coordinator) + + +class ReolinkChannelCoordinatorEntity(ReolinkHostCoordinatorEntity): """Parent class for Reolink hardware camera entities connected to a channel of the NVR.""" def __init__( self, reolink_data: ReolinkData, channel: int, - coordinator: DataUpdateCoordinator | None = None, ) -> None: - """Initialize ReolinkCoordinatorEntity for a hardware camera connected to a channel of the NVR.""" - super().__init__(reolink_data, coordinator) + """Initialize ReolinkChannelCoordinatorEntity for a hardware camera connected to a channel of the NVR.""" + super().__init__(reolink_data) self._channel = channel diff --git a/homeassistant/components/reolink/light.py b/homeassistant/components/reolink/light.py index dd71f91bb0b..c4923c0088b 100644 --- a/homeassistant/components/reolink/light.py +++ b/homeassistant/components/reolink/light.py @@ -20,7 +20,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import ReolinkData from .const import DOMAIN -from .entity import ReolinkCoordinatorEntity +from .entity import ReolinkChannelCoordinatorEntity @dataclass @@ -89,7 +89,7 @@ async def async_setup_entry( ) -class ReolinkLightEntity(ReolinkCoordinatorEntity, LightEntity): +class ReolinkLightEntity(ReolinkChannelCoordinatorEntity, LightEntity): """Base light entity class for Reolink IP cameras.""" entity_description: ReolinkLightEntityDescription diff --git a/homeassistant/components/reolink/number.py b/homeassistant/components/reolink/number.py index 82c1924e27d..7c50bfa9f07 100644 --- a/homeassistant/components/reolink/number.py +++ b/homeassistant/components/reolink/number.py @@ -19,7 +19,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import ReolinkData from .const import DOMAIN -from .entity import ReolinkCoordinatorEntity +from .entity import ReolinkChannelCoordinatorEntity @dataclass @@ -194,7 +194,7 @@ async def async_setup_entry( ) -class ReolinkNumberEntity(ReolinkCoordinatorEntity, NumberEntity): +class ReolinkNumberEntity(ReolinkChannelCoordinatorEntity, NumberEntity): """Base number entity class for Reolink IP cameras.""" entity_description: ReolinkNumberEntityDescription diff --git a/homeassistant/components/reolink/select.py b/homeassistant/components/reolink/select.py index 8df4afba735..c7bd621a4bc 100644 --- a/homeassistant/components/reolink/select.py +++ b/homeassistant/components/reolink/select.py @@ -15,7 +15,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import ReolinkData from .const import DOMAIN -from .entity import ReolinkCoordinatorEntity +from .entity import ReolinkChannelCoordinatorEntity @dataclass @@ -86,7 +86,7 @@ async def async_setup_entry( ) -class ReolinkSelectEntity(ReolinkCoordinatorEntity, SelectEntity): +class ReolinkSelectEntity(ReolinkChannelCoordinatorEntity, SelectEntity): """Base select entity class for Reolink IP cameras.""" entity_description: ReolinkSelectEntityDescription diff --git a/homeassistant/components/reolink/siren.py b/homeassistant/components/reolink/siren.py index f2b27dda4d1..405c3e2716d 100644 --- a/homeassistant/components/reolink/siren.py +++ b/homeassistant/components/reolink/siren.py @@ -20,7 +20,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import ReolinkData from .const import DOMAIN -from .entity import ReolinkCoordinatorEntity +from .entity import ReolinkChannelCoordinatorEntity @dataclass @@ -56,7 +56,7 @@ async def async_setup_entry( ) -class ReolinkSirenEntity(ReolinkCoordinatorEntity, SirenEntity): +class ReolinkSirenEntity(ReolinkChannelCoordinatorEntity, SirenEntity): """Base siren entity class for Reolink IP cameras.""" _attr_supported_features = ( diff --git a/homeassistant/components/reolink/switch.py b/homeassistant/components/reolink/switch.py index 64d61554856..a7ed9b6a98d 100644 --- a/homeassistant/components/reolink/switch.py +++ b/homeassistant/components/reolink/switch.py @@ -15,7 +15,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import ReolinkData from .const import DOMAIN -from .entity import ReolinkBaseCoordinatorEntity, ReolinkCoordinatorEntity +from .entity import ReolinkChannelCoordinatorEntity, ReolinkHostCoordinatorEntity @dataclass @@ -172,7 +172,7 @@ async def async_setup_entry( async_add_entities(entities) -class ReolinkSwitchEntity(ReolinkCoordinatorEntity, SwitchEntity): +class ReolinkSwitchEntity(ReolinkChannelCoordinatorEntity, SwitchEntity): """Base switch entity class for Reolink IP cameras.""" entity_description: ReolinkSwitchEntityDescription @@ -207,7 +207,7 @@ class ReolinkSwitchEntity(ReolinkCoordinatorEntity, SwitchEntity): self.async_write_ha_state() -class ReolinkNVRSwitchEntity(ReolinkBaseCoordinatorEntity, SwitchEntity): +class ReolinkNVRSwitchEntity(ReolinkHostCoordinatorEntity, SwitchEntity): """Switch entity class for Reolink NVR features.""" entity_description: ReolinkNVRSwitchEntityDescription diff --git a/homeassistant/components/reolink/update.py b/homeassistant/components/reolink/update.py index 5752afc92ac..aeb44cb7740 100644 --- a/homeassistant/components/reolink/update.py +++ b/homeassistant/components/reolink/update.py @@ -2,7 +2,7 @@ from __future__ import annotations import logging -from typing import Any +from typing import Any, Literal from reolink_aio.exceptions import ReolinkError @@ -34,7 +34,9 @@ async def async_setup_entry( async_add_entities([ReolinkUpdateEntity(reolink_data)]) -class ReolinkUpdateEntity(ReolinkBaseCoordinatorEntity, UpdateEntity): +class ReolinkUpdateEntity( + ReolinkBaseCoordinatorEntity[str | Literal[False]], UpdateEntity +): """Update entity for a Netgear device.""" _attr_device_class = UpdateDeviceClass.FIRMWARE @@ -59,9 +61,6 @@ class ReolinkUpdateEntity(ReolinkBaseCoordinatorEntity, UpdateEntity): @property def latest_version(self) -> str | None: """Latest version available for install.""" - if self.coordinator.data is None: - return None - if not self.coordinator.data: return self.installed_version