diff --git a/.coveragerc b/.coveragerc index 5638fc3e8ce..fbae5ff5228 100644 --- a/.coveragerc +++ b/.coveragerc @@ -461,6 +461,7 @@ omit = homeassistant/components/freebox/home_base.py homeassistant/components/freebox/switch.py homeassistant/components/fritz/coordinator.py + homeassistant/components/fritz/entity.py homeassistant/components/fritz/services.py homeassistant/components/fritz/switch.py homeassistant/components/fritzbox_callmonitor/__init__.py diff --git a/homeassistant/components/fritz/binary_sensor.py b/homeassistant/components/fritz/binary_sensor.py index 486d2e914a0..cb1f698bdca 100644 --- a/homeassistant/components/fritz/binary_sensor.py +++ b/homeassistant/components/fritz/binary_sensor.py @@ -17,17 +17,13 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DOMAIN -from .coordinator import ( - AvmWrapper, - ConnectionInfo, - FritzBoxBaseCoordinatorEntity, - FritzEntityDescription, -) +from .coordinator import AvmWrapper, ConnectionInfo +from .entity import FritzBoxBaseCoordinatorEntity, FritzEntityDescription _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class FritzBinarySensorEntityDescription( BinarySensorEntityDescription, FritzEntityDescription ): diff --git a/homeassistant/components/fritz/button.py b/homeassistant/components/fritz/button.py index 8838694334c..a0cbd54eaac 100644 --- a/homeassistant/components/fritz/button.py +++ b/homeassistant/components/fritz/button.py @@ -20,13 +20,8 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import BUTTON_TYPE_WOL, CONNECTION_TYPE_LAN, DATA_FRITZ, DOMAIN, MeshRoles -from .coordinator import ( - AvmWrapper, - FritzData, - FritzDevice, - FritzDeviceBase, - _is_tracked, -) +from .coordinator import AvmWrapper, FritzData, FritzDevice, _is_tracked +from .entity import FritzDeviceBase _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/fritz/coordinator.py b/homeassistant/components/fritz/coordinator.py index 51a67a118ed..7256085b93a 100644 --- a/homeassistant/components/fritz/coordinator.py +++ b/homeassistant/components/fritz/coordinator.py @@ -33,21 +33,14 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, ServiceCall, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import device_registry as dr, entity_registry as er -from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.dispatcher import async_dispatcher_send -from homeassistant.helpers.entity import EntityDescription from homeassistant.helpers.typing import StateType -from homeassistant.helpers.update_coordinator import ( - CoordinatorEntity, - DataUpdateCoordinator, - UpdateFailed, -) +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.util import dt as dt_util from .const import ( CONF_OLD_DISCOVERY, DEFAULT_CONF_OLD_DISCOVERY, - DEFAULT_DEVICE_NAME, DEFAULT_HOST, DEFAULT_SSL, DEFAULT_USERNAME, @@ -960,50 +953,6 @@ class FritzData: wol_buttons: dict = field(default_factory=dict) -class FritzDeviceBase(CoordinatorEntity[AvmWrapper]): - """Entity base class for a device connected to a FRITZ!Box device.""" - - def __init__(self, avm_wrapper: AvmWrapper, device: FritzDevice) -> None: - """Initialize a FRITZ!Box device.""" - super().__init__(avm_wrapper) - self._avm_wrapper = avm_wrapper - self._mac: str = device.mac_address - self._name: str = device.hostname or DEFAULT_DEVICE_NAME - - @property - def name(self) -> str: - """Return device name.""" - return self._name - - @property - def ip_address(self) -> str | None: - """Return the primary ip address of the device.""" - if self._mac: - return self._avm_wrapper.devices[self._mac].ip_address - return None - - @property - def mac_address(self) -> str: - """Return the mac address of the device.""" - return self._mac - - @property - def hostname(self) -> str | None: - """Return hostname of the device.""" - if self._mac: - return self._avm_wrapper.devices[self._mac].hostname - return None - - async def async_process_update(self) -> None: - """Update device.""" - raise NotImplementedError - - async def async_on_demand_update(self) -> None: - """Update state.""" - await self.async_process_update() - self.async_write_ha_state() - - class FritzDevice: """Representation of a device connected to the FRITZ!Box.""" @@ -1102,87 +1051,6 @@ class SwitchInfo(TypedDict): init_state: bool -class FritzBoxBaseEntity: - """Fritz host entity base class.""" - - def __init__(self, avm_wrapper: AvmWrapper, device_name: str) -> None: - """Init device info class.""" - self._avm_wrapper = avm_wrapper - self._device_name = device_name - - @property - def mac_address(self) -> str: - """Return the mac address of the main device.""" - return self._avm_wrapper.mac - - @property - def device_info(self) -> DeviceInfo: - """Return the device information.""" - return DeviceInfo( - configuration_url=f"http://{self._avm_wrapper.host}", - connections={(dr.CONNECTION_NETWORK_MAC, self.mac_address)}, - identifiers={(DOMAIN, self._avm_wrapper.unique_id)}, - manufacturer="AVM", - model=self._avm_wrapper.model, - name=self._device_name, - sw_version=self._avm_wrapper.current_firmware, - ) - - -@dataclass(frozen=True) -class FritzRequireKeysMixin: - """Fritz entity description mix in.""" - - value_fn: Callable[[FritzStatus, Any], Any] | None - - -@dataclass(frozen=True) -class FritzEntityDescription(EntityDescription, FritzRequireKeysMixin): - """Fritz entity base description.""" - - -class FritzBoxBaseCoordinatorEntity(CoordinatorEntity[AvmWrapper]): - """Fritz host coordinator entity base class.""" - - entity_description: FritzEntityDescription - _attr_has_entity_name = True - - def __init__( - self, - avm_wrapper: AvmWrapper, - device_name: str, - description: FritzEntityDescription, - ) -> None: - """Init device info class.""" - super().__init__(avm_wrapper) - self.entity_description = description - self._device_name = device_name - self._attr_unique_id = f"{avm_wrapper.unique_id}-{description.key}" - - async def async_added_to_hass(self) -> None: - """When entity is added to hass.""" - await super().async_added_to_hass() - if self.entity_description.value_fn is not None: - self.async_on_remove( - await self.coordinator.async_register_entity_updates( - self.entity_description.key, self.entity_description.value_fn - ) - ) - - @property - def device_info(self) -> DeviceInfo: - """Return the device information.""" - return DeviceInfo( - configuration_url=f"http://{self.coordinator.host}", - connections={(dr.CONNECTION_NETWORK_MAC, self.coordinator.mac)}, - identifiers={(DOMAIN, self.coordinator.unique_id)}, - manufacturer="AVM", - model=self.coordinator.model, - name=self._device_name, - sw_version=self.coordinator.current_firmware, - ) - - @dataclass class ConnectionInfo: """Fritz sensor connection information class.""" diff --git a/homeassistant/components/fritz/device_tracker.py b/homeassistant/components/fritz/device_tracker.py index bd5b88ab94b..6bf182458e0 100644 --- a/homeassistant/components/fritz/device_tracker.py +++ b/homeassistant/components/fritz/device_tracker.py @@ -16,9 +16,9 @@ from .coordinator import ( AvmWrapper, FritzData, FritzDevice, - FritzDeviceBase, device_filter_out_from_trackers, ) +from .entity import FritzDeviceBase _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/fritz/entity.py b/homeassistant/components/fritz/entity.py new file mode 100644 index 00000000000..45665c786d4 --- /dev/null +++ b/homeassistant/components/fritz/entity.py @@ -0,0 +1,137 @@ +"""AVM FRITZ!Tools entities.""" + +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +from typing import Any + +from fritzconnection.lib.fritzstatus import FritzStatus + +from homeassistant.helpers import device_registry as dr +from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.entity import EntityDescription +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DEFAULT_DEVICE_NAME, DOMAIN +from .coordinator import AvmWrapper, FritzDevice + + +class FritzDeviceBase(CoordinatorEntity[AvmWrapper]): + """Entity base class for a device connected to a FRITZ!Box device.""" + + def __init__(self, avm_wrapper: AvmWrapper, device: FritzDevice) -> None: + """Initialize a FRITZ!Box device.""" + super().__init__(avm_wrapper) + self._avm_wrapper = avm_wrapper + self._mac: str = device.mac_address + self._name: str = device.hostname or DEFAULT_DEVICE_NAME + + @property + def name(self) -> str: + """Return device name.""" + return self._name + + @property + def ip_address(self) -> str | None: + """Return the primary ip address of the device.""" + if self._mac: + return self._avm_wrapper.devices[self._mac].ip_address + return None + + @property + def mac_address(self) -> str: + """Return the mac address of the device.""" + return self._mac + + @property + def hostname(self) -> str | None: + """Return hostname of the device.""" + if self._mac: + return self._avm_wrapper.devices[self._mac].hostname + return None + + async def async_process_update(self) -> None: + """Update device.""" + raise NotImplementedError + + async def async_on_demand_update(self) -> None: + """Update state.""" + await self.async_process_update() + self.async_write_ha_state() + + +class FritzBoxBaseEntity: + """Fritz host entity base class.""" + + def __init__(self, avm_wrapper: AvmWrapper, device_name: str) -> None: + """Init device info class.""" + self._avm_wrapper = avm_wrapper + self._device_name = device_name + + @property + def mac_address(self) -> str: + """Return the mac address of the main device.""" + return self._avm_wrapper.mac + + @property + def device_info(self) -> DeviceInfo: + """Return the device information.""" + return DeviceInfo( + configuration_url=f"http://{self._avm_wrapper.host}", + connections={(dr.CONNECTION_NETWORK_MAC, self.mac_address)}, + identifiers={(DOMAIN, self._avm_wrapper.unique_id)}, + manufacturer="AVM", + model=self._avm_wrapper.model, + name=self._device_name, + sw_version=self._avm_wrapper.current_firmware, + ) + + +@dataclass(frozen=True, kw_only=True) +class FritzEntityDescription(EntityDescription): + """Fritz entity base description.""" + + value_fn: Callable[[FritzStatus, Any], Any] | None + + +class FritzBoxBaseCoordinatorEntity(CoordinatorEntity[AvmWrapper]): + """Fritz host coordinator entity base class.""" + + entity_description: FritzEntityDescription + _attr_has_entity_name = True + + def __init__( + self, + avm_wrapper: AvmWrapper, + device_name: str, + description: FritzEntityDescription, + ) -> None: + """Init device info class.""" + super().__init__(avm_wrapper) + self.entity_description = description + self._device_name = device_name + self._attr_unique_id = f"{avm_wrapper.unique_id}-{description.key}" + + async def async_added_to_hass(self) -> None: + """When entity is added to hass.""" + await super().async_added_to_hass() + if self.entity_description.value_fn is not None: + self.async_on_remove( + await self.coordinator.async_register_entity_updates( + self.entity_description.key, self.entity_description.value_fn + ) + ) + + @property + def device_info(self) -> DeviceInfo: + """Return the device information.""" + return DeviceInfo( + configuration_url=f"http://{self.coordinator.host}", + connections={(dr.CONNECTION_NETWORK_MAC, self.coordinator.mac)}, + identifiers={(DOMAIN, self.coordinator.unique_id)}, + manufacturer="AVM", + model=self.coordinator.model, + name=self._device_name, + sw_version=self.coordinator.current_firmware, + ) diff --git a/homeassistant/components/fritz/image.py b/homeassistant/components/fritz/image.py index cd8a287c637..19c98446ccd 100644 --- a/homeassistant/components/fritz/image.py +++ b/homeassistant/components/fritz/image.py @@ -15,7 +15,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util import dt as dt_util, slugify from .const import DOMAIN -from .coordinator import AvmWrapper, FritzBoxBaseEntity +from .coordinator import AvmWrapper +from .entity import FritzBoxBaseEntity _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/fritz/sensor.py b/homeassistant/components/fritz/sensor.py index 6da728ff930..11ee0ad5510 100644 --- a/homeassistant/components/fritz/sensor.py +++ b/homeassistant/components/fritz/sensor.py @@ -28,12 +28,8 @@ from homeassistant.helpers.typing import StateType from homeassistant.util.dt import utcnow from .const import DOMAIN, DSL_CONNECTION, UPTIME_DEVIATION -from .coordinator import ( - AvmWrapper, - ConnectionInfo, - FritzBoxBaseCoordinatorEntity, - FritzEntityDescription, -) +from .coordinator import AvmWrapper, ConnectionInfo +from .entity import FritzBoxBaseCoordinatorEntity, FritzEntityDescription _LOGGER = logging.getLogger(__name__) @@ -143,7 +139,7 @@ def _retrieve_link_attenuation_received_state( return status.attenuation[1] / 10 # type: ignore[no-any-return] -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class FritzSensorEntityDescription(SensorEntityDescription, FritzEntityDescription): """Describes Fritz sensor entity.""" diff --git a/homeassistant/components/fritz/switch.py b/homeassistant/components/fritz/switch.py index a19af3702d0..8af5b8ba529 100644 --- a/homeassistant/components/fritz/switch.py +++ b/homeassistant/components/fritz/switch.py @@ -29,13 +29,12 @@ from .const import ( ) from .coordinator import ( AvmWrapper, - FritzBoxBaseEntity, FritzData, FritzDevice, - FritzDeviceBase, SwitchInfo, device_filter_out_from_trackers, ) +from .entity import FritzBoxBaseEntity, FritzDeviceBase _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/fritz/update.py b/homeassistant/components/fritz/update.py index 0e896caa5cd..6969f201f27 100644 --- a/homeassistant/components/fritz/update.py +++ b/homeassistant/components/fritz/update.py @@ -17,16 +17,13 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DOMAIN -from .coordinator import ( - AvmWrapper, - FritzBoxBaseCoordinatorEntity, - FritzEntityDescription, -) +from .coordinator import AvmWrapper +from .entity import FritzBoxBaseCoordinatorEntity, FritzEntityDescription _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class FritzUpdateEntityDescription(UpdateEntityDescription, FritzEntityDescription): """Describes Fritz update entity."""