mirror of
https://github.com/home-assistant/core.git
synced 2026-04-02 08:26:41 +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
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Callable
|
||||
from datetime import UTC, datetime, timedelta
|
||||
import logging
|
||||
@@ -11,13 +10,8 @@ from time import time
|
||||
from typing import Any
|
||||
|
||||
from reolink_aio.api import RETRY_ATTEMPTS
|
||||
from reolink_aio.exceptions import (
|
||||
CredentialsInvalidError,
|
||||
LoginPrivacyModeError,
|
||||
ReolinkError,
|
||||
)
|
||||
from reolink_aio.exceptions import CredentialsInvalidError, ReolinkError
|
||||
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import CONF_PORT, EVENT_HOMEASSISTANT_STOP, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
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.event import async_call_later
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import (
|
||||
BATTERY_PASSIVE_WAKE_UPDATE_INTERVAL,
|
||||
@@ -40,6 +33,7 @@ from .const import (
|
||||
CONF_USE_HTTPS,
|
||||
DOMAIN,
|
||||
)
|
||||
from .coordinator import ReolinkDeviceCoordinator, ReolinkFirmwareCoordinator
|
||||
from .exceptions import PasswordIncompatible, ReolinkException, UserNotAdmin
|
||||
from .host import ReolinkHost
|
||||
from .services import async_setup_services
|
||||
@@ -60,10 +54,7 @@ PLATFORMS = [
|
||||
Platform.SWITCH,
|
||||
Platform.UPDATE,
|
||||
]
|
||||
DEVICE_UPDATE_INTERVAL_MIN = timedelta(seconds=60)
|
||||
DEVICE_UPDATE_INTERVAL_PER_CAM = timedelta(seconds=10)
|
||||
FIRMWARE_UPDATE_INTERVAL = timedelta(hours=24)
|
||||
NUM_CRED_ERRORS = 3
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
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(
|
||||
device_coordinator = ReolinkDeviceCoordinator(
|
||||
hass,
|
||||
_LOGGER,
|
||||
config_entry=config_entry,
|
||||
name=f"reolink.{host.api.nvr_name}",
|
||||
update_method=async_device_config_update,
|
||||
update_interval=max(
|
||||
DEVICE_UPDATE_INTERVAL_MIN,
|
||||
DEVICE_UPDATE_INTERVAL_PER_CAM * host.api.num_cameras,
|
||||
),
|
||||
config_entry,
|
||||
host,
|
||||
min_timeout=min_timeout,
|
||||
)
|
||||
firmware_coordinator = DataUpdateCoordinator(
|
||||
|
||||
firmware_coordinator = ReolinkFirmwareCoordinator(
|
||||
hass,
|
||||
_LOGGER,
|
||||
config_entry=config_entry,
|
||||
name=f"reolink.{host.api.nvr_name}.firmware",
|
||||
update_method=async_check_firmware_update,
|
||||
update_interval=None, # Do not fetch data automatically, resume 24h schedule
|
||||
config_entry,
|
||||
host,
|
||||
min_timeout=min_timeout,
|
||||
)
|
||||
device_coordinator.firmware_coordinator = firmware_coordinator
|
||||
|
||||
async def first_firmware_check(*args: Any) -> None:
|
||||
"""Start first firmware check delayed to continue 24h schedule."""
|
||||
@@ -305,7 +214,7 @@ async def async_setup_entry(
|
||||
|
||||
async def register_callbacks(
|
||||
host: ReolinkHost,
|
||||
device_coordinator: DataUpdateCoordinator[None],
|
||||
device_coordinator: ReolinkDeviceCoordinator,
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""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.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
|
||||
from homeassistant.helpers.entity import EntityDescription
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
DataUpdateCoordinator,
|
||||
)
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import ReolinkData
|
||||
from .const import DOMAIN
|
||||
from .coordinator import ReolinkCoordinator
|
||||
from .util import ReolinkData
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
@@ -49,7 +47,7 @@ class ReolinkChimeEntityDescription(ReolinkEntityDescription):
|
||||
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.
|
||||
|
||||
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__(
|
||||
self,
|
||||
reolink_data: ReolinkData,
|
||||
coordinator: DataUpdateCoordinator[None] | None = None,
|
||||
coordinator: ReolinkCoordinator | None = None,
|
||||
) -> None:
|
||||
"""Initialize ReolinkHostCoordinatorEntity."""
|
||||
if coordinator is None:
|
||||
@@ -161,7 +159,7 @@ class ReolinkChannelCoordinatorEntity(ReolinkHostCoordinatorEntity):
|
||||
self,
|
||||
reolink_data: ReolinkData,
|
||||
channel: int,
|
||||
coordinator: DataUpdateCoordinator[None] | None = None,
|
||||
coordinator: ReolinkCoordinator | None = None,
|
||||
) -> None:
|
||||
"""Initialize ReolinkChannelCoordinatorEntity for a hardware camera connected to a channel of the NVR."""
|
||||
super().__init__(reolink_data, coordinator)
|
||||
@@ -250,7 +248,7 @@ class ReolinkHostChimeCoordinatorEntity(ReolinkHostCoordinatorEntity):
|
||||
self,
|
||||
reolink_data: ReolinkData,
|
||||
chime: Chime,
|
||||
coordinator: DataUpdateCoordinator[None] | None = None,
|
||||
coordinator: ReolinkCoordinator | None = None,
|
||||
) -> None:
|
||||
"""Initialize ReolinkHostChimeCoordinatorEntity for a chime."""
|
||||
super().__init__(reolink_data, coordinator)
|
||||
@@ -287,7 +285,7 @@ class ReolinkChimeCoordinatorEntity(ReolinkChannelCoordinatorEntity):
|
||||
self,
|
||||
reolink_data: ReolinkData,
|
||||
chime: Chime,
|
||||
coordinator: DataUpdateCoordinator[None] | None = None,
|
||||
coordinator: ReolinkCoordinator | None = None,
|
||||
) -> None:
|
||||
"""Initialize ReolinkChimeCoordinatorEntity for a chime."""
|
||||
assert chime.channel is not None
|
||||
|
||||
@@ -18,13 +18,14 @@ from homeassistant.core import CALLBACK_TYPE, HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.event import async_call_later
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
DataUpdateCoordinator,
|
||||
)
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import DEVICE_UPDATE_INTERVAL_MIN, DEVICE_UPDATE_INTERVAL_PER_CAM
|
||||
from .const import DOMAIN
|
||||
from .coordinator import (
|
||||
DEVICE_UPDATE_INTERVAL_MIN,
|
||||
DEVICE_UPDATE_INTERVAL_PER_CAM,
|
||||
ReolinkCoordinator,
|
||||
)
|
||||
from .entity import (
|
||||
ReolinkChannelCoordinatorEntity,
|
||||
ReolinkChannelEntityDescription,
|
||||
@@ -94,9 +95,7 @@ async def async_setup_entry(
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class ReolinkUpdateBaseEntity(
|
||||
CoordinatorEntity[DataUpdateCoordinator[None]], UpdateEntity
|
||||
):
|
||||
class ReolinkUpdateBaseEntity(CoordinatorEntity[ReolinkCoordinator], UpdateEntity):
|
||||
"""Base update entity class for Reolink."""
|
||||
|
||||
_attr_release_url = "https://reolink.com/download-center/"
|
||||
@@ -105,7 +104,7 @@ class ReolinkUpdateBaseEntity(
|
||||
self,
|
||||
reolink_data: ReolinkData,
|
||||
channel: int | None,
|
||||
coordinator: DataUpdateCoordinator[None],
|
||||
coordinator: ReolinkCoordinator,
|
||||
) -> None:
|
||||
"""Initialize Reolink update entity."""
|
||||
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.storage import Store
|
||||
from homeassistant.helpers.translation import async_get_exception_message
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .coordinator import ReolinkDeviceCoordinator, ReolinkFirmwareCoordinator
|
||||
from .host import ReolinkHost
|
||||
|
||||
STORAGE_VERSION = 1
|
||||
@@ -45,8 +45,8 @@ class ReolinkData:
|
||||
"""Data for the Reolink integration."""
|
||||
|
||||
host: ReolinkHost
|
||||
device_coordinator: DataUpdateCoordinator[None]
|
||||
firmware_coordinator: DataUpdateCoordinator[None]
|
||||
device_coordinator: ReolinkDeviceCoordinator
|
||||
firmware_coordinator: ReolinkFirmwareCoordinator
|
||||
|
||||
|
||||
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 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.const import STATE_OFF, STATE_ON, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
@@ -16,7 +16,6 @@ from reolink_aio.exceptions import (
|
||||
)
|
||||
|
||||
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.const import (
|
||||
CONF_BC_ONLY,
|
||||
@@ -25,6 +24,7 @@ from homeassistant.components.reolink.const import (
|
||||
CONF_USE_HTTPS,
|
||||
DOMAIN,
|
||||
)
|
||||
from homeassistant.components.reolink.coordinator import DEVICE_UPDATE_INTERVAL_MIN
|
||||
from homeassistant.components.reolink.exceptions import ReolinkWebhookException
|
||||
from homeassistant.components.reolink.host import DEFAULT_TIMEOUT
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
|
||||
@@ -10,7 +10,7 @@ import pytest
|
||||
from reolink_aio.enums import SubType
|
||||
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 (
|
||||
FIRST_ONVIF_LONG_POLL_TIMEOUT,
|
||||
FIRST_ONVIF_TIMEOUT,
|
||||
|
||||
@@ -14,11 +14,7 @@ from reolink_aio.exceptions import (
|
||||
ReolinkError,
|
||||
)
|
||||
|
||||
from homeassistant.components.reolink import (
|
||||
DEVICE_UPDATE_INTERVAL_MIN,
|
||||
FIRMWARE_UPDATE_INTERVAL,
|
||||
NUM_CRED_ERRORS,
|
||||
)
|
||||
from homeassistant.components.reolink import FIRMWARE_UPDATE_INTERVAL
|
||||
from homeassistant.components.reolink.const import (
|
||||
BATTERY_ALL_WAKE_UPDATE_INTERVAL,
|
||||
BATTERY_PASSIVE_WAKE_UPDATE_INTERVAL,
|
||||
@@ -26,6 +22,10 @@ from homeassistant.components.reolink.const import (
|
||||
CONF_FIRMWARE_CHECK_TIME,
|
||||
DOMAIN,
|
||||
)
|
||||
from homeassistant.components.reolink.coordinator import (
|
||||
DEVICE_UPDATE_INTERVAL_MIN,
|
||||
NUM_CRED_ERRORS,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
|
||||
@@ -7,7 +7,7 @@ import pytest
|
||||
from reolink_aio.api import Chime
|
||||
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.config_entries import ConfigEntryState
|
||||
from homeassistant.const import (
|
||||
|
||||
@@ -7,7 +7,7 @@ import pytest
|
||||
from reolink_aio.api import Chime
|
||||
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.config_entries import ConfigEntryState
|
||||
from homeassistant.const import (
|
||||
|
||||
Reference in New Issue
Block a user