From 40ae661ee569e7126eef3a7477935640f820fa12 Mon Sep 17 00:00:00 2001 From: Josh Wu Date: Thu, 30 Nov 2023 12:49:46 +0800 Subject: [PATCH] More SCO support and warnings and typo fix --- bumble/device.py | 11 ++++- bumble/hfp.py | 10 ++-- examples/run_cig_setup.py | 8 +-- examples/run_esco_connection.py | 87 +++++++++++++++++++++++++++++++++ 4 files changed, 109 insertions(+), 7 deletions(-) create mode 100644 examples/run_esco_connection.py diff --git a/bumble/device.py b/bumble/device.py index 75caece..bda761b 100644 --- a/bumble/device.py +++ b/bumble/device.py @@ -3570,6 +3570,7 @@ class Device(CompositeEventEmitter): # [Classic only] @host_event_handler @with_connection_from_address + @experimental('Only for testing.') def on_sco_connection( self, acl_connection: Connection, sco_handle: int, link_type: int ) -> None: @@ -3578,23 +3579,27 @@ class Device(CompositeEventEmitter): f'sco_handle=[0x{sco_handle:04X}], ' f'link_type=[0x{link_type:02X}] ***' ) - self.sco_links[sco_handle] = ScoLink( + sco_link = self.sco_links[sco_handle] = ScoLink( device=self, acl_connection=acl_connection, handle=sco_handle, link_type=link_type, ) + self.emit('sco_connection', sco_link) # [Classic only] @host_event_handler @with_connection_from_address + @experimental('Only for testing.') def on_sco_connection_failure( self, acl_connection: Connection, status: int ) -> None: logger.debug(f'*** SCO connection failure: {acl_connection.peer_address}***') + self.emit('sco_connection_failure') # [Classic only] @host_event_handler + @experimental('Only for testing') def on_sco_packet(self, sco_handle: int, packet: HCI_SynchronousDataPacket) -> None: if sco_link := self.sco_links.get(sco_handle, None): sco_link.emit('pdu', packet) @@ -3602,6 +3607,7 @@ class Device(CompositeEventEmitter): # [LE only] @host_event_handler @with_connection_from_handle + @experimental('Only for testing') def on_cis_request( self, acl_connection: Connection, @@ -3628,6 +3634,7 @@ class Device(CompositeEventEmitter): # [LE only] @host_event_handler + @experimental('Only for testing') def on_cis_establishment(self, cis_handle: int) -> None: cis_link = self.cis_links[cis_handle] cis_link.state = CisLink.State.ESTABLISHED @@ -3647,6 +3654,7 @@ class Device(CompositeEventEmitter): # [LE only] @host_event_handler + @experimental('Only for testing') def on_cis_establishment_failure(self, cis_handle: int, status: int) -> None: logger.debug(f'*** CIS Establishment Failure: cis=[0x{cis_handle:04X}] ***') if cis_link := self.cis_links.pop(cis_handle, None): @@ -3655,6 +3663,7 @@ class Device(CompositeEventEmitter): # [LE only] @host_event_handler + @experimental('Only for testing') def on_iso_packet(self, handle: int, packet: HCI_IsoDataPacket) -> None: if cis_link := self.cis_links.get(handle, None): cis_link.emit('pdu', packet) diff --git a/bumble/hfp.py b/bumble/hfp.py index 42683d5..a655b8f 100644 --- a/bumble/hfp.py +++ b/bumble/hfp.py @@ -850,10 +850,10 @@ class EscoParameters: # Common input_coding_format: HCI_Enhanced_Setup_Synchronous_Connection_Command.CodingFormat = ( - HCI_Enhanced_Setup_Synchronous_Connection_Command.CodingFormat.TRANSPARENT + HCI_Enhanced_Setup_Synchronous_Connection_Command.CodingFormat.PCM ) output_coding_format: HCI_Enhanced_Setup_Synchronous_Connection_Command.CodingFormat = ( - HCI_Enhanced_Setup_Synchronous_Connection_Command.CodingFormat.TRANSPARENT + HCI_Enhanced_Setup_Synchronous_Connection_Command.CodingFormat.PCM ) input_coded_data_size: int = 16 output_coded_data_size: int = 16 @@ -960,6 +960,8 @@ _ESCO_PARAMETERS_MSBC_T1 = EscoParameters( | HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.NO_2_EV5 | HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.NO_3_EV5 ), + input_bandwidth=32000, + output_bandwidth=32000, retransmission_effort=HCI_Enhanced_Setup_Synchronous_Connection_Command.RetransmissionEffort.OPTIMIZE_FOR_QUALITY, ) @@ -974,10 +976,12 @@ _ESCO_PARAMETERS_MSBC_T2 = EscoParameters( | HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.NO_2_EV5 | HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.NO_3_EV5 ), + input_bandwidth=32000, + output_bandwidth=32000, retransmission_effort=HCI_Enhanced_Setup_Synchronous_Connection_Command.RetransmissionEffort.OPTIMIZE_FOR_QUALITY, ) -ESCO_PERAMETERS = { +ESCO_PARAMETERS = { DefaultCodecParameters.SCO_CVSD_D0: _ESCO_PARAMETERS_CVSD_D0, DefaultCodecParameters.SCO_CVSD_D1: _ESCO_PARAMETERS_CVSD_D1, DefaultCodecParameters.ESCO_CVSD_S1: _ESCO_PARAMETERS_CVSD_S1, diff --git a/examples/run_cig_setup.py b/examples/run_cig_setup.py index a7f7260..ff12bfc 100644 --- a/examples/run_cig_setup.py +++ b/examples/run_cig_setup.py @@ -1,4 +1,4 @@ -# Copyright 2021-2022 Google LLC +# Copyright 2021-2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,11 +20,13 @@ import logging import sys import os from bumble.device import ( - HCI_LE_Set_Extended_Advertising_Parameters_Command, Device, Connection, ) -from bumble.hci import OwnAddressType +from bumble.hci import ( + OwnAddressType, + HCI_LE_Set_Extended_Advertising_Parameters_Command, +) from bumble.transport import open_transport_or_link diff --git a/examples/run_esco_connection.py b/examples/run_esco_connection.py new file mode 100644 index 0000000..a136360 --- /dev/null +++ b/examples/run_esco_connection.py @@ -0,0 +1,87 @@ +# Copyright 2021-2022 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 dataclasses +import logging +import sys +import os +from bumble.core import BT_BR_EDR_TRANSPORT +from bumble.device import Device, ScoLink +from bumble.hci import HCI_Enhanced_Setup_Synchronous_Connection_Command +from bumble.hfp import DefaultCodecParameters, ESCO_PARAMETERS + +from bumble.transport import open_transport_or_link + + +# ----------------------------------------------------------------------------- +async def main() -> None: + if len(sys.argv) < 3: + print( + 'Usage: run_esco_connection.py ' + ' ' + ) + print( + 'example: run_esco_connection.py classic1.json' + 'tcp-client:127.0.0.1:6402 tcp-client:127.0.0.1:6402' + ) + return + + print('<<< connecting to HCI...') + hci_transports = await asyncio.gather( + open_transport_or_link(sys.argv[2]), open_transport_or_link(sys.argv[3]) + ) + print('<<< connected') + + devices = [ + Device.from_config_file_with_hci( + sys.argv[1], hci_transport.source, hci_transport.sink + ) + for hci_transport in hci_transports + ] + + devices[0].classic_enabled = True + devices[1].classic_enabled = True + + await asyncio.gather(*[device.power_on() for device in devices]) + + connections = await asyncio.gather( + devices[0].accept(devices[1].public_address), + devices[1].connect(devices[0].public_address, transport=BT_BR_EDR_TRANSPORT), + ) + + def on_sco(sco_link: ScoLink): + connections[0].abort_on('disconnection', sco_link.disconnect()) + + devices[0].once('sco_connection', on_sco) + + await devices[0].send_command( + HCI_Enhanced_Setup_Synchronous_Connection_Command( + connection_handle=connections[0].handle, + **dataclasses.asdict(ESCO_PARAMETERS[DefaultCodecParameters.ESCO_CVSD_S3]) + # type: ignore[call-args] + ) + ) + + await asyncio.gather( + *[hci_transport.source.terminated for hci_transport in hci_transports] + ) + + +# ----------------------------------------------------------------------------- +logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', 'DEBUG').upper()) +asyncio.run(main())