diff --git a/homeassistant/components/bluetooth/__init__.py b/homeassistant/components/bluetooth/__init__.py
index d0a69bfe37951f1e7f0e370a8f9d5066c47ce196..add7dad1a1fb6d0ac7f17552b99be8ec8c20a7c5 100644
--- a/homeassistant/components/bluetooth/__init__.py
+++ b/homeassistant/components/bluetooth/__init__.py
@@ -58,9 +58,10 @@ from .api import (
     async_register_scanner,
     async_scanner_by_source,
     async_scanner_count,
+    async_scanner_devices_by_address,
     async_track_unavailable,
 )
-from .base_scanner import BaseHaRemoteScanner, BaseHaScanner
+from .base_scanner import BaseHaRemoteScanner, BaseHaScanner, BluetoothScannerDevice
 from .const import (
     BLUETOOTH_DISCOVERY_COOLDOWN_SECONDS,
     CONF_ADAPTER,
@@ -99,6 +100,7 @@ __all__ = [
     "async_track_unavailable",
     "async_scanner_by_source",
     "async_scanner_count",
+    "async_scanner_devices_by_address",
     "BaseHaScanner",
     "BaseHaRemoteScanner",
     "BluetoothCallbackMatcher",
@@ -107,6 +109,7 @@ __all__ = [
     "BluetoothServiceInfoBleak",
     "BluetoothScanningMode",
     "BluetoothCallback",
+    "BluetoothScannerDevice",
     "HaBluetoothConnector",
     "SOURCE_LOCAL",
     "FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS",
diff --git a/homeassistant/components/bluetooth/api.py b/homeassistant/components/bluetooth/api.py
index cd6b4ac959b85d9fdfeb1c40ab22afc69ce1b4a1..6c232e2a42cfca351b004a66ab681c41992c0d26 100644
--- a/homeassistant/components/bluetooth/api.py
+++ b/homeassistant/components/bluetooth/api.py
@@ -13,7 +13,7 @@ from home_assistant_bluetooth import BluetoothServiceInfoBleak
 
 from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback as hass_callback
 
-from .base_scanner import BaseHaScanner
+from .base_scanner import BaseHaScanner, BluetoothScannerDevice
 from .const import DATA_MANAGER
 from .manager import BluetoothManager
 from .match import BluetoothCallbackMatcher
@@ -93,6 +93,14 @@ def async_ble_device_from_address(
     return _get_manager(hass).async_ble_device_from_address(address, connectable)
 
 
+@hass_callback
+def async_scanner_devices_by_address(
+    hass: HomeAssistant, address: str, connectable: bool = True
+) -> list[BluetoothScannerDevice]:
+    """Return all discovered BluetoothScannerDevice for an address."""
+    return _get_manager(hass).async_scanner_devices_by_address(address, connectable)
+
+
 @hass_callback
 def async_address_present(
     hass: HomeAssistant, address: str, connectable: bool = True
diff --git a/homeassistant/components/bluetooth/base_scanner.py b/homeassistant/components/bluetooth/base_scanner.py
index 8868b0a0883487a65cdd84475a538b7d2f4e8337..d9fcc750ed410dff977fb7a7860d8dcb855d81ba 100644
--- a/homeassistant/components/bluetooth/base_scanner.py
+++ b/homeassistant/components/bluetooth/base_scanner.py
@@ -4,6 +4,7 @@ from __future__ import annotations
 from abc import ABC, abstractmethod
 from collections.abc import Callable, Generator
 from contextlib import contextmanager
+from dataclasses import dataclass
 import datetime
 from datetime import timedelta
 import logging
@@ -39,6 +40,15 @@ MONOTONIC_TIME: Final = monotonic_time_coarse
 _LOGGER = logging.getLogger(__name__)
 
 
+@dataclass
+class BluetoothScannerDevice:
+    """Data for a bluetooth device from a given scanner."""
+
+    scanner: BaseHaScanner
+    ble_device: BLEDevice
+    advertisement: AdvertisementData
+
+
 class BaseHaScanner(ABC):
     """Base class for Ha Scanners."""
 
diff --git a/homeassistant/components/bluetooth/manager.py b/homeassistant/components/bluetooth/manager.py
index c863299d2064265d9199143216dcedc2530d968e..91d658cdf5872dca79bac1c43a45423d19533f93 100644
--- a/homeassistant/components/bluetooth/manager.py
+++ b/homeassistant/components/bluetooth/manager.py
@@ -29,7 +29,7 @@ from homeassistant.helpers.event import async_track_time_interval
 from homeassistant.util.dt import monotonic_time_coarse
 
 from .advertisement_tracker import AdvertisementTracker
-from .base_scanner import BaseHaScanner
+from .base_scanner import BaseHaScanner, BluetoothScannerDevice
 from .const import (
     FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS,
     UNAVAILABLE_TRACK_SECONDS,
@@ -217,18 +217,22 @@ class BluetoothManager:
         uninstall_multiple_bleak_catcher()
 
     @hass_callback
-    def async_get_scanner_discovered_devices_and_advertisement_data_by_address(
+    def async_scanner_devices_by_address(
         self, address: str, connectable: bool
-    ) -> list[tuple[BaseHaScanner, BLEDevice, AdvertisementData]]:
-        """Get scanner, devices, and advertisement_data by address."""
-        types_ = (True,) if connectable else (True, False)
-        results: list[tuple[BaseHaScanner, BLEDevice, AdvertisementData]] = []
-        for type_ in types_:
-            for scanner in self._get_scanners_by_type(type_):
-                devices_and_adv_data = scanner.discovered_devices_and_advertisement_data
-                if device_adv_data := devices_and_adv_data.get(address):
-                    results.append((scanner, *device_adv_data))
-        return results
+    ) -> list[BluetoothScannerDevice]:
+        """Get BluetoothScannerDevice by address."""
+        scanners = self._get_scanners_by_type(True)
+        if not connectable:
+            scanners.extend(self._get_scanners_by_type(False))
+        return [
+            BluetoothScannerDevice(scanner, *device_adv)
+            for scanner in scanners
+            if (
+                device_adv := scanner.discovered_devices_and_advertisement_data.get(
+                    address
+                )
+            )
+        ]
 
     @hass_callback
     def _async_all_discovered_addresses(self, connectable: bool) -> Iterable[str]:
diff --git a/homeassistant/components/bluetooth/wrappers.py b/homeassistant/components/bluetooth/wrappers.py
index 4a1be63903fc2a9ecbaee4f4df596ae069f26e5d..6b463423c73dd2b8a845a8571b1e91d2552cacdd 100644
--- a/homeassistant/components/bluetooth/wrappers.py
+++ b/homeassistant/components/bluetooth/wrappers.py
@@ -12,11 +12,7 @@ from typing import TYPE_CHECKING, Any, Final
 from bleak import BleakClient, BleakError
 from bleak.backends.client import BaseBleakClient, get_platform_client_backend_type
 from bleak.backends.device import BLEDevice
-from bleak.backends.scanner import (
-    AdvertisementData,
-    AdvertisementDataCallback,
-    BaseBleakScanner,
-)
+from bleak.backends.scanner import AdvertisementDataCallback, BaseBleakScanner
 from bleak_retry_connector import (
     NO_RSSI_VALUE,
     ble_device_description,
@@ -28,7 +24,7 @@ from homeassistant.core import CALLBACK_TYPE, callback as hass_callback
 from homeassistant.helpers.frame import report
 
 from . import models
-from .base_scanner import BaseHaScanner
+from .base_scanner import BaseHaScanner, BluetoothScannerDevice
 
 FILTER_UUIDS: Final = "UUIDs"
 _LOGGER = logging.getLogger(__name__)
@@ -149,9 +145,7 @@ class HaBleakScannerWrapper(BaseBleakScanner):
 
 
 def _rssi_sorter_with_connection_failure_penalty(
-    scanner_device_advertisement_data: tuple[
-        BaseHaScanner, BLEDevice, AdvertisementData
-    ],
+    device: BluetoothScannerDevice,
     connection_failure_count: dict[BaseHaScanner, int],
     rssi_diff: int,
 ) -> float:
@@ -168,9 +162,8 @@ def _rssi_sorter_with_connection_failure_penalty(
     best adapter twice before moving on to the next best adapter since
     the first failure may be a transient service resolution issue.
     """
-    scanner, _, advertisement_data = scanner_device_advertisement_data
-    base_rssi = advertisement_data.rssi or NO_RSSI_VALUE
-    if connect_failures := connection_failure_count.get(scanner):
+    base_rssi = device.advertisement.rssi or NO_RSSI_VALUE
+    if connect_failures := connection_failure_count.get(device.scanner):
         if connect_failures > 1 and not rssi_diff:
             rssi_diff = 1
         return base_rssi - (rssi_diff * connect_failures * 0.51)
@@ -300,14 +293,10 @@ class HaBleakClientWrapper(BleakClient):
         that has a free connection slot.
         """
         address = self.__address
-        scanner_device_advertisement_datas = manager.async_get_scanner_discovered_devices_and_advertisement_data_by_address(  # noqa: E501
-            address, True
-        )
-        sorted_scanner_device_advertisement_datas = sorted(
-            scanner_device_advertisement_datas,
-            key=lambda scanner_device_advertisement_data: (
-                scanner_device_advertisement_data[2].rssi or NO_RSSI_VALUE
-            ),
+        devices = manager.async_scanner_devices_by_address(self.__address, True)
+        sorted_devices = sorted(
+            devices,
+            key=lambda device: device.advertisement.rssi or NO_RSSI_VALUE,
             reverse=True,
         )
 
@@ -315,31 +304,28 @@ class HaBleakClientWrapper(BleakClient):
         # to prefer the adapter/scanner with the less failures so
         # we don't keep trying to connect with an adapter
         # that is failing
-        if (
-            self.__connect_failures
-            and len(sorted_scanner_device_advertisement_datas) > 1
-        ):
+        if self.__connect_failures and len(sorted_devices) > 1:
             # We use the rssi diff between to the top two
             # to adjust the rssi sorter so that each failure
             # will reduce the rssi sorter by the diff amount
             rssi_diff = (
-                sorted_scanner_device_advertisement_datas[0][2].rssi
-                - sorted_scanner_device_advertisement_datas[1][2].rssi
+                sorted_devices[0].advertisement.rssi
+                - sorted_devices[1].advertisement.rssi
             )
             adjusted_rssi_sorter = partial(
                 _rssi_sorter_with_connection_failure_penalty,
                 connection_failure_count=self.__connect_failures,
                 rssi_diff=rssi_diff,
             )
-            sorted_scanner_device_advertisement_datas = sorted(
-                scanner_device_advertisement_datas,
+            sorted_devices = sorted(
+                devices,
                 key=adjusted_rssi_sorter,
                 reverse=True,
             )
 
-        for (scanner, ble_device, _) in sorted_scanner_device_advertisement_datas:
+        for device in sorted_devices:
             if backend := self._async_get_backend_for_ble_device(
-                manager, scanner, ble_device
+                manager, device.scanner, device.ble_device
             ):
                 return backend
 
diff --git a/tests/components/bluetooth/test_api.py b/tests/components/bluetooth/test_api.py
index acb09c22ba73c87380b3677fecead2cc68aaed23..c875710d8e585e367ad436a76a86a50949a73ce5 100644
--- a/tests/components/bluetooth/test_api.py
+++ b/tests/components/bluetooth/test_api.py
@@ -1,10 +1,18 @@
 """Tests for the Bluetooth integration API."""
 
 
+from bleak.backends.scanner import AdvertisementData, BLEDevice
+
 from homeassistant.components import bluetooth
-from homeassistant.components.bluetooth import async_scanner_by_source
+from homeassistant.components.bluetooth import (
+    BaseHaRemoteScanner,
+    BaseHaScanner,
+    HaBluetoothConnector,
+    async_scanner_by_source,
+    async_scanner_devices_by_address,
+)
 
-from . import FakeScanner
+from . import FakeScanner, MockBleakClient, _get_manager, generate_advertisement_data
 
 
 async def test_scanner_by_source(hass, enable_bluetooth):
@@ -16,3 +24,116 @@ async def test_scanner_by_source(hass, enable_bluetooth):
     assert async_scanner_by_source(hass, "hci2") is hci2_scanner
     cancel_hci2()
     assert async_scanner_by_source(hass, "hci2") is None
+
+
+async def test_async_scanner_devices_by_address_connectable(hass, enable_bluetooth):
+    """Test getting scanner devices by address with connectable devices."""
+    manager = _get_manager()
+
+    class FakeInjectableScanner(BaseHaRemoteScanner):
+        def inject_advertisement(
+            self, device: BLEDevice, advertisement_data: AdvertisementData
+        ) -> None:
+            """Inject an advertisement."""
+            self._async_on_advertisement(
+                device.address,
+                advertisement_data.rssi,
+                device.name,
+                advertisement_data.service_uuids,
+                advertisement_data.service_data,
+                advertisement_data.manufacturer_data,
+                advertisement_data.tx_power,
+                {"scanner_specific_data": "test"},
+            )
+
+    new_info_callback = manager.scanner_adv_received
+    connector = (
+        HaBluetoothConnector(MockBleakClient, "mock_bleak_client", lambda: False),
+    )
+    scanner = FakeInjectableScanner(
+        hass, "esp32", "esp32", new_info_callback, connector, False
+    )
+    unsetup = scanner.async_setup()
+    cancel = manager.async_register_scanner(scanner, True)
+    switchbot_device = BLEDevice(
+        "44:44:33:11:23:45",
+        "wohand",
+        {},
+        rssi=-100,
+    )
+    switchbot_device_adv = generate_advertisement_data(
+        local_name="wohand",
+        service_uuids=["050a021a-0000-1000-8000-00805f9b34fb"],
+        service_data={"050a021a-0000-1000-8000-00805f9b34fb": b"\n\xff"},
+        manufacturer_data={1: b"\x01"},
+        rssi=-100,
+    )
+    scanner.inject_advertisement(switchbot_device, switchbot_device_adv)
+    assert async_scanner_devices_by_address(
+        hass, switchbot_device.address, connectable=True
+    ) == async_scanner_devices_by_address(hass, "44:44:33:11:23:45", connectable=False)
+    devices = async_scanner_devices_by_address(
+        hass, switchbot_device.address, connectable=False
+    )
+    assert len(devices) == 1
+    assert devices[0].scanner == scanner
+    assert devices[0].ble_device.name == switchbot_device.name
+    assert devices[0].advertisement.local_name == switchbot_device_adv.local_name
+    unsetup()
+    cancel()
+
+
+async def test_async_scanner_devices_by_address_non_connectable(hass, enable_bluetooth):
+    """Test getting scanner devices by address with non-connectable devices."""
+    manager = _get_manager()
+    switchbot_device = BLEDevice(
+        "44:44:33:11:23:45",
+        "wohand",
+        {},
+        rssi=-100,
+    )
+    switchbot_device_adv = generate_advertisement_data(
+        local_name="wohand",
+        service_uuids=["050a021a-0000-1000-8000-00805f9b34fb"],
+        service_data={"050a021a-0000-1000-8000-00805f9b34fb": b"\n\xff"},
+        manufacturer_data={1: b"\x01"},
+        rssi=-100,
+    )
+
+    class FakeStaticScanner(BaseHaScanner):
+        @property
+        def discovered_devices(self) -> list[BLEDevice]:
+            """Return a list of discovered devices."""
+            return [switchbot_device]
+
+        @property
+        def discovered_devices_and_advertisement_data(
+            self,
+        ) -> dict[str, tuple[BLEDevice, AdvertisementData]]:
+            """Return a list of discovered devices and their advertisement data."""
+            return {switchbot_device.address: (switchbot_device, switchbot_device_adv)}
+
+    connector = (
+        HaBluetoothConnector(MockBleakClient, "mock_bleak_client", lambda: False),
+    )
+    scanner = FakeStaticScanner(hass, "esp32", "esp32", connector)
+    cancel = manager.async_register_scanner(scanner, False)
+
+    assert scanner.discovered_devices_and_advertisement_data == {
+        switchbot_device.address: (switchbot_device, switchbot_device_adv)
+    }
+
+    assert (
+        async_scanner_devices_by_address(
+            hass, switchbot_device.address, connectable=True
+        )
+        == []
+    )
+    devices = async_scanner_devices_by_address(
+        hass, switchbot_device.address, connectable=False
+    )
+    assert len(devices) == 1
+    assert devices[0].scanner == scanner
+    assert devices[0].ble_device.name == switchbot_device.name
+    assert devices[0].advertisement.local_name == switchbot_device_adv.local_name
+    cancel()