bumble 0.0.180__py3-none-any.whl → 0.0.182__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/bench.py +397 -133
- bumble/apps/ble_rpa_tool.py +63 -0
- bumble/apps/console.py +4 -4
- bumble/apps/controller_info.py +64 -6
- bumble/apps/controller_loopback.py +200 -0
- bumble/apps/l2cap_bridge.py +32 -24
- bumble/apps/pair.py +6 -8
- bumble/att.py +53 -11
- bumble/controller.py +159 -24
- bumble/crypto.py +10 -0
- bumble/device.py +580 -113
- bumble/drivers/__init__.py +27 -31
- bumble/drivers/common.py +45 -0
- bumble/drivers/rtk.py +11 -4
- bumble/gatt.py +66 -51
- bumble/gatt_server.py +30 -22
- bumble/hci.py +258 -91
- bumble/helpers.py +14 -0
- bumble/hfp.py +37 -27
- bumble/hid.py +282 -61
- bumble/host.py +158 -93
- bumble/l2cap.py +11 -6
- bumble/link.py +55 -1
- bumble/profiles/asha_service.py +2 -2
- bumble/profiles/bap.py +1247 -0
- bumble/profiles/cap.py +52 -0
- bumble/profiles/csip.py +119 -9
- bumble/rfcomm.py +31 -20
- bumble/smp.py +1 -1
- bumble/transport/__init__.py +51 -22
- bumble/transport/android_emulator.py +1 -1
- bumble/transport/common.py +2 -1
- bumble/transport/hci_socket.py +1 -4
- bumble/transport/usb.py +1 -1
- bumble/utils.py +3 -6
- {bumble-0.0.180.dist-info → bumble-0.0.182.dist-info}/METADATA +1 -1
- {bumble-0.0.180.dist-info → bumble-0.0.182.dist-info}/RECORD +42 -37
- {bumble-0.0.180.dist-info → bumble-0.0.182.dist-info}/entry_points.txt +1 -0
- {bumble-0.0.180.dist-info → bumble-0.0.182.dist-info}/LICENSE +0 -0
- {bumble-0.0.180.dist-info → bumble-0.0.182.dist-info}/WHEEL +0 -0
- {bumble-0.0.180.dist-info → bumble-0.0.182.dist-info}/top_level.txt +0 -0
bumble/apps/bench.py
CHANGED
|
@@ -80,12 +80,13 @@ SPEED_TX_UUID = 'E789C754-41A1-45F4-A948-A0A1A90DBA53'
|
|
|
80
80
|
SPEED_RX_UUID = '016A2CC7-E14B-4819-935F-1F56EAE4098D'
|
|
81
81
|
|
|
82
82
|
DEFAULT_RFCOMM_UUID = 'E6D55659-C8B4-4B85-96BB-B1143AF6D3AE'
|
|
83
|
-
DEFAULT_L2CAP_PSM =
|
|
83
|
+
DEFAULT_L2CAP_PSM = 128
|
|
84
84
|
DEFAULT_L2CAP_MAX_CREDITS = 128
|
|
85
|
-
DEFAULT_L2CAP_MTU =
|
|
85
|
+
DEFAULT_L2CAP_MTU = 1024
|
|
86
86
|
DEFAULT_L2CAP_MPS = 1024
|
|
87
87
|
|
|
88
88
|
DEFAULT_LINGER_TIME = 1.0
|
|
89
|
+
DEFAULT_POST_CONNECTION_WAIT_TIME = 1.0
|
|
89
90
|
|
|
90
91
|
DEFAULT_RFCOMM_CHANNEL = 8
|
|
91
92
|
|
|
@@ -95,7 +96,7 @@ DEFAULT_RFCOMM_CHANNEL = 8
|
|
|
95
96
|
# -----------------------------------------------------------------------------
|
|
96
97
|
def parse_packet(packet):
|
|
97
98
|
if len(packet) < 1:
|
|
98
|
-
|
|
99
|
+
logging.info(
|
|
99
100
|
color(f'!!! Packet too short (got {len(packet)} bytes, need >= 1)', 'red')
|
|
100
101
|
)
|
|
101
102
|
raise ValueError('packet too short')
|
|
@@ -103,7 +104,7 @@ def parse_packet(packet):
|
|
|
103
104
|
try:
|
|
104
105
|
packet_type = PacketType(packet[0])
|
|
105
106
|
except ValueError:
|
|
106
|
-
|
|
107
|
+
logging.info(color(f'!!! Invalid packet type 0x{packet[0]:02X}', 'red'))
|
|
107
108
|
raise
|
|
108
109
|
|
|
109
110
|
return (packet_type, packet[1:])
|
|
@@ -111,7 +112,7 @@ def parse_packet(packet):
|
|
|
111
112
|
|
|
112
113
|
def parse_packet_sequence(packet_data):
|
|
113
114
|
if len(packet_data) < 5:
|
|
114
|
-
|
|
115
|
+
logging.info(
|
|
115
116
|
color(
|
|
116
117
|
f'!!!Packet too short (got {len(packet_data)} bytes, need >= 5)',
|
|
117
118
|
'red',
|
|
@@ -155,7 +156,7 @@ def print_connection(connection):
|
|
|
155
156
|
|
|
156
157
|
mtu = connection.att_mtu
|
|
157
158
|
|
|
158
|
-
|
|
159
|
+
logging.info(
|
|
159
160
|
f'{color("@@@ Connection:", "yellow")} '
|
|
160
161
|
f'{connection_parameters} '
|
|
161
162
|
f'{data_length} '
|
|
@@ -239,6 +240,23 @@ async def find_rfcomm_channel_with_uuid(connection: Connection, uuid: str) -> in
|
|
|
239
240
|
return 0
|
|
240
241
|
|
|
241
242
|
|
|
243
|
+
def log_stats(title, stats):
|
|
244
|
+
stats_min = min(stats)
|
|
245
|
+
stats_max = max(stats)
|
|
246
|
+
stats_avg = sum(stats) / len(stats)
|
|
247
|
+
logging.info(
|
|
248
|
+
color(
|
|
249
|
+
(
|
|
250
|
+
f'### {title} stats: '
|
|
251
|
+
f'min={stats_min:.2f}, '
|
|
252
|
+
f'max={stats_max:.2f}, '
|
|
253
|
+
f'average={stats_avg:.2f}'
|
|
254
|
+
),
|
|
255
|
+
'cyan',
|
|
256
|
+
)
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
|
|
242
260
|
class PacketType(enum.IntEnum):
|
|
243
261
|
RESET = 0
|
|
244
262
|
SEQUENCE = 1
|
|
@@ -252,45 +270,88 @@ PACKET_FLAG_LAST = 1
|
|
|
252
270
|
# Sender
|
|
253
271
|
# -----------------------------------------------------------------------------
|
|
254
272
|
class Sender:
|
|
255
|
-
def __init__(
|
|
273
|
+
def __init__(
|
|
274
|
+
self,
|
|
275
|
+
packet_io,
|
|
276
|
+
start_delay,
|
|
277
|
+
repeat,
|
|
278
|
+
repeat_delay,
|
|
279
|
+
pace,
|
|
280
|
+
packet_size,
|
|
281
|
+
packet_count,
|
|
282
|
+
):
|
|
256
283
|
self.tx_start_delay = start_delay
|
|
257
284
|
self.tx_packet_size = packet_size
|
|
258
285
|
self.tx_packet_count = packet_count
|
|
259
286
|
self.packet_io = packet_io
|
|
260
287
|
self.packet_io.packet_listener = self
|
|
288
|
+
self.repeat = repeat
|
|
289
|
+
self.repeat_delay = repeat_delay
|
|
290
|
+
self.pace = pace
|
|
261
291
|
self.start_time = 0
|
|
262
292
|
self.bytes_sent = 0
|
|
293
|
+
self.stats = []
|
|
263
294
|
self.done = asyncio.Event()
|
|
264
295
|
|
|
265
296
|
def reset(self):
|
|
266
297
|
pass
|
|
267
298
|
|
|
268
299
|
async def run(self):
|
|
269
|
-
|
|
300
|
+
logging.info(color('--- Waiting for I/O to be ready...', 'blue'))
|
|
270
301
|
await self.packet_io.ready.wait()
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
302
|
+
logging.info(color('--- Go!', 'blue'))
|
|
303
|
+
|
|
304
|
+
for run in range(self.repeat + 1):
|
|
305
|
+
self.done.clear()
|
|
306
|
+
|
|
307
|
+
if run > 0 and self.repeat and self.repeat_delay:
|
|
308
|
+
logging.info(color(f'*** Repeat delay: {self.repeat_delay}', 'green'))
|
|
309
|
+
await asyncio.sleep(self.repeat_delay)
|
|
310
|
+
|
|
311
|
+
if self.tx_start_delay:
|
|
312
|
+
logging.info(color(f'*** Startup delay: {self.tx_start_delay}', 'blue'))
|
|
313
|
+
await asyncio.sleep(self.tx_start_delay)
|
|
314
|
+
|
|
315
|
+
logging.info(color('=== Sending RESET', 'magenta'))
|
|
316
|
+
await self.packet_io.send_packet(bytes([PacketType.RESET]))
|
|
317
|
+
self.start_time = time.time()
|
|
318
|
+
self.bytes_sent = 0
|
|
319
|
+
for tx_i in range(self.tx_packet_count):
|
|
320
|
+
packet_flags = (
|
|
321
|
+
PACKET_FLAG_LAST if tx_i == self.tx_packet_count - 1 else 0
|
|
322
|
+
)
|
|
323
|
+
packet = struct.pack(
|
|
324
|
+
'>bbI',
|
|
325
|
+
PacketType.SEQUENCE,
|
|
326
|
+
packet_flags,
|
|
327
|
+
tx_i,
|
|
328
|
+
) + bytes(self.tx_packet_size - 6 - self.packet_io.overhead_size)
|
|
329
|
+
logging.info(
|
|
330
|
+
color(
|
|
331
|
+
f'Sending packet {tx_i}: {self.tx_packet_size} bytes', 'yellow'
|
|
332
|
+
)
|
|
333
|
+
)
|
|
334
|
+
self.bytes_sent += len(packet)
|
|
335
|
+
await self.packet_io.send_packet(packet)
|
|
291
336
|
|
|
292
|
-
|
|
293
|
-
|
|
337
|
+
if self.pace is None:
|
|
338
|
+
continue
|
|
339
|
+
|
|
340
|
+
if self.pace > 0:
|
|
341
|
+
await asyncio.sleep(self.pace / 1000)
|
|
342
|
+
else:
|
|
343
|
+
await self.packet_io.drain()
|
|
344
|
+
|
|
345
|
+
await self.done.wait()
|
|
346
|
+
|
|
347
|
+
run_counter = f'[{run + 1} of {self.repeat + 1}]' if self.repeat else ''
|
|
348
|
+
logging.info(color(f'=== {run_counter} Done!', 'magenta'))
|
|
349
|
+
|
|
350
|
+
if self.repeat:
|
|
351
|
+
log_stats('Run', self.stats)
|
|
352
|
+
|
|
353
|
+
if self.repeat:
|
|
354
|
+
logging.info(color('--- End of runs', 'blue'))
|
|
294
355
|
|
|
295
356
|
def on_packet_received(self, packet):
|
|
296
357
|
try:
|
|
@@ -301,7 +362,8 @@ class Sender:
|
|
|
301
362
|
if packet_type == PacketType.ACK:
|
|
302
363
|
elapsed = time.time() - self.start_time
|
|
303
364
|
average_tx_speed = self.bytes_sent / elapsed
|
|
304
|
-
|
|
365
|
+
self.stats.append(average_tx_speed)
|
|
366
|
+
logging.info(
|
|
305
367
|
color(
|
|
306
368
|
f'@@@ Received ACK. Speed: average={average_tx_speed:.4f}'
|
|
307
369
|
f' ({self.bytes_sent} bytes in {elapsed:.2f} seconds)',
|
|
@@ -315,17 +377,21 @@ class Sender:
|
|
|
315
377
|
# Receiver
|
|
316
378
|
# -----------------------------------------------------------------------------
|
|
317
379
|
class Receiver:
|
|
318
|
-
|
|
380
|
+
expected_packet_index: int
|
|
381
|
+
start_timestamp: float
|
|
382
|
+
last_timestamp: float
|
|
383
|
+
|
|
384
|
+
def __init__(self, packet_io, linger):
|
|
319
385
|
self.reset()
|
|
320
386
|
self.packet_io = packet_io
|
|
321
387
|
self.packet_io.packet_listener = self
|
|
388
|
+
self.linger = linger
|
|
322
389
|
self.done = asyncio.Event()
|
|
323
390
|
|
|
324
391
|
def reset(self):
|
|
325
392
|
self.expected_packet_index = 0
|
|
326
|
-
self.
|
|
327
|
-
self.
|
|
328
|
-
self.bytes_received = 0
|
|
393
|
+
self.measurements = [(time.time(), 0)]
|
|
394
|
+
self.total_bytes_received = 0
|
|
329
395
|
|
|
330
396
|
def on_packet_received(self, packet):
|
|
331
397
|
try:
|
|
@@ -333,44 +399,50 @@ class Receiver:
|
|
|
333
399
|
except ValueError:
|
|
334
400
|
return
|
|
335
401
|
|
|
336
|
-
now = time.time()
|
|
337
|
-
|
|
338
402
|
if packet_type == PacketType.RESET:
|
|
339
|
-
|
|
403
|
+
logging.info(color('=== Received RESET', 'magenta'))
|
|
340
404
|
self.reset()
|
|
341
|
-
self.start_timestamp = now
|
|
342
405
|
return
|
|
343
406
|
|
|
344
407
|
try:
|
|
345
408
|
packet_flags, packet_index = parse_packet_sequence(packet_data)
|
|
346
409
|
except ValueError:
|
|
347
410
|
return
|
|
348
|
-
|
|
411
|
+
logging.info(
|
|
349
412
|
f'<<< Received packet {packet_index}: '
|
|
350
|
-
f'flags=0x{packet_flags:02X},
|
|
413
|
+
f'flags=0x{packet_flags:02X}, '
|
|
414
|
+
f'{len(packet) + self.packet_io.overhead_size} bytes'
|
|
351
415
|
)
|
|
352
416
|
|
|
353
417
|
if packet_index != self.expected_packet_index:
|
|
354
|
-
|
|
418
|
+
logging.info(
|
|
355
419
|
color(
|
|
356
420
|
f'!!! Unexpected packet, expected {self.expected_packet_index} '
|
|
357
421
|
f'but received {packet_index}'
|
|
358
422
|
)
|
|
359
423
|
)
|
|
360
424
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
self.
|
|
425
|
+
now = time.time()
|
|
426
|
+
elapsed_since_start = now - self.measurements[0][0]
|
|
427
|
+
elapsed_since_last = now - self.measurements[-1][0]
|
|
428
|
+
self.measurements.append((now, len(packet)))
|
|
429
|
+
self.total_bytes_received += len(packet)
|
|
364
430
|
instant_rx_speed = len(packet) / elapsed_since_last
|
|
365
|
-
average_rx_speed = self.
|
|
366
|
-
|
|
431
|
+
average_rx_speed = self.total_bytes_received / elapsed_since_start
|
|
432
|
+
window = self.measurements[-64:]
|
|
433
|
+
windowed_rx_speed = sum(measurement[1] for measurement in window[1:]) / (
|
|
434
|
+
window[-1][0] - window[0][0]
|
|
435
|
+
)
|
|
436
|
+
logging.info(
|
|
367
437
|
color(
|
|
368
|
-
|
|
438
|
+
'Speed: '
|
|
439
|
+
f'instant={instant_rx_speed:.4f}, '
|
|
440
|
+
f'windowed={windowed_rx_speed:.4f}, '
|
|
441
|
+
f'average={average_rx_speed:.4f}',
|
|
369
442
|
'yellow',
|
|
370
443
|
)
|
|
371
444
|
)
|
|
372
445
|
|
|
373
|
-
self.last_timestamp = now
|
|
374
446
|
self.expected_packet_index = packet_index + 1
|
|
375
447
|
|
|
376
448
|
if packet_flags & PACKET_FLAG_LAST:
|
|
@@ -379,52 +451,104 @@ class Receiver:
|
|
|
379
451
|
struct.pack('>bbI', PacketType.ACK, packet_flags, packet_index)
|
|
380
452
|
)
|
|
381
453
|
)
|
|
382
|
-
|
|
383
|
-
self.
|
|
454
|
+
logging.info(color('@@@ Received last packet', 'green'))
|
|
455
|
+
if not self.linger:
|
|
456
|
+
self.done.set()
|
|
384
457
|
|
|
385
458
|
async def run(self):
|
|
386
459
|
await self.done.wait()
|
|
387
|
-
|
|
460
|
+
logging.info(color('=== Done!', 'magenta'))
|
|
388
461
|
|
|
389
462
|
|
|
390
463
|
# -----------------------------------------------------------------------------
|
|
391
464
|
# Ping
|
|
392
465
|
# -----------------------------------------------------------------------------
|
|
393
466
|
class Ping:
|
|
394
|
-
def __init__(
|
|
467
|
+
def __init__(
|
|
468
|
+
self,
|
|
469
|
+
packet_io,
|
|
470
|
+
start_delay,
|
|
471
|
+
repeat,
|
|
472
|
+
repeat_delay,
|
|
473
|
+
pace,
|
|
474
|
+
packet_size,
|
|
475
|
+
packet_count,
|
|
476
|
+
):
|
|
395
477
|
self.tx_start_delay = start_delay
|
|
396
478
|
self.tx_packet_size = packet_size
|
|
397
479
|
self.tx_packet_count = packet_count
|
|
398
480
|
self.packet_io = packet_io
|
|
399
481
|
self.packet_io.packet_listener = self
|
|
482
|
+
self.repeat = repeat
|
|
483
|
+
self.repeat_delay = repeat_delay
|
|
484
|
+
self.pace = pace
|
|
400
485
|
self.done = asyncio.Event()
|
|
401
486
|
self.current_packet_index = 0
|
|
402
487
|
self.ping_sent_time = 0.0
|
|
403
488
|
self.latencies = []
|
|
489
|
+
self.min_stats = []
|
|
490
|
+
self.max_stats = []
|
|
491
|
+
self.avg_stats = []
|
|
404
492
|
|
|
405
493
|
def reset(self):
|
|
406
494
|
pass
|
|
407
495
|
|
|
408
496
|
async def run(self):
|
|
409
|
-
|
|
497
|
+
logging.info(color('--- Waiting for I/O to be ready...', 'blue'))
|
|
410
498
|
await self.packet_io.ready.wait()
|
|
411
|
-
|
|
499
|
+
logging.info(color('--- Go!', 'blue'))
|
|
412
500
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
await asyncio.sleep(self.tx_start_delay)
|
|
501
|
+
for run in range(self.repeat + 1):
|
|
502
|
+
self.done.clear()
|
|
416
503
|
|
|
417
|
-
|
|
418
|
-
|
|
504
|
+
if run > 0 and self.repeat and self.repeat_delay:
|
|
505
|
+
logging.info(color(f'*** Repeat delay: {self.repeat_delay}', 'green'))
|
|
506
|
+
await asyncio.sleep(self.repeat_delay)
|
|
419
507
|
|
|
420
|
-
|
|
508
|
+
if self.tx_start_delay:
|
|
509
|
+
logging.info(color(f'*** Startup delay: {self.tx_start_delay}', 'blue'))
|
|
510
|
+
await asyncio.sleep(self.tx_start_delay)
|
|
421
511
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
512
|
+
logging.info(color('=== Sending RESET', 'magenta'))
|
|
513
|
+
await self.packet_io.send_packet(bytes([PacketType.RESET]))
|
|
514
|
+
|
|
515
|
+
self.current_packet_index = 0
|
|
516
|
+
self.latencies = []
|
|
517
|
+
await self.send_next_ping()
|
|
518
|
+
|
|
519
|
+
await self.done.wait()
|
|
520
|
+
|
|
521
|
+
min_latency = min(self.latencies)
|
|
522
|
+
max_latency = max(self.latencies)
|
|
523
|
+
avg_latency = sum(self.latencies) / len(self.latencies)
|
|
524
|
+
logging.info(
|
|
525
|
+
color(
|
|
526
|
+
'@@@ Latencies: '
|
|
527
|
+
f'min={min_latency:.2f}, '
|
|
528
|
+
f'max={max_latency:.2f}, '
|
|
529
|
+
f'average={avg_latency:.2f}'
|
|
530
|
+
)
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
self.min_stats.append(min_latency)
|
|
534
|
+
self.max_stats.append(max_latency)
|
|
535
|
+
self.avg_stats.append(avg_latency)
|
|
536
|
+
|
|
537
|
+
run_counter = f'[{run + 1} of {self.repeat + 1}]' if self.repeat else ''
|
|
538
|
+
logging.info(color(f'=== {run_counter} Done!', 'magenta'))
|
|
539
|
+
|
|
540
|
+
if self.repeat:
|
|
541
|
+
log_stats('Min Latency', self.min_stats)
|
|
542
|
+
log_stats('Max Latency', self.max_stats)
|
|
543
|
+
log_stats('Average Latency', self.avg_stats)
|
|
544
|
+
|
|
545
|
+
if self.repeat:
|
|
546
|
+
logging.info(color('--- End of runs', 'blue'))
|
|
426
547
|
|
|
427
548
|
async def send_next_ping(self):
|
|
549
|
+
if self.pace:
|
|
550
|
+
await asyncio.sleep(self.pace / 1000)
|
|
551
|
+
|
|
428
552
|
packet = struct.pack(
|
|
429
553
|
'>bbI',
|
|
430
554
|
PacketType.SEQUENCE,
|
|
@@ -433,7 +557,7 @@ class Ping:
|
|
|
433
557
|
else 0,
|
|
434
558
|
self.current_packet_index,
|
|
435
559
|
) + bytes(self.tx_packet_size - 6)
|
|
436
|
-
|
|
560
|
+
logging.info(color(f'Sending packet {self.current_packet_index}', 'yellow'))
|
|
437
561
|
self.ping_sent_time = time.time()
|
|
438
562
|
await self.packet_io.send_packet(packet)
|
|
439
563
|
|
|
@@ -453,7 +577,7 @@ class Ping:
|
|
|
453
577
|
if packet_type == PacketType.ACK:
|
|
454
578
|
latency = elapsed * 1000
|
|
455
579
|
self.latencies.append(latency)
|
|
456
|
-
|
|
580
|
+
logging.info(
|
|
457
581
|
color(
|
|
458
582
|
f'<<< Received ACK [{packet_index}], latency={latency:.2f}ms',
|
|
459
583
|
'green',
|
|
@@ -463,7 +587,7 @@ class Ping:
|
|
|
463
587
|
if packet_index == self.current_packet_index:
|
|
464
588
|
self.current_packet_index += 1
|
|
465
589
|
else:
|
|
466
|
-
|
|
590
|
+
logging.info(
|
|
467
591
|
color(
|
|
468
592
|
f'!!! Unexpected packet, expected {self.current_packet_index} '
|
|
469
593
|
f'but received {packet_index}'
|
|
@@ -481,10 +605,13 @@ class Ping:
|
|
|
481
605
|
# Pong
|
|
482
606
|
# -----------------------------------------------------------------------------
|
|
483
607
|
class Pong:
|
|
484
|
-
|
|
608
|
+
expected_packet_index: int
|
|
609
|
+
|
|
610
|
+
def __init__(self, packet_io, linger):
|
|
485
611
|
self.reset()
|
|
486
612
|
self.packet_io = packet_io
|
|
487
613
|
self.packet_io.packet_listener = self
|
|
614
|
+
self.linger = linger
|
|
488
615
|
self.done = asyncio.Event()
|
|
489
616
|
|
|
490
617
|
def reset(self):
|
|
@@ -497,7 +624,7 @@ class Pong:
|
|
|
497
624
|
return
|
|
498
625
|
|
|
499
626
|
if packet_type == PacketType.RESET:
|
|
500
|
-
|
|
627
|
+
logging.info(color('=== Received RESET', 'magenta'))
|
|
501
628
|
self.reset()
|
|
502
629
|
return
|
|
503
630
|
|
|
@@ -505,7 +632,7 @@ class Pong:
|
|
|
505
632
|
packet_flags, packet_index = parse_packet_sequence(packet_data)
|
|
506
633
|
except ValueError:
|
|
507
634
|
return
|
|
508
|
-
|
|
635
|
+
logging.info(
|
|
509
636
|
color(
|
|
510
637
|
f'<<< Received packet {packet_index}: '
|
|
511
638
|
f'flags=0x{packet_flags:02X}, {len(packet)} bytes',
|
|
@@ -514,7 +641,7 @@ class Pong:
|
|
|
514
641
|
)
|
|
515
642
|
|
|
516
643
|
if packet_index != self.expected_packet_index:
|
|
517
|
-
|
|
644
|
+
logging.info(
|
|
518
645
|
color(
|
|
519
646
|
f'!!! Unexpected packet, expected {self.expected_packet_index} '
|
|
520
647
|
f'but received {packet_index}'
|
|
@@ -529,12 +656,12 @@ class Pong:
|
|
|
529
656
|
)
|
|
530
657
|
)
|
|
531
658
|
|
|
532
|
-
if packet_flags & PACKET_FLAG_LAST:
|
|
659
|
+
if packet_flags & PACKET_FLAG_LAST and not self.linger:
|
|
533
660
|
self.done.set()
|
|
534
661
|
|
|
535
662
|
async def run(self):
|
|
536
663
|
await self.done.wait()
|
|
537
|
-
|
|
664
|
+
logging.info(color('=== Done!', 'magenta'))
|
|
538
665
|
|
|
539
666
|
|
|
540
667
|
# -----------------------------------------------------------------------------
|
|
@@ -547,41 +674,42 @@ class GattClient:
|
|
|
547
674
|
self.speed_tx = None
|
|
548
675
|
self.packet_listener = None
|
|
549
676
|
self.ready = asyncio.Event()
|
|
677
|
+
self.overhead_size = 0
|
|
550
678
|
|
|
551
679
|
async def on_connection(self, connection):
|
|
552
680
|
peer = Peer(connection)
|
|
553
681
|
|
|
554
682
|
if self.att_mtu:
|
|
555
|
-
|
|
683
|
+
logging.info(color(f'*** Requesting MTU update: {self.att_mtu}', 'blue'))
|
|
556
684
|
await peer.request_mtu(self.att_mtu)
|
|
557
685
|
|
|
558
|
-
|
|
686
|
+
logging.info(color('*** Discovering services...', 'blue'))
|
|
559
687
|
await peer.discover_services()
|
|
560
688
|
|
|
561
689
|
speed_services = peer.get_services_by_uuid(SPEED_SERVICE_UUID)
|
|
562
690
|
if not speed_services:
|
|
563
|
-
|
|
691
|
+
logging.info(color('!!! Speed Service not found', 'red'))
|
|
564
692
|
return
|
|
565
693
|
speed_service = speed_services[0]
|
|
566
|
-
|
|
694
|
+
logging.info(color('*** Discovering characteristics...', 'blue'))
|
|
567
695
|
await speed_service.discover_characteristics()
|
|
568
696
|
|
|
569
697
|
speed_txs = speed_service.get_characteristics_by_uuid(SPEED_TX_UUID)
|
|
570
698
|
if not speed_txs:
|
|
571
|
-
|
|
699
|
+
logging.info(color('!!! Speed TX not found', 'red'))
|
|
572
700
|
return
|
|
573
701
|
self.speed_tx = speed_txs[0]
|
|
574
702
|
|
|
575
703
|
speed_rxs = speed_service.get_characteristics_by_uuid(SPEED_RX_UUID)
|
|
576
704
|
if not speed_rxs:
|
|
577
|
-
|
|
705
|
+
logging.info(color('!!! Speed RX not found', 'red'))
|
|
578
706
|
return
|
|
579
707
|
self.speed_rx = speed_rxs[0]
|
|
580
708
|
|
|
581
|
-
|
|
709
|
+
logging.info(color('*** Subscribing to RX', 'blue'))
|
|
582
710
|
await self.speed_rx.subscribe(self.on_packet_received)
|
|
583
711
|
|
|
584
|
-
|
|
712
|
+
logging.info(color('*** Discovery complete', 'blue'))
|
|
585
713
|
|
|
586
714
|
connection.on('disconnection', self.on_disconnection)
|
|
587
715
|
self.ready.set()
|
|
@@ -596,6 +724,9 @@ class GattClient:
|
|
|
596
724
|
async def send_packet(self, packet):
|
|
597
725
|
await self.speed_tx.write_value(packet)
|
|
598
726
|
|
|
727
|
+
async def drain(self):
|
|
728
|
+
pass
|
|
729
|
+
|
|
599
730
|
|
|
600
731
|
# -----------------------------------------------------------------------------
|
|
601
732
|
# GattServer
|
|
@@ -605,6 +736,7 @@ class GattServer:
|
|
|
605
736
|
self.device = device
|
|
606
737
|
self.packet_listener = None
|
|
607
738
|
self.ready = asyncio.Event()
|
|
739
|
+
self.overhead_size = 0
|
|
608
740
|
|
|
609
741
|
# Setup the GATT service
|
|
610
742
|
self.speed_tx = Characteristic(
|
|
@@ -633,10 +765,10 @@ class GattServer:
|
|
|
633
765
|
|
|
634
766
|
def on_rx_subscription(self, _connection, notify_enabled, _indicate_enabled):
|
|
635
767
|
if notify_enabled:
|
|
636
|
-
|
|
768
|
+
logging.info(color('*** RX subscription', 'blue'))
|
|
637
769
|
self.ready.set()
|
|
638
770
|
else:
|
|
639
|
-
|
|
771
|
+
logging.info(color('*** RX un-subscription', 'blue'))
|
|
640
772
|
self.ready.clear()
|
|
641
773
|
|
|
642
774
|
def on_tx_write(self, _, value):
|
|
@@ -646,6 +778,9 @@ class GattServer:
|
|
|
646
778
|
async def send_packet(self, packet):
|
|
647
779
|
await self.device.notify_subscribers(self.speed_rx, packet)
|
|
648
780
|
|
|
781
|
+
async def drain(self):
|
|
782
|
+
pass
|
|
783
|
+
|
|
649
784
|
|
|
650
785
|
# -----------------------------------------------------------------------------
|
|
651
786
|
# StreamedPacketIO
|
|
@@ -657,6 +792,7 @@ class StreamedPacketIO:
|
|
|
657
792
|
self.rx_packet = b''
|
|
658
793
|
self.rx_packet_header = b''
|
|
659
794
|
self.rx_packet_need = 0
|
|
795
|
+
self.overhead_size = 2
|
|
660
796
|
|
|
661
797
|
def on_packet(self, packet):
|
|
662
798
|
while packet:
|
|
@@ -684,7 +820,7 @@ class StreamedPacketIO:
|
|
|
684
820
|
|
|
685
821
|
async def send_packet(self, packet):
|
|
686
822
|
if not self.io_sink:
|
|
687
|
-
|
|
823
|
+
logging.info(color('!!! No sink, dropping packet', 'red'))
|
|
688
824
|
return
|
|
689
825
|
|
|
690
826
|
# pylint: disable-next=not-callable
|
|
@@ -708,13 +844,14 @@ class L2capClient(StreamedPacketIO):
|
|
|
708
844
|
self.max_credits = max_credits
|
|
709
845
|
self.mtu = mtu
|
|
710
846
|
self.mps = mps
|
|
847
|
+
self.l2cap_channel = None
|
|
711
848
|
self.ready = asyncio.Event()
|
|
712
849
|
|
|
713
850
|
async def on_connection(self, connection: Connection) -> None:
|
|
714
851
|
connection.on('disconnection', self.on_disconnection)
|
|
715
852
|
|
|
716
853
|
# Connect a new L2CAP channel
|
|
717
|
-
|
|
854
|
+
logging.info(color(f'>>> Opening L2CAP channel on PSM = {self.psm}', 'yellow'))
|
|
718
855
|
try:
|
|
719
856
|
l2cap_channel = await connection.create_l2cap_channel(
|
|
720
857
|
spec=l2cap.LeCreditBasedChannelSpec(
|
|
@@ -724,14 +861,15 @@ class L2capClient(StreamedPacketIO):
|
|
|
724
861
|
mps=self.mps,
|
|
725
862
|
)
|
|
726
863
|
)
|
|
727
|
-
|
|
864
|
+
logging.info(color(f'*** L2CAP channel: {l2cap_channel}', 'cyan'))
|
|
728
865
|
except Exception as error:
|
|
729
|
-
|
|
866
|
+
logging.info(color(f'!!! Connection failed: {error}', 'red'))
|
|
730
867
|
return
|
|
731
868
|
|
|
732
|
-
l2cap_channel.sink = self.on_packet
|
|
733
|
-
l2cap_channel.on('close', self.on_l2cap_close)
|
|
734
869
|
self.io_sink = l2cap_channel.write
|
|
870
|
+
self.l2cap_channel = l2cap_channel
|
|
871
|
+
l2cap_channel.on('close', self.on_l2cap_close)
|
|
872
|
+
l2cap_channel.sink = self.on_packet
|
|
735
873
|
|
|
736
874
|
self.ready.set()
|
|
737
875
|
|
|
@@ -739,7 +877,11 @@ class L2capClient(StreamedPacketIO):
|
|
|
739
877
|
pass
|
|
740
878
|
|
|
741
879
|
def on_l2cap_close(self):
|
|
742
|
-
|
|
880
|
+
logging.info(color('*** L2CAP channel closed', 'red'))
|
|
881
|
+
|
|
882
|
+
async def drain(self):
|
|
883
|
+
assert self.l2cap_channel
|
|
884
|
+
await self.l2cap_channel.drain()
|
|
743
885
|
|
|
744
886
|
|
|
745
887
|
# -----------------------------------------------------------------------------
|
|
@@ -765,7 +907,9 @@ class L2capServer(StreamedPacketIO):
|
|
|
765
907
|
),
|
|
766
908
|
handler=self.on_l2cap_channel,
|
|
767
909
|
)
|
|
768
|
-
|
|
910
|
+
logging.info(
|
|
911
|
+
color(f'### Listening for L2CAP connection on PSM {psm}', 'yellow')
|
|
912
|
+
)
|
|
769
913
|
|
|
770
914
|
async def on_connection(self, connection):
|
|
771
915
|
connection.on('disconnection', self.on_disconnection)
|
|
@@ -774,18 +918,23 @@ class L2capServer(StreamedPacketIO):
|
|
|
774
918
|
pass
|
|
775
919
|
|
|
776
920
|
def on_l2cap_channel(self, l2cap_channel):
|
|
777
|
-
|
|
921
|
+
logging.info(color(f'*** L2CAP channel: {l2cap_channel}', 'cyan'))
|
|
778
922
|
|
|
779
923
|
self.io_sink = l2cap_channel.write
|
|
924
|
+
self.l2cap_channel = l2cap_channel
|
|
780
925
|
l2cap_channel.on('close', self.on_l2cap_close)
|
|
781
926
|
l2cap_channel.sink = self.on_packet
|
|
782
927
|
|
|
783
928
|
self.ready.set()
|
|
784
929
|
|
|
785
930
|
def on_l2cap_close(self):
|
|
786
|
-
|
|
931
|
+
logging.info(color('*** L2CAP channel closed', 'red'))
|
|
787
932
|
self.l2cap_channel = None
|
|
788
933
|
|
|
934
|
+
async def drain(self):
|
|
935
|
+
assert self.l2cap_channel
|
|
936
|
+
await self.l2cap_channel.drain()
|
|
937
|
+
|
|
789
938
|
|
|
790
939
|
# -----------------------------------------------------------------------------
|
|
791
940
|
# RfcommClient
|
|
@@ -796,6 +945,7 @@ class RfcommClient(StreamedPacketIO):
|
|
|
796
945
|
self.device = device
|
|
797
946
|
self.channel = channel
|
|
798
947
|
self.uuid = uuid
|
|
948
|
+
self.rfcomm_session = None
|
|
799
949
|
self.ready = asyncio.Event()
|
|
800
950
|
|
|
801
951
|
async def on_connection(self, connection):
|
|
@@ -804,39 +954,44 @@ class RfcommClient(StreamedPacketIO):
|
|
|
804
954
|
# Find the channel number if not specified
|
|
805
955
|
channel = self.channel
|
|
806
956
|
if channel == 0:
|
|
807
|
-
|
|
957
|
+
logging.info(
|
|
808
958
|
color(f'@@@ Discovering channel number from UUID {self.uuid}', 'cyan')
|
|
809
959
|
)
|
|
810
960
|
channel = await find_rfcomm_channel_with_uuid(connection, self.uuid)
|
|
811
|
-
|
|
961
|
+
logging.info(color(f'@@@ Channel number = {channel}', 'cyan'))
|
|
812
962
|
if channel == 0:
|
|
813
|
-
|
|
963
|
+
logging.info(color('!!! No RFComm service with this UUID found', 'red'))
|
|
814
964
|
await connection.disconnect()
|
|
815
965
|
return
|
|
816
966
|
|
|
817
967
|
# Create a client and start it
|
|
818
|
-
|
|
968
|
+
logging.info(color('*** Starting RFCOMM client...', 'blue'))
|
|
819
969
|
rfcomm_client = bumble.rfcomm.Client(connection)
|
|
820
970
|
rfcomm_mux = await rfcomm_client.start()
|
|
821
|
-
|
|
971
|
+
logging.info(color('*** Started', 'blue'))
|
|
822
972
|
|
|
823
|
-
|
|
973
|
+
logging.info(color(f'### Opening session for channel {channel}...', 'yellow'))
|
|
824
974
|
try:
|
|
825
975
|
rfcomm_session = await rfcomm_mux.open_dlc(channel)
|
|
826
|
-
|
|
976
|
+
logging.info(color(f'### Session open: {rfcomm_session}', 'yellow'))
|
|
827
977
|
except bumble.core.ConnectionError as error:
|
|
828
|
-
|
|
978
|
+
logging.info(color(f'!!! Session open failed: {error}', 'red'))
|
|
829
979
|
await rfcomm_mux.disconnect()
|
|
830
980
|
return
|
|
831
981
|
|
|
832
982
|
rfcomm_session.sink = self.on_packet
|
|
833
983
|
self.io_sink = rfcomm_session.write
|
|
984
|
+
self.rfcomm_session = rfcomm_session
|
|
834
985
|
|
|
835
986
|
self.ready.set()
|
|
836
987
|
|
|
837
988
|
def on_disconnection(self, _):
|
|
838
989
|
pass
|
|
839
990
|
|
|
991
|
+
async def drain(self):
|
|
992
|
+
assert self.rfcomm_session
|
|
993
|
+
await self.rfcomm_session.drain()
|
|
994
|
+
|
|
840
995
|
|
|
841
996
|
# -----------------------------------------------------------------------------
|
|
842
997
|
# RfcommServer
|
|
@@ -844,6 +999,7 @@ class RfcommClient(StreamedPacketIO):
|
|
|
844
999
|
class RfcommServer(StreamedPacketIO):
|
|
845
1000
|
def __init__(self, device, channel):
|
|
846
1001
|
super().__init__()
|
|
1002
|
+
self.dlc = None
|
|
847
1003
|
self.ready = asyncio.Event()
|
|
848
1004
|
|
|
849
1005
|
# Create and register a server
|
|
@@ -855,7 +1011,7 @@ class RfcommServer(StreamedPacketIO):
|
|
|
855
1011
|
# Setup the SDP to advertise this channel
|
|
856
1012
|
device.sdp_service_records = make_sdp_records(channel_number)
|
|
857
1013
|
|
|
858
|
-
|
|
1014
|
+
logging.info(
|
|
859
1015
|
color(
|
|
860
1016
|
f'### Listening for RFComm connection on channel {channel_number}',
|
|
861
1017
|
'yellow',
|
|
@@ -869,9 +1025,14 @@ class RfcommServer(StreamedPacketIO):
|
|
|
869
1025
|
pass
|
|
870
1026
|
|
|
871
1027
|
def on_dlc(self, dlc):
|
|
872
|
-
|
|
1028
|
+
logging.info(color(f'*** DLC connected: {dlc}', 'blue'))
|
|
873
1029
|
dlc.sink = self.on_packet
|
|
874
1030
|
self.io_sink = dlc.write
|
|
1031
|
+
self.dlc = dlc
|
|
1032
|
+
|
|
1033
|
+
async def drain(self):
|
|
1034
|
+
assert self.dlc
|
|
1035
|
+
await self.dlc.drain()
|
|
875
1036
|
|
|
876
1037
|
|
|
877
1038
|
# -----------------------------------------------------------------------------
|
|
@@ -935,12 +1096,12 @@ class Central(Connection.Listener):
|
|
|
935
1096
|
self.connection_parameter_preferences = None
|
|
936
1097
|
|
|
937
1098
|
async def run(self):
|
|
938
|
-
|
|
1099
|
+
logging.info(color('>>> Connecting to HCI...', 'green'))
|
|
939
1100
|
async with await open_transport_or_link(self.transport) as (
|
|
940
1101
|
hci_source,
|
|
941
1102
|
hci_sink,
|
|
942
1103
|
):
|
|
943
|
-
|
|
1104
|
+
logging.info(color('>>> Connected', 'green'))
|
|
944
1105
|
|
|
945
1106
|
central_address = DEFAULT_CENTRAL_ADDRESS
|
|
946
1107
|
self.device = Device.with_hci(
|
|
@@ -952,7 +1113,13 @@ class Central(Connection.Listener):
|
|
|
952
1113
|
|
|
953
1114
|
await self.device.power_on()
|
|
954
1115
|
|
|
955
|
-
|
|
1116
|
+
if self.classic:
|
|
1117
|
+
await self.device.set_discoverable(False)
|
|
1118
|
+
await self.device.set_connectable(False)
|
|
1119
|
+
|
|
1120
|
+
logging.info(
|
|
1121
|
+
color(f'### Connecting to {self.peripheral_address}...', 'cyan')
|
|
1122
|
+
)
|
|
956
1123
|
try:
|
|
957
1124
|
self.connection = await self.device.connect(
|
|
958
1125
|
self.peripheral_address,
|
|
@@ -960,21 +1127,26 @@ class Central(Connection.Listener):
|
|
|
960
1127
|
transport=BT_BR_EDR_TRANSPORT if self.classic else BT_LE_TRANSPORT,
|
|
961
1128
|
)
|
|
962
1129
|
except CommandTimeoutError:
|
|
963
|
-
|
|
1130
|
+
logging.info(color('!!! Connection timed out', 'red'))
|
|
964
1131
|
return
|
|
965
1132
|
except bumble.core.ConnectionError as error:
|
|
966
|
-
|
|
1133
|
+
logging.info(color(f'!!! Connection error: {error}', 'red'))
|
|
967
1134
|
return
|
|
968
1135
|
except HCI_StatusError as error:
|
|
969
|
-
|
|
1136
|
+
logging.info(color(f'!!! Connection failed: {error.error_name}'))
|
|
970
1137
|
return
|
|
971
|
-
|
|
1138
|
+
logging.info(color('### Connected', 'cyan'))
|
|
972
1139
|
self.connection.listener = self
|
|
973
1140
|
print_connection(self.connection)
|
|
974
1141
|
|
|
1142
|
+
# Wait a bit after the connection, some controllers aren't very good when
|
|
1143
|
+
# we start sending data right away while some connection parameters are
|
|
1144
|
+
# updated post connection
|
|
1145
|
+
await asyncio.sleep(DEFAULT_POST_CONNECTION_WAIT_TIME)
|
|
1146
|
+
|
|
975
1147
|
# Request a new data length if requested
|
|
976
1148
|
if self.extended_data_length:
|
|
977
|
-
|
|
1149
|
+
logging.info(color('+++ Requesting extended data length', 'cyan'))
|
|
978
1150
|
await self.connection.set_data_length(
|
|
979
1151
|
self.extended_data_length[0], self.extended_data_length[1]
|
|
980
1152
|
)
|
|
@@ -982,16 +1154,16 @@ class Central(Connection.Listener):
|
|
|
982
1154
|
# Authenticate if requested
|
|
983
1155
|
if self.authenticate:
|
|
984
1156
|
# Request authentication
|
|
985
|
-
|
|
1157
|
+
logging.info(color('*** Authenticating...', 'cyan'))
|
|
986
1158
|
await self.connection.authenticate()
|
|
987
|
-
|
|
1159
|
+
logging.info(color('*** Authenticated', 'cyan'))
|
|
988
1160
|
|
|
989
1161
|
# Encrypt if requested
|
|
990
1162
|
if self.encrypt:
|
|
991
1163
|
# Enable encryption
|
|
992
|
-
|
|
1164
|
+
logging.info(color('*** Enabling encryption...', 'cyan'))
|
|
993
1165
|
await self.connection.encrypt()
|
|
994
|
-
|
|
1166
|
+
logging.info(color('*** Encryption on', 'cyan'))
|
|
995
1167
|
|
|
996
1168
|
# Set the PHY if requested
|
|
997
1169
|
if self.phy is not None:
|
|
@@ -1000,7 +1172,7 @@ class Central(Connection.Listener):
|
|
|
1000
1172
|
tx_phys=[self.phy], rx_phys=[self.phy]
|
|
1001
1173
|
)
|
|
1002
1174
|
except HCI_Error as error:
|
|
1003
|
-
|
|
1175
|
+
logging.info(
|
|
1004
1176
|
color(
|
|
1005
1177
|
f'!!! Unable to set the PHY: {error.error_name}', 'yellow'
|
|
1006
1178
|
)
|
|
@@ -1010,9 +1182,10 @@ class Central(Connection.Listener):
|
|
|
1010
1182
|
|
|
1011
1183
|
await role.run()
|
|
1012
1184
|
await asyncio.sleep(DEFAULT_LINGER_TIME)
|
|
1185
|
+
await self.connection.disconnect()
|
|
1013
1186
|
|
|
1014
1187
|
def on_disconnection(self, reason):
|
|
1015
|
-
|
|
1188
|
+
logging.info(color(f'!!! Disconnection: reason={reason}', 'red'))
|
|
1016
1189
|
self.connection = None
|
|
1017
1190
|
|
|
1018
1191
|
def on_connection_parameters_update(self):
|
|
@@ -1047,12 +1220,12 @@ class Peripheral(Device.Listener, Connection.Listener):
|
|
|
1047
1220
|
self.connected = asyncio.Event()
|
|
1048
1221
|
|
|
1049
1222
|
async def run(self):
|
|
1050
|
-
|
|
1223
|
+
logging.info(color('>>> Connecting to HCI...', 'green'))
|
|
1051
1224
|
async with await open_transport_or_link(self.transport) as (
|
|
1052
1225
|
hci_source,
|
|
1053
1226
|
hci_sink,
|
|
1054
1227
|
):
|
|
1055
|
-
|
|
1228
|
+
logging.info(color('>>> Connected', 'green'))
|
|
1056
1229
|
|
|
1057
1230
|
peripheral_address = DEFAULT_PERIPHERAL_ADDRESS
|
|
1058
1231
|
self.device = Device.with_hci(
|
|
@@ -1072,7 +1245,7 @@ class Peripheral(Device.Listener, Connection.Listener):
|
|
|
1072
1245
|
await self.device.start_advertising(auto_restart=True)
|
|
1073
1246
|
|
|
1074
1247
|
if self.classic:
|
|
1075
|
-
|
|
1248
|
+
logging.info(
|
|
1076
1249
|
color(
|
|
1077
1250
|
'### Waiting for connection on'
|
|
1078
1251
|
f' {self.device.public_address}...',
|
|
@@ -1080,14 +1253,14 @@ class Peripheral(Device.Listener, Connection.Listener):
|
|
|
1080
1253
|
)
|
|
1081
1254
|
)
|
|
1082
1255
|
else:
|
|
1083
|
-
|
|
1256
|
+
logging.info(
|
|
1084
1257
|
color(
|
|
1085
1258
|
f'### Waiting for connection on {peripheral_address}...',
|
|
1086
1259
|
'cyan',
|
|
1087
1260
|
)
|
|
1088
1261
|
)
|
|
1089
1262
|
await self.connected.wait()
|
|
1090
|
-
|
|
1263
|
+
logging.info(color('### Connected', 'cyan'))
|
|
1091
1264
|
|
|
1092
1265
|
await self.mode.on_connection(self.connection)
|
|
1093
1266
|
await self.role.run()
|
|
@@ -1098,9 +1271,14 @@ class Peripheral(Device.Listener, Connection.Listener):
|
|
|
1098
1271
|
self.connection = connection
|
|
1099
1272
|
self.connected.set()
|
|
1100
1273
|
|
|
1274
|
+
# Stop being discoverable and connectable
|
|
1275
|
+
if self.classic:
|
|
1276
|
+
AsyncRunner.spawn(self.device.set_discoverable(False))
|
|
1277
|
+
AsyncRunner.spawn(self.device.set_connectable(False))
|
|
1278
|
+
|
|
1101
1279
|
# Request a new data length if needed
|
|
1102
1280
|
if self.extended_data_length:
|
|
1103
|
-
|
|
1281
|
+
logging.info("+++ Requesting extended data length")
|
|
1104
1282
|
AsyncRunner.spawn(
|
|
1105
1283
|
connection.set_data_length(
|
|
1106
1284
|
self.extended_data_length[0], self.extended_data_length[1]
|
|
@@ -1108,10 +1286,14 @@ class Peripheral(Device.Listener, Connection.Listener):
|
|
|
1108
1286
|
)
|
|
1109
1287
|
|
|
1110
1288
|
def on_disconnection(self, reason):
|
|
1111
|
-
|
|
1289
|
+
logging.info(color(f'!!! Disconnection: reason={reason}', 'red'))
|
|
1112
1290
|
self.connection = None
|
|
1113
1291
|
self.role.reset()
|
|
1114
1292
|
|
|
1293
|
+
if self.classic:
|
|
1294
|
+
AsyncRunner.spawn(self.device.set_discoverable(True))
|
|
1295
|
+
AsyncRunner.spawn(self.device.set_connectable(True))
|
|
1296
|
+
|
|
1115
1297
|
def on_connection_parameters_update(self):
|
|
1116
1298
|
print_connection(self.connection)
|
|
1117
1299
|
|
|
@@ -1139,10 +1321,22 @@ def create_mode_factory(ctx, default_mode):
|
|
|
1139
1321
|
return GattServer(device)
|
|
1140
1322
|
|
|
1141
1323
|
if mode == 'l2cap-client':
|
|
1142
|
-
return L2capClient(
|
|
1324
|
+
return L2capClient(
|
|
1325
|
+
device,
|
|
1326
|
+
psm=ctx.obj['l2cap_psm'],
|
|
1327
|
+
mtu=ctx.obj['l2cap_mtu'],
|
|
1328
|
+
mps=ctx.obj['l2cap_mps'],
|
|
1329
|
+
max_credits=ctx.obj['l2cap_max_credits'],
|
|
1330
|
+
)
|
|
1143
1331
|
|
|
1144
1332
|
if mode == 'l2cap-server':
|
|
1145
|
-
return L2capServer(
|
|
1333
|
+
return L2capServer(
|
|
1334
|
+
device,
|
|
1335
|
+
psm=ctx.obj['l2cap_psm'],
|
|
1336
|
+
mtu=ctx.obj['l2cap_mtu'],
|
|
1337
|
+
mps=ctx.obj['l2cap_mps'],
|
|
1338
|
+
max_credits=ctx.obj['l2cap_max_credits'],
|
|
1339
|
+
)
|
|
1146
1340
|
|
|
1147
1341
|
if mode == 'rfcomm-client':
|
|
1148
1342
|
return RfcommClient(
|
|
@@ -1168,23 +1362,29 @@ def create_role_factory(ctx, default_role):
|
|
|
1168
1362
|
return Sender(
|
|
1169
1363
|
packet_io,
|
|
1170
1364
|
start_delay=ctx.obj['start_delay'],
|
|
1365
|
+
repeat=ctx.obj['repeat'],
|
|
1366
|
+
repeat_delay=ctx.obj['repeat_delay'],
|
|
1367
|
+
pace=ctx.obj['pace'],
|
|
1171
1368
|
packet_size=ctx.obj['packet_size'],
|
|
1172
1369
|
packet_count=ctx.obj['packet_count'],
|
|
1173
1370
|
)
|
|
1174
1371
|
|
|
1175
1372
|
if role == 'receiver':
|
|
1176
|
-
return Receiver(packet_io)
|
|
1373
|
+
return Receiver(packet_io, ctx.obj['linger'])
|
|
1177
1374
|
|
|
1178
1375
|
if role == 'ping':
|
|
1179
1376
|
return Ping(
|
|
1180
1377
|
packet_io,
|
|
1181
1378
|
start_delay=ctx.obj['start_delay'],
|
|
1379
|
+
repeat=ctx.obj['repeat'],
|
|
1380
|
+
repeat_delay=ctx.obj['repeat_delay'],
|
|
1381
|
+
pace=ctx.obj['pace'],
|
|
1182
1382
|
packet_size=ctx.obj['packet_size'],
|
|
1183
1383
|
packet_count=ctx.obj['packet_count'],
|
|
1184
1384
|
)
|
|
1185
1385
|
|
|
1186
1386
|
if role == 'pong':
|
|
1187
|
-
return Pong(packet_io)
|
|
1387
|
+
return Pong(packet_io, ctx.obj['linger'])
|
|
1188
1388
|
|
|
1189
1389
|
raise ValueError('invalid role')
|
|
1190
1390
|
|
|
@@ -1229,7 +1429,7 @@ def create_role_factory(ctx, default_role):
|
|
|
1229
1429
|
@click.option(
|
|
1230
1430
|
'--rfcomm-uuid',
|
|
1231
1431
|
default=DEFAULT_RFCOMM_UUID,
|
|
1232
|
-
help='RFComm service UUID to use (ignored
|
|
1432
|
+
help='RFComm service UUID to use (ignored if --rfcomm-channel is not 0)',
|
|
1233
1433
|
)
|
|
1234
1434
|
@click.option(
|
|
1235
1435
|
'--l2cap-psm',
|
|
@@ -1237,13 +1437,31 @@ def create_role_factory(ctx, default_role):
|
|
|
1237
1437
|
default=DEFAULT_L2CAP_PSM,
|
|
1238
1438
|
help='L2CAP PSM to use',
|
|
1239
1439
|
)
|
|
1440
|
+
@click.option(
|
|
1441
|
+
'--l2cap-mtu',
|
|
1442
|
+
type=int,
|
|
1443
|
+
default=DEFAULT_L2CAP_MTU,
|
|
1444
|
+
help='L2CAP MTU to use',
|
|
1445
|
+
)
|
|
1446
|
+
@click.option(
|
|
1447
|
+
'--l2cap-mps',
|
|
1448
|
+
type=int,
|
|
1449
|
+
default=DEFAULT_L2CAP_MPS,
|
|
1450
|
+
help='L2CAP MPS to use',
|
|
1451
|
+
)
|
|
1452
|
+
@click.option(
|
|
1453
|
+
'--l2cap-max-credits',
|
|
1454
|
+
type=int,
|
|
1455
|
+
default=DEFAULT_L2CAP_MAX_CREDITS,
|
|
1456
|
+
help='L2CAP maximum number of credits allowed for the peer',
|
|
1457
|
+
)
|
|
1240
1458
|
@click.option(
|
|
1241
1459
|
'--packet-size',
|
|
1242
1460
|
'-s',
|
|
1243
1461
|
metavar='SIZE',
|
|
1244
1462
|
type=click.IntRange(8, 4096),
|
|
1245
1463
|
default=500,
|
|
1246
|
-
help='Packet size (
|
|
1464
|
+
help='Packet size (client or ping role)',
|
|
1247
1465
|
)
|
|
1248
1466
|
@click.option(
|
|
1249
1467
|
'--packet-count',
|
|
@@ -1251,7 +1469,7 @@ def create_role_factory(ctx, default_role):
|
|
|
1251
1469
|
metavar='COUNT',
|
|
1252
1470
|
type=int,
|
|
1253
1471
|
default=10,
|
|
1254
|
-
help='Packet count (
|
|
1472
|
+
help='Packet count (client or ping role)',
|
|
1255
1473
|
)
|
|
1256
1474
|
@click.option(
|
|
1257
1475
|
'--start-delay',
|
|
@@ -1259,7 +1477,39 @@ def create_role_factory(ctx, default_role):
|
|
|
1259
1477
|
metavar='SECONDS',
|
|
1260
1478
|
type=int,
|
|
1261
1479
|
default=1,
|
|
1262
|
-
help='Start delay (
|
|
1480
|
+
help='Start delay (client or ping role)',
|
|
1481
|
+
)
|
|
1482
|
+
@click.option(
|
|
1483
|
+
'--repeat',
|
|
1484
|
+
metavar='N',
|
|
1485
|
+
type=int,
|
|
1486
|
+
default=0,
|
|
1487
|
+
help=(
|
|
1488
|
+
'Repeat the run N times (client and ping roles)'
|
|
1489
|
+
'(0, which is the fault, to run just once) '
|
|
1490
|
+
),
|
|
1491
|
+
)
|
|
1492
|
+
@click.option(
|
|
1493
|
+
'--repeat-delay',
|
|
1494
|
+
metavar='SECONDS',
|
|
1495
|
+
type=int,
|
|
1496
|
+
default=1,
|
|
1497
|
+
help=('Delay, in seconds, between repeats'),
|
|
1498
|
+
)
|
|
1499
|
+
@click.option(
|
|
1500
|
+
'--pace',
|
|
1501
|
+
metavar='MILLISECONDS',
|
|
1502
|
+
type=int,
|
|
1503
|
+
default=0,
|
|
1504
|
+
help=(
|
|
1505
|
+
'Wait N milliseconds between packets '
|
|
1506
|
+
'(0, which is the fault, to send as fast as possible) '
|
|
1507
|
+
),
|
|
1508
|
+
)
|
|
1509
|
+
@click.option(
|
|
1510
|
+
'--linger',
|
|
1511
|
+
is_flag=True,
|
|
1512
|
+
help="Don't exit at the end of a run (server and pong roles)",
|
|
1263
1513
|
)
|
|
1264
1514
|
@click.pass_context
|
|
1265
1515
|
def bench(
|
|
@@ -1272,9 +1522,16 @@ def bench(
|
|
|
1272
1522
|
packet_size,
|
|
1273
1523
|
packet_count,
|
|
1274
1524
|
start_delay,
|
|
1525
|
+
repeat,
|
|
1526
|
+
repeat_delay,
|
|
1527
|
+
pace,
|
|
1528
|
+
linger,
|
|
1275
1529
|
rfcomm_channel,
|
|
1276
1530
|
rfcomm_uuid,
|
|
1277
1531
|
l2cap_psm,
|
|
1532
|
+
l2cap_mtu,
|
|
1533
|
+
l2cap_mps,
|
|
1534
|
+
l2cap_max_credits,
|
|
1278
1535
|
):
|
|
1279
1536
|
ctx.ensure_object(dict)
|
|
1280
1537
|
ctx.obj['device_config'] = device_config
|
|
@@ -1284,9 +1541,16 @@ def bench(
|
|
|
1284
1541
|
ctx.obj['rfcomm_channel'] = rfcomm_channel
|
|
1285
1542
|
ctx.obj['rfcomm_uuid'] = rfcomm_uuid
|
|
1286
1543
|
ctx.obj['l2cap_psm'] = l2cap_psm
|
|
1544
|
+
ctx.obj['l2cap_mtu'] = l2cap_mtu
|
|
1545
|
+
ctx.obj['l2cap_mps'] = l2cap_mps
|
|
1546
|
+
ctx.obj['l2cap_max_credits'] = l2cap_max_credits
|
|
1287
1547
|
ctx.obj['packet_size'] = packet_size
|
|
1288
1548
|
ctx.obj['packet_count'] = packet_count
|
|
1289
1549
|
ctx.obj['start_delay'] = start_delay
|
|
1550
|
+
ctx.obj['repeat'] = repeat
|
|
1551
|
+
ctx.obj['repeat_delay'] = repeat_delay
|
|
1552
|
+
ctx.obj['pace'] = pace
|
|
1553
|
+
ctx.obj['linger'] = linger
|
|
1290
1554
|
|
|
1291
1555
|
ctx.obj['extended_data_length'] = (
|
|
1292
1556
|
[int(x) for x in extended_data_length.split('/')]
|