From 1adaaf49cc10e939b93f62b1ae9b61a7bb8363ab Mon Sep 17 00:00:00 2001
From: epenet <6771947+epenet@users.noreply.github.com>
Date: Wed, 25 Sep 2024 07:28:29 +0200
Subject: [PATCH] Add specific EntityDescription to describe device tracker
 entities (#126586)

* Add TrackerEntityDescription to describe tracker entities

* Improve

* Adjust components

* Add ScannerEntityDescription

* Simplify

* Revert

* Set TrackerEntity default source type to SourceType.GPS

* Fix rebase

* Adjust default

* Remove source_type from EntityDescription

* Fix rebase

* Docstring

* Remove BaseTrackerEntityDescription
---
 .../components/device_tracker/__init__.py     |  2 ++
 .../components/device_tracker/config_entry.py | 12 ++++++++++-
 .../components/renault/device_tracker.py      | 20 ++++++++++++++++---
 .../components/starlink/device_tracker.py     | 10 +++++-----
 .../components/unifi/device_tracker.py        |  5 ++++-
 pylint/plugins/hass_enforce_class_module.py   |  8 +++++++-
 6 files changed, 46 insertions(+), 11 deletions(-)

diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py
index 92c961eb148..28991483cda 100644
--- a/homeassistant/components/device_tracker/__init__.py
+++ b/homeassistant/components/device_tracker/__init__.py
@@ -16,7 +16,9 @@ from homeassistant.loader import bind_hass
 
 from .config_entry import (  # noqa: F401
     ScannerEntity,
+    ScannerEntityDescription,
     TrackerEntity,
+    TrackerEntityDescription,
     async_setup_entry,
     async_unload_entry,
 )
diff --git a/homeassistant/components/device_tracker/config_entry.py b/homeassistant/components/device_tracker/config_entry.py
index 8fbd85ae288..fe2b4aa4369 100644
--- a/homeassistant/components/device_tracker/config_entry.py
+++ b/homeassistant/components/device_tracker/config_entry.py
@@ -24,7 +24,7 @@ from homeassistant.helpers.device_registry import (
     EventDeviceRegistryUpdatedData,
 )
 from homeassistant.helpers.dispatcher import async_dispatcher_send
-from homeassistant.helpers.entity import Entity
+from homeassistant.helpers.entity import Entity, EntityDescription
 from homeassistant.helpers.entity_component import EntityComponent
 from homeassistant.helpers.entity_platform import EntityPlatform
 from homeassistant.helpers.typing import StateType
@@ -198,6 +198,10 @@ class BaseTrackerEntity(Entity):
         return attr
 
 
+class TrackerEntityDescription(EntityDescription, frozen_or_thawed=True):
+    """A class that describes tracker entities."""
+
+
 CACHED_TRACKER_PROPERTIES_WITH_ATTR_ = {
     "latitude",
     "location_accuracy",
@@ -211,6 +215,7 @@ class TrackerEntity(
 ):
     """Base class for a tracked device."""
 
+    entity_description: TrackerEntityDescription
     _attr_latitude: float | None = None
     _attr_location_accuracy: int = 0
     _attr_location_name: str | None = None
@@ -285,6 +290,10 @@ class TrackerEntity(
         return attr
 
 
+class ScannerEntityDescription(EntityDescription, frozen_or_thawed=True):
+    """A class that describes tracker entities."""
+
+
 CACHED_SCANNER_PROPERTIES_WITH_ATTR_ = {
     "ip_address",
     "mac_address",
@@ -297,6 +306,7 @@ class ScannerEntity(
 ):
     """Base class for a tracked device that is on a scanned network."""
 
+    entity_description: ScannerEntityDescription
     _attr_hostname: str | None = None
     _attr_ip_address: str | None = None
     _attr_mac_address: str | None = None
diff --git a/homeassistant/components/renault/device_tracker.py b/homeassistant/components/renault/device_tracker.py
index 1fde6c80cd6..2f7aeda5c39 100644
--- a/homeassistant/components/renault/device_tracker.py
+++ b/homeassistant/components/renault/device_tracker.py
@@ -2,9 +2,14 @@
 
 from __future__ import annotations
 
+from dataclasses import dataclass
+
 from renault_api.kamereon.models import KamereonVehicleLocationData
 
-from homeassistant.components.device_tracker import TrackerEntity
+from homeassistant.components.device_tracker import (
+    TrackerEntity,
+    TrackerEntityDescription,
+)
 from homeassistant.core import HomeAssistant
 from homeassistant.helpers.entity_platform import AddEntitiesCallback
 
@@ -12,6 +17,13 @@ from . import RenaultConfigEntry
 from .entity import RenaultDataEntity, RenaultDataEntityDescription
 
 
+@dataclass(frozen=True, kw_only=True)
+class RenaultTrackerEntityDescription(
+    TrackerEntityDescription, RenaultDataEntityDescription
+):
+    """Class describing Renault tracker entities."""
+
+
 async def async_setup_entry(
     hass: HomeAssistant,
     config_entry: RenaultConfigEntry,
@@ -32,6 +44,8 @@ class RenaultDeviceTracker(
 ):
     """Mixin for device tracker specific attributes."""
 
+    entity_description: RenaultTrackerEntityDescription
+
     @property
     def latitude(self) -> float | None:
         """Return latitude value of the device."""
@@ -43,8 +57,8 @@ class RenaultDeviceTracker(
         return self.coordinator.data.gpsLongitude if self.coordinator.data else None
 
 
-DEVICE_TRACKER_TYPES: tuple[RenaultDataEntityDescription, ...] = (
-    RenaultDataEntityDescription(
+DEVICE_TRACKER_TYPES: tuple[RenaultTrackerEntityDescription, ...] = (
+    RenaultTrackerEntityDescription(
         key="location",
         coordinator="location",
         translation_key="location",
diff --git a/homeassistant/components/starlink/device_tracker.py b/homeassistant/components/starlink/device_tracker.py
index 13861823722..5174be19760 100644
--- a/homeassistant/components/starlink/device_tracker.py
+++ b/homeassistant/components/starlink/device_tracker.py
@@ -4,10 +4,12 @@ from collections.abc import Callable
 from dataclasses import dataclass
 from typing import Any
 
-from homeassistant.components.device_tracker import TrackerEntity
+from homeassistant.components.device_tracker import (
+    TrackerEntity,
+    TrackerEntityDescription,
+)
 from homeassistant.config_entries import ConfigEntry
 from homeassistant.core import HomeAssistant
-from homeassistant.helpers.entity import EntityDescription
 from homeassistant.helpers.entity_platform import AddEntitiesCallback
 
 from .const import ATTR_ALTITUDE, DOMAIN
@@ -28,9 +30,7 @@ async def async_setup_entry(
 
 
 @dataclass(frozen=True, kw_only=True)
-class StarlinkDeviceTrackerEntityDescription(  # pylint: disable=hass-enforce-class-module
-    EntityDescription
-):
+class StarlinkDeviceTrackerEntityDescription(TrackerEntityDescription):
     """Describes a Starlink button entity."""
 
     latitude_fn: Callable[[StarlinkData], float]
diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py
index 5cdb3488367..c6694fce109 100644
--- a/homeassistant/components/unifi/device_tracker.py
+++ b/homeassistant/components/unifi/device_tracker.py
@@ -21,6 +21,7 @@ from aiounifi.models.event import Event, EventKey
 from homeassistant.components.device_tracker import (
     DOMAIN as DEVICE_TRACKER_DOMAIN,
     ScannerEntity,
+    ScannerEntityDescription,
 )
 from homeassistant.core import Event as core_Event, HomeAssistant, callback
 from homeassistant.helpers.dispatcher import async_dispatcher_connect
@@ -141,7 +142,9 @@ def async_device_heartbeat_timedelta_fn(hub: UnifiHub, obj_id: str) -> timedelta
 
 
 @dataclass(frozen=True, kw_only=True)
-class UnifiTrackerEntityDescription(UnifiEntityDescription[HandlerT, ApiItemT]):
+class UnifiTrackerEntityDescription(
+    UnifiEntityDescription[HandlerT, ApiItemT], ScannerEntityDescription
+):
     """Class describing UniFi device tracker entity."""
 
     heartbeat_timedelta_fn: Callable[[UnifiHub, str], timedelta]
diff --git a/pylint/plugins/hass_enforce_class_module.py b/pylint/plugins/hass_enforce_class_module.py
index 95527126a30..2320a4af8b7 100644
--- a/pylint/plugins/hass_enforce_class_module.py
+++ b/pylint/plugins/hass_enforce_class_module.py
@@ -36,7 +36,13 @@ _MODULES: dict[str, set[str]] = {
     "cover": {"CoverEntity", "CoverEntityDescription"},
     "date": {"DateEntity", "DateEntityDescription"},
     "datetime": {"DateTimeEntity", "DateTimeEntityDescription"},
-    "device_tracker": {"DeviceTrackerEntity", "ScannerEntity", "TrackerEntity"},
+    "device_tracker": {
+        "DeviceTrackerEntity",
+        "ScannerEntity",
+        "ScannerEntityDescription",
+        "TrackerEntity",
+        "TrackerEntityDescription",
+    },
     "event": {"EventEntity", "EventEntityDescription"},
     "fan": {"FanEntity", "FanEntityDescription"},
     "geo_location": {"GeolocationEvent"},
-- 
GitLab