Fix connectable Bluetooth devices not being seen if the nearest scanner is non-connectable (#80388)

If we saw the non-connectable scanner advertisement first we would reject
the connectable scanner advertisement because it had worse signal strength.

In this case we need to check both
This commit is contained in:
J. Nick Koston 2022-10-17 20:13:26 -05:00 committed by GitHub
parent d38d21ab3a
commit f70f972d88
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 118 additions and 13 deletions

View file

@ -123,7 +123,6 @@ class BluetoothManager:
tuple[AdvertisementDataCallback, dict[str, set[str]]]
] = []
self._all_history: dict[str, BluetoothServiceInfoBleak] = {}
self._non_connectable_history: dict[str, BluetoothServiceInfoBleak] = {}
self._connectable_history: dict[str, BluetoothServiceInfoBleak] = {}
self._non_connectable_scanners: list[BaseHaScanner] = []
self._connectable_scanners: list[BaseHaScanner] = []
@ -157,9 +156,8 @@ class BluetoothManager:
service_info.as_dict()
for service_info in self._connectable_history.values()
],
"non_connectable_history": [
service_info.as_dict()
for service_info in self._non_connectable_history.values()
"all_history": [
service_info.as_dict() for service_info in self._all_history.values()
],
"advertisement_tracker": self._advertisement_tracker.async_diagnostics(),
}
@ -256,7 +254,6 @@ class BluetoothManager:
"""Watch for unavailable devices and cleanup state history."""
monotonic_now = MONOTONIC_TIME()
connectable_history = self._connectable_history
non_connectable_history = self._non_connectable_history
all_history = self._all_history
tracker = self._advertisement_tracker
intervals = tracker.intervals
@ -280,8 +277,6 @@ class BluetoothManager:
if time_since_seen <= advertising_interval:
continue
non_connectable_history.pop(address, None)
# The second loop (connectable=False) is responsible for removing
# the device from all the interval tracking since it is no longer
# available for both connectable and non-connectable
@ -363,8 +358,24 @@ class BluetoothManager:
device = service_info.device
address = device.address
all_history = self._all_history
connectable = service_info.connectable
connectable_history = self._connectable_history
source = service_info.source
# This logic is complex due to the many combinations of scanners that are supported.
#
# We need to handle multiple connectable and non-connectable scanners
# and we need to handle the case where a device is connectable on one scanner
# but not on another.
#
# The device may also be connectable only by a scanner that has worse signal strength
# than a non-connectable scanner.
#
# all_history - the history of all advertisements from all scanners with the best
# advertisement from each scanner
# connectable_history - the history of all connectable advertisements from all scanners
# with the best advertisement from each connectable scanner
#
if (
(old_service_info := all_history.get(address))
and source != old_service_info.source
@ -372,12 +383,35 @@ class BluetoothManager:
old_service_info, service_info
)
):
# If we are rejecting the new advertisement and the device is connectable
# but not in the connectable history or the connectable source is the same
# as the new source, we need to add it to the connectable history
if connectable:
old_connectable_service_info = connectable_history.get(address)
if old_connectable_service_info and (
# If its the same as the preferred source, we are done
# as we know we prefer the old advertisement
# from the check above
(old_connectable_service_info is old_service_info)
# If the old connectable source is different from the preferred
# source, we need to check it as well to see if we prefer
# the old connectable advertisement
or (
source != old_connectable_service_info.source
and self._prefer_previous_adv_from_different_source(
old_connectable_service_info, service_info
)
)
):
return
connectable_history[address] = service_info
return
if connectable := service_info.connectable:
self._connectable_history[address] = service_info
else:
self._non_connectable_history[address] = service_info
if connectable:
connectable_history[address] = service_info
all_history[address] = service_info
# Track advertisement intervals to determine when we need to