From 4fc13585cc7aaceed2fc9493d31a0668dd52c013 Mon Sep 17 00:00:00 2001 From: Josh Wu Date: Fri, 17 Mar 2023 22:58:03 +0800 Subject: [PATCH 1/2] Handle BR/EDR connection roles --- bumble/device.py | 10 +++++++++- bumble/host.py | 29 +++++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/bumble/device.py b/bumble/device.py index 512bb1d..eec3685 100644 --- a/bumble/device.py +++ b/bumble/device.py @@ -615,7 +615,9 @@ class Connection(CompositeEventEmitter): assert self.transport == BT_BR_EDR_TRANSPORT self.handle = handle self.peer_resolvable_address = peer_resolvable_address - self.role = role + # Quirk: role might be known before complete + if self.role is None: + self.role = role self.parameters = parameters @property @@ -2905,6 +2907,12 @@ class Device(CompositeEventEmitter): ) connection.emit('connection_data_length_change') + # [Classic only] + @host_event_handler + @with_connection_from_address + def on_role_change(self, connection, new_role): + connection.role = new_role + @with_connection_from_handle def on_pairing_start(self, connection): connection.emit('pairing_start') diff --git a/bumble/host.py b/bumble/host.py index 87ec610..96391a1 100644 --- a/bumble/host.py +++ b/bumble/host.py @@ -24,7 +24,10 @@ from bumble.colors import color from bumble.l2cap import L2CAP_PDU from bumble.snoop import Snooper +from typing import Optional + from .hci import ( + Address, HCI_ACL_DATA_PACKET, HCI_COMMAND_COMPLETE_EVENT, HCI_COMMAND_PACKET, @@ -142,6 +145,24 @@ class Host(AbortableEventEmitter): if controller_sink: self.set_packet_sink(controller_sink) + def find_connection_by_bd_addr( + self, + bd_addr: Address, + transport: Optional[int] = None, + check_address_type: bool = False, + ) -> Optional[Connection]: + for connection in self.connections.values(): + if connection.peer_address.to_bytes() == bd_addr.to_bytes(): + if ( + check_address_type + and connection.peer_address.address_type != bd_addr.address_type + ): + continue + if transport is None or connection.transport == transport: + return connection + + return None + async def flush(self) -> None: # Make sure no command is pending await self.command_semaphore.acquire() @@ -582,7 +603,7 @@ class Host(AbortableEventEmitter): BT_BR_EDR_TRANSPORT, event.bd_addr, None, - BT_CENTRAL_ROLE, + role, None, ) else: @@ -719,7 +740,11 @@ class Host(AbortableEventEmitter): f'role change for {event.bd_addr}: ' f'{HCI_Constant.role_name(event.new_role)}' ) - # TODO: lookup the connection and update the role + if connection := self.find_connection_by_bd_addr( + event.bd_addr, BT_BR_EDR_TRANSPORT + ): + connection.role = event.new_role + self.emit('role_change', event.bd_addr, event.new_role) else: logger.debug( f'role change for {event.bd_addr} failed: ' From 9e1358536b6b3529e32d1d35d25b7161bfc3f935 Mon Sep 17 00:00:00 2001 From: Josh Wu Date: Mon, 20 Mar 2023 15:23:52 +0800 Subject: [PATCH 2/2] Add switch_role --- bumble/device.py | 41 +++++++++++++++++++++++++++++++++++++++++ bumble/host.py | 5 +++-- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/bumble/device.py b/bumble/device.py index eec3685..c0e2f90 100644 --- a/bumble/device.py +++ b/bumble/device.py @@ -98,6 +98,7 @@ from .hci import ( HCI_Read_RSSI_Command, HCI_Reject_Connection_Request_Command, HCI_Remote_Name_Request_Command, + HCI_Switch_Role_Command, HCI_Set_Connection_Encryption_Command, HCI_StatusError, HCI_User_Confirmation_Request_Negative_Reply_Command, @@ -665,6 +666,9 @@ class Connection(CompositeEventEmitter): async def encrypt(self, enable: bool = True) -> None: return await self.device.encrypt(self, enable) + async def switch_role(self, role: int) -> None: + return await self.device.switch_role(self, role) + async def sustain(self, timeout=None): """Idles the current task waiting for a disconnect or timeout""" @@ -2291,6 +2295,34 @@ class Device(CompositeEventEmitter): 'connection_encryption_failure', on_encryption_failure ) + # [Classic only] + async def switch_role(self, connection: Connection, role: int): + pending_role_change = asyncio.get_running_loop().create_future() + + def on_role_change(new_role): + pending_role_change.set_result(new_role) + + def on_role_change_failure(error_code): + pending_role_change.set_exception(HCI_Error(error_code)) + + connection.on('role_change', on_role_change) + connection.on('role_change_failure', on_role_change_failure) + + try: + result = await self.send_command( + HCI_Switch_Role_Command(bd_addr=connection.peer_address, role=role) # type: ignore[call-arg] + ) + if result.status != HCI_COMMAND_STATUS_PENDING: + logger.warning( + 'HCI_Switch_Role_Command failed: ' + f'{HCI_Constant.error_name(result.status)}' + ) + raise HCI_StatusError(result) + await connection.abort_on('disconnection', pending_role_change) + finally: + connection.remove_listener('role_change', on_role_change) + connection.remove_listener('role_change_failure', on_role_change_failure) + # [Classic only] async def request_remote_name(self, remote: Union[Address, Connection]) -> str: # Set up event handlers @@ -2912,6 +2944,15 @@ class Device(CompositeEventEmitter): @with_connection_from_address def on_role_change(self, connection, new_role): connection.role = new_role + connection.emit('role_change', new_role) + + # [Classic only] + @host_event_handler + @try_with_connection_from_address + def on_role_change_failure(self, connection, address, error): + if connection: + connection.emit('role_change_failure', error) + self.emit('role_change_failure', address, error) @with_connection_from_handle def on_pairing_start(self, connection): diff --git a/bumble/host.py b/bumble/host.py index 96391a1..1cdf7da 100644 --- a/bumble/host.py +++ b/bumble/host.py @@ -603,7 +603,7 @@ class Host(AbortableEventEmitter): BT_BR_EDR_TRANSPORT, event.bd_addr, None, - role, + BT_CENTRAL_ROLE, None, ) else: @@ -744,12 +744,13 @@ class Host(AbortableEventEmitter): event.bd_addr, BT_BR_EDR_TRANSPORT ): connection.role = event.new_role - self.emit('role_change', event.bd_addr, event.new_role) + self.emit('role_change', event.bd_addr, event.new_role) else: logger.debug( f'role change for {event.bd_addr} failed: ' f'{HCI_Constant.error_name(event.status)}' ) + self.emit('role_change_failure', event.bd_addr, event.status) def on_hci_le_data_length_change_event(self, event): self.emit(