mirror of
https://github.com/google/bumble.git
synced 2026-05-09 04:08:02 +00:00
wip
This commit is contained in:
107
apps/bench.py
107
apps/bench.py
@@ -40,6 +40,8 @@ from bumble.hci import (
|
|||||||
HCI_LE_1M_PHY,
|
HCI_LE_1M_PHY,
|
||||||
HCI_LE_2M_PHY,
|
HCI_LE_2M_PHY,
|
||||||
HCI_LE_CODED_PHY,
|
HCI_LE_CODED_PHY,
|
||||||
|
HCI_CENTRAL_ROLE,
|
||||||
|
HCI_PERIPHERAL_ROLE,
|
||||||
HCI_Constant,
|
HCI_Constant,
|
||||||
HCI_Error,
|
HCI_Error,
|
||||||
HCI_StatusError,
|
HCI_StatusError,
|
||||||
@@ -57,6 +59,7 @@ from bumble.transport import open_transport_or_link
|
|||||||
import bumble.rfcomm
|
import bumble.rfcomm
|
||||||
import bumble.core
|
import bumble.core
|
||||||
from bumble.utils import AsyncRunner
|
from bumble.utils import AsyncRunner
|
||||||
|
from bumble.pairing import PairingConfig
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -128,40 +131,34 @@ def le_phy_name(phy_id):
|
|||||||
|
|
||||||
|
|
||||||
def print_connection(connection):
|
def print_connection(connection):
|
||||||
|
params = []
|
||||||
if connection.transport == BT_LE_TRANSPORT:
|
if connection.transport == BT_LE_TRANSPORT:
|
||||||
phy_state = (
|
params.append(
|
||||||
'PHY='
|
'PHY='
|
||||||
f'TX:{le_phy_name(connection.phy.tx_phy)}/'
|
f'TX:{le_phy_name(connection.phy.tx_phy)}/'
|
||||||
f'RX:{le_phy_name(connection.phy.rx_phy)}'
|
f'RX:{le_phy_name(connection.phy.rx_phy)}'
|
||||||
)
|
)
|
||||||
|
|
||||||
data_length = (
|
params.append(
|
||||||
'DL=('
|
'DL=('
|
||||||
f'TX:{connection.data_length[0]}/{connection.data_length[1]},'
|
f'TX:{connection.data_length[0]}/{connection.data_length[1]},'
|
||||||
f'RX:{connection.data_length[2]}/{connection.data_length[3]}'
|
f'RX:{connection.data_length[2]}/{connection.data_length[3]}'
|
||||||
')'
|
')'
|
||||||
)
|
)
|
||||||
connection_parameters = (
|
|
||||||
|
params.append(
|
||||||
'Parameters='
|
'Parameters='
|
||||||
f'{connection.parameters.connection_interval * 1.25:.2f}/'
|
f'{connection.parameters.connection_interval * 1.25:.2f}/'
|
||||||
f'{connection.parameters.peripheral_latency}/'
|
f'{connection.parameters.peripheral_latency}/'
|
||||||
f'{connection.parameters.supervision_timeout * 10} '
|
f'{connection.parameters.supervision_timeout * 10} '
|
||||||
)
|
)
|
||||||
|
|
||||||
|
params.append(f'MTU={connection.att_mtu}')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
phy_state = ''
|
params.append(f'Role={HCI_Constant.role_name(connection.role)}')
|
||||||
data_length = ''
|
|
||||||
connection_parameters = ''
|
|
||||||
|
|
||||||
mtu = connection.att_mtu
|
logging.info(color('@@@ Connection: ', 'yellow') + ' '.join(params))
|
||||||
|
|
||||||
logging.info(
|
|
||||||
f'{color("@@@ Connection:", "yellow")} '
|
|
||||||
f'{connection_parameters} '
|
|
||||||
f'{data_length} '
|
|
||||||
f'{phy_state} '
|
|
||||||
f'MTU={mtu}'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def make_sdp_records(channel):
|
def make_sdp_records(channel):
|
||||||
@@ -214,6 +211,17 @@ def log_stats(title, stats):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def switch_roles(connection, role):
|
||||||
|
target_role = HCI_CENTRAL_ROLE if role == "central" else HCI_PERIPHERAL_ROLE
|
||||||
|
if connection.role != target_role:
|
||||||
|
logging.info(f'{color("### Switching roles to:", "cyan")} {role}')
|
||||||
|
try:
|
||||||
|
await connection.switch_role(target_role)
|
||||||
|
logging.info(color('### Role switch complete', 'cyan'))
|
||||||
|
except HCI_Error as error:
|
||||||
|
logging.info(f'{color("### Role switch failed:", "red")} {error}')
|
||||||
|
|
||||||
|
|
||||||
class PacketType(enum.IntEnum):
|
class PacketType(enum.IntEnum):
|
||||||
RESET = 0
|
RESET = 0
|
||||||
SEQUENCE = 1
|
SEQUENCE = 1
|
||||||
@@ -1000,6 +1008,8 @@ class RfcommServer(StreamedPacketIO):
|
|||||||
self.max_credits = max_credits
|
self.max_credits = max_credits
|
||||||
self.credits_threshold = credits_threshold
|
self.credits_threshold = credits_threshold
|
||||||
self.dlc = None
|
self.dlc = None
|
||||||
|
self.max_credits = max_credits
|
||||||
|
self.credits_threshold = credits_threshold
|
||||||
self.ready = asyncio.Event()
|
self.ready = asyncio.Event()
|
||||||
|
|
||||||
# Create and register a server
|
# Create and register a server
|
||||||
@@ -1034,6 +1044,10 @@ class RfcommServer(StreamedPacketIO):
|
|||||||
|
|
||||||
def on_dlc(self, dlc):
|
def on_dlc(self, dlc):
|
||||||
logging.info(color(f'*** DLC connected: {dlc}', 'blue'))
|
logging.info(color(f'*** DLC connected: {dlc}', 'blue'))
|
||||||
|
if self.credits_threshold is not None:
|
||||||
|
dlc.rx_threshold = self.credits_threshold
|
||||||
|
if self.max_credits is not None:
|
||||||
|
dlc.rx_max_credits = self.max_credits
|
||||||
dlc.sink = self.on_packet
|
dlc.sink = self.on_packet
|
||||||
self.io_sink = dlc.write
|
self.io_sink = dlc.write
|
||||||
self.dlc = dlc
|
self.dlc = dlc
|
||||||
@@ -1063,6 +1077,7 @@ class Central(Connection.Listener):
|
|||||||
authenticate,
|
authenticate,
|
||||||
encrypt,
|
encrypt,
|
||||||
extended_data_length,
|
extended_data_length,
|
||||||
|
role_switch,
|
||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.transport = transport
|
self.transport = transport
|
||||||
@@ -1073,6 +1088,7 @@ class Central(Connection.Listener):
|
|||||||
self.authenticate = authenticate
|
self.authenticate = authenticate
|
||||||
self.encrypt = encrypt or authenticate
|
self.encrypt = encrypt or authenticate
|
||||||
self.extended_data_length = extended_data_length
|
self.extended_data_length = extended_data_length
|
||||||
|
self.role_switch = role_switch
|
||||||
self.device = None
|
self.device = None
|
||||||
self.connection = None
|
self.connection = None
|
||||||
|
|
||||||
@@ -1123,6 +1139,11 @@ class Central(Connection.Listener):
|
|||||||
role = self.role_factory(mode)
|
role = self.role_factory(mode)
|
||||||
self.device.classic_enabled = self.classic
|
self.device.classic_enabled = self.classic
|
||||||
|
|
||||||
|
# Set up a pairing config factory with minimal requirements.
|
||||||
|
self.device.pairing_config_factory = lambda _: PairingConfig(
|
||||||
|
sc=False, mitm=False, bonding=False
|
||||||
|
)
|
||||||
|
|
||||||
await self.device.power_on()
|
await self.device.power_on()
|
||||||
|
|
||||||
if self.classic:
|
if self.classic:
|
||||||
@@ -1151,6 +1172,10 @@ class Central(Connection.Listener):
|
|||||||
self.connection.listener = self
|
self.connection.listener = self
|
||||||
print_connection(self.connection)
|
print_connection(self.connection)
|
||||||
|
|
||||||
|
# Switch roles if needed.
|
||||||
|
if self.role_switch:
|
||||||
|
await switch_roles(self.connection, self.role_switch)
|
||||||
|
|
||||||
# Wait a bit after the connection, some controllers aren't very good when
|
# Wait a bit after the connection, some controllers aren't very good when
|
||||||
# we start sending data right away while some connection parameters are
|
# we start sending data right away while some connection parameters are
|
||||||
# updated post connection
|
# updated post connection
|
||||||
@@ -1212,20 +1237,30 @@ class Central(Connection.Listener):
|
|||||||
def on_connection_data_length_change(self):
|
def on_connection_data_length_change(self):
|
||||||
print_connection(self.connection)
|
print_connection(self.connection)
|
||||||
|
|
||||||
|
def on_role_change(self):
|
||||||
|
print_connection(self.connection)
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Peripheral
|
# Peripheral
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
class Peripheral(Device.Listener, Connection.Listener):
|
class Peripheral(Device.Listener, Connection.Listener):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, transport, classic, extended_data_length, role_factory, mode_factory
|
self,
|
||||||
|
transport,
|
||||||
|
role_factory,
|
||||||
|
mode_factory,
|
||||||
|
classic,
|
||||||
|
extended_data_length,
|
||||||
|
role_switch,
|
||||||
):
|
):
|
||||||
self.transport = transport
|
self.transport = transport
|
||||||
self.classic = classic
|
self.classic = classic
|
||||||
self.extended_data_length = extended_data_length
|
|
||||||
self.role_factory = role_factory
|
self.role_factory = role_factory
|
||||||
self.role = None
|
|
||||||
self.mode_factory = mode_factory
|
self.mode_factory = mode_factory
|
||||||
|
self.extended_data_length = extended_data_length
|
||||||
|
self.role_switch = role_switch
|
||||||
|
self.role = None
|
||||||
self.mode = None
|
self.mode = None
|
||||||
self.device = None
|
self.device = None
|
||||||
self.connection = None
|
self.connection = None
|
||||||
@@ -1248,6 +1283,11 @@ class Peripheral(Device.Listener, Connection.Listener):
|
|||||||
self.role = self.role_factory(self.mode)
|
self.role = self.role_factory(self.mode)
|
||||||
self.device.classic_enabled = self.classic
|
self.device.classic_enabled = self.classic
|
||||||
|
|
||||||
|
# Set up a pairing config factory with minimal requirements.
|
||||||
|
self.device.pairing_config_factory = lambda _: PairingConfig(
|
||||||
|
sc=False, mitm=False, bonding=False
|
||||||
|
)
|
||||||
|
|
||||||
await self.device.power_on()
|
await self.device.power_on()
|
||||||
|
|
||||||
if self.classic:
|
if self.classic:
|
||||||
@@ -1274,6 +1314,7 @@ class Peripheral(Device.Listener, Connection.Listener):
|
|||||||
|
|
||||||
await self.connected.wait()
|
await self.connected.wait()
|
||||||
logging.info(color('### Connected', 'cyan'))
|
logging.info(color('### Connected', 'cyan'))
|
||||||
|
print_connection(self.connection)
|
||||||
|
|
||||||
await self.mode.on_connection(self.connection)
|
await self.mode.on_connection(self.connection)
|
||||||
await self.role.run()
|
await self.role.run()
|
||||||
@@ -1290,7 +1331,7 @@ class Peripheral(Device.Listener, Connection.Listener):
|
|||||||
AsyncRunner.spawn(self.device.set_connectable(False))
|
AsyncRunner.spawn(self.device.set_connectable(False))
|
||||||
|
|
||||||
# Request a new data length if needed
|
# Request a new data length if needed
|
||||||
if self.extended_data_length:
|
if not self.classic and self.extended_data_length:
|
||||||
logging.info("+++ Requesting extended data length")
|
logging.info("+++ Requesting extended data length")
|
||||||
AsyncRunner.spawn(
|
AsyncRunner.spawn(
|
||||||
connection.set_data_length(
|
connection.set_data_length(
|
||||||
@@ -1298,6 +1339,10 @@ class Peripheral(Device.Listener, Connection.Listener):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Switch roles if needed.
|
||||||
|
if self.role_switch:
|
||||||
|
AsyncRunner.spawn(switch_roles(connection, self.role_switch))
|
||||||
|
|
||||||
def on_disconnection(self, reason):
|
def on_disconnection(self, reason):
|
||||||
logging.info(color(f'!!! Disconnection: reason={reason}', 'red'))
|
logging.info(color(f'!!! Disconnection: reason={reason}', 'red'))
|
||||||
self.connection = None
|
self.connection = None
|
||||||
@@ -1319,6 +1364,9 @@ class Peripheral(Device.Listener, Connection.Listener):
|
|||||||
def on_connection_data_length_change(self):
|
def on_connection_data_length_change(self):
|
||||||
print_connection(self.connection)
|
print_connection(self.connection)
|
||||||
|
|
||||||
|
def on_role_change(self):
|
||||||
|
print_connection(self.connection)
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
def create_mode_factory(ctx, default_mode):
|
def create_mode_factory(ctx, default_mode):
|
||||||
@@ -1448,6 +1496,11 @@ def create_role_factory(ctx, default_role):
|
|||||||
'--extended-data-length',
|
'--extended-data-length',
|
||||||
help='Request a data length upon connection, specified as tx_octets/tx_time',
|
help='Request a data length upon connection, specified as tx_octets/tx_time',
|
||||||
)
|
)
|
||||||
|
@click.option(
|
||||||
|
'--role-switch',
|
||||||
|
type=click.Choice(['central', 'peripheral']),
|
||||||
|
help='Request role switch upon connection (central or peripheral)',
|
||||||
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
'--rfcomm-channel',
|
'--rfcomm-channel',
|
||||||
type=int,
|
type=int,
|
||||||
@@ -1484,6 +1537,11 @@ def create_role_factory(ctx, default_role):
|
|||||||
type=int,
|
type=int,
|
||||||
help='RFComm credits threshold',
|
help='RFComm credits threshold',
|
||||||
)
|
)
|
||||||
|
@click.option(
|
||||||
|
'--rfcomm-send-credits-threshold',
|
||||||
|
type=int,
|
||||||
|
help='RFComm send credits threshold',
|
||||||
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
'--l2cap-psm',
|
'--l2cap-psm',
|
||||||
type=int,
|
type=int,
|
||||||
@@ -1512,7 +1570,7 @@ def create_role_factory(ctx, default_role):
|
|||||||
'--packet-size',
|
'--packet-size',
|
||||||
'-s',
|
'-s',
|
||||||
metavar='SIZE',
|
metavar='SIZE',
|
||||||
type=click.IntRange(8, 4096),
|
type=click.IntRange(8, 8192),
|
||||||
default=500,
|
default=500,
|
||||||
help='Packet size (client or ping role)',
|
help='Packet size (client or ping role)',
|
||||||
)
|
)
|
||||||
@@ -1572,6 +1630,7 @@ def bench(
|
|||||||
mode,
|
mode,
|
||||||
att_mtu,
|
att_mtu,
|
||||||
extended_data_length,
|
extended_data_length,
|
||||||
|
role_switch,
|
||||||
packet_size,
|
packet_size,
|
||||||
packet_count,
|
packet_count,
|
||||||
start_delay,
|
start_delay,
|
||||||
@@ -1614,12 +1673,12 @@ def bench(
|
|||||||
ctx.obj['repeat_delay'] = repeat_delay
|
ctx.obj['repeat_delay'] = repeat_delay
|
||||||
ctx.obj['pace'] = pace
|
ctx.obj['pace'] = pace
|
||||||
ctx.obj['linger'] = linger
|
ctx.obj['linger'] = linger
|
||||||
|
|
||||||
ctx.obj['extended_data_length'] = (
|
ctx.obj['extended_data_length'] = (
|
||||||
[int(x) for x in extended_data_length.split('/')]
|
[int(x) for x in extended_data_length.split('/')]
|
||||||
if extended_data_length
|
if extended_data_length
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
|
ctx.obj['role_switch'] = role_switch
|
||||||
ctx.obj['classic'] = mode in ('rfcomm-client', 'rfcomm-server')
|
ctx.obj['classic'] = mode in ('rfcomm-client', 'rfcomm-server')
|
||||||
|
|
||||||
|
|
||||||
@@ -1663,6 +1722,7 @@ def central(
|
|||||||
authenticate,
|
authenticate,
|
||||||
encrypt or authenticate,
|
encrypt or authenticate,
|
||||||
ctx.obj['extended_data_length'],
|
ctx.obj['extended_data_length'],
|
||||||
|
ctx.obj['role_switch'],
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
asyncio.run(run_central())
|
asyncio.run(run_central())
|
||||||
@@ -1679,10 +1739,11 @@ def peripheral(ctx, transport):
|
|||||||
async def run_peripheral():
|
async def run_peripheral():
|
||||||
await Peripheral(
|
await Peripheral(
|
||||||
transport,
|
transport,
|
||||||
ctx.obj['classic'],
|
|
||||||
ctx.obj['extended_data_length'],
|
|
||||||
role_factory,
|
role_factory,
|
||||||
mode_factory,
|
mode_factory,
|
||||||
|
ctx.obj['classic'],
|
||||||
|
ctx.obj['extended_data_length'],
|
||||||
|
ctx.obj['role_switch'],
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
asyncio.run(run_peripheral())
|
asyncio.run(run_peripheral())
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ from bumble.colors import color
|
|||||||
from bumble.core import name_or_number
|
from bumble.core import name_or_number
|
||||||
from bumble.hci import (
|
from bumble.hci import (
|
||||||
map_null_terminated_utf8_string,
|
map_null_terminated_utf8_string,
|
||||||
LeFeatureMask,
|
LeFeature,
|
||||||
HCI_SUCCESS,
|
HCI_SUCCESS,
|
||||||
HCI_VERSION_NAMES,
|
HCI_VERSION_NAMES,
|
||||||
LMP_VERSION_NAMES,
|
LMP_VERSION_NAMES,
|
||||||
@@ -140,7 +140,7 @@ async def get_le_info(host: Host) -> None:
|
|||||||
|
|
||||||
print(color('LE Features:', 'yellow'))
|
print(color('LE Features:', 'yellow'))
|
||||||
for feature in host.supported_le_features:
|
for feature in host.supported_le_features:
|
||||||
print(LeFeatureMask(feature).name)
|
print(f' {LeFeature(feature).name}')
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -224,7 +224,7 @@ async def async_main(latency_probes, transport):
|
|||||||
print()
|
print()
|
||||||
print(color('Supported Commands:', 'yellow'))
|
print(color('Supported Commands:', 'yellow'))
|
||||||
for command in host.supported_commands:
|
for command in host.supported_commands:
|
||||||
print(' ', HCI_Command.command_name(command))
|
print(f' {HCI_Command.command_name(command)}')
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ import struct
|
|||||||
from typing import Any, Callable, Dict, Iterable, List, Optional, Type, Union, ClassVar
|
from typing import Any, Callable, Dict, Iterable, List, Optional, Type, Union, ClassVar
|
||||||
|
|
||||||
from bumble import crypto
|
from bumble import crypto
|
||||||
from .colors import color
|
from bumble.colors import color
|
||||||
from .core import (
|
from bumble.core import (
|
||||||
BT_BR_EDR_TRANSPORT,
|
BT_BR_EDR_TRANSPORT,
|
||||||
AdvertisingData,
|
AdvertisingData,
|
||||||
DeviceClass,
|
DeviceClass,
|
||||||
@@ -36,6 +36,7 @@ from .core import (
|
|||||||
name_or_number,
|
name_or_number,
|
||||||
padded_bytes,
|
padded_bytes,
|
||||||
)
|
)
|
||||||
|
from bumble.utils import OpenIntEnum
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -1104,7 +1105,7 @@ HCI_SUPPORTED_COMMANDS_MASKS = {
|
|||||||
|
|
||||||
# LE Supported Features
|
# LE Supported Features
|
||||||
# See Bluetooth spec @ Vol 6, Part B, 4.6 FEATURE SUPPORT
|
# See Bluetooth spec @ Vol 6, Part B, 4.6 FEATURE SUPPORT
|
||||||
class LeFeature(enum.IntEnum):
|
class LeFeature(OpenIntEnum):
|
||||||
LE_ENCRYPTION = 0
|
LE_ENCRYPTION = 0
|
||||||
CONNECTION_PARAMETERS_REQUEST_PROCEDURE = 1
|
CONNECTION_PARAMETERS_REQUEST_PROCEDURE = 1
|
||||||
EXTENDED_REJECT_INDICATION = 2
|
EXTENDED_REJECT_INDICATION = 2
|
||||||
|
|||||||
@@ -734,7 +734,13 @@ class DLC(EventEmitter):
|
|||||||
self.emit('close')
|
self.emit('close')
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f'DLC(dlci={self.dlci},state={self.state.name})'
|
return (
|
||||||
|
f'DLC(dlci={self.dlci}, '
|
||||||
|
f'state={self.state.name}, '
|
||||||
|
f'max_frame_size={self.max_frame_size}, '
|
||||||
|
f'window_size={self.window_size}'
|
||||||
|
')'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user