From 79e5974946e25f84b13332b11989a1888535c945 Mon Sep 17 00:00:00 2001 From: timrid <6593626+timrid@users.noreply.github.com> Date: Thu, 5 Feb 2026 12:46:41 +0100 Subject: [PATCH 1/2] Multiple le connections are now working correctly --- bumble/controller.py | 3 +++ tests/device_test.py | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/bumble/controller.py b/bumble/controller.py index 12e997e0..77634987 100644 --- a/bumble/controller.py +++ b/bumble/controller.py @@ -604,6 +604,8 @@ def on_le_disconnected(self, connection: Connection, reason: int) -> None: ) ) + del self.le_connections[connection.peer_address] + def create_le_connection(self, peer_address: hci.Address) -> None: ''' Called when we receive advertisement matching connection filter. @@ -1217,6 +1219,7 @@ def on_hci_disconnect_command( else: # Remove the connection del self.classic_connections[connection.peer_address] + del self.le_connections[connection.peer_address] elif sco_link := self.find_classic_sco_link_by_handle(handle): if self.link: if ( diff --git a/tests/device_test.py b/tests/device_test.py index 74837d28..f1535d7c 100644 --- a/tests/device_test.py +++ b/tests/device_test.py @@ -309,6 +309,26 @@ async def test_legacy_advertising_disconnection(auto_restart): assert not devices[0].is_advertising +# ----------------------------------------------------------------------------- +@pytest.mark.asyncio +async def test_le_multiple_connects(): + devices = TwoDevices() + for controller in devices.controllers: + controller.le_features |= hci.LeFeatureMask.LE_EXTENDED_ADVERTISING + for dev in devices: + await dev.power_on() + await devices[0].start_advertising(auto_restart=True, advertising_interval_min=1.0) + + connection = await devices[1].connect(devices[0].random_address) + await connection.disconnect() + + await async_barrier() + await async_barrier() + + # a second connection attempt is working + connection = await devices[1].connect(devices[0].random_address) + await connection.disconnect() + # ----------------------------------------------------------------------------- @pytest.mark.asyncio async def test_advertising_and_scanning(): From 81d9adb9830c378daaedbdcf166ab34b3a522ce5 Mon Sep 17 00:00:00 2001 From: timrid <6593626+timrid@users.noreply.github.com> Date: Thu, 5 Feb 2026 18:50:31 +0100 Subject: [PATCH 2/2] delete only the required connection --- bumble/controller.py | 36 ++++++++++++++++++++++++------------ tests/device_test.py | 1 + 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/bumble/controller.py b/bumble/controller.py index 77634987..9fbe80ae 100644 --- a/bumble/controller.py +++ b/bumble/controller.py @@ -481,6 +481,18 @@ def find_connection_by_handle(self, handle: int) -> Connection | None: return connection return None + def find_le_connection_by_handle(self, handle: int) -> Connection | None: + for connection in self.le_connections.values(): + if connection.handle == handle: + return connection + return None + + def find_classic_connection_by_handle(self, handle: int) -> Connection | None: + for connection in self.classic_connections.values(): + if connection.handle == handle: + return connection + return None + def find_classic_sco_link_by_handle(self, handle: int) -> ScoLink | None: for connection in self.sco_links.values(): if connection.handle == handle: @@ -1203,22 +1215,22 @@ def on_hci_disconnect_command( # Notify the link of the disconnection handle = command.connection_handle - if connection := self.find_connection_by_handle(handle): + if connection := self.find_classic_connection_by_handle(handle): if self.link: - if connection.transport == PhysicalTransport.BR_EDR: - self.send_lmp_packet( - connection.peer_address, - lmp.LmpDetach(command.reason), - ) - self.on_classic_disconnected( - connection.peer_address, command.reason - ) - else: - connection.send_ll_control_pdu(ll.TerminateInd(command.reason)) - self.on_le_disconnected(connection, command.reason) + self.send_lmp_packet( + connection.peer_address, + lmp.LmpDetach(command.reason), + ) + self.on_classic_disconnected(connection.peer_address, command.reason) else: # Remove the connection del self.classic_connections[connection.peer_address] + elif connection := self.find_le_connection_by_handle(handle): + if self.link: + connection.send_ll_control_pdu(ll.TerminateInd(command.reason)) + self.on_le_disconnected(connection, command.reason) + else: + # Remove the connection del self.le_connections[connection.peer_address] elif sco_link := self.find_classic_sco_link_by_handle(handle): if self.link: diff --git a/tests/device_test.py b/tests/device_test.py index f1535d7c..af18c78c 100644 --- a/tests/device_test.py +++ b/tests/device_test.py @@ -329,6 +329,7 @@ async def test_le_multiple_connects(): connection = await devices[1].connect(devices[0].random_address) await connection.disconnect() + # ----------------------------------------------------------------------------- @pytest.mark.asyncio async def test_advertising_and_scanning():