fix(linux): duty-cycle BLE scan so other bonded LE devices can reconnect - #750
Open
pedrosekine wants to merge 1 commit into
Open
fix(linux): duty-cycle BLE scan so other bonded LE devices can reconnect#750pedrosekine wants to merge 1 commit into
pedrosekine wants to merge 1 commit into
Conversation
BleManager set setLowEnergyDiscoveryTimeout(0), so the discovery session stayed open for as long as the scan was wanted - which is the whole time the AirPods are not connected. The Linux kernel will not re-arm the passive accept-list scan while any discovery session is active (hci_update_passive_scan_sync returns early unless discovery.state == DISCOVERY_STOPPED). That passive scan is what reconnects bonded LE peripherals, so while librepods was scanning, no other bonded LE device on the machine could auto-reconnect at all. On my laptop an MX Master 3 and MX Keys stopped reconnecting after suspend and had to be connected by hand every time. Scan in bounded 4s windows with an 8s idle gap instead. The idle gap lets BlueZ drop the discovery session so the kernel can re-arm passive scanning; the next window re-arms ours. This also makes onScanFinished() reachable. With a 0 timeout the agent never emitted finished(), and its restart was guarded by `if (discoveryAgent->isActive())` - a condition that cannot hold in a finished() handler, so the branch was dead either way. Detection coverage is unchanged: on my adapter a single 4s window sees the same 11 Apple manufacturer-data advertisers that a continuous scan does, since advertisers broadcast every 20ms-2s. The cost is up to 8s of extra latency before an advertising device is first noticed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bsha6PUDqP7yiNEMM8UG5a
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
BleManagersetssetLowEnergyDiscoveryTimeout(0)("continuous scanning"), so the BlueZ discovery session stays open for as long as scanning is wanted — which is the entire time the AirPods are not connected (initializeBluetooth(), and again on disconnect).The Linux kernel will not re-arm the passive accept-list scan while any discovery session is active —
hci_update_passive_scan_sync()returns early unlessdiscovery.state == DISCOVERY_STOPPED. That passive scan is precisely the mechanism that reconnects bonded LE peripherals.The result: while librepods is scanning, no other bonded LE device on the machine can auto-reconnect. On my laptop, an MX Master 3 mouse and MX Keys keyboard silently stopped reconnecting after suspend and had to be connected by hand every time. With the AirPods left in their case overnight, the adapter sat at
Discovering: yesfor 13 hours straight.It's an easy one to misattribute — it looks like a mouse, BlueZ, or kernel problem, and nothing points at the AirPods daemon.
Reproducing
Stop librepods and a bonded LE mouse reconnects on its own within seconds.
Fix
Scan in bounded 4s windows with an 8s idle gap. The idle gap lets BlueZ drop the discovery session so the kernel can re-arm passive scanning; the next window re-arms ours.
This also makes
onScanFinished()reachable. With a0timeout the agent never emittedfinished(), and the restart inside it was guarded byif (discoveryAgent->isActive())— a condition that cannot hold in afinished()handler, so the branch was dead regardless.isScanning()now also reportstrueacross the idle gap, since its one caller (onSystemGoingToSleep) means "are we in a scanning state", not "is the radio scanning this instant".Detection coverage
Unchanged. On my adapter a single 4s window sees the same 11 Apple manufacturer-data advertisers that a continuous scan does — BLE advertisers broadcast every 20ms–2s, so a 4s window is far longer than needed. The cost is up to 8s of extra latency before an advertising device is first noticed. Once connected, everything runs over the AAP L2CAP link and is untouched by this change.
Happy to make the two constants configurable, or to tune them, if you'd prefer different values.
Testing
On Arch Linux, BlueZ 5.87, Qt 6, MediaTek MT7922 adapter, AirPods Pro:
0x07proximity-pairing parse path. My AirPods never emitted a0x07advert during testing — not in 20s of continuous scanning, nor a 2-minute capture across case open/close cycles — so I had nothing to feed the parser. This patch doesn't touch that code, and a continuous scan saw no0x07either, so I don't believe it's affected. Flagging it rather than implying coverage I don't have.Unrelated issues noticed
Not touched here, happy to file separately:
main()callsQLocalServer::removeServer("app_server")and deletes/tmp/app_serverimmediately before probing it withsocket_check.connectToServer("app_server"), so the connect always fails.--headlessisn't parsed — the arg loop only handles--debugand--hide, though the flag appears in the systemd unit in the README.🤖 Generated with Claude Code
https://claude.ai/code/session_01Bsha6PUDqP7yiNEMM8UG5a