diff --git a/homeassistant/components/proximity/__init__.py b/homeassistant/components/proximity/__init__.py
index 23a8fc3bf649ab3f5429b5b04b8b95fe6f3b63b2..d2db7632b52c51133263024cad26ca07210040c6 100644
--- a/homeassistant/components/proximity/__init__.py
+++ b/homeassistant/components/proximity/__init__.py
@@ -5,46 +5,26 @@ import logging
 
 import voluptuous as vol
 
-from homeassistant.const import (
-    ATTR_LATITUDE,
-    ATTR_LONGITUDE,
-    CONF_DEVICES,
-    CONF_UNIT_OF_MEASUREMENT,
-    CONF_ZONE,
-    UnitOfLength,
-)
-from homeassistant.core import HomeAssistant, State, callback
+from homeassistant.const import CONF_DEVICES, CONF_UNIT_OF_MEASUREMENT, CONF_ZONE
+from homeassistant.core import HomeAssistant
 import homeassistant.helpers.config_validation as cv
-from homeassistant.helpers.entity import Entity
-from homeassistant.helpers.event import async_track_state_change
 from homeassistant.helpers.typing import ConfigType
-from homeassistant.util.location import distance
-from homeassistant.util.unit_conversion import DistanceConverter
+from homeassistant.helpers.update_coordinator import CoordinatorEntity
+
+from .const import (
+    ATTR_DIR_OF_TRAVEL,
+    ATTR_NEAREST,
+    CONF_IGNORED_ZONES,
+    CONF_TOLERANCE,
+    DEFAULT_PROXIMITY_ZONE,
+    DEFAULT_TOLERANCE,
+    DOMAIN,
+    UNITS,
+)
+from .coordinator import ProximityDataUpdateCoordinator
 
 _LOGGER = logging.getLogger(__name__)
 
