bumble 0.0.193__py3-none-any.whl → 0.0.195__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bumble/_version.py +2 -2
- bumble/apps/auracast.py +407 -0
- bumble/apps/bench.py +146 -35
- bumble/apps/controller_info.py +3 -3
- bumble/apps/rfcomm_bridge.py +511 -0
- bumble/core.py +689 -115
- bumble/device.py +441 -12
- bumble/hci.py +250 -12
- bumble/host.py +25 -0
- bumble/l2cap.py +5 -2
- bumble/pandora/host.py +3 -2
- bumble/profiles/bap.py +101 -5
- bumble/profiles/le_audio.py +49 -0
- bumble/profiles/pbp.py +46 -0
- bumble/rfcomm.py +158 -61
- bumble/sdp.py +1 -1
- {bumble-0.0.193.dist-info → bumble-0.0.195.dist-info}/METADATA +1 -1
- {bumble-0.0.193.dist-info → bumble-0.0.195.dist-info}/RECORD +22 -18
- {bumble-0.0.193.dist-info → bumble-0.0.195.dist-info}/entry_points.txt +1 -0
- {bumble-0.0.193.dist-info → bumble-0.0.195.dist-info}/LICENSE +0 -0
- {bumble-0.0.193.dist-info → bumble-0.0.195.dist-info}/WHEEL +0 -0
- {bumble-0.0.193.dist-info → bumble-0.0.195.dist-info}/top_level.txt +0 -0
bumble/apps/bench.py
CHANGED
|
@@ -40,6 +40,8 @@ from bumble.hci import (
|
|
|
40
40
|
HCI_LE_1M_PHY,
|
|
41
41
|
HCI_LE_2M_PHY,
|
|
42
42
|
HCI_LE_CODED_PHY,
|
|
43
|
+
HCI_CENTRAL_ROLE,
|
|
44
|
+
HCI_PERIPHERAL_ROLE,
|
|
43
45
|
HCI_Constant,
|
|
44
46
|
HCI_Error,
|
|
45
47
|
HCI_StatusError,
|
|
@@ -57,6 +59,7 @@ from bumble.transport import open_transport_or_link
|
|
|
57
59
|
import bumble.rfcomm
|
|
58
60
|
import bumble.core
|
|
59
61
|
from bumble.utils import AsyncRunner
|
|
62
|
+
from bumble.pairing import PairingConfig
|
|
60
63
|
|
|
61
64
|
|
|
62
65
|
# -----------------------------------------------------------------------------
|
|
@@ -128,40 +131,34 @@ def le_phy_name(phy_id):
|
|
|
128
131
|
|
|
129
132
|
|
|
130
133
|
def print_connection(connection):
|
|
134
|
+
params = []
|
|
131
135
|
if connection.transport == BT_LE_TRANSPORT:
|
|
132
|
-
|
|
136
|
+
params.append(
|
|
133
137
|
'PHY='
|
|
134
138
|
f'TX:{le_phy_name(connection.phy.tx_phy)}/'
|
|
135
139
|
f'RX:{le_phy_name(connection.phy.rx_phy)}'
|
|
136
140
|
)
|
|
137
141
|
|
|
138
|
-
|
|
142
|
+
params.append(
|
|
139
143
|
'DL=('
|
|
140
144
|
f'TX:{connection.data_length[0]}/{connection.data_length[1]},'
|
|
141
145
|
f'RX:{connection.data_length[2]}/{connection.data_length[3]}'
|
|
142
146
|
')'
|
|
143
147
|
)
|
|
144
|
-
|
|
148
|
+
|
|
149
|
+
params.append(
|
|
145
150
|
'Parameters='
|
|
146
151
|
f'{connection.parameters.connection_interval * 1.25:.2f}/'
|
|
147
152
|
f'{connection.parameters.peripheral_latency}/'
|
|
148
153
|
f'{connection.parameters.supervision_timeout * 10} '
|
|
149
154
|
)
|
|
150
155
|
|
|
156
|
+
params.append(f'MTU={connection.att_mtu}')
|
|
157
|
+
|
|
151
158
|
else:
|
|
152
|
-
|
|
153
|
-
data_length = ''
|
|
154
|
-
connection_parameters = ''
|
|
159
|
+
params.append(f'Role={HCI_Constant.role_name(connection.role)}')
|
|
155
160
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
logging.info(
|
|
159
|
-
f'{color("@@@ Connection:", "yellow")} '
|
|
160
|
-
f'{connection_parameters} '
|
|
161
|
-
f'{data_length} '
|
|
162
|
-
f'{phy_state} '
|
|
163
|
-
f'MTU={mtu}'
|
|
164
|
-
)
|
|
161
|
+
logging.info(color('@@@ Connection: ', 'yellow') + ' '.join(params))
|
|
165
162
|
|
|
166
163
|
|
|
167
164
|
def make_sdp_records(channel):
|
|
@@ -214,6 +211,17 @@ def log_stats(title, stats):
|
|
|
214
211
|
)
|
|
215
212
|
|
|
216
213
|
|
|
214
|
+
async def switch_roles(connection, role):
|
|
215
|
+
target_role = HCI_CENTRAL_ROLE if role == "central" else HCI_PERIPHERAL_ROLE
|
|
216
|
+
if connection.role != target_role:
|
|
217
|
+
logging.info(f'{color("### Switching roles to:", "cyan")} {role}')
|
|
218
|
+
try:
|
|
219
|
+
await connection.switch_role(target_role)
|
|
220
|
+
logging.info(color('### Role switch complete', 'cyan'))
|
|
221
|
+
except HCI_Error as error:
|
|
222
|
+
logging.info(f'{color("### Role switch failed:", "red")} {error}')
|
|
223
|
+
|
|
224
|
+
|
|
217
225
|
class PacketType(enum.IntEnum):
|
|
218
226
|
RESET = 0
|
|
219
227
|
SEQUENCE = 1
|
|
@@ -899,14 +907,26 @@ class L2capServer(StreamedPacketIO):
|
|
|
899
907
|
# RfcommClient
|
|
900
908
|
# -----------------------------------------------------------------------------
|
|
901
909
|
class RfcommClient(StreamedPacketIO):
|
|
902
|
-
def __init__(
|
|
910
|
+
def __init__(
|
|
911
|
+
self,
|
|
912
|
+
device,
|
|
913
|
+
channel,
|
|
914
|
+
uuid,
|
|
915
|
+
l2cap_mtu,
|
|
916
|
+
max_frame_size,
|
|
917
|
+
initial_credits,
|
|
918
|
+
max_credits,
|
|
919
|
+
credits_threshold,
|
|
920
|
+
):
|
|
903
921
|
super().__init__()
|
|
904
922
|
self.device = device
|
|
905
923
|
self.channel = channel
|
|
906
924
|
self.uuid = uuid
|
|
907
925
|
self.l2cap_mtu = l2cap_mtu
|
|
908
926
|
self.max_frame_size = max_frame_size
|
|
909
|
-
self.
|
|
927
|
+
self.initial_credits = initial_credits
|
|
928
|
+
self.max_credits = max_credits
|
|
929
|
+
self.credits_threshold = credits_threshold
|
|
910
930
|
self.rfcomm_session = None
|
|
911
931
|
self.ready = asyncio.Event()
|
|
912
932
|
|
|
@@ -940,12 +960,17 @@ class RfcommClient(StreamedPacketIO):
|
|
|
940
960
|
logging.info(color(f'### Opening session for channel {channel}...', 'yellow'))
|
|
941
961
|
try:
|
|
942
962
|
dlc_options = {}
|
|
943
|
-
if self.max_frame_size:
|
|
963
|
+
if self.max_frame_size is not None:
|
|
944
964
|
dlc_options['max_frame_size'] = self.max_frame_size
|
|
945
|
-
if self.
|
|
946
|
-
dlc_options['
|
|
965
|
+
if self.initial_credits is not None:
|
|
966
|
+
dlc_options['initial_credits'] = self.initial_credits
|
|
947
967
|
rfcomm_session = await rfcomm_mux.open_dlc(channel, **dlc_options)
|
|
948
968
|
logging.info(color(f'### Session open: {rfcomm_session}', 'yellow'))
|
|
969
|
+
if self.max_credits is not None:
|
|
970
|
+
rfcomm_session.rx_max_credits = self.max_credits
|
|
971
|
+
if self.credits_threshold is not None:
|
|
972
|
+
rfcomm_session.rx_credits_threshold = self.credits_threshold
|
|
973
|
+
|
|
949
974
|
except bumble.core.ConnectionError as error:
|
|
950
975
|
logging.info(color(f'!!! Session open failed: {error}', 'red'))
|
|
951
976
|
await rfcomm_mux.disconnect()
|
|
@@ -969,8 +994,19 @@ class RfcommClient(StreamedPacketIO):
|
|
|
969
994
|
# RfcommServer
|
|
970
995
|
# -----------------------------------------------------------------------------
|
|
971
996
|
class RfcommServer(StreamedPacketIO):
|
|
972
|
-
def __init__(
|
|
997
|
+
def __init__(
|
|
998
|
+
self,
|
|
999
|
+
device,
|
|
1000
|
+
channel,
|
|
1001
|
+
l2cap_mtu,
|
|
1002
|
+
max_frame_size,
|
|
1003
|
+
initial_credits,
|
|
1004
|
+
max_credits,
|
|
1005
|
+
credits_threshold,
|
|
1006
|
+
):
|
|
973
1007
|
super().__init__()
|
|
1008
|
+
self.max_credits = max_credits
|
|
1009
|
+
self.credits_threshold = credits_threshold
|
|
974
1010
|
self.dlc = None
|
|
975
1011
|
self.ready = asyncio.Event()
|
|
976
1012
|
|
|
@@ -981,7 +1017,12 @@ class RfcommServer(StreamedPacketIO):
|
|
|
981
1017
|
rfcomm_server = bumble.rfcomm.Server(device, **server_options)
|
|
982
1018
|
|
|
983
1019
|
# Listen for incoming DLC connections
|
|
984
|
-
|
|
1020
|
+
dlc_options = {}
|
|
1021
|
+
if max_frame_size is not None:
|
|
1022
|
+
dlc_options['max_frame_size'] = max_frame_size
|
|
1023
|
+
if initial_credits is not None:
|
|
1024
|
+
dlc_options['initial_credits'] = initial_credits
|
|
1025
|
+
channel_number = rfcomm_server.listen(self.on_dlc, channel, **dlc_options)
|
|
985
1026
|
|
|
986
1027
|
# Setup the SDP to advertise this channel
|
|
987
1028
|
device.sdp_service_records = make_sdp_records(channel_number)
|
|
@@ -1001,9 +1042,17 @@ class RfcommServer(StreamedPacketIO):
|
|
|
1001
1042
|
|
|
1002
1043
|
def on_dlc(self, dlc):
|
|
1003
1044
|
logging.info(color(f'*** DLC connected: {dlc}', 'blue'))
|
|
1045
|
+
if self.credits_threshold is not None:
|
|
1046
|
+
dlc.rx_threshold = self.credits_threshold
|
|
1047
|
+
if self.max_credits is not None:
|
|
1048
|
+
dlc.rx_max_credits = self.max_credits
|
|
1004
1049
|
dlc.sink = self.on_packet
|
|
1005
1050
|
self.io_sink = dlc.write
|
|
1006
1051
|
self.dlc = dlc
|
|
1052
|
+
if self.max_credits is not None:
|
|
1053
|
+
dlc.rx_max_credits = self.max_credits
|
|
1054
|
+
if self.credits_threshold is not None:
|
|
1055
|
+
dlc.rx_credits_threshold = self.credits_threshold
|
|
1007
1056
|
|
|
1008
1057
|
async def drain(self):
|
|
1009
1058
|
assert self.dlc
|
|
@@ -1026,6 +1075,7 @@ class Central(Connection.Listener):
|
|
|
1026
1075
|
authenticate,
|
|
1027
1076
|
encrypt,
|
|
1028
1077
|
extended_data_length,
|
|
1078
|
+
role_switch,
|
|
1029
1079
|
):
|
|
1030
1080
|
super().__init__()
|
|
1031
1081
|
self.transport = transport
|
|
@@ -1036,6 +1086,7 @@ class Central(Connection.Listener):
|
|
|
1036
1086
|
self.authenticate = authenticate
|
|
1037
1087
|
self.encrypt = encrypt or authenticate
|
|
1038
1088
|
self.extended_data_length = extended_data_length
|
|
1089
|
+
self.role_switch = role_switch
|
|
1039
1090
|
self.device = None
|
|
1040
1091
|
self.connection = None
|
|
1041
1092
|
|
|
@@ -1086,6 +1137,11 @@ class Central(Connection.Listener):
|
|
|
1086
1137
|
role = self.role_factory(mode)
|
|
1087
1138
|
self.device.classic_enabled = self.classic
|
|
1088
1139
|
|
|
1140
|
+
# Set up a pairing config factory with minimal requirements.
|
|
1141
|
+
self.device.pairing_config_factory = lambda _: PairingConfig(
|
|
1142
|
+
sc=False, mitm=False, bonding=False
|
|
1143
|
+
)
|
|
1144
|
+
|
|
1089
1145
|
await self.device.power_on()
|
|
1090
1146
|
|
|
1091
1147
|
if self.classic:
|
|
@@ -1114,6 +1170,10 @@ class Central(Connection.Listener):
|
|
|
1114
1170
|
self.connection.listener = self
|
|
1115
1171
|
print_connection(self.connection)
|
|
1116
1172
|
|
|
1173
|
+
# Switch roles if needed.
|
|
1174
|
+
if self.role_switch:
|
|
1175
|
+
await switch_roles(self.connection, self.role_switch)
|
|
1176
|
+
|
|
1117
1177
|
# Wait a bit after the connection, some controllers aren't very good when
|
|
1118
1178
|
# we start sending data right away while some connection parameters are
|
|
1119
1179
|
# updated post connection
|
|
@@ -1175,20 +1235,30 @@ class Central(Connection.Listener):
|
|
|
1175
1235
|
def on_connection_data_length_change(self):
|
|
1176
1236
|
print_connection(self.connection)
|
|
1177
1237
|
|
|
1238
|
+
def on_role_change(self):
|
|
1239
|
+
print_connection(self.connection)
|
|
1240
|
+
|
|
1178
1241
|
|
|
1179
1242
|
# -----------------------------------------------------------------------------
|
|
1180
1243
|
# Peripheral
|
|
1181
1244
|
# -----------------------------------------------------------------------------
|
|
1182
1245
|
class Peripheral(Device.Listener, Connection.Listener):
|
|
1183
1246
|
def __init__(
|
|
1184
|
-
self,
|
|
1247
|
+
self,
|
|
1248
|
+
transport,
|
|
1249
|
+
role_factory,
|
|
1250
|
+
mode_factory,
|
|
1251
|
+
classic,
|
|
1252
|
+
extended_data_length,
|
|
1253
|
+
role_switch,
|
|
1185
1254
|
):
|
|
1186
1255
|
self.transport = transport
|
|
1187
1256
|
self.classic = classic
|
|
1188
|
-
self.extended_data_length = extended_data_length
|
|
1189
1257
|
self.role_factory = role_factory
|
|
1190
|
-
self.role = None
|
|
1191
1258
|
self.mode_factory = mode_factory
|
|
1259
|
+
self.extended_data_length = extended_data_length
|
|
1260
|
+
self.role_switch = role_switch
|
|
1261
|
+
self.role = None
|
|
1192
1262
|
self.mode = None
|
|
1193
1263
|
self.device = None
|
|
1194
1264
|
self.connection = None
|
|
@@ -1211,6 +1281,11 @@ class Peripheral(Device.Listener, Connection.Listener):
|
|
|
1211
1281
|
self.role = self.role_factory(self.mode)
|
|
1212
1282
|
self.device.classic_enabled = self.classic
|
|
1213
1283
|
|
|
1284
|
+
# Set up a pairing config factory with minimal requirements.
|
|
1285
|
+
self.device.pairing_config_factory = lambda _: PairingConfig(
|
|
1286
|
+
sc=False, mitm=False, bonding=False
|
|
1287
|
+
)
|
|
1288
|
+
|
|
1214
1289
|
await self.device.power_on()
|
|
1215
1290
|
|
|
1216
1291
|
if self.classic:
|
|
@@ -1237,6 +1312,7 @@ class Peripheral(Device.Listener, Connection.Listener):
|
|
|
1237
1312
|
|
|
1238
1313
|
await self.connected.wait()
|
|
1239
1314
|
logging.info(color('### Connected', 'cyan'))
|
|
1315
|
+
print_connection(self.connection)
|
|
1240
1316
|
|
|
1241
1317
|
await self.mode.on_connection(self.connection)
|
|
1242
1318
|
await self.role.run()
|
|
@@ -1253,7 +1329,7 @@ class Peripheral(Device.Listener, Connection.Listener):
|
|
|
1253
1329
|
AsyncRunner.spawn(self.device.set_connectable(False))
|
|
1254
1330
|
|
|
1255
1331
|
# Request a new data length if needed
|
|
1256
|
-
if self.extended_data_length:
|
|
1332
|
+
if not self.classic and self.extended_data_length:
|
|
1257
1333
|
logging.info("+++ Requesting extended data length")
|
|
1258
1334
|
AsyncRunner.spawn(
|
|
1259
1335
|
connection.set_data_length(
|
|
@@ -1261,6 +1337,10 @@ class Peripheral(Device.Listener, Connection.Listener):
|
|
|
1261
1337
|
)
|
|
1262
1338
|
)
|
|
1263
1339
|
|
|
1340
|
+
# Switch roles if needed.
|
|
1341
|
+
if self.role_switch:
|
|
1342
|
+
AsyncRunner.spawn(switch_roles(connection, self.role_switch))
|
|
1343
|
+
|
|
1264
1344
|
def on_disconnection(self, reason):
|
|
1265
1345
|
logging.info(color(f'!!! Disconnection: reason={reason}', 'red'))
|
|
1266
1346
|
self.connection = None
|
|
@@ -1282,6 +1362,9 @@ class Peripheral(Device.Listener, Connection.Listener):
|
|
|
1282
1362
|
def on_connection_data_length_change(self):
|
|
1283
1363
|
print_connection(self.connection)
|
|
1284
1364
|
|
|
1365
|
+
def on_role_change(self):
|
|
1366
|
+
print_connection(self.connection)
|
|
1367
|
+
|
|
1285
1368
|
|
|
1286
1369
|
# -----------------------------------------------------------------------------
|
|
1287
1370
|
def create_mode_factory(ctx, default_mode):
|
|
@@ -1321,7 +1404,9 @@ def create_mode_factory(ctx, default_mode):
|
|
|
1321
1404
|
uuid=ctx.obj['rfcomm_uuid'],
|
|
1322
1405
|
l2cap_mtu=ctx.obj['rfcomm_l2cap_mtu'],
|
|
1323
1406
|
max_frame_size=ctx.obj['rfcomm_max_frame_size'],
|
|
1324
|
-
|
|
1407
|
+
initial_credits=ctx.obj['rfcomm_initial_credits'],
|
|
1408
|
+
max_credits=ctx.obj['rfcomm_max_credits'],
|
|
1409
|
+
credits_threshold=ctx.obj['rfcomm_credits_threshold'],
|
|
1325
1410
|
)
|
|
1326
1411
|
|
|
1327
1412
|
if mode == 'rfcomm-server':
|
|
@@ -1329,6 +1414,10 @@ def create_mode_factory(ctx, default_mode):
|
|
|
1329
1414
|
device,
|
|
1330
1415
|
channel=ctx.obj['rfcomm_channel'],
|
|
1331
1416
|
l2cap_mtu=ctx.obj['rfcomm_l2cap_mtu'],
|
|
1417
|
+
max_frame_size=ctx.obj['rfcomm_max_frame_size'],
|
|
1418
|
+
initial_credits=ctx.obj['rfcomm_initial_credits'],
|
|
1419
|
+
max_credits=ctx.obj['rfcomm_max_credits'],
|
|
1420
|
+
credits_threshold=ctx.obj['rfcomm_credits_threshold'],
|
|
1332
1421
|
)
|
|
1333
1422
|
|
|
1334
1423
|
raise ValueError('invalid mode')
|
|
@@ -1405,6 +1494,11 @@ def create_role_factory(ctx, default_role):
|
|
|
1405
1494
|
'--extended-data-length',
|
|
1406
1495
|
help='Request a data length upon connection, specified as tx_octets/tx_time',
|
|
1407
1496
|
)
|
|
1497
|
+
@click.option(
|
|
1498
|
+
'--role-switch',
|
|
1499
|
+
type=click.Choice(['central', 'peripheral']),
|
|
1500
|
+
help='Request role switch upon connection (central or peripheral)',
|
|
1501
|
+
)
|
|
1408
1502
|
@click.option(
|
|
1409
1503
|
'--rfcomm-channel',
|
|
1410
1504
|
type=int,
|
|
@@ -1427,9 +1521,19 @@ def create_role_factory(ctx, default_role):
|
|
|
1427
1521
|
help='RFComm maximum frame size',
|
|
1428
1522
|
)
|
|
1429
1523
|
@click.option(
|
|
1430
|
-
'--rfcomm-
|
|
1524
|
+
'--rfcomm-initial-credits',
|
|
1431
1525
|
type=int,
|
|
1432
|
-
help='RFComm
|
|
1526
|
+
help='RFComm initial credits',
|
|
1527
|
+
)
|
|
1528
|
+
@click.option(
|
|
1529
|
+
'--rfcomm-max-credits',
|
|
1530
|
+
type=int,
|
|
1531
|
+
help='RFComm max credits',
|
|
1532
|
+
)
|
|
1533
|
+
@click.option(
|
|
1534
|
+
'--rfcomm-credits-threshold',
|
|
1535
|
+
type=int,
|
|
1536
|
+
help='RFComm credits threshold',
|
|
1433
1537
|
)
|
|
1434
1538
|
@click.option(
|
|
1435
1539
|
'--l2cap-psm',
|
|
@@ -1459,7 +1563,7 @@ def create_role_factory(ctx, default_role):
|
|
|
1459
1563
|
'--packet-size',
|
|
1460
1564
|
'-s',
|
|
1461
1565
|
metavar='SIZE',
|
|
1462
|
-
type=click.IntRange(8,
|
|
1566
|
+
type=click.IntRange(8, 8192),
|
|
1463
1567
|
default=500,
|
|
1464
1568
|
help='Packet size (client or ping role)',
|
|
1465
1569
|
)
|
|
@@ -1519,6 +1623,7 @@ def bench(
|
|
|
1519
1623
|
mode,
|
|
1520
1624
|
att_mtu,
|
|
1521
1625
|
extended_data_length,
|
|
1626
|
+
role_switch,
|
|
1522
1627
|
packet_size,
|
|
1523
1628
|
packet_count,
|
|
1524
1629
|
start_delay,
|
|
@@ -1530,7 +1635,9 @@ def bench(
|
|
|
1530
1635
|
rfcomm_uuid,
|
|
1531
1636
|
rfcomm_l2cap_mtu,
|
|
1532
1637
|
rfcomm_max_frame_size,
|
|
1533
|
-
|
|
1638
|
+
rfcomm_initial_credits,
|
|
1639
|
+
rfcomm_max_credits,
|
|
1640
|
+
rfcomm_credits_threshold,
|
|
1534
1641
|
l2cap_psm,
|
|
1535
1642
|
l2cap_mtu,
|
|
1536
1643
|
l2cap_mps,
|
|
@@ -1545,7 +1652,9 @@ def bench(
|
|
|
1545
1652
|
ctx.obj['rfcomm_uuid'] = rfcomm_uuid
|
|
1546
1653
|
ctx.obj['rfcomm_l2cap_mtu'] = rfcomm_l2cap_mtu
|
|
1547
1654
|
ctx.obj['rfcomm_max_frame_size'] = rfcomm_max_frame_size
|
|
1548
|
-
ctx.obj['
|
|
1655
|
+
ctx.obj['rfcomm_initial_credits'] = rfcomm_initial_credits
|
|
1656
|
+
ctx.obj['rfcomm_max_credits'] = rfcomm_max_credits
|
|
1657
|
+
ctx.obj['rfcomm_credits_threshold'] = rfcomm_credits_threshold
|
|
1549
1658
|
ctx.obj['l2cap_psm'] = l2cap_psm
|
|
1550
1659
|
ctx.obj['l2cap_mtu'] = l2cap_mtu
|
|
1551
1660
|
ctx.obj['l2cap_mps'] = l2cap_mps
|
|
@@ -1557,12 +1666,12 @@ def bench(
|
|
|
1557
1666
|
ctx.obj['repeat_delay'] = repeat_delay
|
|
1558
1667
|
ctx.obj['pace'] = pace
|
|
1559
1668
|
ctx.obj['linger'] = linger
|
|
1560
|
-
|
|
1561
1669
|
ctx.obj['extended_data_length'] = (
|
|
1562
1670
|
[int(x) for x in extended_data_length.split('/')]
|
|
1563
1671
|
if extended_data_length
|
|
1564
1672
|
else None
|
|
1565
1673
|
)
|
|
1674
|
+
ctx.obj['role_switch'] = role_switch
|
|
1566
1675
|
ctx.obj['classic'] = mode in ('rfcomm-client', 'rfcomm-server')
|
|
1567
1676
|
|
|
1568
1677
|
|
|
@@ -1606,6 +1715,7 @@ def central(
|
|
|
1606
1715
|
authenticate,
|
|
1607
1716
|
encrypt or authenticate,
|
|
1608
1717
|
ctx.obj['extended_data_length'],
|
|
1718
|
+
ctx.obj['role_switch'],
|
|
1609
1719
|
).run()
|
|
1610
1720
|
|
|
1611
1721
|
asyncio.run(run_central())
|
|
@@ -1622,10 +1732,11 @@ def peripheral(ctx, transport):
|
|
|
1622
1732
|
async def run_peripheral():
|
|
1623
1733
|
await Peripheral(
|
|
1624
1734
|
transport,
|
|
1625
|
-
ctx.obj['classic'],
|
|
1626
|
-
ctx.obj['extended_data_length'],
|
|
1627
1735
|
role_factory,
|
|
1628
1736
|
mode_factory,
|
|
1737
|
+
ctx.obj['classic'],
|
|
1738
|
+
ctx.obj['extended_data_length'],
|
|
1739
|
+
ctx.obj['role_switch'],
|
|
1629
1740
|
).run()
|
|
1630
1741
|
|
|
1631
1742
|
asyncio.run(run_peripheral())
|
bumble/apps/controller_info.py
CHANGED
|
@@ -27,7 +27,7 @@ from bumble.colors import color
|
|
|
27
27
|
from bumble.core import name_or_number
|
|
28
28
|
from bumble.hci import (
|
|
29
29
|
map_null_terminated_utf8_string,
|
|
30
|
-
|
|
30
|
+
LeFeature,
|
|
31
31
|
HCI_SUCCESS,
|
|
32
32
|
HCI_VERSION_NAMES,
|
|
33
33
|
LMP_VERSION_NAMES,
|
|
@@ -140,7 +140,7 @@ async def get_le_info(host: Host) -> None:
|
|
|
140
140
|
|
|
141
141
|
print(color('LE Features:', 'yellow'))
|
|
142
142
|
for feature in host.supported_le_features:
|
|
143
|
-
print(
|
|
143
|
+
print(f' {LeFeature(feature).name}')
|
|
144
144
|
|
|
145
145
|
|
|
146
146
|
# -----------------------------------------------------------------------------
|
|
@@ -224,7 +224,7 @@ async def async_main(latency_probes, transport):
|
|
|
224
224
|
print()
|
|
225
225
|
print(color('Supported Commands:', 'yellow'))
|
|
226
226
|
for command in host.supported_commands:
|
|
227
|
-
print('
|
|
227
|
+
print(f' {HCI_Command.command_name(command)}')
|
|
228
228
|
|
|
229
229
|
|
|
230
230
|
# -----------------------------------------------------------------------------
|