diff --git a/custom_components/eufy_security/__init__.py b/custom_components/eufy_security/__init__.py index c89d81f..13e2ed5 100644 --- a/custom_components/eufy_security/__init__.py +++ b/custom_components/eufy_security/__init__.py @@ -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): + 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) @@ -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 diff --git a/custom_components/eufy_security/alarm_control_panel.py b/custom_components/eufy_security/alarm_control_panel.py index 1610a3c..68199c4 100644 --- a/custom_components/eufy_security/alarm_control_panel.py +++ b/custom_components/eufy_security/alarm_control_panel.py @@ -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 @@ -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: diff --git a/custom_components/eufy_security/binary_sensor.py b/custom_components/eufy_security/binary_sensor.py index 242585b..2996313 100644 --- a/custom_components/eufy_security/binary_sensor.py +++ b/custom_components/eufy_security/binary_sensor.py @@ -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 @@ -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, diff --git a/custom_components/eufy_security/button.py b/custom_components/eufy_security/button.py index d438b72..1270c1b 100644 --- a/custom_components/eufy_security/button.py +++ b/custom_components/eufy_security/button.py @@ -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 @@ -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: diff --git a/custom_components/eufy_security/camera.py b/custom_components/eufy_security/camera.py index ecc4341..8879816 100644 --- a/custom_components/eufy_security/camera.py +++ b/custom_components/eufy_security/camera.py @@ -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 ( @@ -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: diff --git a/custom_components/eufy_security/config_flow.py b/custom_components/eufy_security/config_flow.py index 439c5aa..e65bd66 100644 --- a/custom_components/eufy_security/config_flow.py +++ b/custom_components/eufy_security/config_flow.py @@ -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 @@ -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) @@ -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") @@ -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: diff --git a/custom_components/eufy_security/const.py b/custom_components/eufy_security/const.py index 4e7a6cb..51eb832 100644 --- a/custom_components/eufy_security/const.py +++ b/custom_components/eufy_security/const.py @@ -21,7 +21,6 @@ NAME = "Eufy Security" DOMAIN = "eufy_security" VERSION = "1.0.0" -COORDINATOR = "coordinator" DISCONNECTED = "eufy-security-ws-disconnected" PLATFORMS: list[str] = [ diff --git a/custom_components/eufy_security/coordinator.py b/custom_components/eufy_security/coordinator.py index cad292c..6e046ce 100644 --- a/custom_components/eufy_security/coordinator.py +++ b/custom_components/eufy_security/coordinator.py @@ -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 @@ -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) diff --git a/custom_components/eufy_security/device_tracker.py b/custom_components/eufy_security/device_tracker.py index ca93ca6..c52d202 100644 --- a/custom_components/eufy_security/device_tracker.py +++ b/custom_components/eufy_security/device_tracker.py @@ -8,7 +8,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ( - COORDINATOR, DOMAIN, Platform, PlatformToPropertyType, @@ -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 ) diff --git a/custom_components/eufy_security/image.py b/custom_components/eufy_security/image.py index fb94106..71f459c 100644 --- a/custom_components/eufy_security/image.py +++ b/custom_components/eufy_security/image.py @@ -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 @@ -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: diff --git a/custom_components/eufy_security/lock.py b/custom_components/eufy_security/lock.py index 0e7d27f..9f2035f 100644 --- a/custom_components/eufy_security/lock.py +++ b/custom_components/eufy_security/lock.py @@ -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 @@ -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: diff --git a/custom_components/eufy_security/manifest.json b/custom_components/eufy_security/manifest.json index fbbb88f..b27ca41 100644 --- a/custom_components/eufy_security/manifest.json +++ b/custom_components/eufy_security/manifest.json @@ -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" } diff --git a/custom_components/eufy_security/number.py b/custom_components/eufy_security/number.py index 5175812..75925c5 100644 --- a/custom_components/eufy_security/number.py +++ b/custom_components/eufy_security/number.py @@ -6,7 +6,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ( - COORDINATOR, DOMAIN, Platform, PlatformToPropertyType, @@ -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 ) diff --git a/custom_components/eufy_security/select.py b/custom_components/eufy_security/select.py index b66293e..1b81a22 100644 --- a/custom_components/eufy_security/select.py +++ b/custom_components/eufy_security/select.py @@ -6,7 +6,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ( - COORDINATOR, DOMAIN, Platform, PlatformToPropertyType, @@ -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 ) diff --git a/custom_components/eufy_security/sensor.py b/custom_components/eufy_security/sensor.py index 923d9ab..dfa4bd3 100644 --- a/custom_components/eufy_security/sensor.py +++ b/custom_components/eufy_security/sensor.py @@ -6,7 +6,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -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 @@ -39,7 +39,7 @@ class CameraSensor(Enum): async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback) -> None: """Setup 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.SENSOR.name].value ) diff --git a/custom_components/eufy_security/switch.py b/custom_components/eufy_security/switch.py index 4cdd2c5..137714d 100644 --- a/custom_components/eufy_security/switch.py +++ b/custom_components/eufy_security/switch.py @@ -7,7 +7,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ( - COORDINATOR, DOMAIN, Platform, PlatformToPropertyType, @@ -24,7 +23,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 )