bumble 0.0.181__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 +302 -67
- bumble/apps/controller_info.py +30 -4
- bumble/apps/controller_loopback.py +200 -0
- bumble/apps/l2cap_bridge.py +32 -24
- bumble/controller.py +131 -23
- bumble/device.py +15 -3
- bumble/hci.py +37 -1
- bumble/l2cap.py +10 -5
- bumble/link.py +55 -1
- bumble/profiles/csip.py +60 -8
- bumble/rfcomm.py +7 -3
- bumble/transport/__init__.py +2 -1
- {bumble-0.0.181.dist-info → bumble-0.0.182.dist-info}/METADATA +1 -1
- {bumble-0.0.181.dist-info → bumble-0.0.182.dist-info}/RECORD +19 -18
- {bumble-0.0.181.dist-info → bumble-0.0.182.dist-info}/LICENSE +0 -0
- {bumble-0.0.181.dist-info → bumble-0.0.182.dist-info}/WHEEL +0 -0
- {bumble-0.0.181.dist-info → bumble-0.0.182.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.181.dist-info → bumble-0.0.182.dist-info}/top_level.txt +0 -0
bumble/apps/bench.py
CHANGED
|
@@ -80,10 +80,10 @@ 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
85
|
DEFAULT_L2CAP_MTU = 1024
|
|
86
|
-
DEFAULT_L2CAP_MPS =
|
|
86
|
+
DEFAULT_L2CAP_MPS = 1024
|
|
87
87
|
|
|
88
88
|
DEFAULT_LINGER_TIME = 1.0
|
|
89
89
|
DEFAULT_POST_CONNECTION_WAIT_TIME = 1.0
|
|
@@ -240,6 +240,23 @@ async def find_rfcomm_channel_with_uuid(connection: Connection, uuid: str) -> in
|
|
|
240
240
|
return 0
|
|
241
241
|
|
|
242
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
|
+
|
|
243
260
|
class PacketType(enum.IntEnum):
|
|
244
261
|
RESET = 0
|
|
245
262
|
SEQUENCE = 1
|
|
@@ -253,14 +270,27 @@ PACKET_FLAG_LAST = 1
|
|
|
253
270
|
# Sender
|
|
254
271
|
# -----------------------------------------------------------------------------
|
|
255
272
|
class Sender:
|
|
256
|
-
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
|
+
):
|
|
257
283
|
self.tx_start_delay = start_delay
|
|
258
284
|
self.tx_packet_size = packet_size
|
|
259
285
|
self.tx_packet_count = packet_count
|
|
260
286
|
self.packet_io = packet_io
|
|
261
287
|
self.packet_io.packet_listener = self
|
|
288
|
+
self.repeat = repeat
|
|
289
|
+
self.repeat_delay = repeat_delay
|
|
290
|
+
self.pace = pace
|
|
262
291
|
self.start_time = 0
|
|
263
292
|
self.bytes_sent = 0
|
|
293
|
+
self.stats = []
|
|
264
294
|
self.done = asyncio.Event()
|
|
265
295
|
|
|
266
296
|
def reset(self):
|
|
@@ -271,27 +301,57 @@ class Sender:
|
|
|
271
301
|
await self.packet_io.ready.wait()
|
|
272
302
|
logging.info(color('--- Go!', 'blue'))
|
|
273
303
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
await asyncio.sleep(self.tx_start_delay)
|
|
277
|
-
|
|
278
|
-
logging.info(color('=== Sending RESET', 'magenta'))
|
|
279
|
-
await self.packet_io.send_packet(bytes([PacketType.RESET]))
|
|
280
|
-
self.start_time = time.time()
|
|
281
|
-
for tx_i in range(self.tx_packet_count):
|
|
282
|
-
packet_flags = PACKET_FLAG_LAST if tx_i == self.tx_packet_count - 1 else 0
|
|
283
|
-
packet = struct.pack(
|
|
284
|
-
'>bbI',
|
|
285
|
-
PacketType.SEQUENCE,
|
|
286
|
-
packet_flags,
|
|
287
|
-
tx_i,
|
|
288
|
-
) + bytes(self.tx_packet_size - 6)
|
|
289
|
-
logging.info(color(f'Sending packet {tx_i}: {len(packet)} bytes', 'yellow'))
|
|
290
|
-
self.bytes_sent += len(packet)
|
|
291
|
-
await self.packet_io.send_packet(packet)
|
|
304
|
+
for run in range(self.repeat + 1):
|
|
305
|
+
self.done.clear()
|
|
292
306
|
|
|
293
|
-
|
|
294
|
-
|
|
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)
|
|
336
|
+
|
|
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'))
|
|
295
355
|
|
|
296
356
|
def on_packet_received(self, packet):
|
|
297
357
|
try:
|
|
@@ -302,6 +362,7 @@ class Sender:
|
|
|
302
362
|
if packet_type == PacketType.ACK:
|
|
303
363
|
elapsed = time.time() - self.start_time
|
|
304
364
|
average_tx_speed = self.bytes_sent / elapsed
|
|
365
|
+
self.stats.append(average_tx_speed)
|
|
305
366
|
logging.info(
|
|
306
367
|
color(
|
|
307
368
|
f'@@@ Received ACK. Speed: average={average_tx_speed:.4f}'
|
|
@@ -320,17 +381,17 @@ class Receiver:
|
|
|
320
381
|
start_timestamp: float
|
|
321
382
|
last_timestamp: float
|
|
322
383
|
|
|
323
|
-
def __init__(self, packet_io):
|
|
384
|
+
def __init__(self, packet_io, linger):
|
|
324
385
|
self.reset()
|
|
325
386
|
self.packet_io = packet_io
|
|
326
387
|
self.packet_io.packet_listener = self
|
|
388
|
+
self.linger = linger
|
|
327
389
|
self.done = asyncio.Event()
|
|
328
390
|
|
|
329
391
|
def reset(self):
|
|
330
392
|
self.expected_packet_index = 0
|
|
331
|
-
self.
|
|
332
|
-
self.
|
|
333
|
-
self.bytes_received = 0
|
|
393
|
+
self.measurements = [(time.time(), 0)]
|
|
394
|
+
self.total_bytes_received = 0
|
|
334
395
|
|
|
335
396
|
def on_packet_received(self, packet):
|
|
336
397
|
try:
|
|
@@ -338,12 +399,9 @@ class Receiver:
|
|
|
338
399
|
except ValueError:
|
|
339
400
|
return
|
|
340
401
|
|
|
341
|
-
now = time.time()
|
|
342
|
-
|
|
343
402
|
if packet_type == PacketType.RESET:
|
|
344
403
|
logging.info(color('=== Received RESET', 'magenta'))
|
|
345
404
|
self.reset()
|
|
346
|
-
self.start_timestamp = now
|
|
347
405
|
return
|
|
348
406
|
|
|
349
407
|
try:
|
|
@@ -352,7 +410,8 @@ class Receiver:
|
|
|
352
410
|
return
|
|
353
411
|
logging.info(
|
|
354
412
|
f'<<< Received packet {packet_index}: '
|
|
355
|
-
f'flags=0x{packet_flags:02X},
|
|
413
|
+
f'flags=0x{packet_flags:02X}, '
|
|
414
|
+
f'{len(packet) + self.packet_io.overhead_size} bytes'
|
|
356
415
|
)
|
|
357
416
|
|
|
358
417
|
if packet_index != self.expected_packet_index:
|
|
@@ -363,19 +422,27 @@ class Receiver:
|
|
|
363
422
|
)
|
|
364
423
|
)
|
|
365
424
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
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)
|
|
369
430
|
instant_rx_speed = len(packet) / elapsed_since_last
|
|
370
|
-
average_rx_speed = self.
|
|
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
|
+
)
|
|
371
436
|
logging.info(
|
|
372
437
|
color(
|
|
373
|
-
|
|
438
|
+
'Speed: '
|
|
439
|
+
f'instant={instant_rx_speed:.4f}, '
|
|
440
|
+
f'windowed={windowed_rx_speed:.4f}, '
|
|
441
|
+
f'average={average_rx_speed:.4f}',
|
|
374
442
|
'yellow',
|
|
375
443
|
)
|
|
376
444
|
)
|
|
377
445
|
|
|
378
|
-
self.last_timestamp = now
|
|
379
446
|
self.expected_packet_index = packet_index + 1
|
|
380
447
|
|
|
381
448
|
if packet_flags & PACKET_FLAG_LAST:
|
|
@@ -385,7 +452,8 @@ class Receiver:
|
|
|
385
452
|
)
|
|
386
453
|
)
|
|
387
454
|
logging.info(color('@@@ Received last packet', 'green'))
|
|
388
|
-
self.
|
|
455
|
+
if not self.linger:
|
|
456
|
+
self.done.set()
|
|
389
457
|
|
|
390
458
|
async def run(self):
|
|
391
459
|
await self.done.wait()
|
|
@@ -396,16 +464,31 @@ class Receiver:
|
|
|
396
464
|
# Ping
|
|
397
465
|
# -----------------------------------------------------------------------------
|
|
398
466
|
class Ping:
|
|
399
|
-
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
|
+
):
|
|
400
477
|
self.tx_start_delay = start_delay
|
|
401
478
|
self.tx_packet_size = packet_size
|
|
402
479
|
self.tx_packet_count = packet_count
|
|
403
480
|
self.packet_io = packet_io
|
|
404
481
|
self.packet_io.packet_listener = self
|
|
482
|
+
self.repeat = repeat
|
|
483
|
+
self.repeat_delay = repeat_delay
|
|
484
|
+
self.pace = pace
|
|
405
485
|
self.done = asyncio.Event()
|
|
406
486
|
self.current_packet_index = 0
|
|
407
487
|
self.ping_sent_time = 0.0
|
|
408
488
|
self.latencies = []
|
|
489
|
+
self.min_stats = []
|
|
490
|
+
self.max_stats = []
|
|
491
|
+
self.avg_stats = []
|
|
409
492
|
|
|
410
493
|
def reset(self):
|
|
411
494
|
pass
|
|
@@ -415,21 +498,57 @@ class Ping:
|
|
|
415
498
|
await self.packet_io.ready.wait()
|
|
416
499
|
logging.info(color('--- Go!', 'blue'))
|
|
417
500
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
await asyncio.sleep(self.tx_start_delay)
|
|
501
|
+
for run in range(self.repeat + 1):
|
|
502
|
+
self.done.clear()
|
|
421
503
|
|
|
422
|
-
|
|
423
|
-
|
|
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)
|
|
424
507
|
|
|
425
|
-
|
|
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)
|
|
426
511
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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'))
|
|
431
547
|
|
|
432
548
|
async def send_next_ping(self):
|
|
549
|
+
if self.pace:
|
|
550
|
+
await asyncio.sleep(self.pace / 1000)
|
|
551
|
+
|
|
433
552
|
packet = struct.pack(
|
|
434
553
|
'>bbI',
|
|
435
554
|
PacketType.SEQUENCE,
|
|
@@ -488,10 +607,11 @@ class Ping:
|
|
|
488
607
|
class Pong:
|
|
489
608
|
expected_packet_index: int
|
|
490
609
|
|
|
491
|
-
def __init__(self, packet_io):
|
|
610
|
+
def __init__(self, packet_io, linger):
|
|
492
611
|
self.reset()
|
|
493
612
|
self.packet_io = packet_io
|
|
494
613
|
self.packet_io.packet_listener = self
|
|
614
|
+
self.linger = linger
|
|
495
615
|
self.done = asyncio.Event()
|
|
496
616
|
|
|
497
617
|
def reset(self):
|
|
@@ -536,7 +656,7 @@ class Pong:
|
|
|
536
656
|
)
|
|
537
657
|
)
|
|
538
658
|
|
|
539
|
-
if packet_flags & PACKET_FLAG_LAST:
|
|
659
|
+
if packet_flags & PACKET_FLAG_LAST and not self.linger:
|
|
540
660
|
self.done.set()
|
|
541
661
|
|
|
542
662
|
async def run(self):
|
|
@@ -554,6 +674,7 @@ class GattClient:
|
|
|
554
674
|
self.speed_tx = None
|
|
555
675
|
self.packet_listener = None
|
|
556
676
|
self.ready = asyncio.Event()
|
|
677
|
+
self.overhead_size = 0
|
|
557
678
|
|
|
558
679
|
async def on_connection(self, connection):
|
|
559
680
|
peer = Peer(connection)
|
|
@@ -603,6 +724,9 @@ class GattClient:
|
|
|
603
724
|
async def send_packet(self, packet):
|
|
604
725
|
await self.speed_tx.write_value(packet)
|
|
605
726
|
|
|
727
|
+
async def drain(self):
|
|
728
|
+
pass
|
|
729
|
+
|
|
606
730
|
|
|
607
731
|
# -----------------------------------------------------------------------------
|
|
608
732
|
# GattServer
|
|
@@ -612,6 +736,7 @@ class GattServer:
|
|
|
612
736
|
self.device = device
|
|
613
737
|
self.packet_listener = None
|
|
614
738
|
self.ready = asyncio.Event()
|
|
739
|
+
self.overhead_size = 0
|
|
615
740
|
|
|
616
741
|
# Setup the GATT service
|
|
617
742
|
self.speed_tx = Characteristic(
|
|
@@ -653,6 +778,9 @@ class GattServer:
|
|
|
653
778
|
async def send_packet(self, packet):
|
|
654
779
|
await self.device.notify_subscribers(self.speed_rx, packet)
|
|
655
780
|
|
|
781
|
+
async def drain(self):
|
|
782
|
+
pass
|
|
783
|
+
|
|
656
784
|
|
|
657
785
|
# -----------------------------------------------------------------------------
|
|
658
786
|
# StreamedPacketIO
|
|
@@ -664,6 +792,7 @@ class StreamedPacketIO:
|
|
|
664
792
|
self.rx_packet = b''
|
|
665
793
|
self.rx_packet_header = b''
|
|
666
794
|
self.rx_packet_need = 0
|
|
795
|
+
self.overhead_size = 2
|
|
667
796
|
|
|
668
797
|
def on_packet(self, packet):
|
|
669
798
|
while packet:
|
|
@@ -715,6 +844,7 @@ class L2capClient(StreamedPacketIO):
|
|
|
715
844
|
self.max_credits = max_credits
|
|
716
845
|
self.mtu = mtu
|
|
717
846
|
self.mps = mps
|
|
847
|
+
self.l2cap_channel = None
|
|
718
848
|
self.ready = asyncio.Event()
|
|
719
849
|
|
|
720
850
|
async def on_connection(self, connection: Connection) -> None:
|
|
@@ -736,9 +866,10 @@ class L2capClient(StreamedPacketIO):
|
|
|
736
866
|
logging.info(color(f'!!! Connection failed: {error}', 'red'))
|
|
737
867
|
return
|
|
738
868
|
|
|
739
|
-
l2cap_channel.sink = self.on_packet
|
|
740
|
-
l2cap_channel.on('close', self.on_l2cap_close)
|
|
741
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
|
|
742
873
|
|
|
743
874
|
self.ready.set()
|
|
744
875
|
|
|
@@ -748,6 +879,10 @@ class L2capClient(StreamedPacketIO):
|
|
|
748
879
|
def on_l2cap_close(self):
|
|
749
880
|
logging.info(color('*** L2CAP channel closed', 'red'))
|
|
750
881
|
|
|
882
|
+
async def drain(self):
|
|
883
|
+
assert self.l2cap_channel
|
|
884
|
+
await self.l2cap_channel.drain()
|
|
885
|
+
|
|
751
886
|
|
|
752
887
|
# -----------------------------------------------------------------------------
|
|
753
888
|
# L2capServer
|
|
@@ -786,6 +921,7 @@ class L2capServer(StreamedPacketIO):
|
|
|
786
921
|
logging.info(color(f'*** L2CAP channel: {l2cap_channel}', 'cyan'))
|
|
787
922
|
|
|
788
923
|
self.io_sink = l2cap_channel.write
|
|
924
|
+
self.l2cap_channel = l2cap_channel
|
|
789
925
|
l2cap_channel.on('close', self.on_l2cap_close)
|
|
790
926
|
l2cap_channel.sink = self.on_packet
|
|
791
927
|
|
|
@@ -795,6 +931,10 @@ class L2capServer(StreamedPacketIO):
|
|
|
795
931
|
logging.info(color('*** L2CAP channel closed', 'red'))
|
|
796
932
|
self.l2cap_channel = None
|
|
797
933
|
|
|
934
|
+
async def drain(self):
|
|
935
|
+
assert self.l2cap_channel
|
|
936
|
+
await self.l2cap_channel.drain()
|
|
937
|
+
|
|
798
938
|
|
|
799
939
|
# -----------------------------------------------------------------------------
|
|
800
940
|
# RfcommClient
|
|
@@ -805,6 +945,7 @@ class RfcommClient(StreamedPacketIO):
|
|
|
805
945
|
self.device = device
|
|
806
946
|
self.channel = channel
|
|
807
947
|
self.uuid = uuid
|
|
948
|
+
self.rfcomm_session = None
|
|
808
949
|
self.ready = asyncio.Event()
|
|
809
950
|
|
|
810
951
|
async def on_connection(self, connection):
|
|
@@ -840,12 +981,17 @@ class RfcommClient(StreamedPacketIO):
|
|
|
840
981
|
|
|
841
982
|
rfcomm_session.sink = self.on_packet
|
|
842
983
|
self.io_sink = rfcomm_session.write
|
|
984
|
+
self.rfcomm_session = rfcomm_session
|
|
843
985
|
|
|
844
986
|
self.ready.set()
|
|
845
987
|
|
|
846
988
|
def on_disconnection(self, _):
|
|
847
989
|
pass
|
|
848
990
|
|
|
991
|
+
async def drain(self):
|
|
992
|
+
assert self.rfcomm_session
|
|
993
|
+
await self.rfcomm_session.drain()
|
|
994
|
+
|
|
849
995
|
|
|
850
996
|
# -----------------------------------------------------------------------------
|
|
851
997
|
# RfcommServer
|
|
@@ -853,6 +999,7 @@ class RfcommClient(StreamedPacketIO):
|
|
|
853
999
|
class RfcommServer(StreamedPacketIO):
|
|
854
1000
|
def __init__(self, device, channel):
|
|
855
1001
|
super().__init__()
|
|
1002
|
+
self.dlc = None
|
|
856
1003
|
self.ready = asyncio.Event()
|
|
857
1004
|
|
|
858
1005
|
# Create and register a server
|
|
@@ -881,6 +1028,11 @@ class RfcommServer(StreamedPacketIO):
|
|
|
881
1028
|
logging.info(color(f'*** DLC connected: {dlc}', 'blue'))
|
|
882
1029
|
dlc.sink = self.on_packet
|
|
883
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()
|
|
884
1036
|
|
|
885
1037
|
|
|
886
1038
|
# -----------------------------------------------------------------------------
|
|
@@ -1030,6 +1182,7 @@ class Central(Connection.Listener):
|
|
|
1030
1182
|
|
|
1031
1183
|
await role.run()
|
|
1032
1184
|
await asyncio.sleep(DEFAULT_LINGER_TIME)
|
|
1185
|
+
await self.connection.disconnect()
|
|
1033
1186
|
|
|
1034
1187
|
def on_disconnection(self, reason):
|
|
1035
1188
|
logging.info(color(f'!!! Disconnection: reason={reason}', 'red'))
|
|
@@ -1120,12 +1273,8 @@ class Peripheral(Device.Listener, Connection.Listener):
|
|
|
1120
1273
|
|
|
1121
1274
|
# Stop being discoverable and connectable
|
|
1122
1275
|
if self.classic:
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
await self.device.set_discoverable(False)
|
|
1126
|
-
await self.device.set_connectable(False)
|
|
1127
|
-
|
|
1128
|
-
AsyncRunner.spawn(stop_being_discoverable_connectable())
|
|
1276
|
+
AsyncRunner.spawn(self.device.set_discoverable(False))
|
|
1277
|
+
AsyncRunner.spawn(self.device.set_connectable(False))
|
|
1129
1278
|
|
|
1130
1279
|
# Request a new data length if needed
|
|
1131
1280
|
if self.extended_data_length:
|
|
@@ -1141,6 +1290,10 @@ class Peripheral(Device.Listener, Connection.Listener):
|
|
|
1141
1290
|
self.connection = None
|
|
1142
1291
|
self.role.reset()
|
|
1143
1292
|
|
|
1293
|
+
if self.classic:
|
|
1294
|
+
AsyncRunner.spawn(self.device.set_discoverable(True))
|
|
1295
|
+
AsyncRunner.spawn(self.device.set_connectable(True))
|
|
1296
|
+
|
|
1144
1297
|
def on_connection_parameters_update(self):
|
|
1145
1298
|
print_connection(self.connection)
|
|
1146
1299
|
|
|
@@ -1168,10 +1321,22 @@ def create_mode_factory(ctx, default_mode):
|
|
|
1168
1321
|
return GattServer(device)
|
|
1169
1322
|
|
|
1170
1323
|
if mode == 'l2cap-client':
|
|
1171
|
-
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
|
+
)
|
|
1172
1331
|
|
|
1173
1332
|
if mode == 'l2cap-server':
|
|
1174
|
-
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
|
+
)
|
|
1175
1340
|
|
|
1176
1341
|
if mode == 'rfcomm-client':
|
|
1177
1342
|
return RfcommClient(
|
|
@@ -1197,23 +1362,29 @@ def create_role_factory(ctx, default_role):
|
|
|
1197
1362
|
return Sender(
|
|
1198
1363
|
packet_io,
|
|
1199
1364
|
start_delay=ctx.obj['start_delay'],
|
|
1365
|
+
repeat=ctx.obj['repeat'],
|
|
1366
|
+
repeat_delay=ctx.obj['repeat_delay'],
|
|
1367
|
+
pace=ctx.obj['pace'],
|
|
1200
1368
|
packet_size=ctx.obj['packet_size'],
|
|
1201
1369
|
packet_count=ctx.obj['packet_count'],
|
|
1202
1370
|
)
|
|
1203
1371
|
|
|
1204
1372
|
if role == 'receiver':
|
|
1205
|
-
return Receiver(packet_io)
|
|
1373
|
+
return Receiver(packet_io, ctx.obj['linger'])
|
|
1206
1374
|
|
|
1207
1375
|
if role == 'ping':
|
|
1208
1376
|
return Ping(
|
|
1209
1377
|
packet_io,
|
|
1210
1378
|
start_delay=ctx.obj['start_delay'],
|
|
1379
|
+
repeat=ctx.obj['repeat'],
|
|
1380
|
+
repeat_delay=ctx.obj['repeat_delay'],
|
|
1381
|
+
pace=ctx.obj['pace'],
|
|
1211
1382
|
packet_size=ctx.obj['packet_size'],
|
|
1212
1383
|
packet_count=ctx.obj['packet_count'],
|
|
1213
1384
|
)
|
|
1214
1385
|
|
|
1215
1386
|
if role == 'pong':
|
|
1216
|
-
return Pong(packet_io)
|
|
1387
|
+
return Pong(packet_io, ctx.obj['linger'])
|
|
1217
1388
|
|
|
1218
1389
|
raise ValueError('invalid role')
|
|
1219
1390
|
|
|
@@ -1258,7 +1429,7 @@ def create_role_factory(ctx, default_role):
|
|
|
1258
1429
|
@click.option(
|
|
1259
1430
|
'--rfcomm-uuid',
|
|
1260
1431
|
default=DEFAULT_RFCOMM_UUID,
|
|
1261
|
-
help='RFComm service UUID to use (ignored
|
|
1432
|
+
help='RFComm service UUID to use (ignored if --rfcomm-channel is not 0)',
|
|
1262
1433
|
)
|
|
1263
1434
|
@click.option(
|
|
1264
1435
|
'--l2cap-psm',
|
|
@@ -1266,13 +1437,31 @@ def create_role_factory(ctx, default_role):
|
|
|
1266
1437
|
default=DEFAULT_L2CAP_PSM,
|
|
1267
1438
|
help='L2CAP PSM to use',
|
|
1268
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
|
+
)
|
|
1269
1458
|
@click.option(
|
|
1270
1459
|
'--packet-size',
|
|
1271
1460
|
'-s',
|
|
1272
1461
|
metavar='SIZE',
|
|
1273
1462
|
type=click.IntRange(8, 4096),
|
|
1274
1463
|
default=500,
|
|
1275
|
-
help='Packet size (
|
|
1464
|
+
help='Packet size (client or ping role)',
|
|
1276
1465
|
)
|
|
1277
1466
|
@click.option(
|
|
1278
1467
|
'--packet-count',
|
|
@@ -1280,7 +1469,7 @@ def create_role_factory(ctx, default_role):
|
|
|
1280
1469
|
metavar='COUNT',
|
|
1281
1470
|
type=int,
|
|
1282
1471
|
default=10,
|
|
1283
|
-
help='Packet count (
|
|
1472
|
+
help='Packet count (client or ping role)',
|
|
1284
1473
|
)
|
|
1285
1474
|
@click.option(
|
|
1286
1475
|
'--start-delay',
|
|
@@ -1288,7 +1477,39 @@ def create_role_factory(ctx, default_role):
|
|
|
1288
1477
|
metavar='SECONDS',
|
|
1289
1478
|
type=int,
|
|
1290
1479
|
default=1,
|
|
1291
|
-
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)",
|
|
1292
1513
|
)
|
|
1293
1514
|
@click.pass_context
|
|
1294
1515
|
def bench(
|
|
@@ -1301,9 +1522,16 @@ def bench(
|
|
|
1301
1522
|
packet_size,
|
|
1302
1523
|
packet_count,
|
|
1303
1524
|
start_delay,
|
|
1525
|
+
repeat,
|
|
1526
|
+
repeat_delay,
|
|
1527
|
+
pace,
|
|
1528
|
+
linger,
|
|
1304
1529
|
rfcomm_channel,
|
|
1305
1530
|
rfcomm_uuid,
|
|
1306
1531
|
l2cap_psm,
|
|
1532
|
+
l2cap_mtu,
|
|
1533
|
+
l2cap_mps,
|
|
1534
|
+
l2cap_max_credits,
|
|
1307
1535
|
):
|
|
1308
1536
|
ctx.ensure_object(dict)
|
|
1309
1537
|
ctx.obj['device_config'] = device_config
|
|
@@ -1313,9 +1541,16 @@ def bench(
|
|
|
1313
1541
|
ctx.obj['rfcomm_channel'] = rfcomm_channel
|
|
1314
1542
|
ctx.obj['rfcomm_uuid'] = rfcomm_uuid
|
|
1315
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
|
|
1316
1547
|
ctx.obj['packet_size'] = packet_size
|
|
1317
1548
|
ctx.obj['packet_count'] = packet_count
|
|
1318
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
|
|
1319
1554
|
|
|
1320
1555
|
ctx.obj['extended_data_length'] = (
|
|
1321
1556
|
[int(x) for x in extended_data_length.split('/')]
|