mirror of
https://github.com/home-assistant/core.git
synced 2026-04-18 07:56:03 +01:00
Move DataUpdateCoordinator to separate module in reolink (#164914)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from datetime import UTC, datetime, timedelta
|
from datetime import UTC, datetime, timedelta
|
||||||
import logging
|
import logging
|
||||||
@@ -11,13 +10,8 @@ from time import time
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from reolink_aio.api import RETRY_ATTEMPTS
|
from reolink_aio.api import RETRY_ATTEMPTS
|
||||||
from reolink_aio.exceptions import (
|
from reolink_aio.exceptions import CredentialsInvalidError, ReolinkError
|
||||||
CredentialsInvalidError,
|
|
||||||
LoginPrivacyModeError,
|
|
||||||
ReolinkError,
|
|
||||||
)
|
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
|
||||||
from homeassistant.const import CONF_PORT, EVENT_HOMEASSISTANT_STOP, Platform
|
from homeassistant.const import CONF_PORT, EVENT_HOMEASSISTANT_STOP, Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||||
@@ -29,7 +23,6 @@ from homeassistant.helpers import (
|
|||||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
|
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
|
||||||
from homeassistant.helpers.event import async_call_later
|
from homeassistant.helpers.event import async_call_later
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
BATTERY_PASSIVE_WAKE_UPDATE_INTERVAL,
|
BATTERY_PASSIVE_WAKE_UPDATE_INTERVAL,
|
||||||
@@ -40,6 +33,7 @@ from .const import (
|
|||||||
CONF_USE_HTTPS,
|
CONF_USE_HTTPS,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
)
|
)
|
||||||
|
from .coordinator import ReolinkDeviceCoordinator, ReolinkFirmwareCoordinator
|
||||||
from .exceptions import PasswordIncompatible, ReolinkException, UserNotAdmin
|
from .exceptions import PasswordIncompatible, ReolinkException, UserNotAdmin
|
||||||
from .host import ReolinkHost
|
from .host import ReolinkHost
|
||||||
from .services import async_setup_services
|
from .services import async_setup_services
|
||||||
@@ -60,10 +54,7 @@ PLATFORMS = [
|
|||||||
Platform.SWITCH,
|
Platform.SWITCH,
|
||||||
Platform.UPDATE,
|
Platform.UPDATE,
|
||||||
]
|
]
|
||||||
DEVICE_UPDATE_INTERVAL_MIN = timedelta(seconds=60)
|
|
||||||
DEVICE_UPDATE_INTERVAL_PER_CAM = timedelta(seconds=10)
|
|
||||||
FIRMWARE_UPDATE_INTERVAL = timedelta(hours=24)
|
FIRMWARE_UPDATE_INTERVAL = timedelta(hours=24)
|
||||||
NUM_CRED_ERRORS = 3
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
|
CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
|
||||||
|
|
||||||
@@ -141,103 +132,21 @@ async def async_setup_entry(
|
|||||||
hass.config_entries.async_update_entry(config_entry, data=data)
|
hass.config_entries.async_update_entry(config_entry, data=data)
|
||||||
|
|
||||||
min_timeout = host.api.timeout * (RETRY_ATTEMPTS + 2)
|
min_timeout = host.api.timeout * (RETRY_ATTEMPTS + 2)
|
||||||
update_timeout = max(min_timeout, min_timeout * host.api.num_cameras / 10)
|
|
||||||
|
|
||||||
# Track firmware versions to detect external updates (e.g., via Reolink app)
|
device_coordinator = ReolinkDeviceCoordinator(
|
||||||
last_known_firmware: dict[int | None, str | None] = {}
|
|
||||||
|
|
||||||
async def async_device_config_update() -> None:
|
|
||||||
"""Update the host state cache and renew the ONVIF-subscription."""
|
|
||||||
nonlocal last_known_firmware
|
|
||||||
|
|
||||||
async with asyncio.timeout(update_timeout):
|
|
||||||
try:
|
|
||||||
await host.update_states()
|
|
||||||
except CredentialsInvalidError as err:
|
|
||||||
host.credential_errors += 1
|
|
||||||
if host.credential_errors >= NUM_CRED_ERRORS:
|
|
||||||
await host.stop()
|
|
||||||
raise ConfigEntryAuthFailed(err) from err
|
|
||||||
raise UpdateFailed(str(err)) from err
|
|
||||||
except LoginPrivacyModeError:
|
|
||||||
pass # HTTP API is shutdown when privacy mode is active
|
|
||||||
except ReolinkError as err:
|
|
||||||
host.credential_errors = 0
|
|
||||||
raise UpdateFailed(str(err)) from err
|
|
||||||
|
|
||||||
host.credential_errors = 0
|
|
||||||
|
|
||||||
# Check for firmware version changes (external update detection)
|
|
||||||
firmware_changed = False
|
|
||||||
for ch in (*host.api.channels, None):
|
|
||||||
new_version = host.api.camera_sw_version(ch)
|
|
||||||
old_version = last_known_firmware.get(ch)
|
|
||||||
if (
|
|
||||||
old_version is not None
|
|
||||||
and new_version is not None
|
|
||||||
and new_version != old_version
|
|
||||||
):
|
|
||||||
firmware_changed = True
|
|
||||||
last_known_firmware[ch] = new_version
|
|
||||||
|
|
||||||
# Notify firmware coordinator if firmware changed externally
|
|
||||||
if firmware_changed and firmware_coordinator is not None:
|
|
||||||
firmware_coordinator.async_set_updated_data(None)
|
|
||||||
|
|
||||||
async with asyncio.timeout(min_timeout):
|
|
||||||
await host.renew()
|
|
||||||
|
|
||||||
if host.api.new_devices and config_entry.state == ConfigEntryState.LOADED:
|
|
||||||
# Their are new cameras/chimes connected, reload to add them.
|
|
||||||
_LOGGER.debug(
|
|
||||||
"Reloading Reolink %s to add new device (capabilities)",
|
|
||||||
host.api.nvr_name,
|
|
||||||
)
|
|
||||||
hass.async_create_task(
|
|
||||||
hass.config_entries.async_reload(config_entry.entry_id)
|
|
||||||
)
|
|
||||||
|
|
||||||
async def async_check_firmware_update() -> None:
|
|
||||||
"""Check for firmware updates."""
|
|
||||||
async with asyncio.timeout(min_timeout):
|
|
||||||
try:
|
|
||||||
await host.api.check_new_firmware(host.firmware_ch_list)
|
|
||||||
except ReolinkError as err:
|
|
||||||
if host.starting:
|
|
||||||
_LOGGER.debug(
|
|
||||||
"Error checking Reolink firmware update at startup "
|
|
||||||
"from %s, possibly internet access is blocked",
|
|
||||||
host.api.nvr_name,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
raise UpdateFailed(
|
|
||||||
f"Error checking Reolink firmware update from {host.api.nvr_name}, "
|
|
||||||
"if the camera is blocked from accessing the internet, "
|
|
||||||
"disable the update entity"
|
|
||||||
) from err
|
|
||||||
finally:
|
|
||||||
host.starting = False
|
|
||||||
|
|
||||||
device_coordinator = DataUpdateCoordinator(
|
|
||||||
hass,
|
hass,
|
||||||
_LOGGER,
|
config_entry,
|
||||||
config_entry=config_entry,
|
host,
|
||||||
name=f"reolink.{host.api.nvr_name}",
|
min_timeout=min_timeout,
|
||||||
update_method=async_device_config_update,
|
|
||||||
update_interval=max(
|
|
||||||
DEVICE_UPDATE_INTERVAL_MIN,
|
|
||||||
DEVICE_UPDATE_INTERVAL_PER_CAM * host.api.num_cameras,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
firmware_coordinator = DataUpdateCoordinator(
|
|
||||||
|
firmware_coordinator = ReolinkFirmwareCoordinator(
|
||||||
hass,
|
hass,
|
||||||
_LOGGER,
|
config_entry,
|
||||||
config_entry=config_entry,
|
host,
|
||||||
name=f"reolink.{host.api.nvr_name}.firmware",
|
min_timeout=min_timeout,
|
||||||
update_method=async_check_firmware_update,
|
|
||||||
update_interval=None, # Do not fetch data automatically, resume 24h schedule
|
|
||||||
)
|
)
|
||||||
|
device_coordinator.firmware_coordinator = firmware_coordinator
|
||||||
|
|
||||||
async def first_firmware_check(*args: Any) -> None:
|
async def first_firmware_check(*args: Any) -> None:
|
||||||
"""Start first firmware check delayed to continue 24h schedule."""
|
"""Start first firmware check delayed to continue 24h schedule."""
|
||||||
@@ -305,7 +214,7 @@ async def async_setup_entry(
|
|||||||
|
|
||||||
async def register_callbacks(
|
async def register_callbacks(
|
||||||
host: ReolinkHost,
|
host: ReolinkHost,
|
||||||
device_coordinator: DataUpdateCoordinator[None],
|
device_coordinator: ReolinkDeviceCoordinator,
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Register update callbacks."""
|
"""Register update callbacks."""
|
||||||
|
|||||||
178
homeassistant/components/reolink/coordinator.py
Normal file
178
homeassistant/components/reolink/coordinator.py
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
"""Data update coordinators for Reolink."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from datetime import timedelta
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from reolink_aio.exceptions import (
|
||||||
|
CredentialsInvalidError,
|
||||||
|
LoginPrivacyModeError,
|
||||||
|
ReolinkError,
|
||||||
|
)
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||||
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
|
from .host import ReolinkHost
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
NUM_CRED_ERRORS = 3
|
||||||
|
|
||||||
|
DEVICE_UPDATE_INTERVAL_MIN = timedelta(seconds=60)
|
||||||
|
DEVICE_UPDATE_INTERVAL_PER_CAM = timedelta(seconds=10)
|
||||||
|
|
||||||
|
|
||||||
|
class ReolinkCoordinator(DataUpdateCoordinator[None]):
|
||||||
|
"""Coordinator for Reolink."""
|
||||||
|
|
||||||
|
config_entry: ConfigEntry
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: ConfigEntry,
|
||||||
|
host: ReolinkHost,
|
||||||
|
name: str,
|
||||||
|
*,
|
||||||
|
min_timeout: float,
|
||||||
|
update_interval: timedelta | None,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the device coordinator."""
|
||||||
|
super().__init__(
|
||||||
|
hass,
|
||||||
|
_LOGGER,
|
||||||
|
config_entry=config_entry,
|
||||||
|
name=name,
|
||||||
|
update_interval=update_interval,
|
||||||
|
)
|
||||||
|
self._host = host
|
||||||
|
self._min_timeout = min_timeout
|
||||||
|
|
||||||
|
|
||||||
|
class ReolinkDeviceCoordinator(ReolinkCoordinator):
|
||||||
|
"""Coordinator for Reolink device state updates."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: ConfigEntry,
|
||||||
|
host: ReolinkHost,
|
||||||
|
*,
|
||||||
|
min_timeout: float,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the device coordinator."""
|
||||||
|
super().__init__(
|
||||||
|
hass,
|
||||||
|
config_entry,
|
||||||
|
host,
|
||||||
|
f"reolink.{host.api.nvr_name}",
|
||||||
|
min_timeout=min_timeout,
|
||||||
|
update_interval=max(
|
||||||
|
DEVICE_UPDATE_INTERVAL_MIN,
|
||||||
|
DEVICE_UPDATE_INTERVAL_PER_CAM * host.api.num_cameras,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self._update_timeout = max(min_timeout, min_timeout * host.api.num_cameras / 10)
|
||||||
|
self._last_known_firmware: dict[int | None, str | None] = {}
|
||||||
|
self.firmware_coordinator: ReolinkFirmwareCoordinator | None = None
|
||||||
|
|
||||||
|
async def _async_update_data(self) -> None:
|
||||||
|
"""Update the host state cache and renew the ONVIF-subscription."""
|
||||||
|
async with asyncio.timeout(self._update_timeout):
|
||||||
|
try:
|
||||||
|
await self._host.update_states()
|
||||||
|
except CredentialsInvalidError as err:
|
||||||
|
self._host.credential_errors += 1
|
||||||
|
if self._host.credential_errors >= NUM_CRED_ERRORS:
|
||||||
|
await self._host.stop()
|
||||||
|
raise ConfigEntryAuthFailed(err) from err
|
||||||
|
raise UpdateFailed(str(err)) from err
|
||||||
|
except LoginPrivacyModeError:
|
||||||
|
pass # HTTP API is shutdown when privacy mode is active
|
||||||
|
except ReolinkError as err:
|
||||||
|
self._host.credential_errors = 0
|
||||||
|
raise UpdateFailed(str(err)) from err
|
||||||
|
|
||||||
|
self._host.credential_errors = 0
|
||||||
|
|
||||||
|
# Check for firmware version changes (external update detection)
|
||||||
|
firmware_changed = False
|
||||||
|
for ch in (*self._host.api.channels, None):
|
||||||
|
new_version = self._host.api.camera_sw_version(ch)
|
||||||
|
old_version = self._last_known_firmware.get(ch)
|
||||||
|
if (
|
||||||
|
old_version is not None
|
||||||
|
and new_version is not None
|
||||||
|
and new_version != old_version
|
||||||
|
):
|
||||||
|
firmware_changed = True
|
||||||
|
self._last_known_firmware[ch] = new_version
|
||||||
|
|
||||||
|
# Notify firmware coordinator if firmware changed externally
|
||||||
|
if firmware_changed and self.firmware_coordinator is not None:
|
||||||
|
self.firmware_coordinator.async_set_updated_data(None)
|
||||||
|
|
||||||
|
async with asyncio.timeout(self._min_timeout):
|
||||||
|
await self._host.renew()
|
||||||
|
|
||||||
|
if (
|
||||||
|
self._host.api.new_devices
|
||||||
|
and self.config_entry.state == ConfigEntryState.LOADED
|
||||||
|
):
|
||||||
|
# There are new cameras/chimes connected, reload to add them.
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Reloading Reolink %s to add new device (capabilities)",
|
||||||
|
self._host.api.nvr_name,
|
||||||
|
)
|
||||||
|
self.hass.async_create_task(
|
||||||
|
self.hass.config_entries.async_reload(self.config_entry.entry_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ReolinkFirmwareCoordinator(ReolinkCoordinator):
|
||||||
|
"""Coordinator for Reolink firmware update checks."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: ConfigEntry,
|
||||||
|
host: ReolinkHost,
|
||||||
|
*,
|
||||||
|
min_timeout: float,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the firmware coordinator."""
|
||||||
|
super().__init__(
|
||||||
|
hass,
|
||||||
|
config_entry,
|
||||||
|
host,
|
||||||
|
f"reolink.{host.api.nvr_name}.firmware",
|
||||||
|
min_timeout=min_timeout,
|
||||||
|
update_interval=None, # Do not fetch data automatically, resume 24h schedule
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _async_update_data(self) -> None:
|
||||||
|
"""Check for firmware updates."""
|
||||||
|
async with asyncio.timeout(self._min_timeout):
|
||||||
|
try:
|
||||||
|
await self._host.api.check_new_firmware(self._host.firmware_ch_list)
|
||||||
|
except ReolinkError as err:
|
||||||
|
if self._host.starting:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Error checking Reolink firmware update at startup "
|
||||||
|
"from %s, possibly internet access is blocked",
|
||||||
|
self._host.api.nvr_name,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
raise UpdateFailed(
|
||||||
|
f"Error checking Reolink firmware update from {self._host.api.nvr_name}, "
|
||||||
|
"if the camera is blocked from accessing the internet, "
|
||||||
|
"disable the update entity"
|
||||||
|
) from err
|
||||||
|
finally:
|
||||||
|
self._host.starting = False
|
||||||
@@ -10,13 +10,11 @@ from reolink_aio.api import DUAL_LENS_MODELS, Chime, Host
|
|||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
|
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
|
||||||
from homeassistant.helpers.entity import EntityDescription
|
from homeassistant.helpers.entity import EntityDescription
|
||||||
from homeassistant.helpers.update_coordinator import (
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
CoordinatorEntity,
|
|
||||||
DataUpdateCoordinator,
|
|
||||||
)
|
|
||||||
|
|
||||||
from . import ReolinkData
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
from .coordinator import ReolinkCoordinator
|
||||||
|
from .util import ReolinkData
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
@@ -49,7 +47,7 @@ class ReolinkChimeEntityDescription(ReolinkEntityDescription):
|
|||||||
supported: Callable[[Chime], bool] = lambda chime: True
|
supported: Callable[[Chime], bool] = lambda chime: True
|
||||||
|
|
||||||
|
|
||||||
class ReolinkHostCoordinatorEntity(CoordinatorEntity[DataUpdateCoordinator[None]]):
|
class ReolinkHostCoordinatorEntity(CoordinatorEntity[ReolinkCoordinator]):
|
||||||
"""Parent class for entities that control the Reolink NVR itself, without a channel.
|
"""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
|
A camera connected directly to HomeAssistant without using a NVR is in the reolink API
|
||||||
@@ -62,7 +60,7 @@ class ReolinkHostCoordinatorEntity(CoordinatorEntity[DataUpdateCoordinator[None]
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
reolink_data: ReolinkData,
|
reolink_data: ReolinkData,
|
||||||
coordinator: DataUpdateCoordinator[None] | None = None,
|
coordinator: ReolinkCoordinator | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize ReolinkHostCoordinatorEntity."""
|
"""Initialize ReolinkHostCoordinatorEntity."""
|
||||||
if coordinator is None:
|
if coordinator is None:
|
||||||
@@ -161,7 +159,7 @@ class ReolinkChannelCoordinatorEntity(ReolinkHostCoordinatorEntity):
|
|||||||
self,
|
self,
|
||||||
reolink_data: ReolinkData,
|
reolink_data: ReolinkData,
|
||||||
channel: int,
|
channel: int,
|
||||||
coordinator: DataUpdateCoordinator[None] | None = None,
|
coordinator: ReolinkCoordinator | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize ReolinkChannelCoordinatorEntity for a hardware camera connected to a channel of the NVR."""
|
"""Initialize ReolinkChannelCoordinatorEntity for a hardware camera connected to a channel of the NVR."""
|
||||||
super().__init__(reolink_data, coordinator)
|
super().__init__(reolink_data, coordinator)
|
||||||
@@ -250,7 +248,7 @@ class ReolinkHostChimeCoordinatorEntity(ReolinkHostCoordinatorEntity):
|
|||||||
self,
|
self,
|
||||||
reolink_data: ReolinkData,
|
reolink_data: ReolinkData,
|
||||||
chime: Chime,
|
chime: Chime,
|
||||||
coordinator: DataUpdateCoordinator[None] | None = None,
|
coordinator: ReolinkCoordinator | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize ReolinkHostChimeCoordinatorEntity for a chime."""
|
"""Initialize ReolinkHostChimeCoordinatorEntity for a chime."""
|
||||||
super().__init__(reolink_data, coordinator)
|
super().__init__(reolink_data, coordinator)
|
||||||
@@ -287,7 +285,7 @@ class ReolinkChimeCoordinatorEntity(ReolinkChannelCoordinatorEntity):
|
|||||||
self,
|
self,
|
||||||
reolink_data: ReolinkData,
|
reolink_data: ReolinkData,
|
||||||
chime: Chime,
|
chime: Chime,
|
||||||
coordinator: DataUpdateCoordinator[None] | None = None,
|
coordinator: ReolinkCoordinator | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize ReolinkChimeCoordinatorEntity for a chime."""
|
"""Initialize ReolinkChimeCoordinatorEntity for a chime."""
|
||||||
assert chime.channel is not None
|
assert chime.channel is not None
|
||||||
|
|||||||
@@ -18,13 +18,14 @@ from homeassistant.core import CALLBACK_TYPE, HomeAssistant
|
|||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
from homeassistant.helpers.event import async_call_later
|
from homeassistant.helpers.event import async_call_later
|
||||||
from homeassistant.helpers.update_coordinator import (
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
CoordinatorEntity,
|
|
||||||
DataUpdateCoordinator,
|
|
||||||
)
|
|
||||||
|
|
||||||
from . import DEVICE_UPDATE_INTERVAL_MIN, DEVICE_UPDATE_INTERVAL_PER_CAM
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
from .coordinator import (
|
||||||
|
DEVICE_UPDATE_INTERVAL_MIN,
|
||||||
|
DEVICE_UPDATE_INTERVAL_PER_CAM,
|
||||||
|
ReolinkCoordinator,
|
||||||
|
)
|
||||||
from .entity import (
|
from .entity import (
|
||||||
ReolinkChannelCoordinatorEntity,
|
ReolinkChannelCoordinatorEntity,
|
||||||
ReolinkChannelEntityDescription,
|
ReolinkChannelEntityDescription,
|
||||||
@@ -94,9 +95,7 @@ async def async_setup_entry(
|
|||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
class ReolinkUpdateBaseEntity(
|
class ReolinkUpdateBaseEntity(CoordinatorEntity[ReolinkCoordinator], UpdateEntity):
|
||||||
CoordinatorEntity[DataUpdateCoordinator[None]], UpdateEntity
|
|
||||||
):
|
|
||||||
"""Base update entity class for Reolink."""
|
"""Base update entity class for Reolink."""
|
||||||
|
|
||||||
_attr_release_url = "https://reolink.com/download-center/"
|
_attr_release_url = "https://reolink.com/download-center/"
|
||||||
@@ -105,7 +104,7 @@ class ReolinkUpdateBaseEntity(
|
|||||||
self,
|
self,
|
||||||
reolink_data: ReolinkData,
|
reolink_data: ReolinkData,
|
||||||
channel: int | None,
|
channel: int | None,
|
||||||
coordinator: DataUpdateCoordinator[None],
|
coordinator: ReolinkCoordinator,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize Reolink update entity."""
|
"""Initialize Reolink update entity."""
|
||||||
CoordinatorEntity.__init__(self, coordinator)
|
CoordinatorEntity.__init__(self, coordinator)
|
||||||
|
|||||||
@@ -28,11 +28,11 @@ from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
|||||||
from homeassistant.helpers import device_registry as dr
|
from homeassistant.helpers import device_registry as dr
|
||||||
from homeassistant.helpers.storage import Store
|
from homeassistant.helpers.storage import Store
|
||||||
from homeassistant.helpers.translation import async_get_exception_message
|
from homeassistant.helpers.translation import async_get_exception_message
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from .coordinator import ReolinkDeviceCoordinator, ReolinkFirmwareCoordinator
|
||||||
from .host import ReolinkHost
|
from .host import ReolinkHost
|
||||||
|
|
||||||
STORAGE_VERSION = 1
|
STORAGE_VERSION = 1
|
||||||
@@ -45,8 +45,8 @@ class ReolinkData:
|
|||||||
"""Data for the Reolink integration."""
|
"""Data for the Reolink integration."""
|
||||||
|
|
||||||
host: ReolinkHost
|
host: ReolinkHost
|
||||||
device_coordinator: DataUpdateCoordinator[None]
|
device_coordinator: ReolinkDeviceCoordinator
|
||||||
firmware_coordinator: DataUpdateCoordinator[None]
|
firmware_coordinator: ReolinkFirmwareCoordinator
|
||||||
|
|
||||||
|
|
||||||
def is_connected(hass: HomeAssistant, config_entry: config_entries.ConfigEntry) -> bool:
|
def is_connected(hass: HomeAssistant, config_entry: config_entries.ConfigEntry) -> bool:
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from unittest.mock import MagicMock, patch
|
|||||||
|
|
||||||
from freezegun.api import FrozenDateTimeFactory
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
|
|
||||||
from homeassistant.components.reolink import DEVICE_UPDATE_INTERVAL_MIN
|
from homeassistant.components.reolink.coordinator import DEVICE_UPDATE_INTERVAL_MIN
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.const import STATE_OFF, STATE_ON, Platform
|
from homeassistant.const import STATE_OFF, STATE_ON, Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ from reolink_aio.exceptions import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.components.reolink import DEVICE_UPDATE_INTERVAL_MIN
|
|
||||||
from homeassistant.components.reolink.config_flow import DEFAULT_PROTOCOL
|
from homeassistant.components.reolink.config_flow import DEFAULT_PROTOCOL
|
||||||
from homeassistant.components.reolink.const import (
|
from homeassistant.components.reolink.const import (
|
||||||
CONF_BC_ONLY,
|
CONF_BC_ONLY,
|
||||||
@@ -25,6 +24,7 @@ from homeassistant.components.reolink.const import (
|
|||||||
CONF_USE_HTTPS,
|
CONF_USE_HTTPS,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
)
|
)
|
||||||
|
from homeassistant.components.reolink.coordinator import DEVICE_UPDATE_INTERVAL_MIN
|
||||||
from homeassistant.components.reolink.exceptions import ReolinkWebhookException
|
from homeassistant.components.reolink.exceptions import ReolinkWebhookException
|
||||||
from homeassistant.components.reolink.host import DEFAULT_TIMEOUT
|
from homeassistant.components.reolink.host import DEFAULT_TIMEOUT
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import pytest
|
|||||||
from reolink_aio.enums import SubType
|
from reolink_aio.enums import SubType
|
||||||
from reolink_aio.exceptions import NotSupportedError, ReolinkError, SubscriptionError
|
from reolink_aio.exceptions import NotSupportedError, ReolinkError, SubscriptionError
|
||||||
|
|
||||||
from homeassistant.components.reolink import DEVICE_UPDATE_INTERVAL_MIN
|
from homeassistant.components.reolink.coordinator import DEVICE_UPDATE_INTERVAL_MIN
|
||||||
from homeassistant.components.reolink.host import (
|
from homeassistant.components.reolink.host import (
|
||||||
FIRST_ONVIF_LONG_POLL_TIMEOUT,
|
FIRST_ONVIF_LONG_POLL_TIMEOUT,
|
||||||
FIRST_ONVIF_TIMEOUT,
|
FIRST_ONVIF_TIMEOUT,
|
||||||
|
|||||||
@@ -14,11 +14,7 @@ from reolink_aio.exceptions import (
|
|||||||
ReolinkError,
|
ReolinkError,
|
||||||
)
|
)
|
||||||
|
|
||||||
from homeassistant.components.reolink import (
|
from homeassistant.components.reolink import FIRMWARE_UPDATE_INTERVAL
|
||||||
DEVICE_UPDATE_INTERVAL_MIN,
|
|
||||||
FIRMWARE_UPDATE_INTERVAL,
|
|
||||||
NUM_CRED_ERRORS,
|
|
||||||
)
|
|
||||||
from homeassistant.components.reolink.const import (
|
from homeassistant.components.reolink.const import (
|
||||||
BATTERY_ALL_WAKE_UPDATE_INTERVAL,
|
BATTERY_ALL_WAKE_UPDATE_INTERVAL,
|
||||||
BATTERY_PASSIVE_WAKE_UPDATE_INTERVAL,
|
BATTERY_PASSIVE_WAKE_UPDATE_INTERVAL,
|
||||||
@@ -26,6 +22,10 @@ from homeassistant.components.reolink.const import (
|
|||||||
CONF_FIRMWARE_CHECK_TIME,
|
CONF_FIRMWARE_CHECK_TIME,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
)
|
)
|
||||||
|
from homeassistant.components.reolink.coordinator import (
|
||||||
|
DEVICE_UPDATE_INTERVAL_MIN,
|
||||||
|
NUM_CRED_ERRORS,
|
||||||
|
)
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_HOST,
|
CONF_HOST,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import pytest
|
|||||||
from reolink_aio.api import Chime
|
from reolink_aio.api import Chime
|
||||||
from reolink_aio.exceptions import InvalidParameterError, ReolinkError
|
from reolink_aio.exceptions import InvalidParameterError, ReolinkError
|
||||||
|
|
||||||
from homeassistant.components.reolink import DEVICE_UPDATE_INTERVAL_MIN
|
from homeassistant.components.reolink.coordinator import DEVICE_UPDATE_INTERVAL_MIN
|
||||||
from homeassistant.components.select import DOMAIN as SELECT_DOMAIN
|
from homeassistant.components.select import DOMAIN as SELECT_DOMAIN
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import pytest
|
|||||||
from reolink_aio.api import Chime
|
from reolink_aio.api import Chime
|
||||||
from reolink_aio.exceptions import ReolinkError
|
from reolink_aio.exceptions import ReolinkError
|
||||||
|
|
||||||
from homeassistant.components.reolink import DEVICE_UPDATE_INTERVAL_MIN
|
from homeassistant.components.reolink.coordinator import DEVICE_UPDATE_INTERVAL_MIN
|
||||||
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
|||||||
Reference in New Issue
Block a user