diff --git a/homeassistant/components/bluetooth/__init__.py b/homeassistant/components/bluetooth/__init__.py index 2afb638b230..f175b01b798 100644 --- a/homeassistant/components/bluetooth/__init__.py +++ b/homeassistant/components/bluetooth/__init__.py @@ -55,7 +55,7 @@ from .models import ( HaBluetoothConnector, ProcessAdvertisementCallback, ) -from .scanner import HaScanner, ScannerStartError, create_bleak_scanner +from .scanner import HaScanner, ScannerStartError from .util import adapter_human_name, adapter_unique_name, async_default_adapter if TYPE_CHECKING: @@ -400,13 +400,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: passive = entry.options.get(CONF_PASSIVE) mode = BluetoothScanningMode.PASSIVE if passive else BluetoothScanningMode.ACTIVE + scanner = HaScanner(hass, mode, adapter, address) try: - bleak_scanner = create_bleak_scanner(mode, adapter) + scanner.async_setup() except RuntimeError as err: raise ConfigEntryNotReady( f"{adapter_human_name(adapter, address)}: {err}" ) from err - scanner = HaScanner(hass, bleak_scanner, adapter, address) info_callback = async_get_advertisement_callback(hass) entry.async_on_unload(scanner.async_register_callback(info_callback)) try: diff --git a/homeassistant/components/bluetooth/scanner.py b/homeassistant/components/bluetooth/scanner.py index 857a0e4c01c..9bc68059a7f 100644 --- a/homeassistant/components/bluetooth/scanner.py +++ b/homeassistant/components/bluetooth/scanner.py @@ -16,7 +16,7 @@ from bleak.assigned_numbers import AdvertisementDataType from bleak.backends.bluezdbus.advertisement_monitor import OrPattern from bleak.backends.bluezdbus.scanner import BlueZScannerArgs from bleak.backends.device import BLEDevice -from bleak.backends.scanner import AdvertisementData +from bleak.backends.scanner import AdvertisementData, AdvertisementDataCallback from bleak_retry_connector import get_device_by_adapter from dbus_fast import InvalidMessageError @@ -86,11 +86,14 @@ class ScannerStartError(HomeAssistantError): def create_bleak_scanner( - scanning_mode: BluetoothScanningMode, adapter: str | None + detection_callback: AdvertisementDataCallback, + scanning_mode: BluetoothScanningMode, + adapter: str | None, ) -> bleak.BleakScanner: """Create a Bleak scanner.""" scanner_kwargs: dict[str, Any] = { - "scanning_mode": SCANNING_MODE_TO_BLEAK[scanning_mode] + "detection_callback": detection_callback, + "scanning_mode": SCANNING_MODE_TO_BLEAK[scanning_mode], } if platform.system() == "Linux": # Only Linux supports multiple adapters @@ -117,16 +120,18 @@ class HaScanner(BaseHaScanner): over ethernet, usb over ethernet, etc. """ + scanner: bleak.BleakScanner + def __init__( self, hass: HomeAssistant, - scanner: bleak.BleakScanner, + mode: BluetoothScanningMode, adapter: str, address: str, ) -> None: """Init bluetooth discovery.""" self.hass = hass - self.scanner = scanner + self.mode = mode self.adapter = adapter self._start_stop_lock = asyncio.Lock() self._cancel_watchdog: CALLBACK_TYPE | None = None @@ -141,6 +146,13 @@ class HaScanner(BaseHaScanner): """Return a list of discovered devices.""" return self.scanner.discovered_devices + @hass_callback + def async_setup(self) -> None: + """Set up the scanner.""" + self.scanner = create_bleak_scanner( + self._async_detection_callback, self.mode, self.adapter + ) + async def async_get_device_by_address(self, address: str) -> BLEDevice | None: """Get a device by address.""" if platform.system() == "Linux": @@ -218,8 +230,6 @@ class HaScanner(BaseHaScanner): async def async_start(self) -> None: """Start bluetooth scanner.""" - self.scanner.register_detection_callback(self._async_detection_callback) - async with self._start_stop_lock: await self._async_start() diff --git a/tests/components/bluetooth/test_init.py b/tests/components/bluetooth/test_init.py index 7ee1a9840db..2e311d9d97e 100644 --- a/tests/components/bluetooth/test_init.py +++ b/tests/components/bluetooth/test_init.py @@ -2,7 +2,7 @@ import asyncio from datetime import timedelta import time -from unittest.mock import MagicMock, Mock, patch +from unittest.mock import ANY, MagicMock, Mock, patch from bleak import BleakError from bleak.backends.scanner import AdvertisementData, BLEDevice @@ -114,6 +114,7 @@ async def test_setup_and_stop_passive(hass, mock_bleak_scanner_start, one_adapte "adapter": "hci0", "bluez": scanner.PASSIVE_SCANNER_ARGS, "scanning_mode": "passive", + "detection_callback": ANY, } @@ -161,6 +162,7 @@ async def test_setup_and_stop_old_bluez( assert init_kwargs == { "adapter": "hci0", "scanning_mode": "active", + "detection_callback": ANY, } diff --git a/tests/components/bluetooth/test_scanner.py b/tests/components/bluetooth/test_scanner.py index 91e8ab50971..a4666352479 100644 --- a/tests/components/bluetooth/test_scanner.py +++ b/tests/components/bluetooth/test_scanner.py @@ -174,6 +174,10 @@ async def test_recovery_from_dbus_restart(hass, one_adapter): mock_discovered = [] class MockBleakScanner: + def __init__(self, detection_callback, *args, **kwargs): + nonlocal _callback + _callback = detection_callback + async def start(self, *args, **kwargs): """Mock Start.""" nonlocal called_start @@ -190,23 +194,15 @@ async def test_recovery_from_dbus_restart(hass, one_adapter): nonlocal mock_discovered return mock_discovered - def register_detection_callback(self, callback: AdvertisementDataCallback): - """Mock Register Detection Callback.""" - nonlocal _callback - _callback = callback - - scanner = MockBleakScanner() - with patch( "homeassistant.components.bluetooth.scanner.OriginalBleakScanner", - return_value=scanner, + MockBleakScanner, ): await async_setup_with_one_adapter(hass) assert called_start == 1 start_time_monotonic = time.monotonic() - scanner = _get_manager() mock_discovered = [MagicMock()] # Ensure we don't restart the scanner if we don't need to