Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 30 additions & 43 deletions custom_components/eufy_security/__init__.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,42 @@
"""Module to initialize integration"""
import asyncio
from datetime import timedelta
from __future__ import annotations

import logging
from typing import TypeAlias

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.components.persistent_notification import create
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import DeviceEntry
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.typing import ConfigType
from .const import COORDINATOR, DOMAIN, PLATFORMS
from .const import DOMAIN, PLATFORMS
from .coordinator import EufySecurityDataUpdateCoordinator

_LOGGER: logging.Logger = logging.getLogger(__package__)

EufySecurityConfigEntry: TypeAlias = ConfigEntry[EufySecurityDataUpdateCoordinator]


async def async_setup(hass: HomeAssistant, config: ConfigType):
"""initialize the integration"""
if DOMAIN not in hass.data:
hass.data[DOMAIN] = {}

async def handle_send_message(call):
coordinator: EufySecurityDataUpdateCoordinator = hass.data[DOMAIN][COORDINATOR]
_LOGGER.debug(f"{DOMAIN} - send_message - call.data: {call.data}")
message = call.data.get("message")
_LOGGER.debug(f"{DOMAIN} - end_message - message: {message}")
await coordinator.send_message(message)
for entry in hass.config_entries.async_entries(DOMAIN):

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you should extract coordinator here:

call.hass.config_entries.async_loaded_entries(DOMAIN).runtime_data

and we should fail here in case we cannot find the enrty with runtime_data, rather than being silent

if hasattr(entry, "runtime_data") and entry.runtime_data:
await entry.runtime_data.send_message(call.data.get("message"))
return

async def handle_force_sync(call):
coordinator: EufySecurityDataUpdateCoordinator = hass.data[DOMAIN][COORDINATOR]
await coordinator.async_refresh()
for entry in hass.config_entries.async_entries(DOMAIN):
if hasattr(entry, "runtime_data") and entry.runtime_data:
await entry.runtime_data.async_refresh()
return

async def handle_log_level(call):
coordinator: EufySecurityDataUpdateCoordinator = hass.data[DOMAIN][COORDINATOR]
await coordinator.set_log_level(call.data.get("log_level"))
for entry in hass.config_entries.async_entries(DOMAIN):
if hasattr(entry, "runtime_data") and entry.runtime_data:
await entry.runtime_data.set_log_level(call.data.get("log_level"))
return

hass.services.async_register(DOMAIN, "force_sync", handle_force_sync)
hass.services.async_register(DOMAIN, "send_message", handle_send_message)
Expand All @@ -43,68 +45,53 @@ async def handle_log_level(call):
return True


async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
async def async_setup_entry(hass: HomeAssistant, config_entry: EufySecurityConfigEntry):
"""setup config entry"""
if hass.data.get(DOMAIN) is None:
hass.data.setdefault(DOMAIN, {})

coordinator = hass.data[DOMAIN][COORDINATOR] = hass.data[DOMAIN].get(
COORDINATOR, EufySecurityDataUpdateCoordinator(hass, config_entry)
)
coordinator = EufySecurityDataUpdateCoordinator(hass, config_entry)
config_entry.runtime_data = coordinator

await coordinator.initialize()
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
for platform in PLATFORMS:
coordinator.platforms.append(platform.value)

async def update(event_time_utc):
local_coordinator = hass.data[DOMAIN][COORDINATOR]
await local_coordinator.async_refresh()

config_entry.add_update_listener(async_reload_entry)
# async_track_time_interval(hass, update, timedelta(seconds=coordinator.config.sync_interval))

return True


async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, config_entry: EufySecurityConfigEntry) -> bool:
"""unload active entities"""
_LOGGER.debug(f"async_unload_entry 1")
coordinator = hass.data[DOMAIN][COORDINATOR]
coordinator = config_entry.runtime_data
unloaded = await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS)

if unloaded:
await coordinator.disconnect()
hass.data[DOMAIN] = {}

_LOGGER.debug(f"async_unload_entry 2")
return unloaded


