diff --git a/bumble/core.py b/bumble/core.py index b00c40ee..4dff4325 100644 --- a/bumble/core.py +++ b/bumble/core.py @@ -142,6 +142,10 @@ def __init__( self.peer_address = peer_address +class ConnectionParameterUpdateError(BaseError): + """Connection Parameter Update Error""" + + # ----------------------------------------------------------------------------- # UUID # diff --git a/bumble/device.py b/bumble/device.py index fca2e7a3..9a784e72 100644 --- a/bumble/device.py +++ b/bumble/device.py @@ -141,6 +141,7 @@ BT_LE_TRANSPORT, BT_PERIPHERAL_ROLE, AdvertisingData, + ConnectionParameterUpdateError, CommandTimeoutError, ConnectionPHY, InvalidStateError, @@ -723,6 +724,7 @@ async def update_parameters( connection_interval_max, max_latency, supervision_timeout, + use_l2cap=False, ): return await self.device.update_connection_parameters( self, @@ -730,6 +732,7 @@ async def update_parameters( connection_interval_max, max_latency, supervision_timeout, + use_l2cap=use_l2cap, ) async def set_phy(self, tx_phys=None, rx_phys=None, phy_options=None): @@ -2110,11 +2113,30 @@ async def update_connection_parameters( supervision_timeout, min_ce_length=0, max_ce_length=0, - ): + use_l2cap=False, + ) -> None: ''' NOTE: the name of the parameters may look odd, but it just follows the names used in the Bluetooth spec. ''' + + if use_l2cap: + if connection.role != BT_PERIPHERAL_ROLE: + raise InvalidStateError( + 'only peripheral can update connection parameters with l2cap' + ) + l2cap_result = ( + await self.l2cap_channel_manager.update_connection_parameters( + connection, + connection_interval_min, + connection_interval_max, + max_latency, + supervision_timeout, + ) + ) + if l2cap_result != l2cap.L2CAP_CONNECTION_PARAMETERS_ACCEPTED_RESULT: + raise ConnectionParameterUpdateError(l2cap_result) + result = await self.send_command( HCI_LE_Connection_Update_Command( connection_handle=connection.handle, @@ -2124,7 +2146,7 @@ async def update_connection_parameters( supervision_timeout=supervision_timeout, min_ce_length=min_ce_length, max_ce_length=max_ce_length, - ) + ) # type: ignore[call-arg] ) if result.status != HCI_Command_Status_Event.PENDING: raise HCI_StatusError(result) diff --git a/bumble/l2cap.py b/bumble/l2cap.py index 270d9098..fea8a1d6 100644 --- a/bumble/l2cap.py +++ b/bumble/l2cap.py @@ -1387,6 +1387,7 @@ class ChannelManager: le_coc_requests: Dict[int, L2CAP_LE_Credit_Based_Connection_Request] fixed_channels: Dict[int, Optional[Callable[[int, bytes], Any]]] _host: Optional[Host] + connection_parameters_update_response: Optional[asyncio.Future[int]] def __init__( self, @@ -1408,6 +1409,7 @@ def __init__( self.le_coc_requests = {} # LE CoC connection requests, by identifier self.extended_features = extended_features self.connectionless_mtu = connectionless_mtu + self.connection_parameters_update_response = None @property def host(self) -> Host: @@ -1865,11 +1867,45 @@ def on_l2cap_connection_parameter_update_request( ), ) + async def update_connection_parameters( + self, + connection: Connection, + interval_min: int, + interval_max: int, + latency: int, + timeout: int, + ) -> int: + # Check that there isn't already a request pending + if self.connection_parameters_update_response: + raise InvalidStateError('request already pending') + self.connection_parameters_update_response = ( + asyncio.get_running_loop().create_future() + ) + self.send_control_frame( + connection, + L2CAP_LE_SIGNALING_CID, + L2CAP_Connection_Parameter_Update_Request( + interval_min=interval_min, + interval_max=interval_max, + latency=latency, + timeout=timeout, + ), + ) + return await self.connection_parameters_update_response + def on_l2cap_connection_parameter_update_response( self, connection: Connection, cid: int, response ) -> None: - # TODO: check response - pass + if self.connection_parameters_update_response: + self.connection_parameters_update_response.set_result(response.result) + self.connection_parameters_update_response = None + else: + logger.warning( + color( + 'received l2cap_connection_parameter_update_response without a pending request', + 'red', + ) + ) def on_l2cap_le_credit_based_connection_request( self, connection: Connection, cid: int, request