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/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 = 1234
83
+ DEFAULT_L2CAP_PSM = 128
84
84
  DEFAULT_L2CAP_MAX_CREDITS = 128
85
85
  DEFAULT_L2CAP_MTU = 1024
86
- DEFAULT_L2CAP_MPS = 1022
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__(self, packet_io, start_delay, packet_size, packet_count):
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
- if self.tx_start_delay:
275
- logging.info(color(f'*** Startup delay: {self.tx_start_delay}', 'blue'))
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
- await self.done.wait()
294
- logging.info(color('=== Done!', 'magenta'))
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.start_timestamp = 0.0
332
- self.last_timestamp = 0.0
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}, {len(packet)} bytes'
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
- elapsed_since_start = now - self.start_timestamp
367
- elapsed_since_last = now - self.last_timestamp
368
- self.bytes_received += len(packet)
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.bytes_received / elapsed_since_start
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
- f'Speed: instant={instant_rx_speed:.4f}, average={average_rx_speed:.4f}',
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.done.set()
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__(self, packet_io, start_delay, packet_size, packet_count):
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
- if self.tx_start_delay:
419
- logging.info(color(f'*** Startup delay: {self.tx_start_delay}', 'blue'))
420
- await asyncio.sleep(self.tx_start_delay)
501
+ for run in range(self.repeat + 1):
502
+ self.done.clear()
421
503
 
422
- logging.info(color('=== Sending RESET', 'magenta'))
423
- await self.packet_io.send_packet(bytes([PacketType.RESET]))
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
- await self.send_next_ping()
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
- await self.done.wait()
428
- average_latency = sum(self.latencies) / len(self.latencies)
429
- logging.info(color(f'@@@ Average latency: {average_latency:.2f}'))
430
- logging.info(color('=== Done!', 'magenta'))
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
- async def stop_being_discoverable_connectable():
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(device, psm=ctx.obj['l2cap_psm'])
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(device, psm=ctx.obj['l2cap_psm'])
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 is --rfcomm-channel is not 0)',
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 (server role)',
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 (server role)',
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 (server role)',
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('/')]