async def async_reload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
async def async_reload_entry(hass: HomeAssistant, config_entry: EufySecurityConfigEntry) -> None:
"""reload integration"""
_LOGGER.debug(f"async_reload_entry 1")
await async_unload_entry(hass, config_entry)
_LOGGER.debug(f"async_reload_entry 2")
await async_setup_entry(hass, config_entry)
_LOGGER.debug(f"async_reload_entry 3")


async def async_remove_config_entry_device(
hass: HomeAssistant, config_entry: ConfigEntry, device_entry: DeviceEntry
hass: HomeAssistant, config_entry: EufySecurityConfigEntry, device_entry: DeviceEntry
) -> bool:
"""Remove a config entry from a device."""
serial_no = next(iter(device_entry.identifiers))[1]
_LOGGER.debug(f"async_remove_config_entry_device device_entry {serial_no}")
coordinator = hass.data[DOMAIN][COORDINATOR]
_LOGGER.debug("async_remove_config_entry_device device_entry %s", serial_no)
coordinator = config_entry.runtime_data
if serial_no in coordinator.devices or serial_no in coordinator.stations:
_LOGGER.debug(f"async_remove_config_entry_device error exists {serial_no}")
_LOGGER.debug("async_remove_config_entry_device error exists %s", serial_no)
create(
hass,
f"Device is still accessible on account, cannot be deleted!",
"Device is still accessible on account, cannot be deleted!",
title="Eufy Security - Error",
notification_id="eufy_security_delete_device_error",
)
return False
_LOGGER.debug(f"async_remove_config_entry_device deleted {serial_no}")
_LOGGER.debug("async_remove_config_entry_device deleted %s", serial_no)
return True
4 changes: 2 additions & 2 deletions custom_components/eufy_security/alarm_control_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from homeassistant.helpers import entity_platform
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .const import COORDINATOR, DOMAIN, Schema
from .const import DOMAIN, Schema
from .coordinator import EufySecurityDataUpdateCoordinator
from .entity import EufySecurityEntity
from .eufy_security_api.const import MessageField
Expand Down Expand Up @@ -60,7 +60,7 @@ class CurrentModeToStateValue(Enum):

async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback) -> None:
"""Setup alarm control panel entities."""
coordinator: EufySecurityDataUpdateCoordinator = hass.data[DOMAIN][COORDINATOR]
coordinator: EufySecurityDataUpdateCoordinator = config_entry.runtime_data
product_properties = []
for product in coordinator.stations.values():
if product.has(MessageField.GUARD_MODE.value) is True:
Expand Down
4 changes: 2 additions & 2 deletions custom_components/eufy_security/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import COORDINATOR, DOMAIN, Platform, PlatformToPropertyType
from .const import DOMAIN, Platform, PlatformToPropertyType
from .coordinator import EufySecurityDataUpdateCoordinator
from .entity import EufySecurityEntity
from .eufy_security_api.metadata import Metadata
Expand All @@ -19,7 +19,7 @@

async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback) -> None:
"""Setup binary sensor entities."""
coordinator: EufySecurityDataUpdateCoordinator = hass.data[DOMAIN][COORDINATOR]
coordinator: EufySecurityDataUpdateCoordinator = config_entry.runtime_data
product_properties = get_product_properties_by_filter(
[coordinator.devices.values(), coordinator.stations.values()],
PlatformToPropertyType[Platform.BINARY_SENSOR.name].value,
Expand Down
4 changes: 2 additions & 2 deletions custom_components/eufy_security/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .const import COORDINATOR, DOMAIN
from .const import DOMAIN
from .coordinator import EufySecurityDataUpdateCoordinator
from .entity import EufySecurityEntity
from .eufy_security_api.metadata import Metadata
Expand All @@ -16,7 +16,7 @@

async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback) -> None:
"""Setup binary sensor entities."""
coordinator: EufySecurityDataUpdateCoordinator = hass.data[DOMAIN][COORDINATOR]
coordinator: EufySecurityDataUpdateCoordinator = config_entry.runtime_data
product_properties = []
for product in list(coordinator.devices.values()) + list(coordinator.stations.values()):
for command in ProductCommand:
Expand Down
4 changes: 2 additions & 2 deletions custom_components/eufy_security/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .const import COORDINATOR, DOMAIN, Schema
from .const import DOMAIN, Schema
from .coordinator import EufySecurityDataUpdateCoordinator
from .entity import EufySecurityEntity
from .eufy_security_api.camera import (
Expand All @@ -34,7 +34,7 @@

async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback) -> None:
"""Setup camera entities."""
coordinator: EufySecurityDataUpdateCoordinator = hass.data[DOMAIN][COORDINATOR]
coordinator: EufySecurityDataUpdateCoordinator = config_entry.runtime_data
product_properties = []
for product in coordinator.devices.values():
if product.is_camera is True:
Expand Down
18 changes: 10 additions & 8 deletions custom_components/eufy_security/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import async_call_later

