Add connection parameter update from peripheral
diff --git a/bumble/core.py b/bumble/core.py
index b00c40e..4dff432 100644
--- a/bumble/core.py
+++ b/bumble/core.py
@@ -142,6 +142,10 @@
         self.peer_address = peer_address
 
 
+class ConnectionParameterUpdateError(BaseError):
+    """Connection Parameter Update Error"""
+
+
 # -----------------------------------------------------------------------------
 # UUID
 #
diff --git a/bumble/device.py b/bumble/device.py
index fca2e7a..9a784e7 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 @@
         connection_interval_max,
         max_latency,
         supervision_timeout,
+        use_l2cap=False,
     ):
         return await self.device.update_connection_parameters(
             self,
@@ -730,6 +732,7 @@
             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 @@
         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 @@
                 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 270d909..fea8a1d 100644
--- a/bumble/l2cap.py
+++ b/bumble/l2cap.py
@@ -1387,6 +1387,7 @@
     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 @@
         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 @@
                 ),
             )
 
+    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