Summary
BaseBleScanner._scan() in toio/device_interface/ble.py has different early-termination behavior depending on which parameter is used:
| Parameter |
Early termination |
condition_met.set() called |
address |
Yes — when all addresses found |
Yes (line 193) |
cube_id |
Yes — when all IDs found |
Yes (line 204) |
num |
No — always waits full timeout |
No (line 205-211) |
Code Reference
# address: early termination ✅
if len(found_cubes) >= len(address):
condition_met.set()
# cube_id: early termination ✅
if len(found_cubes) >= len(cube_id):
condition_met.set()
# num (else branch): NO early termination ❌
else:
found_cubes[device.address] = CubeInfo(...)
# condition_met.set() is never called
This has been the case since the initial commit (b1ace13).
Measured Impact
We benchmarked on Raspberry Pi 5 with real toio Core Cubes:
| Method |
Scan time |
Notes |
BLEScanner.scan(num=2, timeout=12) |
12,037ms |
Always waits full timeout |
scan_with_id(cube_id={"A91","e6a"}, timeout=12) |
1,925ms |
Terminates when both found |
scan_with_id(cube_id={"A91","e6a"}, timeout=5) |
1,488ms |
Same early termination |
The scan(num=N) path is 6x slower than scan_with_id() for the same cubes in the same environment.
Question
Is this intentional for RSSI optimization?
Since UniversalBleScanner.scan() defaults to sort="rssi", the full-timeout behavior could be by design:
- Full timeout: Discovers ALL nearby cubes → sorts by RSSI → returns top N (best signal)
- Early termination: Returns first N discovered → RSSI sort has no selection pool
If this is intentional, it would be helpful to document this behavior. If it's an oversight, the fix is a two-line addition:
else:
found_cubes[device.address] = CubeInfo(...)
if num is not None and len(found_cubes) >= num:
condition_met.set()
Environment
- Raspberry Pi 5 (CYW43455 BLE)
- toio.py v1.1.0
- Python 3.11
- bleak (latest)
- 4 toio Core Cubes in range