from .const import COORDINATOR, DOMAIN
from .const import DOMAIN
from .eufy_security_api.api_client import ApiClient
from .eufy_security_api.exceptions import WebSocketConnectionException
from .model import Config, ConfigField
Expand Down Expand Up @@ -65,7 +65,11 @@ async def async_step_user(self, user_input=None):
self._errors = {}

if self.source == SOURCE_REAUTH:
coordinator = self.hass.data[DOMAIN][COORDINATOR]
config_entry_id = None
for entry in self._async_current_entries():
config_entry_id = entry.entry_id

coordinator = self.hass.config_entries.async_get_entry(config_entry_id).runtime_data
if coordinator.config.mfa_required is True:
mfa_input = user_input[ConfigField.mfa_input.name]
await coordinator.set_mfa_and_connect(mfa_input)
Expand All @@ -76,14 +80,9 @@ async def async_step_user(self, user_input=None):
coordinator.config.captcha_img = None
await coordinator.set_captcha_and_connect(captcha_id, captcha_input)

config_entry_id = None
for entry in self._async_current_entries():
config_entry_id = entry.entry_id

async def try_reloading(_now):
_LOGGER.debug(f"{DOMAIN} try_reloading start after captcha/mfa")
await coordinator.disconnect()
self.hass.data[DOMAIN] = {}
await self.hass.config_entries.async_reload(config_entry_id)
_LOGGER.debug(f"{DOMAIN} try_reloading finish after captcha/mfa")

Expand Down Expand Up @@ -133,7 +132,10 @@ async def async_step_reauth(self, user_input=None):

async def async_step_reauth_confirm(self, user_input=None):
"""Re-authenticate via captcha or mfa code"""
coordinator = self.hass.data[DOMAIN][COORDINATOR]
entry_id = None
for entry in self._async_current_entries():
entry_id = entry.entry_id
coordinator = self.hass.config_entries.async_get_entry(entry_id).runtime_data
_LOGGER.debug(f"{DOMAIN} async_step_reauth_confirm - {coordinator.config}")
if user_input is None:
if coordinator.config.mfa_required is True:
Expand Down
1 change: 0 additions & 1 deletion custom_components/eufy_security/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
NAME = "Eufy Security"
DOMAIN = "eufy_security"
VERSION = "1.0.0"
COORDINATOR = "coordinator"
DISCONNECTED = "eufy-security-ws-disconnected"

