mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Immediately prefer advertisements from alternate sources when a scanner goes away (#81357)
This commit is contained in:
parent
9be204629b
commit
5b09ab93dc
@ -127,6 +127,7 @@ class BluetoothManager:
|
|||||||
self._non_connectable_scanners: list[BaseHaScanner] = []
|
self._non_connectable_scanners: list[BaseHaScanner] = []
|
||||||
self._connectable_scanners: list[BaseHaScanner] = []
|
self._connectable_scanners: list[BaseHaScanner] = []
|
||||||
self._adapters: dict[str, AdapterDetails] = {}
|
self._adapters: dict[str, AdapterDetails] = {}
|
||||||
|
self._sources: set[str] = set()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supports_passive_scan(self) -> bool:
|
def supports_passive_scan(self) -> bool:
|
||||||
@ -379,6 +380,7 @@ class BluetoothManager:
|
|||||||
if (
|
if (
|
||||||
(old_service_info := all_history.get(address))
|
(old_service_info := all_history.get(address))
|
||||||
and source != old_service_info.source
|
and source != old_service_info.source
|
||||||
|
and old_service_info.source in self._sources
|
||||||
and self._prefer_previous_adv_from_different_source(
|
and self._prefer_previous_adv_from_different_source(
|
||||||
old_service_info, service_info
|
old_service_info, service_info
|
||||||
)
|
)
|
||||||
@ -398,6 +400,7 @@ class BluetoothManager:
|
|||||||
# the old connectable advertisement
|
# the old connectable advertisement
|
||||||
or (
|
or (
|
||||||
source != old_connectable_service_info.source
|
source != old_connectable_service_info.source
|
||||||
|
and old_connectable_service_info.source in self._sources
|
||||||
and self._prefer_previous_adv_from_different_source(
|
and self._prefer_previous_adv_from_different_source(
|
||||||
old_connectable_service_info, service_info
|
old_connectable_service_info, service_info
|
||||||
)
|
)
|
||||||
@ -597,8 +600,10 @@ class BluetoothManager:
|
|||||||
def _unregister_scanner() -> None:
|
def _unregister_scanner() -> None:
|
||||||
self._advertisement_tracker.async_remove_source(scanner.source)
|
self._advertisement_tracker.async_remove_source(scanner.source)
|
||||||
scanners.remove(scanner)
|
scanners.remove(scanner)
|
||||||
|
self._sources.remove(scanner.source)
|
||||||
|
|
||||||
scanners.append(scanner)
|
scanners.append(scanner)
|
||||||
|
self._sources.add(scanner.source)
|
||||||
return _unregister_scanner
|
return _unregister_scanner
|
||||||
|
|
||||||
@hass_callback
|
@hass_callback
|
||||||
|
@ -5,11 +5,14 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
|||||||
|
|
||||||
from bleak.backends.scanner import BLEDevice
|
from bleak.backends.scanner import BLEDevice
|
||||||
from bluetooth_adapters import AdvertisementHistory
|
from bluetooth_adapters import AdvertisementHistory
|
||||||
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components import bluetooth
|
from homeassistant.components import bluetooth
|
||||||
|
from homeassistant.components.bluetooth import models
|
||||||
from homeassistant.components.bluetooth.manager import (
|
from homeassistant.components.bluetooth.manager import (
|
||||||
FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS,
|
FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS,
|
||||||
)
|
)
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
@ -20,8 +23,28 @@ from . import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def register_hci0_scanner(hass: HomeAssistant) -> None:
|
||||||
|
"""Register an hci0 scanner."""
|
||||||
|
cancel = bluetooth.async_register_scanner(
|
||||||
|
hass, models.BaseHaScanner(hass, "hci0"), True
|
||||||
|
)
|
||||||
|
yield
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def register_hci1_scanner(hass: HomeAssistant) -> None:
|
||||||
|
"""Register an hci1 scanner."""
|
||||||
|
cancel = bluetooth.async_register_scanner(
|
||||||
|
hass, models.BaseHaScanner(hass, "hci1"), True
|
||||||
|
)
|
||||||
|
yield
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
|
||||||
async def test_advertisements_do_not_switch_adapters_for_no_reason(
|
async def test_advertisements_do_not_switch_adapters_for_no_reason(
|
||||||
hass, enable_bluetooth
|
hass, enable_bluetooth, register_hci0_scanner, register_hci1_scanner
|
||||||
):
|
):
|
||||||
"""Test we only switch adapters when needed."""
|
"""Test we only switch adapters when needed."""
|
||||||
|
|
||||||
@ -68,7 +91,9 @@ async def test_advertisements_do_not_switch_adapters_for_no_reason(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_switching_adapters_based_on_rssi(hass, enable_bluetooth):
|
async def test_switching_adapters_based_on_rssi(
|
||||||
|
hass, enable_bluetooth, register_hci0_scanner, register_hci1_scanner
|
||||||
|
):
|
||||||
"""Test switching adapters based on rssi."""
|
"""Test switching adapters based on rssi."""
|
||||||
|
|
||||||
address = "44:44:33:11:23:45"
|
address = "44:44:33:11:23:45"
|
||||||
@ -122,7 +147,9 @@ async def test_switching_adapters_based_on_rssi(hass, enable_bluetooth):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_switching_adapters_based_on_zero_rssi(hass, enable_bluetooth):
|
async def test_switching_adapters_based_on_zero_rssi(
|
||||||
|
hass, enable_bluetooth, register_hci0_scanner, register_hci1_scanner
|
||||||
|
):
|
||||||
"""Test switching adapters based on zero rssi."""
|
"""Test switching adapters based on zero rssi."""
|
||||||
|
|
||||||
address = "44:44:33:11:23:45"
|
address = "44:44:33:11:23:45"
|
||||||
@ -176,7 +203,9 @@ async def test_switching_adapters_based_on_zero_rssi(hass, enable_bluetooth):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_switching_adapters_based_on_stale(hass, enable_bluetooth):
|
async def test_switching_adapters_based_on_stale(
|
||||||
|
hass, enable_bluetooth, register_hci0_scanner, register_hci1_scanner
|
||||||
|
):
|
||||||
"""Test switching adapters based on the previous advertisement being stale."""
|
"""Test switching adapters based on the previous advertisement being stale."""
|
||||||
|
|
||||||
address = "44:44:33:11:23:41"
|
address = "44:44:33:11:23:41"
|
||||||
@ -256,7 +285,7 @@ async def test_restore_history_from_dbus(hass, one_adapter):
|
|||||||
|
|
||||||
|
|
||||||
async def test_switching_adapters_based_on_rssi_connectable_to_non_connectable(
|
async def test_switching_adapters_based_on_rssi_connectable_to_non_connectable(
|
||||||
hass, enable_bluetooth
|
hass, enable_bluetooth, register_hci0_scanner, register_hci1_scanner
|
||||||
):
|
):
|
||||||
"""Test switching adapters based on rssi from connectable to non connectable."""
|
"""Test switching adapters based on rssi from connectable to non connectable."""
|
||||||
|
|
||||||
@ -339,7 +368,7 @@ async def test_switching_adapters_based_on_rssi_connectable_to_non_connectable(
|
|||||||
|
|
||||||
|
|
||||||
async def test_connectable_advertisement_can_be_retrieved_with_best_path_is_non_connectable(
|
async def test_connectable_advertisement_can_be_retrieved_with_best_path_is_non_connectable(
|
||||||
hass, enable_bluetooth
|
hass, enable_bluetooth, register_hci0_scanner, register_hci1_scanner
|
||||||
):
|
):
|
||||||
"""Test we can still get a connectable BLEDevice when the best path is non-connectable.
|
"""Test we can still get a connectable BLEDevice when the best path is non-connectable.
|
||||||
|
|
||||||
@ -384,3 +413,54 @@ async def test_connectable_advertisement_can_be_retrieved_with_best_path_is_non_
|
|||||||
bluetooth.async_ble_device_from_address(hass, address, True)
|
bluetooth.async_ble_device_from_address(hass, address, True)
|
||||||
is switchbot_device_poor_signal
|
is switchbot_device_poor_signal
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_switching_adapters_when_one_goes_away(
|
||||||
|
hass, enable_bluetooth, register_hci0_scanner
|
||||||
|
):
|
||||||
|
"""Test switching adapters when one goes away."""
|
||||||
|
cancel_hci2 = bluetooth.async_register_scanner(
|
||||||
|
hass, models.BaseHaScanner(hass, "hci2"), True
|
||||||
|
)
|
||||||
|
|
||||||
|
address = "44:44:33:11:23:45"
|
||||||
|
|
||||||
|
switchbot_device_good_signal = BLEDevice(address, "wohand_good_signal")
|
||||||
|
switchbot_adv_good_signal = generate_advertisement_data(
|
||||||
|
local_name="wohand_good_signal", service_uuids=[], rssi=-60
|
||||||
|
)
|
||||||
|
inject_advertisement_with_source(
|
||||||
|
hass, switchbot_device_good_signal, switchbot_adv_good_signal, "hci2"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
bluetooth.async_ble_device_from_address(hass, address)
|
||||||
|
is switchbot_device_good_signal
|
||||||
|
)
|
||||||
|
|
||||||
|
switchbot_device_poor_signal = BLEDevice(address, "wohand_poor_signal")
|
||||||
|
switchbot_adv_poor_signal = generate_advertisement_data(
|
||||||
|
local_name="wohand_poor_signal", service_uuids=[], rssi=-100
|
||||||
|
)
|
||||||
|
inject_advertisement_with_source(
|
||||||
|
hass, switchbot_device_poor_signal, switchbot_adv_poor_signal, "hci0"
|
||||||
|
)
|
||||||
|
|
||||||
|
# We want to prefer the good signal when we have options
|
||||||
|
assert (
|
||||||
|
bluetooth.async_ble_device_from_address(hass, address)
|
||||||
|
is switchbot_device_good_signal
|
||||||
|
)
|
||||||
|
|
||||||
|
cancel_hci2()
|
||||||
|
|
||||||
|
inject_advertisement_with_source(
|
||||||
|
hass, switchbot_device_poor_signal, switchbot_adv_poor_signal, "hci0"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Now that hci2 is gone, we should prefer the poor signal
|
||||||
|
# since no poor signal is better than no signal
|
||||||
|
assert (
|
||||||
|
bluetooth.async_ble_device_from_address(hass, address)
|
||||||
|
is switchbot_device_poor_signal
|
||||||
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user