Skip to content

Commit fb938fc

Browse files
authored
feat: consider connection slots when selecting connection path (#293)
1 parent 39bccd1 commit fb938fc

File tree

5 files changed

+467
-3
lines changed

5 files changed

+467
-3
lines changed

src/habluetooth/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
__version__ = "5.3.1"
22

3+
from bleak_retry_connector import Allocations
4+
35
from .advertisement_tracker import (
46
TRACKER_BUFFERING_WOBBLE_SECONDS,
57
AdvertisementTracker,
@@ -45,6 +47,7 @@
4547
"TRACKER_BUFFERING_WOBBLE_SECONDS",
4648
"UNAVAILABLE_TRACK_SECONDS",
4749
"AdvertisementTracker",
50+
"Allocations",
4851
"BaseHaRemoteScanner",
4952
"BaseHaScanner",
5053
"BluetoothManager",

src/habluetooth/base_scanner.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
from bleak.backends.device import BLEDevice
1313
from bleak.backends.scanner import AdvertisementData
14-
from bleak_retry_connector import NO_RSSI_VALUE
14+
from bleak_retry_connector import NO_RSSI_VALUE, Allocations
1515
from bluetooth_adapters import adapter_human_name
1616
from bluetooth_data_tools import monotonic_time_coarse, parse_advertisement_data_bytes
1717

@@ -169,20 +169,51 @@ def _clear_connect_failure(self, address: str) -> None:
169169
"""Clear a connect failure."""
170170
self._connect_failures.pop(address, None)
171171

172+
def get_allocations(self) -> Allocations | None:
173+
"""
174+
Get current connection slot allocations for this scanner.
175+
176+
Returns:
177+
Allocations object with free/limit/allocated info, or None if not available.
178+
179+
Note:
180+
Subclasses should override this method to provide their allocation info.
181+
For local adapters, this will be overridden in HaScanner to query
182+
BleakSlotManager.
183+
For remote scanners, they should override to return their own tracking.
184+
185+
"""
186+
return None
187+
172188
def _score_connection_paths(
173189
self, rssi_diff: _int, scanner_device: BluetoothScannerDevice
174190
) -> float:
175-
"""Score the connection paths."""
191+
"""Score the connection paths considering slot availability."""
176192
address = scanner_device.ble_device.address
177193
score = scanner_device.advertisement.rssi or NO_RSSI_VALUE
178194
scanner_connections_in_progress = len(self._connect_in_progress)
179195
previous_failures = self._connect_failures.get(address, 0)
196+
197+
# Penalize scanners with connections in progress
180198
if scanner_connections_in_progress:
181199
# Very large penalty for multiple connections in progress
182200
# to avoid overloading the adapter
183201
score -= rssi_diff * scanner_connections_in_progress * 1.01
202+
203+
# Penalize based on previous failures
184204
if previous_failures:
185205
score -= rssi_diff * previous_failures * 0.51
206+
207+
# Consider connection slot availability
208+
allocation = self.get_allocations()
209+
if allocation and allocation.slots > 0:
210+
if allocation.free == 0:
211+
# No slots available - return NO_RSSI_VALUE to indicate unavailable
212+
return NO_RSSI_VALUE
213+
if allocation.free == 1:
214+
# Last slot available - small penalty to prefer adapters with more slots
215+
score -= rssi_diff * 0.76
216+
186217
return score
187218

188219
def _connections_in_progress(self) -> int:

src/habluetooth/scanner.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from bleak.assigned_numbers import AdvertisementDataType
1717
from bleak.backends.device import BLEDevice
1818
from bleak.backends.scanner import AdvertisementData, AdvertisementDataCallback
19-
from bleak_retry_connector import restore_discoveries
19+
from bleak_retry_connector import Allocations, restore_discoveries
2020
from bleak_retry_connector.bluez import stop_discovery
2121
from bluetooth_adapters import DEFAULT_ADDRESS
2222
from bluetooth_data_tools import monotonic_time_coarse
@@ -253,6 +253,12 @@ def get_discovered_device_advertisement_data(
253253
"""Return the advertisement data for a discovered device."""
254254
return self.discovered_devices_and_advertisement_data.get(address)
255255

256+
def get_allocations(self) -> Allocations | None:
257+
"""Get current connection slot allocations from BleakSlotManager."""
258+
if self._manager and self._manager.slot_manager:
259+
return self._manager.slot_manager.get_allocations(self.adapter)
260+
return None
261+
256262
def async_setup(self) -> CALLBACK_TYPE:
257263
"""Set up the scanner."""
258264
super().async_setup()

0 commit comments

Comments
 (0)