PLATFORMS: list[str] = [
Expand Down
10 changes: 8 additions & 2 deletions custom_components/eufy_security/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from datetime import timedelta
import logging
import json
import asyncio
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.components.persistent_notification import create
Expand All @@ -29,7 +28,14 @@ class EufySecurityDataUpdateCoordinator(DataUpdateCoordinator):

def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
self.config: Config = Config.parse(config_entry)
super().__init__(hass, _LOGGER, name=DOMAIN, update_method=self._update_local, update_interval=timedelta(seconds=self.config.sync_interval))
super().__init__(
hass,
_LOGGER,
config_entry=config_entry,
name=DOMAIN,
update_method=self._update_local,
update_interval=timedelta(seconds=self.config.sync_interval),
)
self._platforms = []
self.data = {}
self._api = ApiClient(self.config, aiohttp_client.async_get_clientsession(self.hass), self._on_error)
Expand Down
3 changes: 1 addition & 2 deletions custom_components/eufy_security/device_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .const import (
COORDINATOR,
DOMAIN,
Platform,
PlatformToPropertyType,
Expand All @@ -25,7 +24,7 @@
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback) -> None:
"""Setup switch entities."""

coordinator: EufySecurityDataUpdateCoordinator = hass.data[DOMAIN][COORDINATOR]
coordinator: EufySecurityDataUpdateCoordinator = config_entry.runtime_data
product_properties = get_product_properties_by_filter(
[coordinator.devices.values(), coordinator.stations.values()], PlatformToPropertyType[Platform.SWITCH.name].value
)
Expand Down
4 changes: 2 additions & 2 deletions custom_components/eufy_security/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .const import COORDINATOR, DOMAIN, Schema
from .const import DOMAIN, Schema
from .coordinator import EufySecurityDataUpdateCoordinator
from .entity import EufySecurityEntity
from .eufy_security_api.metadata import Metadata
Expand All @@ -24,7 +24,7 @@

async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback) -> None:
"""Setup camera entities."""
coordinator: EufySecurityDataUpdateCoordinator = hass.data[DOMAIN][COORDINATOR]
coordinator: EufySecurityDataUpdateCoordinator = config_entry.runtime_data
product_properties = []
for product in coordinator.devices.values():
if product.is_camera is True:
Expand Down
4 changes: 2 additions & 2 deletions custom_components/eufy_security/lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.exceptions import HomeAssistantError

from .const import COORDINATOR, DOMAIN
from .const import DOMAIN
from .coordinator import EufySecurityDataUpdateCoordinator
from .entity import EufySecurityEntity
from .eufy_security_api.const import MessageField
Expand All @@ -21,7 +21,7 @@

async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback) -> None:
"""Setup lock entities."""
coordinator: EufySecurityDataUpdateCoordinator = hass.data[DOMAIN][COORDINATOR]
coordinator: EufySecurityDataUpdateCoordinator = config_entry.runtime_data
properties = []
for product in coordinator.devices.values():
if product.has(MessageField.LOCKED.value) is True:
Expand Down
8 changes: 4 additions & 4 deletions custom_components/eufy_security/manifest.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"domain": "eufy_security",
"name": "Eufy Security",
"codeowners": ["@fuatakgun"],
"codeowners": ["@fuatakgun", "@yuya4i"],
"config_flow": true,
"dependencies": ["ffmpeg", "stream"],
"documentation": "https://github.com/fuatakgun/eufy_security",
"documentation": "https://github.com/yuya4i/eufy_security",
"integration_type": "hub",
"iot_class": "cloud_push",
"issue_tracker": "https://github.com/fuatakgun/eufy_security/issues",
"issue_tracker": "https://github.com/yuya4i/eufy_security/issues",
"requirements": ["websocket-client==1.8.0", "aiortsp==1.4.0"],
"version": "8.2.2"
"version": "8.3.0"
}
3 changes: 1 addition & 2 deletions custom_components/eufy_security/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .const import (
COORDINATOR,
DOMAIN,
Platform,
PlatformToPropertyType,
Expand All @@ -23,7 +22,7 @@
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback) -> None:
"""Setup switch entities."""

coordinator: EufySecurityDataUpdateCoordinator = hass.data[DOMAIN][COORDINATOR]
coordinator: EufySecurityDataUpdateCoordinator = config_entry.runtime_data
product_properties = get_product_properties_by_filter(
[coordinator.devices.values(), coordinator.stations.values()], PlatformToPropertyType[Platform.NUMBER.name].value
)
Expand Down
3 changes: 1 addition & 2 deletions custom_components/eufy_security/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .const import (
COORDINATOR,
DOMAIN,
Platform,
PlatformToPropertyType,
Expand All @@ -23,7 +22,7 @@
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback) -> None:
"""Setup select entities."""

coordinator: EufySecurityDataUpdateCoordinator = hass.data[DOMAIN][COORDINATOR]
coordinator: EufySecurityDataUpdateCoordinator = config_entry.runtime_data
product_properties = get_product_properties_by_filter(
[coordinator.devices.values(), coordinator.stations.values()], PlatformToPropertyType[Platform.SELECT.name].value
)
Expand Down
Loading
Loading