-ATTR_DIR_OF_TRAVEL = "dir_of_travel"
-ATTR_DIST_FROM = "dist_to_zone"
-ATTR_NEAREST = "nearest"
-
-CONF_IGNORED_ZONES = "ignored_zones"
-CONF_TOLERANCE = "tolerance"
-
-DEFAULT_DIR_OF_TRAVEL = "not set"
-DEFAULT_DIST_TO_ZONE = "not set"
-DEFAULT_NEAREST = "not set"
-DEFAULT_PROXIMITY_ZONE = "home"
-DEFAULT_TOLERANCE = 1
-DOMAIN = "proximity"
-
-UNITS = [
-    UnitOfLength.METERS,
-    UnitOfLength.KILOMETERS,
-    UnitOfLength.FEET,
-    UnitOfLength.YARDS,
-    UnitOfLength.MILES,
-]
-
 ZONE_SCHEMA = vol.Schema(
     {
         vol.Optional(CONF_ZONE, default=DEFAULT_PROXIMITY_ZONE): cv.string,
@@ -62,52 +42,22 @@ CONFIG_SCHEMA = vol.Schema(
 )
 
 
-@callback
-def async_setup_proximity_component(
-    hass: HomeAssistant, name: str, config: ConfigType
-) -> bool:
-    """Set up the individual proximity component."""
-    ignored_zones: list[str] = config[CONF_IGNORED_ZONES]
-    proximity_devices: list[str] = config[CONF_DEVICES]
-    tolerance: int = config[CONF_TOLERANCE]
-    proximity_zone = config[CONF_ZONE]
-    unit_of_measurement: str = config.get(
-        CONF_UNIT_OF_MEASUREMENT, hass.config.units.length_unit
-    )
-    zone_friendly_name = name
-
-    proximity = Proximity(
-        hass,
-        zone_friendly_name,
-        DEFAULT_DIST_TO_ZONE,
-        DEFAULT_DIR_OF_TRAVEL,
-        DEFAULT_NEAREST,
-        ignored_zones,
-        proximity_devices,
-        tolerance,
-        proximity_zone,
-        unit_of_measurement,
-    )
-    proximity.entity_id = f"{DOMAIN}.{zone_friendly_name}"
-
-    proximity.async_write_ha_state()
-
-    async_track_state_change(
-        hass, proximity_devices, proximity.async_check_proximity_state_change
-    )
-
-    return True
-
-
 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
     """Get the zones and offsets from configuration.yaml."""
+    hass.data.setdefault(DOMAIN, {})
     for zone, proximity_config in config[DOMAIN].items():
-        async_setup_proximity_component(hass, zone, proximity_config)
+        _LOGGER.debug("setup %s with config:%s", zone, proximity_config)
+        coordinator = ProximityDataUpdateCoordinator(hass, zone, proximity_config)
+        await coordinator.async_config_entry_first_refresh()
+        hass.data[DOMAIN][zone] = coordinator
+        proximity = Proximity(hass, zone, coordinator)
+        proximity.async_write_ha_state()
+        await proximity.async_added_to_hass()
 
     return True
 
 
-class Proximity(Entity):
+class Proximity(CoordinatorEntity[ProximityDataUpdateCoordinator]):
     """Representation of a Proximity."""
 
     # This entity is legacy and does not have a platform.
@@ -117,203 +67,26 @@ class Proximity(Entity):
     def __init__(
         self,
         hass: HomeAssistant,
-        zone_friendly_name: str,
-        dist_to: str,
-        dir_of_travel: str,
-        nearest: str,
-        ignored_zones: list[str],
-        proximity_devices: list[str],
-        tolerance: int,
-        proximity_zone: str,
-        unit_of_measurement: str,
+        friendly_name: str,
+        coordinator: ProximityDataUpdateCoordinator,
     ) -> None:
         """Initialize the proximity."""
+        super().__init__(coordinator)
         self.hass = hass
-        self.friendly_name = zone_friendly_name
-        self.dist_to: str | int = dist_to
-        self.dir_of_travel = dir_of_travel
-        self.nearest = nearest
-        self.ignored_zones = ignored_zones
-        self.proximity_devices = proximity_devices
-        self.tolerance = tolerance
-        self.proximity_zone = proximity_zone
-        self._unit_of_measurement = unit_of_measurement
+        self.entity_id = f"{DOMAIN}.{friendly_name}"
 
-    @property
-    def name(self) -> str:
-        """Return the name of the entity."""
-        return self.friendly_name
+        self._attr_name = friendly_name
+        self._attr_unit_of_measurement = self.coordinator.unit_of_measurement
 
     @property
-    def state(self) -> str | int:
+    def state(self) -> str | int | float:
         """Return the state."""
-        return self.dist_to
-
-    @property
-    def unit_of_measurement(self) -> str:
-        """Return the unit of measurement of this entity."""
-        return self._unit_of_measurement
+        return self.coordinator.data["dist_to_zone"]
 
     @property
     def extra_state_attributes(self) -> dict[str, str]:
         """Return the state attributes."""
-        return {ATTR_DIR_OF_TRAVEL: self.dir_of_travel, ATTR_NEAREST: self.nearest}
-
-    @callback
-    def async_check_proximity_state_change(
-        self, entity: str, old_state: State | None, new_state: State | None
-    ) -> None:
-        """Perform the proximity checking."""
-        if new_state is None:
-            return
-
-        entity_name = new_state.name
-        devices_to_calculate = False
-        devices_in_zone = ""
-
-        zone_state = self.hass.states.get(f"zone.{self.proximity_zone}")
-        proximity_latitude = (
-            zone_state.attributes.get(ATTR_LATITUDE) if zone_state else None
-        )
-        proximity_longitude = (
-            zone_state.attributes.get(ATTR_LONGITUDE) if zone_state else None
-        )
-
-        # Check for devices in the monitored zone.
-        for device in self.proximity_devices:
-            if (device_state := self.hass.states.get(device)) is None:
-                devices_to_calculate = True
-                continue
-
-            if device_state.state not in self.ignored_zones:
-                devices_to_calculate = True
-
-            # Check the location of all devices.
-            if (device_state.state).lower() == (self.proximity_zone).lower():
-                device_friendly = device_state.name
-                if devices_in_zone != "":
-                    devices_in_zone = f"{devices_in_zone}, "
-                devices_in_zone = devices_in_zone + device_friendly
-
-        # No-one to track so reset the entity.
-        if not devices_to_calculate:
-            self.dist_to = "not set"
-            self.dir_of_travel = "not set"
-            self.nearest = "not set"
-            self.async_write_ha_state()
-            return
-
-        # At least one device is in the monitored zone so update the entity.
-        if devices_in_zone != "":
-            self.dist_to = 0
-            self.dir_of_travel = "arrived"
-            self.nearest = devices_in_zone
-            self.async_write_ha_state()
-            return
-
-        # We can't check proximity because latitude and longitude don't exist.
-        if "latitude" not in new_state.attributes:
-            return
-
-        # Collect distances to the zone for all devices.
-        distances_to_zone: dict[str, float] = {}
-        for device in self.proximity_devices:
-            # Ignore devices in an ignored zone.
-            device_state = self.hass.states.get(device)
-            if not device_state or device_state.state in self.ignored_zones:
-                continue
-
-            # Ignore devices if proximity cannot be calculated.
-            if "latitude" not in device_state.attributes:
-                continue
-
-            # Calculate the distance to the proximity zone.
-            proximity = distance(
-                proximity_latitude,
-                proximity_longitude,
-                device_state.attributes[ATTR_LATITUDE],
-                device_state.attributes[ATTR_LONGITUDE],
-            )
-
-            # Add the device and distance to a dictionary.
-            if not proximity:
-                continue
-            distances_to_zone[device] = round(
-                DistanceConverter.convert(
-                    proximity, UnitOfLength.METERS, self.unit_of_measurement
-                ),
-                1,
-            )
-
-        # Loop through each of the distances collected and work out the
-        # closest.
-        closest_device: str | None = None
-        dist_to_zone: float | None = None
-
-        for device, zone in distances_to_zone.items():
-            if not dist_to_zone or zone < dist_to_zone:
-                closest_device = device
-                dist_to_zone = zone
-
-        # If the closest device is one of the other devices.
-        if closest_device is not None and closest_device != entity:
-            self.dist_to = round(distances_to_zone[closest_device])
-            self.dir_of_travel = "unknown"
-            device_state = self.hass.states.get(closest_device)
-            assert device_state
-            self.nearest = device_state.name
-            self.async_write_ha_state()
-            return
-
-        # Stop if we cannot calculate the direction of travel (i.e. we don't
-        # have a previous state and a current LAT and LONG).
-        if old_state is None or "latitude" not in old_state.attributes:
-            self.dist_to = round(distances_to_zone[entity])
-            self.dir_of_travel = "unknown"
-            self.nearest = entity_name
-            self.async_write_ha_state()
-            return
-
-        # Reset the variables
-        distance_travelled: float = 0
-
-        # Calculate the distance travelled.
-        old_distance = distance(
-            proximity_latitude,
-            proximity_longitude,
-            old_state.attributes[ATTR_LATITUDE],
-            old_state.attributes[ATTR_LONGITUDE],
-        )
-        new_distance = distance(
-            proximity_latitude,
-            proximity_longitude,
-            new_state.attributes[ATTR_LATITUDE],
-            new_state.attributes[ATTR_LONGITUDE],
-        )
-        assert new_distance is not None and old_distance is not None
-        distance_travelled = round(new_distance - old_distance, 1)
-
-        # Check for tolerance
-        if distance_travelled < self.tolerance * -1:
-            direction_of_travel = "towards"
-        elif distance_travelled > self.tolerance:
-            direction_of_travel = "away_from"
-        else:
-            direction_of_travel = "stationary"
-
-        # Update the proximity entity
-        self.dist_to = (
-            round(dist_to_zone) if dist_to_zone is not None else DEFAULT_DIST_TO_ZONE
-        )
-        self.dir_of_travel = direction_of_travel
-        self.nearest = entity_name
-        self.async_write_ha_state()
-        _LOGGER.debug(
-            "proximity.%s update entity: distance=%s: direction=%s: device=%s",
-            self.friendly_name,
-            self.dist_to,
-            direction_of_travel,
-            entity_name,
-        )
-
-        _LOGGER.info("%s: proximity calculation complete", entity_name)
+        return {
+            ATTR_DIR_OF_TRAVEL: str(self.coordinator.data["dir_of_travel"]),
+            ATTR_NEAREST: str(self.coordinator.data["nearest"]),
+        }
diff --git a/homeassistant/components/proximity/const.py b/homeassistant/components/proximity/const.py
new file mode 100644
index 0000000000000000000000000000000000000000..a5cee0ffce33cc998c4809f9f567037db7dcb946
--- /dev/null
+++ b/homeassistant/components/proximity/const.py
@@ -0,0 +1,25 @@
+"""Constants for Proximity integration."""
+
+from homeassistant.const import UnitOfLength
+
+ATTR_DIR_OF_TRAVEL = "dir_of_travel"
+ATTR_DIST_TO = "dist_to_zone"
+ATTR_NEAREST = "nearest"
+
+CONF_IGNORED_ZONES = "ignored_zones"
+CONF_TOLERANCE = "tolerance"
+
+DEFAULT_DIR_OF_TRAVEL = "not set"
+DEFAULT_DIST_TO_ZONE = "not set"
+DEFAULT_NEAREST = "not set"
+DEFAULT_PROXIMITY_ZONE = "home"
+DEFAULT_TOLERANCE = 1
+DOMAIN = "proximity"
+
+UNITS = [
+    UnitOfLength.METERS,
+    UnitOfLength.KILOMETERS,
+    UnitOfLength.FEET,
+    UnitOfLength.YARDS,
+    UnitOfLength.MILES,
+]
diff --git a/homeassistant/components/proximity/coordinator.py b/homeassistant/components/proximity/coordinator.py
new file mode 100644
index 0000000000000000000000000000000000000000..1b5770378dd1fbbe56d8f7ffafde3619e03f13f0
--- /dev/null
+++ b/homeassistant/components/proximity/coordinator.py
@@ -0,0 +1,268 @@
+"""Data update coordinator for the Proximity integration."""
+
+from dataclasses import dataclass
+import logging
+from typing import TypedDict
+
+from homeassistant.const import (
+    ATTR_LATITUDE,
+    ATTR_LONGITUDE,
+    CONF_DEVICES,
+    CONF_UNIT_OF_MEASUREMENT,
+    CONF_ZONE,
+    UnitOfLength,
+)
+from homeassistant.core import HomeAssistant, State
+from homeassistant.helpers.event import async_track_state_change
+from homeassistant.helpers.typing import ConfigType
+from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
+from homeassistant.util.location import distance
+from homeassistant.util.unit_conversion import DistanceConverter
+
+from .const import (
+    CONF_IGNORED_ZONES,
+    CONF_TOLERANCE,
+    DEFAULT_DIR_OF_TRAVEL,
+    DEFAULT_DIST_TO_ZONE,
+    DEFAULT_NEAREST,
+)
+
+_LOGGER = logging.getLogger(__name__)
+
+
+@dataclass
+class StateChangedData:
+    """StateChangedData class."""
+
+    entity_id: str
+    old_state: State | None
+    new_state: State | None
+
+
+class ProximityData(TypedDict):
+    """ProximityData type class."""
+
+    dist_to_zone: str | float
+    dir_of_travel: str | float
+    nearest: str | float
+
+
+class ProximityDataUpdateCoordinator(DataUpdateCoordinator[ProximityData]):
+    """Proximity data update coordinator."""
+
+    def __init__(
+        self, hass: HomeAssistant, friendly_name: str, config: ConfigType
+    ) -> None:
+        """Initialize the Proximity coordinator."""
+        self.ignored_zones: list[str] = config[CONF_IGNORED_ZONES]
+        self.proximity_devices: list[str] = config[CONF_DEVICES]
+        self.tolerance: int = config[CONF_TOLERANCE]
+        self.proximity_zone: str = config[CONF_ZONE]
+        self.unit_of_measurement: str = config.get(
+            CONF_UNIT_OF_MEASUREMENT, hass.config.units.length_unit
+        )
+        self.friendly_name = friendly_name
+
+        super().__init__(
+            hass,
+            _LOGGER,
+            name=friendly_name,
+            update_interval=None,
+        )
+
+        self.data = {
+            "dist_to_zone": DEFAULT_DIST_TO_ZONE,
+            "dir_of_travel": DEFAULT_DIR_OF_TRAVEL,
+            "nearest": DEFAULT_NEAREST,
+        }
+
+        self.state_change_data: StateChangedData | None = None
+        async_track_state_change(
+            hass, self.proximity_devices, self.async_check_proximity_state_change
+        )
+
+    async def async_check_proximity_state_change(
+        self, entity: str, old_state: State | None, new_state: State | None
+    ) -> None:
+        """Fetch and process state change event."""
+        if new_state is None:
+            _LOGGER.debug("no new_state -> abort")
+            return
+
+        # We can't check proximity because latitude and longitude don't exist.
+        if "latitude" not in new_state.attributes:
+            _LOGGER.debug("no latitude and longitude -> abort")
+            return
+
+        self.state_change_data = StateChangedData(entity, old_state, new_state)
+        await self.async_refresh()
+
+    async def _async_update_data(self) -> ProximityData:
+        """Calculate Proximity data."""
+        if self.state_change_data is None or self.state_change_data.new_state is None:
+            return self.data
+
+        entity_name = self.state_change_data.new_state.name
+        devices_to_calculate = False
+        devices_in_zone = ""
+
+        zone_state = self.hass.states.get(f"zone.{self.proximity_zone}")
+        proximity_latitude = (
+            zone_state.attributes.get(ATTR_LATITUDE) if zone_state else None
+        )
+        proximity_longitude = (
+            zone_state.attributes.get(ATTR_LONGITUDE) if zone_state else None
+        )
+
+        # Check for devices in the monitored zone.
+        for device in self.proximity_devices:
+            if (device_state := self.hass.states.get(device)) is None:
+                devices_to_calculate = True
+                continue
+
+            if device_state.state not in self.ignored_zones:
+                devices_to_calculate = True
+
+            # Check the location of all devices.
+            if (device_state.state).lower() == (self.proximity_zone).lower():
+                device_friendly = device_state.name
+                if devices_in_zone != "":
+                    devices_in_zone = f"{devices_in_zone}, "
+                devices_in_zone = devices_in_zone + device_friendly
+
+        # No-one to track so reset the entity.
+        if not devices_to_calculate:
+            _LOGGER.debug("no devices_to_calculate -> abort")
+            return {
+                "dist_to_zone": DEFAULT_DIST_TO_ZONE,
+                "dir_of_travel": DEFAULT_DIR_OF_TRAVEL,
+                "nearest": DEFAULT_NEAREST,
+            }
+
+        # At least one device is in the monitored zone so update the entity.
+        if devices_in_zone != "":
+            _LOGGER.debug("at least on device is in zone -> arrived")
+            return {
+                "dist_to_zone": 0,
+                "dir_of_travel": "arrived",
+                "nearest": devices_in_zone,
+            }
+
+        # Collect distances to the zone for all devices.
+        distances_to_zone: dict[str, float] = {}
+        for device in self.proximity_devices:
+            # Ignore devices in an ignored zone.
+            device_state = self.hass.states.get(device)
+            if not device_state or device_state.state in self.ignored_zones:
+                continue
+
+            # Ignore devices if proximity cannot be calculated.
+            if "latitude" not in device_state.attributes:
+                continue
+
+            # Calculate the distance to the proximity zone.
+            proximity = distance(
+                proximity_latitude,
+                proximity_longitude,
+                device_state.attributes[ATTR_LATITUDE],
+                device_state.attributes[ATTR_LONGITUDE],
+            )
+
+            # Add the device and distance to a dictionary.
+            if not proximity:
+                continue
+            distances_to_zone[device] = round(
+                DistanceConverter.convert(
+                    proximity, UnitOfLength.METERS, self.unit_of_measurement
+                ),
+                1,
+            )
+
+        # Loop through each of the distances collected and work out the
+        # closest.
+        closest_device: str | None = None
+        dist_to_zone: float | None = None
+
+        for device, zone in distances_to_zone.items():
+            if not dist_to_zone or zone < dist_to_zone:
+                closest_device = device
+                dist_to_zone = zone
+
+        # If the closest device is one of the other devices.
+        if (
+            closest_device is not None
+            and closest_device != self.state_change_data.entity_id
+        ):
+            _LOGGER.debug("closest device is one of the other devices -> unknown")
+            device_state = self.hass.states.get(closest_device)
+            assert device_state
+            return {
+                "dist_to_zone": round(distances_to_zone[closest_device]),
+                "dir_of_travel": "unknown",
+                "nearest": device_state.name,
+            }
+
+        # Stop if we cannot calculate the direction of travel (i.e. we don't
+        # have a previous state and a current LAT and LONG).
+        if (
+            self.state_change_data.old_state is None
+            or "latitude" not in self.state_change_data.old_state.attributes
+        ):
+            _LOGGER.debug("no lat and lon in old_state -> unknown")
+            return {
+                "dist_to_zone": round(
+                    distances_to_zone[self.state_change_data.entity_id]
+                ),
+                "dir_of_travel": "unknown",
+                "nearest": entity_name,
+            }
+
+        # Reset the variables
+        distance_travelled: float = 0
+
+        # Calculate the distance travelled.
+        old_distance = distance(
+            proximity_latitude,
+            proximity_longitude,
+            self.state_change_data.old_state.attributes[ATTR_LATITUDE],
+            self.state_change_data.old_state.attributes[ATTR_LONGITUDE],
+        )
+        new_distance = distance(
+            proximity_latitude,
+            proximity_longitude,
+            self.state_change_data.new_state.attributes[ATTR_LATITUDE],
+            self.state_change_data.new_state.attributes[ATTR_LONGITUDE],
+        )
+        assert new_distance is not None and old_distance is not None
+        distance_travelled = round(new_distance - old_distance, 1)
+
+        # Check for tolerance
+        if distance_travelled < self.tolerance * -1:
+            direction_of_travel = "towards"
+        elif distance_travelled > self.tolerance:
+            direction_of_travel = "away_from"
+        else:
+            direction_of_travel = "stationary"
+
+        # Update the proximity entity
+        dist_to: float | str
+        if dist_to_zone is not None:
+            dist_to = round(dist_to_zone)
+        else:
+            dist_to = DEFAULT_DIST_TO_ZONE
+
+        _LOGGER.debug(
+            "proximity.%s update entity: distance=%s: direction=%s: device=%s",
+            self.friendly_name,
+            dist_to,
+            direction_of_travel,
+            entity_name,
+        )
+
+        _LOGGER.info("%s: proximity calculation complete", entity_name)
+
+        return {
+            "dist_to_zone": dist_to,
+            "dir_of_travel": direction_of_travel,
+            "nearest": entity_name,
+        }