| # Copyright 2026 Google LLC |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # https://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| # ----------------------------------------------------------------------------- |
| # Imports |
| # ----------------------------------------------------------------------------- |
| import asyncio |
| import sys |
| from collections.abc import Callable |
| |
| import bumble.logging |
| from bumble.core import BaseError |
| from bumble.device import Connection, Device |
| from bumble.hci import Address, LeFeatureMask |
| from bumble.transport import open_transport |
| |
| # ----------------------------------------------------------------------------- |
| DEFAULT_CENTRAL_ADDRESS = Address("F0:F0:F0:F0:F0:F0") |
| DEFAULT_PERIPHERAL_ADDRESS = Address("F1:F1:F1:F1:F1:F1") |
| |
| |
| # ----------------------------------------------------------------------------- |
| async def run_as_central( |
| device: Device, |
| scenario: Callable | None, |
| ) -> None: |
| # Connect to the peripheral |
| print(f'=== Connecting to {DEFAULT_PERIPHERAL_ADDRESS}...') |
| connection = await device.connect(DEFAULT_PERIPHERAL_ADDRESS) |
| print("=== Connected") |
| |
| if scenario is not None: |
| await asyncio.sleep(1) |
| await scenario(connection) |
| |
| await asyncio.get_running_loop().create_future() |
| |
| |
| async def run_as_peripheral(device: Device, scenario: Callable | None) -> None: |
| # Wait for a connection from the central |
| print(f'=== Advertising as {DEFAULT_PERIPHERAL_ADDRESS}...') |
| await device.start_advertising(auto_restart=True) |
| |
| async def on_connection(connection: Connection) -> None: |
| assert scenario is not None |
| await asyncio.sleep(1) |
| await scenario(connection) |
| |
| if scenario is not None: |
| device.on(Device.EVENT_CONNECTION, on_connection) |
| |
| await asyncio.get_running_loop().create_future() |
| |
| |
| async def change_parameters( |
| connection: Connection, |
| parameter_request_procedure_supported: bool, |
| subrating_supported: bool, |
| shorter_connection_intervals_supported: bool, |
| ) -> None: |
| if parameter_request_procedure_supported: |
| try: |
| print(">>> update_parameters(7.5, 200, 0, 4000)") |
| await connection.update_parameters(7.5, 200, 0, 4000) |
| await asyncio.sleep(3) |
| except BaseError as error: |
| print(f"Error: {error}") |
| |
| if subrating_supported: |
| try: |
| print(">>> update_subrate(1, 2, 2, 1, 4000)") |
| await connection.update_subrate(1, 2, 2, 1, 4000) |
| await asyncio.sleep(3) |
| except BaseError as error: |
| print(f"Error: {error}") |
| |
| if shorter_connection_intervals_supported: |
| try: |
| print( |
| ">>> update_parameters_with_subrate(7.5, 200, 1, 1, 0, 0, 4000, 5, 1000)" |
| ) |
| await connection.update_parameters_with_subrate( |
| 7.5, 200, 1, 1, 0, 0, 4000, 5, 1000 |
| ) |
| await asyncio.sleep(3) |
| except BaseError as error: |
| print(f"Error: {error}") |
| |
| try: |
| print( |
| ">>> update_parameters_with_subrate(0.750, 5, 1, 1, 0, 0, 4000, 0.125, 1000)" |
| ) |
| await connection.update_parameters_with_subrate( |
| 0.750, 5, 1, 1, 0, 0, 4000, 0.125, 1000 |
| ) |
| await asyncio.sleep(3) |
| except BaseError as error: |
| print(f"Error: {error}") |
| |
| print(">>> done") |
| |
| |
| def on_connection(connection: Connection) -> None: |
| print(f"+++ Connection established: {connection}") |
| |
| def on_le_remote_features_change() -> None: |
| print(f'... LE Remote Features change: {connection.peer_le_features.name}') |
| |
| connection.on( |
| connection.EVENT_LE_REMOTE_FEATURES_CHANGE, on_le_remote_features_change |
| ) |
| |
| def on_connection_parameters_change() -> None: |
| print(f'... LE Connection Parameters change: {connection.parameters}') |
| |
| connection.on( |
| connection.EVENT_CONNECTION_PARAMETERS_UPDATE, on_connection_parameters_change |
| ) |
| |
| |
| async def main() -> None: |
| if len(sys.argv) < 3: |
| print( |
| 'Usage: run_connection_updates.py <transport-spec> ' |
| 'central|peripheral initiator|responder' |
| ) |
| return |
| |
| print('<<< connecting to HCI...') |
| async with await open_transport(sys.argv[1]) as hci_transport: |
| print('<<< connected') |
| |
| role = sys.argv[2] |
| direction = sys.argv[3] |
| device = Device.with_hci( |
| role, |
| ( |
| DEFAULT_CENTRAL_ADDRESS |
| if role == "central" |
| else DEFAULT_PERIPHERAL_ADDRESS |
| ), |
| hci_transport.source, |
| hci_transport.sink, |
| ) |
| device.le_subrate_enabled = True |
| device.le_shorter_connection_intervals_enabled = True |
| await device.power_on() |
| |
| parameter_request_procedure_supported = device.supports_le_features( |
| LeFeatureMask.CONNECTION_PARAMETERS_REQUEST_PROCEDURE |
| ) |
| print( |
| "Parameters Request Procedure supported: " |
| f"{parameter_request_procedure_supported}" |
| ) |
| |
| subrating_supported = device.supports_le_features( |
| LeFeatureMask.CONNECTION_SUBRATING |
| ) |
| print(f"Subrating supported: {subrating_supported}") |
| |
| shorter_connection_intervals_supported = device.supports_le_features( |
| LeFeatureMask.SHORTER_CONNECTION_INTERVALS |
| ) |
| print( |
| "Shorter Connection Intervals supported: " |
| f"{shorter_connection_intervals_supported}" |
| ) |
| |
| device.on(Device.EVENT_CONNECTION, on_connection) |
| |
| async def run(connection: Connection) -> None: |
| await change_parameters( |
| connection, |
| parameter_request_procedure_supported, |
| subrating_supported, |
| shorter_connection_intervals_supported, |
| ) |
| |
| scenario = run if direction == "initiator" else None |
| |
| if role == "central": |
| await run_as_central(device, scenario) |
| else: |
| await run_as_peripheral(device, scenario) |
| |
| |
| # ----------------------------------------------------------------------------- |
| bumble.logging.setup_basic_logging('DEBUG') |
| asyncio.run(main()) |