bumble 0.0.204__py3-none-any.whl → 0.0.208__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.
Files changed (51) hide show
  1. bumble/_version.py +9 -4
  2. bumble/apps/auracast.py +631 -98
  3. bumble/apps/bench.py +238 -157
  4. bumble/apps/console.py +19 -12
  5. bumble/apps/controller_info.py +23 -7
  6. bumble/apps/device_info.py +50 -4
  7. bumble/apps/gg_bridge.py +1 -1
  8. bumble/apps/lea_unicast/app.py +61 -201
  9. bumble/att.py +51 -37
  10. bumble/audio/__init__.py +17 -0
  11. bumble/audio/io.py +553 -0
  12. bumble/controller.py +24 -9
  13. bumble/core.py +305 -156
  14. bumble/device.py +1090 -99
  15. bumble/gatt.py +36 -226
  16. bumble/gatt_adapters.py +374 -0
  17. bumble/gatt_client.py +52 -33
  18. bumble/gatt_server.py +5 -5
  19. bumble/hci.py +812 -14
  20. bumble/host.py +367 -65
  21. bumble/l2cap.py +3 -16
  22. bumble/pairing.py +5 -5
  23. bumble/pandora/host.py +7 -12
  24. bumble/profiles/aics.py +48 -57
  25. bumble/profiles/ascs.py +8 -19
  26. bumble/profiles/asha.py +16 -14
  27. bumble/profiles/bass.py +16 -22
  28. bumble/profiles/battery_service.py +13 -3
  29. bumble/profiles/device_information_service.py +16 -14
  30. bumble/profiles/gap.py +12 -8
  31. bumble/profiles/gatt_service.py +167 -0
  32. bumble/profiles/gmap.py +198 -0
  33. bumble/profiles/hap.py +8 -6
  34. bumble/profiles/heart_rate_service.py +20 -4
  35. bumble/profiles/le_audio.py +87 -4
  36. bumble/profiles/mcp.py +11 -9
  37. bumble/profiles/pacs.py +61 -16
  38. bumble/profiles/tmap.py +8 -12
  39. bumble/profiles/{vcp.py → vcs.py} +35 -29
  40. bumble/profiles/vocs.py +62 -85
  41. bumble/sdp.py +223 -93
  42. bumble/smp.py +1 -1
  43. bumble/utils.py +12 -2
  44. bumble/vendor/android/hci.py +1 -1
  45. {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/METADATA +13 -11
  46. {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/RECORD +50 -46
  47. {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/WHEEL +1 -1
  48. {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/entry_points.txt +1 -0
  49. bumble/apps/lea_unicast/liblc3.wasm +0 -0
  50. {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/LICENSE +0 -0
  51. {bumble-0.0.204.dist-info → bumble-0.0.208.dist-info}/top_level.txt +0 -0
bumble/apps/bench.py CHANGED
@@ -16,6 +16,7 @@
16
16
  # Imports
17
17
  # -----------------------------------------------------------------------------
18
18
  import asyncio
19
+ import dataclasses
19
20
  import enum
20
21
  import logging
21
22
  import os
@@ -97,49 +98,22 @@ DEFAULT_RFCOMM_MTU = 2048
97
98
  # -----------------------------------------------------------------------------
98
99
  # Utils
99
100
  # -----------------------------------------------------------------------------
100
- def parse_packet(packet):
101
- if len(packet) < 1:
102
- logging.info(
103
- color(f'!!! Packet too short (got {len(packet)} bytes, need >= 1)', 'red')
104
- )
105
- raise ValueError('packet too short')
106
-
107
- try:
108
- packet_type = PacketType(packet[0])
109
- except ValueError:
110
- logging.info(color(f'!!! Invalid packet type 0x{packet[0]:02X}', 'red'))
111
- raise
112
-
113
- return (packet_type, packet[1:])
114
-
115
-
116
- def parse_packet_sequence(packet_data):
117
- if len(packet_data) < 5:
118
- logging.info(
119
- color(
120
- f'!!!Packet too short (got {len(packet_data)} bytes, need >= 5)',
121
- 'red',
122
- )
123
- )
124
- raise ValueError('packet too short')
125
- return struct.unpack_from('>bI', packet_data, 0)
126
-
127
-
128
101
  def le_phy_name(phy_id):
129
102
  return {HCI_LE_1M_PHY: '1M', HCI_LE_2M_PHY: '2M', HCI_LE_CODED_PHY: 'CODED'}.get(
130
103
  phy_id, HCI_Constant.le_phy_name(phy_id)
131
104
  )
132
105
 
133
106
 
107
+ def print_connection_phy(phy):
108
+ logging.info(
109
+ color('@@@ PHY: ', 'yellow') + f'TX:{le_phy_name(phy.tx_phy)}/'
110
+ f'RX:{le_phy_name(phy.rx_phy)}'
111
+ )
112
+
113
+
134
114
  def print_connection(connection):
135
115
  params = []
136
116
  if connection.transport == BT_LE_TRANSPORT:
137
- params.append(
138
- 'PHY='
139
- f'TX:{le_phy_name(connection.phy.tx_phy)}/'
140
- f'RX:{le_phy_name(connection.phy.rx_phy)}'
141
- )
142
-
143
117
  params.append(
144
118
  'DL=('
145
119
  f'TX:{connection.data_length[0]}/{connection.data_length[1]},'
@@ -225,13 +199,135 @@ async def switch_roles(connection, role):
225
199
  logging.info(f'{color("### Role switch failed:", "red")} {error}')
226
200
 
227
201
 
228
- class PacketType(enum.IntEnum):
229
- RESET = 0
230
- SEQUENCE = 1
231
- ACK = 2
202
+ # -----------------------------------------------------------------------------
203
+ # Packet
204
+ # -----------------------------------------------------------------------------
205
+ @dataclasses.dataclass
206
+ class Packet:
207
+ class PacketType(enum.IntEnum):
208
+ RESET = 0
209
+ SEQUENCE = 1
210
+ ACK = 2
211
+
212
+ class PacketFlags(enum.IntFlag):
213
+ LAST = 1
214
+
215
+ packet_type: PacketType
216
+ flags: PacketFlags = PacketFlags(0)
217
+ sequence: int = 0
218
+ timestamp: int = 0
219
+ payload: bytes = b""
220
+
221
+ @classmethod
222
+ def from_bytes(cls, data: bytes):
223
+ if len(data) < 1:
224
+ logging.warning(
225
+ color(f'!!! Packet too short (got {len(data)} bytes, need >= 1)', 'red')
226
+ )
227
+ raise ValueError('packet too short')
232
228
 
229
+ try:
230
+ packet_type = cls.PacketType(data[0])
231
+ except ValueError:
232
+ logging.warning(color(f'!!! Invalid packet type 0x{data[0]:02X}', 'red'))
233
+ raise
233
234
 
234
- PACKET_FLAG_LAST = 1
235
+ if packet_type == cls.PacketType.RESET:
236
+ return cls(packet_type)
237
+
238
+ flags = cls.PacketFlags(data[1])
239
+ (sequence,) = struct.unpack_from("<I", data, 2)
240
+
241
+ if packet_type == cls.PacketType.ACK:
242
+ if len(data) < 6:
243
+ logging.warning(
244
+ color(
245
+ f'!!! Packet too short (got {len(data)} bytes, need >= 6)',
246
+ 'red',
247
+ )
248
+ )
249
+ return cls(packet_type, flags, sequence)
250
+
251
+ if len(data) < 10:
252
+ logging.warning(
253
+ color(
254
+ f'!!! Packet too short (got {len(data)} bytes, need >= 10)', 'red'
255
+ )
256
+ )
257
+ raise ValueError('packet too short')
258
+
259
+ (timestamp,) = struct.unpack_from("<I", data, 6)
260
+ return cls(packet_type, flags, sequence, timestamp, data[10:])
261
+
262
+ def __bytes__(self):
263
+ if self.packet_type == self.PacketType.RESET:
264
+ return bytes([self.packet_type])
265
+
266
+ if self.packet_type == self.PacketType.ACK:
267
+ return struct.pack("<BBI", self.packet_type, self.flags, self.sequence)
268
+
269
+ return (
270
+ struct.pack(
271
+ "<BBII", self.packet_type, self.flags, self.sequence, self.timestamp
272
+ )
273
+ + self.payload
274
+ )
275
+
276
+
277
+ # -----------------------------------------------------------------------------
278
+ # Jitter Stats
279
+ # -----------------------------------------------------------------------------
280
+ class JitterStats:
281
+ def __init__(self):
282
+ self.reset()
283
+
284
+ def reset(self):
285
+ self.packets = []
286
+ self.receive_times = []
287
+ self.jitter = []
288
+
289
+ def on_packet_received(self, packet):
290
+ now = time.time()
291
+ self.packets.append(packet)
292
+ self.receive_times.append(now)
293
+
294
+ if packet.timestamp and len(self.packets) > 1:
295
+ expected_time = (
296
+ self.receive_times[0]
297
+ + (packet.timestamp - self.packets[0].timestamp) / 1000000
298
+ )
299
+ jitter = now - expected_time
300
+ else:
301
+ jitter = 0.0
302
+
303
+ self.jitter.append(jitter)
304
+ return jitter
305
+
306
+ def show_stats(self):
307
+ if len(self.jitter) < 3:
308
+ return
309
+ average = sum(self.jitter) / len(self.jitter)
310
+ adjusted = [jitter - average for jitter in self.jitter]
311
+
312
+ log_stats('Jitter (signed)', adjusted, 3)
313
+ log_stats('Jitter (absolute)', [abs(jitter) for jitter in adjusted], 3)
314
+
315
+ # Show a histogram
316
+ bin_count = 20
317
+ bins = [0] * bin_count
318
+ interval_min = min(adjusted)
319
+ interval_max = max(adjusted)
320
+ interval_range = interval_max - interval_min
321
+ bin_thresholds = [
322
+ interval_min + i * (interval_range / bin_count) for i in range(bin_count)
323
+ ]
324
+ for jitter in adjusted:
325
+ for i in reversed(range(bin_count)):
326
+ if jitter >= bin_thresholds[i]:
327
+ bins[i] += 1
328
+ break
329
+ for i in range(bin_count):
330
+ logging.info(f'@@@ >= {bin_thresholds[i]:.4f}: {bins[i]}')
235
331
 
236
332
 
237
333
  # -----------------------------------------------------------------------------
@@ -281,19 +377,37 @@ class Sender:
281
377
  await asyncio.sleep(self.tx_start_delay)
282
378
 
283
379
  logging.info(color('=== Sending RESET', 'magenta'))
284
- await self.packet_io.send_packet(bytes([PacketType.RESET]))
380
+ await self.packet_io.send_packet(
381
+ bytes(Packet(packet_type=Packet.PacketType.RESET))
382
+ )
383
+
285
384
  self.start_time = time.time()
286
385
  self.bytes_sent = 0
287
386
  for tx_i in range(self.tx_packet_count):
288
- packet_flags = (
289
- PACKET_FLAG_LAST if tx_i == self.tx_packet_count - 1 else 0
387
+ if self.pace > 0:
388
+ # Wait until it is time to send the next packet
389
+ target_time = self.start_time + (tx_i * self.pace / 1000)
390
+ now = time.time()
391
+ if now < target_time:
392
+ await asyncio.sleep(target_time - now)
393
+ else:
394
+ await self.packet_io.drain()
395
+
396
+ packet = bytes(
397
+ Packet(
398
+ packet_type=Packet.PacketType.SEQUENCE,
399
+ flags=(
400
+ Packet.PacketFlags.LAST
401
+ if tx_i == self.tx_packet_count - 1
402
+ else 0
403
+ ),
404
+ sequence=tx_i,
405
+ timestamp=int((time.time() - self.start_time) * 1000000),
406
+ payload=bytes(
407
+ self.tx_packet_size - 10 - self.packet_io.overhead_size
408
+ ),
409
+ )
290
410
  )
291
- packet = struct.pack(
292
- '>bbI',
293
- PacketType.SEQUENCE,
294
- packet_flags,
295
- tx_i,
296
- ) + bytes(self.tx_packet_size - 6 - self.packet_io.overhead_size)
297
411
  logging.info(
298
412
  color(
299
413
  f'Sending packet {tx_i}: {self.tx_packet_size} bytes', 'yellow'
@@ -302,14 +416,6 @@ class Sender:
302
416
  self.bytes_sent += len(packet)
303
417
  await self.packet_io.send_packet(packet)
304
418
 
305
- if self.pace is None:
306
- continue
307
-
308
- if self.pace > 0:
309
- await asyncio.sleep(self.pace / 1000)
310
- else:
311
- await self.packet_io.drain()
312
-
313
419
  await self.done.wait()
314
420
 
315
421
  run_counter = f'[{run + 1} of {self.repeat + 1}]' if self.repeat else ''
@@ -321,13 +427,13 @@ class Sender:
321
427
  if self.repeat:
322
428
  logging.info(color('--- End of runs', 'blue'))
323
429
 
324
- def on_packet_received(self, packet):
430
+ def on_packet_received(self, data):
325
431
  try:
326
- packet_type, _ = parse_packet(packet)
432
+ packet = Packet.from_bytes(data)
327
433
  except ValueError:
328
434
  return
329
435
 
330
- if packet_type == PacketType.ACK:
436
+ if packet.packet_type == Packet.PacketType.ACK:
331
437
  elapsed = time.time() - self.start_time
332
438
  average_tx_speed = self.bytes_sent / elapsed
333
439
  self.stats.append(average_tx_speed)
@@ -350,52 +456,53 @@ class Receiver:
350
456
  last_timestamp: float
351
457
 
352
458
  def __init__(self, packet_io, linger):
353
- self.reset()
459
+ self.jitter_stats = JitterStats()
354
460
  self.packet_io = packet_io
355
461
  self.packet_io.packet_listener = self
356
462
  self.linger = linger
357
463
  self.done = asyncio.Event()
464
+ self.reset()
358
465
 
359
466
  def reset(self):
360
467
  self.expected_packet_index = 0
361
468
  self.measurements = [(time.time(), 0)]
362
469
  self.total_bytes_received = 0
470
+ self.jitter_stats.reset()
363
471
 
364
- def on_packet_received(self, packet):
472
+ def on_packet_received(self, data):
365
473
  try:
366
- packet_type, packet_data = parse_packet(packet)
474
+ packet = Packet.from_bytes(data)
367
475
  except ValueError:
476
+ logging.exception("invalid packet")
368
477
  return
369
478
 
370
- if packet_type == PacketType.RESET:
479
+ if packet.packet_type == Packet.PacketType.RESET:
371
480
  logging.info(color('=== Received RESET', 'magenta'))
372
481
  self.reset()
373
482
  return
374
483
 
375
- try:
376
- packet_flags, packet_index = parse_packet_sequence(packet_data)
377
- except ValueError:
378
- return
484
+ jitter = self.jitter_stats.on_packet_received(packet)
379
485
  logging.info(
380
- f'<<< Received packet {packet_index}: '
381
- f'flags=0x{packet_flags:02X}, '
382
- f'{len(packet) + self.packet_io.overhead_size} bytes'
486
+ f'<<< Received packet {packet.sequence}: '
487
+ f'flags={packet.flags}, '
488
+ f'jitter={jitter:.4f}, '
489
+ f'{len(data) + self.packet_io.overhead_size} bytes',
383
490
  )
384
491
 
385
- if packet_index != self.expected_packet_index:
492
+ if packet.sequence != self.expected_packet_index:
386
493
  logging.info(
387
494
  color(
388
495
  f'!!! Unexpected packet, expected {self.expected_packet_index} '
389
- f'but received {packet_index}'
496
+ f'but received {packet.sequence}'
390
497
  )
391
498
  )
392
499
 
393
500
  now = time.time()
394
501
  elapsed_since_start = now - self.measurements[0][0]
395
502
  elapsed_since_last = now - self.measurements[-1][0]
396
- self.measurements.append((now, len(packet)))
397
- self.total_bytes_received += len(packet)
398
- instant_rx_speed = len(packet) / elapsed_since_last
503
+ self.measurements.append((now, len(data)))
504
+ self.total_bytes_received += len(data)
505
+ instant_rx_speed = len(data) / elapsed_since_last
399
506
  average_rx_speed = self.total_bytes_received / elapsed_since_start
400
507
  window = self.measurements[-64:]
401
508
  windowed_rx_speed = sum(measurement[1] for measurement in window[1:]) / (
@@ -411,15 +518,17 @@ class Receiver:
411
518
  )
412
519
  )
413
520
 
414
- self.expected_packet_index = packet_index + 1
521
+ self.expected_packet_index = packet.sequence + 1
415
522
 
416
- if packet_flags & PACKET_FLAG_LAST:
523
+ if packet.flags & Packet.PacketFlags.LAST:
417
524
  AsyncRunner.spawn(
418
525
  self.packet_io.send_packet(
419
- struct.pack('>bbI', PacketType.ACK, packet_flags, packet_index)
526
+ bytes(Packet(Packet.PacketType.ACK, packet.flags, packet.sequence))
420
527
  )
421
528
  )
422
529
  logging.info(color('@@@ Received last packet', 'green'))
530
+ self.jitter_stats.show_stats()
531
+
423
532
  if not self.linger:
424
533
  self.done.set()
425
534
 
@@ -479,25 +588,32 @@ class Ping:
479
588
  await asyncio.sleep(self.tx_start_delay)
480
589
 
481
590
  logging.info(color('=== Sending RESET', 'magenta'))
482
- await self.packet_io.send_packet(bytes([PacketType.RESET]))
591
+ await self.packet_io.send_packet(bytes(Packet(Packet.PacketType.RESET)))
483
592
 
484
- packet_interval = self.pace / 1000
485
593
  start_time = time.time()
486
594
  self.next_expected_packet_index = 0
487
595
  for i in range(self.tx_packet_count):
488
- target_time = start_time + (i * packet_interval)
596
+ target_time = start_time + (i * self.pace / 1000)
489
597
  now = time.time()
490
598
  if now < target_time:
491
599
  await asyncio.sleep(target_time - now)
492
-
493
- packet = struct.pack(
494
- '>bbI',
495
- PacketType.SEQUENCE,
496
- (PACKET_FLAG_LAST if i == self.tx_packet_count - 1 else 0),
497
- i,
498
- ) + bytes(self.tx_packet_size - 6)
600
+ now = time.time()
601
+
602
+ packet = bytes(
603
+ Packet(
604
+ packet_type=Packet.PacketType.SEQUENCE,
605
+ flags=(
606
+ Packet.PacketFlags.LAST
607
+ if i == self.tx_packet_count - 1
608
+ else 0
609
+ ),
610
+ sequence=i,
611
+ timestamp=int((now - start_time) * 1000000),
612
+ payload=bytes(self.tx_packet_size - 10),
613
+ )
614
+ )
499
615
  logging.info(color(f'Sending packet {i}', 'yellow'))
500
- self.ping_times.append(time.time())
616
+ self.ping_times.append(now)
501
617
  await self.packet_io.send_packet(packet)
502
618
 
503
619
  await self.done.wait()
@@ -531,40 +647,35 @@ class Ping:
531
647
  if self.repeat:
532
648
  logging.info(color('--- End of runs', 'blue'))
533
649
 
534
- def on_packet_received(self, packet):
650
+ def on_packet_received(self, data):
535
651
  try:
536
- packet_type, packet_data = parse_packet(packet)
652
+ packet = Packet.from_bytes(data)
537
653
  except ValueError:
538
654
  return
539
655
 
540
- try:
541
- packet_flags, packet_index = parse_packet_sequence(packet_data)
542
- except ValueError:
543
- return
544
-
545
- if packet_type == PacketType.ACK:
546
- elapsed = time.time() - self.ping_times[packet_index]
656
+ if packet.packet_type == Packet.PacketType.ACK:
657
+ elapsed = time.time() - self.ping_times[packet.sequence]
547
658
  rtt = elapsed * 1000
548
659
  self.rtts.append(rtt)
549
660
  logging.info(
550
661
  color(
551
- f'<<< Received ACK [{packet_index}], RTT={rtt:.2f}ms',
662
+ f'<<< Received ACK [{packet.sequence}], RTT={rtt:.2f}ms',
552
663
  'green',
553
664
  )
554
665
  )
555
666
 
556
- if packet_index == self.next_expected_packet_index:
667
+ if packet.sequence == self.next_expected_packet_index:
557
668
  self.next_expected_packet_index += 1
558
669
  else:
559
670
  logging.info(
560
671
  color(
561
672
  f'!!! Unexpected packet, '
562
673
  f'expected {self.next_expected_packet_index} '
563
- f'but received {packet_index}'
674
+ f'but received {packet.sequence}'
564
675
  )
565
676
  )
566
677
 
567
- if packet_flags & PACKET_FLAG_LAST:
678
+ if packet.flags & Packet.PacketFlags.LAST:
568
679
  self.done.set()
569
680
  return
570
681
 
@@ -576,89 +687,56 @@ class Pong:
576
687
  expected_packet_index: int
577
688
 
578
689
  def __init__(self, packet_io, linger):
579
- self.reset()
690
+ self.jitter_stats = JitterStats()
580
691
  self.packet_io = packet_io
581
692
  self.packet_io.packet_listener = self
582
693
  self.linger = linger
583
694
  self.done = asyncio.Event()
695
+ self.reset()
584
696
 
585
697
  def reset(self):
586
698
  self.expected_packet_index = 0
587
- self.receive_times = []
588
-
589
- def on_packet_received(self, packet):
590
- self.receive_times.append(time.time())
699
+ self.jitter_stats.reset()
591
700
 
701
+ def on_packet_received(self, data):
592
702
  try:
593
- packet_type, packet_data = parse_packet(packet)
703
+ packet = Packet.from_bytes(data)
594
704
  except ValueError:
595
705
  return
596
706
 
597
- if packet_type == PacketType.RESET:
707
+ if packet.packet_type == Packet.PacketType.RESET:
598
708
  logging.info(color('=== Received RESET', 'magenta'))
599
709
  self.reset()
600
710
  return
601
711
 
602
- try:
603
- packet_flags, packet_index = parse_packet_sequence(packet_data)
604
- except ValueError:
605
- return
606
- interval = (
607
- self.receive_times[-1] - self.receive_times[-2]
608
- if len(self.receive_times) >= 2
609
- else 0
610
- )
712
+ jitter = self.jitter_stats.on_packet_received(packet)
611
713
  logging.info(
612
714
  color(
613
- f'<<< Received packet {packet_index}: '
614
- f'flags=0x{packet_flags:02X}, {len(packet)} bytes, '
615
- f'interval={interval:.4f}',
715
+ f'<<< Received packet {packet.sequence}: '
716
+ f'flags={packet.flags}, {len(data)} bytes, '
717
+ f'jitter={jitter:.4f}',
616
718
  'green',
617
719
  )
618
720
  )
619
721
 
620
- if packet_index != self.expected_packet_index:
722
+ if packet.sequence != self.expected_packet_index:
621
723
  logging.info(
622
724
  color(
623
725
  f'!!! Unexpected packet, expected {self.expected_packet_index} '
624
- f'but received {packet_index}'
726
+ f'but received {packet.sequence}'
625
727
  )
626
728
  )
627
729
 
628
- self.expected_packet_index = packet_index + 1
730
+ self.expected_packet_index = packet.sequence + 1
629
731
 
630
732
  AsyncRunner.spawn(
631
733
  self.packet_io.send_packet(
632
- struct.pack('>bbI', PacketType.ACK, packet_flags, packet_index)
734
+ bytes(Packet(Packet.PacketType.ACK, packet.flags, packet.sequence))
633
735
  )
634
736
  )
635
737
 
636
- if packet_flags & PACKET_FLAG_LAST:
637
- if len(self.receive_times) >= 3:
638
- # Show basic stats
639
- intervals = [
640
- self.receive_times[i + 1] - self.receive_times[i]
641
- for i in range(len(self.receive_times) - 1)
642
- ]
643
- log_stats('Packet intervals', intervals, 3)
644
-
645
- # Show a histogram
646
- bin_count = 20
647
- bins = [0] * bin_count
648
- interval_min = min(intervals)
649
- interval_max = max(intervals)
650
- interval_range = interval_max - interval_min
651
- bin_thresholds = [
652
- interval_min + i * (interval_range / bin_count)
653
- for i in range(bin_count)
654
- ]
655
- for interval in intervals:
656
- for i in reversed(range(bin_count)):
657
- if interval >= bin_thresholds[i]:
658
- bins[i] += 1
659
- break
660
- for i in range(bin_count):
661
- logging.info(f'@@@ >= {bin_thresholds[i]:.4f}: {bins[i]}')
738
+ if packet.flags & Packet.PacketFlags.LAST:
739
+ self.jitter_stats.show_stats()
662
740
 
663
741
  if not self.linger:
664
742
  self.done.set()
@@ -1211,6 +1289,8 @@ class Central(Connection.Listener):
1211
1289
  logging.info(color('### Connected', 'cyan'))
1212
1290
  self.connection.listener = self
1213
1291
  print_connection(self.connection)
1292
+ phy = await self.connection.get_phy()
1293
+ print_connection_phy(phy)
1214
1294
 
1215
1295
  # Switch roles if needed.
1216
1296
  if self.role_switch:
@@ -1268,8 +1348,8 @@ class Central(Connection.Listener):
1268
1348
  def on_connection_parameters_update(self):
1269
1349
  print_connection(self.connection)
1270
1350
 
1271
- def on_connection_phy_update(self):
1272
- print_connection(self.connection)
1351
+ def on_connection_phy_update(self, phy):
1352
+ print_connection_phy(phy)
1273
1353
 
1274
1354
  def on_connection_att_mtu_update(self):
1275
1355
  print_connection(self.connection)
@@ -1395,8 +1475,8 @@ class Peripheral(Device.Listener, Connection.Listener):
1395
1475
  def on_connection_parameters_update(self):
1396
1476
  print_connection(self.connection)
1397
1477
 
1398
- def on_connection_phy_update(self):
1399
- print_connection(self.connection)
1478
+ def on_connection_phy_update(self, phy):
1479
+ print_connection_phy(phy)
1400
1480
 
1401
1481
  def on_connection_att_mtu_update(self):
1402
1482
  print_connection(self.connection)
@@ -1471,7 +1551,7 @@ def create_mode_factory(ctx, default_mode):
1471
1551
  def create_scenario_factory(ctx, default_scenario):
1472
1552
  scenario = ctx.obj['scenario']
1473
1553
  if scenario is None:
1474
- scenarion = default_scenario
1554
+ scenario = default_scenario
1475
1555
 
1476
1556
  def create_scenario(packet_io):
1477
1557
  if scenario == 'send':
@@ -1530,6 +1610,7 @@ def create_scenario_factory(ctx, default_scenario):
1530
1610
  '--att-mtu',
1531
1611
  metavar='MTU',
1532
1612
  type=click.IntRange(23, 517),
1613
+ default=517,
1533
1614
  help='GATT MTU (gatt-client mode)',
1534
1615
  )
1535
1616
  @click.option(
@@ -1605,7 +1686,7 @@ def create_scenario_factory(ctx, default_scenario):
1605
1686
  '--packet-size',
1606
1687
  '-s',
1607
1688
  metavar='SIZE',
1608
- type=click.IntRange(8, 8192),
1689
+ type=click.IntRange(10, 8192),
1609
1690
  default=500,
1610
1691
  help='Packet size (send or ping scenario)',
1611
1692
  )