Setup: Corne split on nice!nano v2 (nrf52840_ble + split + storage), rev b982049, keyboard.toml config. Two computers on the same desk: one bonded to profile 0, the other to profile 1.
What happens: With both machines powered on, switching to profile 0 often never connects — the keyboard just keeps advertising (my status display shows the advertising state indefinitely, 60s+). Switching back to profile 1 works in 1-2s. If I turn Bluetooth off on the profile-1 machine, profile 0 connects fine. So the profiles themselves are fine, they just fight each other.
What I found digging through the code: advertise() in rmk/src/ble/mod.rs always uses Advertisement::ConnectableScannableUndirected with the default (unfiltered) AdvFilterPolicy, and the adv payload is identical for every profile. A profile only decides which bond gets loaded into the stack (update_stack_bonds()). So after a profile switch, every bonded host in range races for the same advertisement and the first CONNECT_IND wins. From reading the code (I don't have a debug probe attached, so this part is inferred, not logged): when the "wrong" host wins, encryption can't succeed against the loaded LTK, and while that connection lingers the keyboard isn't advertising — so the host the profile was actually switched to has no way in until the link drops and the race starts over.
The wireless docs say "If you've connected a host to a profile, other devices will not be able to connect to this profile without manually clearing it first" — that holds at the encryption level, but the other device can still connect and occupy the slot, which blocks the host the profile was switched to.
Suggested fix (patched locally, tested today): do what ZMK does — when the active profile has a bond, advertise only to that peer:
peripheral.set_filter_accept_list(&[peer_identity_addr]) before advertising
filter_policy: FilterConnAndScan when the profile has a bond, Unfiltered when it's empty — so pairing new hosts still works, and BT_CLR returns the profile to open advertising
- I gated it on
identity.irk.is_some(): a bond without an IRK can't be resolved from rotating private addresses, so those fall back to open advertising instead of risking locking that host out
trouble already syncs the controller resolving list from bond IRKs, so RPAs from macOS/Windows resolve before the connection is accepted, and nrf-sdc implements all the needed HCI commands (LeAddDeviceToFilterAcceptList etc.). The whole change is ~20 lines in rmk/src/ble/mod.rs plus two ControllerCmdSync bounds on BleTransport.
Tested it today over repeated switches between the two computers: the wrong machine no longer takes the connection slot, and the intended one reconnects each time (with normal host-side latency, a second or two). Before the patch, switching toward the second machine frequently hung in advertising forever.
Happy to open a PR if you'd take this.
Setup: Corne split on nice!nano v2 (
nrf52840_ble+split+storage), rev b982049, keyboard.toml config. Two computers on the same desk: one bonded to profile 0, the other to profile 1.What happens: With both machines powered on, switching to profile 0 often never connects — the keyboard just keeps advertising (my status display shows the advertising state indefinitely, 60s+). Switching back to profile 1 works in 1-2s. If I turn Bluetooth off on the profile-1 machine, profile 0 connects fine. So the profiles themselves are fine, they just fight each other.
What I found digging through the code:
advertise()inrmk/src/ble/mod.rsalways usesAdvertisement::ConnectableScannableUndirectedwith the default (unfiltered)AdvFilterPolicy, and the adv payload is identical for every profile. A profile only decides which bond gets loaded into the stack (update_stack_bonds()). So after a profile switch, every bonded host in range races for the same advertisement and the first CONNECT_IND wins. From reading the code (I don't have a debug probe attached, so this part is inferred, not logged): when the "wrong" host wins, encryption can't succeed against the loaded LTK, and while that connection lingers the keyboard isn't advertising — so the host the profile was actually switched to has no way in until the link drops and the race starts over.The wireless docs say "If you've connected a host to a profile, other devices will not be able to connect to this profile without manually clearing it first" — that holds at the encryption level, but the other device can still connect and occupy the slot, which blocks the host the profile was switched to.
Suggested fix (patched locally, tested today): do what ZMK does — when the active profile has a bond, advertise only to that peer:
peripheral.set_filter_accept_list(&[peer_identity_addr])before advertisingfilter_policy: FilterConnAndScanwhen the profile has a bond,Unfilteredwhen it's empty — so pairing new hosts still works, and BT_CLR returns the profile to open advertisingidentity.irk.is_some(): a bond without an IRK can't be resolved from rotating private addresses, so those fall back to open advertising instead of risking locking that host outtrouble already syncs the controller resolving list from bond IRKs, so RPAs from macOS/Windows resolve before the connection is accepted, and nrf-sdc implements all the needed HCI commands (
LeAddDeviceToFilterAcceptListetc.). The whole change is ~20 lines inrmk/src/ble/mod.rsplus twoControllerCmdSyncbounds onBleTransport.Tested it today over repeated switches between the two computers: the wrong machine no longer takes the connection slot, and the intended one reconnects each time (with normal host-side latency, a second or two). Before the patch, switching toward the second machine frequently hung in advertising forever.
Happy to open a PR if you'd take this.