bumble 0.0.180__py3-none-any.whl → 0.0.181__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 CHANGED
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.0.180'
16
- __version_tuple__ = version_tuple = (0, 0, 180)
15
+ __version__ = version = '0.0.181'
16
+ __version_tuple__ = version_tuple = (0, 0, 181)
bumble/apps/bench.py CHANGED
@@ -82,10 +82,11 @@ SPEED_RX_UUID = '016A2CC7-E14B-4819-935F-1F56EAE4098D'
82
82
  DEFAULT_RFCOMM_UUID = 'E6D55659-C8B4-4B85-96BB-B1143AF6D3AE'
83
83
  DEFAULT_L2CAP_PSM = 1234
84
84
  DEFAULT_L2CAP_MAX_CREDITS = 128
85
- DEFAULT_L2CAP_MTU = 1022
86
- DEFAULT_L2CAP_MPS = 1024
85
+ DEFAULT_L2CAP_MTU = 1024
86
+ DEFAULT_L2CAP_MPS = 1022
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
- print(
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
- print(color(f'!!! Invalid packet type 0x{packet[0]:02X}', 'red'))
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
- print(
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
- print(
159
+ logging.info(
159
160
  f'{color("@@@ Connection:", "yellow")} '
160
161
  f'{connection_parameters} '
161
162
  f'{data_length} '
@@ -266,15 +267,15 @@ class Sender:
266
267
  pass
267
268
 
268
269
  async def run(self):
269
- print(color('--- Waiting for I/O to be ready...', 'blue'))
270
+ logging.info(color('--- Waiting for I/O to be ready...', 'blue'))
270
271
  await self.packet_io.ready.wait()
271
- print(color('--- Go!', 'blue'))
272
+ logging.info(color('--- Go!', 'blue'))
272
273
 
273
274
  if self.tx_start_delay:
274
- print(color(f'*** Startup delay: {self.tx_start_delay}', 'blue'))
275
+ logging.info(color(f'*** Startup delay: {self.tx_start_delay}', 'blue'))
275
276
  await asyncio.sleep(self.tx_start_delay)
276
277
 
277
- print(color('=== Sending RESET', 'magenta'))
278
+ logging.info(color('=== Sending RESET', 'magenta'))
278
279
  await self.packet_io.send_packet(bytes([PacketType.RESET]))
279
280
  self.start_time = time.time()
280
281
  for tx_i in range(self.tx_packet_count):
@@ -285,12 +286,12 @@ class Sender:
285
286
  packet_flags,
286
287
  tx_i,
287
288
  ) + bytes(self.tx_packet_size - 6)
288
- print(color(f'Sending packet {tx_i}: {len(packet)} bytes', 'yellow'))
289
+ logging.info(color(f'Sending packet {tx_i}: {len(packet)} bytes', 'yellow'))
289
290
  self.bytes_sent += len(packet)
290
291
  await self.packet_io.send_packet(packet)
291
292
 
292
293
  await self.done.wait()
293
- print(color('=== Done!', 'magenta'))
294
+ logging.info(color('=== Done!', 'magenta'))
294
295
 
295
296
  def on_packet_received(self, packet):
296
297
  try:
@@ -301,7 +302,7 @@ class Sender:
301
302
  if packet_type == PacketType.ACK:
302
303
  elapsed = time.time() - self.start_time
303
304
  average_tx_speed = self.bytes_sent / elapsed
304
- print(
305
+ logging.info(
305
306
  color(
306
307
  f'@@@ Received ACK. Speed: average={average_tx_speed:.4f}'
307
308
  f' ({self.bytes_sent} bytes in {elapsed:.2f} seconds)',
@@ -315,6 +316,10 @@ class Sender:
315
316
  # Receiver
316
317
  # -----------------------------------------------------------------------------
317
318
  class Receiver:
319
+ expected_packet_index: int
320
+ start_timestamp: float
321
+ last_timestamp: float
322
+
318
323
  def __init__(self, packet_io):
319
324
  self.reset()
320
325
  self.packet_io = packet_io
@@ -336,7 +341,7 @@ class Receiver:
336
341
  now = time.time()
337
342
 
338
343
  if packet_type == PacketType.RESET:
339
- print(color('=== Received RESET', 'magenta'))
344
+ logging.info(color('=== Received RESET', 'magenta'))
340
345
  self.reset()
341
346
  self.start_timestamp = now
342
347
  return
@@ -345,13 +350,13 @@ class Receiver:
345
350
  packet_flags, packet_index = parse_packet_sequence(packet_data)
346
351
  except ValueError:
347
352
  return
348
- print(
353
+ logging.info(
349
354
  f'<<< Received packet {packet_index}: '
350
355
  f'flags=0x{packet_flags:02X}, {len(packet)} bytes'
351
356
  )
352
357
 
353
358
  if packet_index != self.expected_packet_index:
354
- print(
359
+ logging.info(
355
360
  color(
356
361
  f'!!! Unexpected packet, expected {self.expected_packet_index} '
357
362
  f'but received {packet_index}'
@@ -363,7 +368,7 @@ class Receiver:
363
368
  self.bytes_received += len(packet)
364
369
  instant_rx_speed = len(packet) / elapsed_since_last
365
370
  average_rx_speed = self.bytes_received / elapsed_since_start
366
- print(
371
+ logging.info(
367
372
  color(
368
373
  f'Speed: instant={instant_rx_speed:.4f}, average={average_rx_speed:.4f}',
369
374
  'yellow',
@@ -379,12 +384,12 @@ class Receiver:
379
384
  struct.pack('>bbI', PacketType.ACK, packet_flags, packet_index)
380
385
  )
381
386
  )
382
- print(color('@@@ Received last packet', 'green'))
387
+ logging.info(color('@@@ Received last packet', 'green'))
383
388
  self.done.set()
384
389
 
385
390
  async def run(self):
386
391
  await self.done.wait()
387
- print(color('=== Done!', 'magenta'))
392
+ logging.info(color('=== Done!', 'magenta'))
388
393
 
389
394
 
390
395
  # -----------------------------------------------------------------------------
@@ -406,23 +411,23 @@ class Ping:
406
411
  pass
407
412
 
408
413
  async def run(self):
409
- print(color('--- Waiting for I/O to be ready...', 'blue'))
414
+ logging.info(color('--- Waiting for I/O to be ready...', 'blue'))
410
415
  await self.packet_io.ready.wait()
411
- print(color('--- Go!', 'blue'))
416
+ logging.info(color('--- Go!', 'blue'))
412
417
 
413
418
  if self.tx_start_delay:
414
- print(color(f'*** Startup delay: {self.tx_start_delay}', 'blue'))
419
+ logging.info(color(f'*** Startup delay: {self.tx_start_delay}', 'blue'))
415
420
  await asyncio.sleep(self.tx_start_delay)
416
421
 
417
- print(color('=== Sending RESET', 'magenta'))
422
+ logging.info(color('=== Sending RESET', 'magenta'))
418
423
  await self.packet_io.send_packet(bytes([PacketType.RESET]))
419
424
 
420
425
  await self.send_next_ping()
421
426
 
422
427
  await self.done.wait()
423
428
  average_latency = sum(self.latencies) / len(self.latencies)
424
- print(color(f'@@@ Average latency: {average_latency:.2f}'))
425
- print(color('=== Done!', 'magenta'))
429
+ logging.info(color(f'@@@ Average latency: {average_latency:.2f}'))
430
+ logging.info(color('=== Done!', 'magenta'))
426
431
 
427
432
  async def send_next_ping(self):
428
433
  packet = struct.pack(
@@ -433,7 +438,7 @@ class Ping:
433
438
  else 0,
434
439
  self.current_packet_index,
435
440
  ) + bytes(self.tx_packet_size - 6)
436
- print(color(f'Sending packet {self.current_packet_index}', 'yellow'))
441
+ logging.info(color(f'Sending packet {self.current_packet_index}', 'yellow'))
437
442
  self.ping_sent_time = time.time()
438
443
  await self.packet_io.send_packet(packet)
439
444
 
@@ -453,7 +458,7 @@ class Ping:
453
458
  if packet_type == PacketType.ACK:
454
459
  latency = elapsed * 1000
455
460
  self.latencies.append(latency)
456
- print(
461
+ logging.info(
457
462
  color(
458
463
  f'<<< Received ACK [{packet_index}], latency={latency:.2f}ms',
459
464
  'green',
@@ -463,7 +468,7 @@ class Ping:
463
468
  if packet_index == self.current_packet_index:
464
469
  self.current_packet_index += 1
465
470
  else:
466
- print(
471
+ logging.info(
467
472
  color(
468
473
  f'!!! Unexpected packet, expected {self.current_packet_index} '
469
474
  f'but received {packet_index}'
@@ -481,6 +486,8 @@ class Ping:
481
486
  # Pong
482
487
  # -----------------------------------------------------------------------------
483
488
  class Pong:
489
+ expected_packet_index: int
490
+
484
491
  def __init__(self, packet_io):
485
492
  self.reset()
486
493
  self.packet_io = packet_io
@@ -497,7 +504,7 @@ class Pong:
497
504
  return
498
505
 
499
506
  if packet_type == PacketType.RESET:
500
- print(color('=== Received RESET', 'magenta'))
507
+ logging.info(color('=== Received RESET', 'magenta'))
501
508
  self.reset()
502
509
  return
503
510
 
@@ -505,7 +512,7 @@ class Pong:
505
512
  packet_flags, packet_index = parse_packet_sequence(packet_data)
506
513
  except ValueError:
507
514
  return
508
- print(
515
+ logging.info(
509
516
  color(
510
517
  f'<<< Received packet {packet_index}: '
511
518
  f'flags=0x{packet_flags:02X}, {len(packet)} bytes',
@@ -514,7 +521,7 @@ class Pong:
514
521
  )
515
522
 
516
523
  if packet_index != self.expected_packet_index:
517
- print(
524
+ logging.info(
518
525
  color(
519
526
  f'!!! Unexpected packet, expected {self.expected_packet_index} '
520
527
  f'but received {packet_index}'
@@ -534,7 +541,7 @@ class Pong:
534
541
 
535
542
  async def run(self):
536
543
  await self.done.wait()
537
- print(color('=== Done!', 'magenta'))
544
+ logging.info(color('=== Done!', 'magenta'))
538
545
 
539
546
 
540
547
  # -----------------------------------------------------------------------------
@@ -552,36 +559,36 @@ class GattClient:
552
559
  peer = Peer(connection)
553
560
 
554
561
  if self.att_mtu:
555
- print(color(f'*** Requesting MTU update: {self.att_mtu}', 'blue'))
562
+ logging.info(color(f'*** Requesting MTU update: {self.att_mtu}', 'blue'))
556
563
  await peer.request_mtu(self.att_mtu)
557
564
 
558
- print(color('*** Discovering services...', 'blue'))
565
+ logging.info(color('*** Discovering services...', 'blue'))
559
566
  await peer.discover_services()
560
567
 
561
568
  speed_services = peer.get_services_by_uuid(SPEED_SERVICE_UUID)
562
569
  if not speed_services:
563
- print(color('!!! Speed Service not found', 'red'))
570
+ logging.info(color('!!! Speed Service not found', 'red'))
564
571
  return
565
572
  speed_service = speed_services[0]
566
- print(color('*** Discovering characteristics...', 'blue'))
573
+ logging.info(color('*** Discovering characteristics...', 'blue'))
567
574
  await speed_service.discover_characteristics()
568
575
 
569
576
  speed_txs = speed_service.get_characteristics_by_uuid(SPEED_TX_UUID)
570
577
  if not speed_txs:
571
- print(color('!!! Speed TX not found', 'red'))
578
+ logging.info(color('!!! Speed TX not found', 'red'))
572
579
  return
573
580
  self.speed_tx = speed_txs[0]
574
581
 
575
582
  speed_rxs = speed_service.get_characteristics_by_uuid(SPEED_RX_UUID)
576
583
  if not speed_rxs:
577
- print(color('!!! Speed RX not found', 'red'))
584
+ logging.info(color('!!! Speed RX not found', 'red'))
578
585
  return
579
586
  self.speed_rx = speed_rxs[0]
580
587
 
581
- print(color('*** Subscribing to RX', 'blue'))
588
+ logging.info(color('*** Subscribing to RX', 'blue'))
582
589
  await self.speed_rx.subscribe(self.on_packet_received)
583
590
 
584
- print(color('*** Discovery complete', 'blue'))
591
+ logging.info(color('*** Discovery complete', 'blue'))
585
592
 
586
593
  connection.on('disconnection', self.on_disconnection)
587
594
  self.ready.set()
@@ -633,10 +640,10 @@ class GattServer:
633
640
 
634
641
  def on_rx_subscription(self, _connection, notify_enabled, _indicate_enabled):
635
642
  if notify_enabled:
636
- print(color('*** RX subscription', 'blue'))
643
+ logging.info(color('*** RX subscription', 'blue'))
637
644
  self.ready.set()
638
645
  else:
639
- print(color('*** RX un-subscription', 'blue'))
646
+ logging.info(color('*** RX un-subscription', 'blue'))
640
647
  self.ready.clear()
641
648
 
642
649
  def on_tx_write(self, _, value):
@@ -684,7 +691,7 @@ class StreamedPacketIO:
684
691
 
685
692
  async def send_packet(self, packet):
686
693
  if not self.io_sink:
687
- print(color('!!! No sink, dropping packet', 'red'))
694
+ logging.info(color('!!! No sink, dropping packet', 'red'))
688
695
  return
689
696
 
690
697
  # pylint: disable-next=not-callable
@@ -714,7 +721,7 @@ class L2capClient(StreamedPacketIO):
714
721
  connection.on('disconnection', self.on_disconnection)
715
722
 
716
723
  # Connect a new L2CAP channel
717
- print(color(f'>>> Opening L2CAP channel on PSM = {self.psm}', 'yellow'))
724
+ logging.info(color(f'>>> Opening L2CAP channel on PSM = {self.psm}', 'yellow'))
718
725
  try:
719
726
  l2cap_channel = await connection.create_l2cap_channel(
720
727
  spec=l2cap.LeCreditBasedChannelSpec(
@@ -724,9 +731,9 @@ class L2capClient(StreamedPacketIO):
724
731
  mps=self.mps,
725
732
  )
726
733
  )
727
- print(color('*** L2CAP channel:', 'cyan'), l2cap_channel)
734
+ logging.info(color(f'*** L2CAP channel: {l2cap_channel}', 'cyan'))
728
735
  except Exception as error:
729
- print(color(f'!!! Connection failed: {error}', 'red'))
736
+ logging.info(color(f'!!! Connection failed: {error}', 'red'))
730
737
  return
731
738
 
732
739
  l2cap_channel.sink = self.on_packet
@@ -739,7 +746,7 @@ class L2capClient(StreamedPacketIO):
739
746
  pass
740
747
 
741
748
  def on_l2cap_close(self):
742
- print(color('*** L2CAP channel closed', 'red'))
749
+ logging.info(color('*** L2CAP channel closed', 'red'))
743
750
 
744
751
 
745
752
  # -----------------------------------------------------------------------------
@@ -765,7 +772,9 @@ class L2capServer(StreamedPacketIO):
765
772
  ),
766
773
  handler=self.on_l2cap_channel,
767
774
  )
768
- print(color(f'### Listening for L2CAP connection on PSM {psm}', 'yellow'))
775
+ logging.info(
776
+ color(f'### Listening for L2CAP connection on PSM {psm}', 'yellow')
777
+ )
769
778
 
770
779
  async def on_connection(self, connection):
771
780
  connection.on('disconnection', self.on_disconnection)
@@ -774,7 +783,7 @@ class L2capServer(StreamedPacketIO):
774
783
  pass
775
784
 
776
785
  def on_l2cap_channel(self, l2cap_channel):
777
- print(color('*** L2CAP channel:', 'cyan'), l2cap_channel)
786
+ logging.info(color(f'*** L2CAP channel: {l2cap_channel}', 'cyan'))
778
787
 
779
788
  self.io_sink = l2cap_channel.write
780
789
  l2cap_channel.on('close', self.on_l2cap_close)
@@ -783,7 +792,7 @@ class L2capServer(StreamedPacketIO):
783
792
  self.ready.set()
784
793
 
785
794
  def on_l2cap_close(self):
786
- print(color('*** L2CAP channel closed', 'red'))
795
+ logging.info(color('*** L2CAP channel closed', 'red'))
787
796
  self.l2cap_channel = None
788
797
 
789
798
 
@@ -804,28 +813,28 @@ class RfcommClient(StreamedPacketIO):
804
813
  # Find the channel number if not specified
805
814
  channel = self.channel
806
815
  if channel == 0:
807
- print(
816
+ logging.info(
808
817
  color(f'@@@ Discovering channel number from UUID {self.uuid}', 'cyan')
809
818
  )
810
819
  channel = await find_rfcomm_channel_with_uuid(connection, self.uuid)
811
- print(color(f'@@@ Channel number = {channel}', 'cyan'))
820
+ logging.info(color(f'@@@ Channel number = {channel}', 'cyan'))
812
821
  if channel == 0:
813
- print(color('!!! No RFComm service with this UUID found', 'red'))
822
+ logging.info(color('!!! No RFComm service with this UUID found', 'red'))
814
823
  await connection.disconnect()
815
824
  return
816
825
 
817
826
  # Create a client and start it
818
- print(color('*** Starting RFCOMM client...', 'blue'))
827
+ logging.info(color('*** Starting RFCOMM client...', 'blue'))
819
828
  rfcomm_client = bumble.rfcomm.Client(connection)
820
829
  rfcomm_mux = await rfcomm_client.start()
821
- print(color('*** Started', 'blue'))
830
+ logging.info(color('*** Started', 'blue'))
822
831
 
823
- print(color(f'### Opening session for channel {channel}...', 'yellow'))
832
+ logging.info(color(f'### Opening session for channel {channel}...', 'yellow'))
824
833
  try:
825
834
  rfcomm_session = await rfcomm_mux.open_dlc(channel)
826
- print(color('### Session open', 'yellow'), rfcomm_session)
835
+ logging.info(color(f'### Session open: {rfcomm_session}', 'yellow'))
827
836
  except bumble.core.ConnectionError as error:
828
- print(color(f'!!! Session open failed: {error}', 'red'))
837
+ logging.info(color(f'!!! Session open failed: {error}', 'red'))
829
838
  await rfcomm_mux.disconnect()
830
839
  return
831
840
 
@@ -855,7 +864,7 @@ class RfcommServer(StreamedPacketIO):
855
864
  # Setup the SDP to advertise this channel
856
865
  device.sdp_service_records = make_sdp_records(channel_number)
857
866
 
858
- print(
867
+ logging.info(
859
868
  color(
860
869
  f'### Listening for RFComm connection on channel {channel_number}',
861
870
  'yellow',
@@ -869,7 +878,7 @@ class RfcommServer(StreamedPacketIO):
869
878
  pass
870
879
 
871
880
  def on_dlc(self, dlc):
872
- print(color('*** DLC connected:', 'blue'), dlc)
881
+ logging.info(color(f'*** DLC connected: {dlc}', 'blue'))
873
882
  dlc.sink = self.on_packet
874
883
  self.io_sink = dlc.write
875
884
 
@@ -935,12 +944,12 @@ class Central(Connection.Listener):
935
944
  self.connection_parameter_preferences = None
936
945
 
937
946
  async def run(self):
938
- print(color('>>> Connecting to HCI...', 'green'))
947
+ logging.info(color('>>> Connecting to HCI...', 'green'))
939
948
  async with await open_transport_or_link(self.transport) as (
940
949
  hci_source,
941
950
  hci_sink,
942
951
  ):
943
- print(color('>>> Connected', 'green'))
952
+ logging.info(color('>>> Connected', 'green'))
944
953
 
945
954
  central_address = DEFAULT_CENTRAL_ADDRESS
946
955
  self.device = Device.with_hci(
@@ -952,7 +961,13 @@ class Central(Connection.Listener):
952
961
 
953
962
  await self.device.power_on()
954
963
 
955
- print(color(f'### Connecting to {self.peripheral_address}...', 'cyan'))
964
+ if self.classic:
965
+ await self.device.set_discoverable(False)
966
+ await self.device.set_connectable(False)
967
+
968
+ logging.info(
969
+ color(f'### Connecting to {self.peripheral_address}...', 'cyan')
970
+ )
956
971
  try:
957
972
  self.connection = await self.device.connect(
958
973
  self.peripheral_address,
@@ -960,21 +975,26 @@ class Central(Connection.Listener):
960
975
  transport=BT_BR_EDR_TRANSPORT if self.classic else BT_LE_TRANSPORT,
961
976
  )
962
977
  except CommandTimeoutError:
963
- print(color('!!! Connection timed out', 'red'))
978
+ logging.info(color('!!! Connection timed out', 'red'))
964
979
  return
965
980
  except bumble.core.ConnectionError as error:
966
- print(color(f'!!! Connection error: {error}', 'red'))
981
+ logging.info(color(f'!!! Connection error: {error}', 'red'))
967
982
  return
968
983
  except HCI_StatusError as error:
969
- print(color(f'!!! Connection failed: {error.error_name}'))
984
+ logging.info(color(f'!!! Connection failed: {error.error_name}'))
970
985
  return
971
- print(color('### Connected', 'cyan'))
986
+ logging.info(color('### Connected', 'cyan'))
972
987
  self.connection.listener = self
973
988
  print_connection(self.connection)
974
989
 
990
+ # Wait a bit after the connection, some controllers aren't very good when
991
+ # we start sending data right away while some connection parameters are
992
+ # updated post connection
993
+ await asyncio.sleep(DEFAULT_POST_CONNECTION_WAIT_TIME)
994
+
975
995
  # Request a new data length if requested
976
996
  if self.extended_data_length:
977
- print(color('+++ Requesting extended data length', 'cyan'))
997
+ logging.info(color('+++ Requesting extended data length', 'cyan'))
978
998
  await self.connection.set_data_length(
979
999
  self.extended_data_length[0], self.extended_data_length[1]
980
1000
  )
@@ -982,16 +1002,16 @@ class Central(Connection.Listener):
982
1002
  # Authenticate if requested
983
1003
  if self.authenticate:
984
1004
  # Request authentication
985
- print(color('*** Authenticating...', 'cyan'))
1005
+ logging.info(color('*** Authenticating...', 'cyan'))
986
1006
  await self.connection.authenticate()
987
- print(color('*** Authenticated', 'cyan'))
1007
+ logging.info(color('*** Authenticated', 'cyan'))
988
1008
 
989
1009
  # Encrypt if requested
990
1010
  if self.encrypt:
991
1011
  # Enable encryption
992
- print(color('*** Enabling encryption...', 'cyan'))
1012
+ logging.info(color('*** Enabling encryption...', 'cyan'))
993
1013
  await self.connection.encrypt()
994
- print(color('*** Encryption on', 'cyan'))
1014
+ logging.info(color('*** Encryption on', 'cyan'))
995
1015
 
996
1016
  # Set the PHY if requested
997
1017
  if self.phy is not None:
@@ -1000,7 +1020,7 @@ class Central(Connection.Listener):
1000
1020
  tx_phys=[self.phy], rx_phys=[self.phy]
1001
1021
  )
1002
1022
  except HCI_Error as error:
1003
- print(
1023
+ logging.info(
1004
1024
  color(
1005
1025
  f'!!! Unable to set the PHY: {error.error_name}', 'yellow'
1006
1026
  )
@@ -1012,7 +1032,7 @@ class Central(Connection.Listener):
1012
1032
  await asyncio.sleep(DEFAULT_LINGER_TIME)
1013
1033
 
1014
1034
  def on_disconnection(self, reason):
1015
- print(color(f'!!! Disconnection: reason={reason}', 'red'))
1035
+ logging.info(color(f'!!! Disconnection: reason={reason}', 'red'))
1016
1036
  self.connection = None
1017
1037
 
1018
1038
  def on_connection_parameters_update(self):
@@ -1047,12 +1067,12 @@ class Peripheral(Device.Listener, Connection.Listener):
1047
1067
  self.connected = asyncio.Event()
1048
1068
 
1049
1069
  async def run(self):
1050
- print(color('>>> Connecting to HCI...', 'green'))
1070
+ logging.info(color('>>> Connecting to HCI...', 'green'))
1051
1071
  async with await open_transport_or_link(self.transport) as (
1052
1072
  hci_source,
1053
1073
  hci_sink,
1054
1074
  ):
1055
- print(color('>>> Connected', 'green'))
1075
+ logging.info(color('>>> Connected', 'green'))
1056
1076
 
1057
1077
  peripheral_address = DEFAULT_PERIPHERAL_ADDRESS
1058
1078
  self.device = Device.with_hci(
@@ -1072,7 +1092,7 @@ class Peripheral(Device.Listener, Connection.Listener):
1072
1092
  await self.device.start_advertising(auto_restart=True)
1073
1093
 
1074
1094
  if self.classic:
1075
- print(
1095
+ logging.info(
1076
1096
  color(
1077
1097
  '### Waiting for connection on'
1078
1098
  f' {self.device.public_address}...',
@@ -1080,14 +1100,14 @@ class Peripheral(Device.Listener, Connection.Listener):
1080
1100
  )
1081
1101
  )
1082
1102
  else:
1083
- print(
1103
+ logging.info(
1084
1104
  color(
1085
1105
  f'### Waiting for connection on {peripheral_address}...',
1086
1106
  'cyan',
1087
1107
  )
1088
1108
  )
1089
1109
  await self.connected.wait()
1090
- print(color('### Connected', 'cyan'))
1110
+ logging.info(color('### Connected', 'cyan'))
1091
1111
 
1092
1112
  await self.mode.on_connection(self.connection)
1093
1113
  await self.role.run()
@@ -1098,9 +1118,18 @@ class Peripheral(Device.Listener, Connection.Listener):
1098
1118
  self.connection = connection
1099
1119
  self.connected.set()
1100
1120
 
1121
+ # Stop being discoverable and connectable
1122
+ 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())
1129
+
1101
1130
  # Request a new data length if needed
1102
1131
  if self.extended_data_length:
1103
- print("+++ Requesting extended data length")
1132
+ logging.info("+++ Requesting extended data length")
1104
1133
  AsyncRunner.spawn(
1105
1134
  connection.set_data_length(
1106
1135
  self.extended_data_length[0], self.extended_data_length[1]
@@ -1108,7 +1137,7 @@ class Peripheral(Device.Listener, Connection.Listener):
1108
1137
  )
1109
1138
 
1110
1139
  def on_disconnection(self, reason):
1111
- print(color(f'!!! Disconnection: reason={reason}', 'red'))
1140
+ logging.info(color(f'!!! Disconnection: reason={reason}', 'red'))
1112
1141
  self.connection = None
1113
1142
  self.role.reset()
1114
1143
 
@@ -0,0 +1,63 @@
1
+ # Copyright 2023 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import click
16
+ from bumble.colors import color
17
+ from bumble.hci import Address
18
+ from bumble.helpers import generate_irk, verify_rpa_with_irk
19
+
20
+
21
+ @click.group()
22
+ def cli():
23
+ '''
24
+ This is a tool for generating IRK, RPA,
25
+ and verifying IRK/RPA pairs
26
+ '''
27
+
28
+
29
+ @click.command()
30
+ def gen_irk() -> None:
31
+ print(generate_irk().hex())
32
+
33
+
34
+ @click.command()
35
+ @click.argument("irk", type=str)
36
+ def gen_rpa(irk: str) -> None:
37
+ irk_bytes = bytes.fromhex(irk)
38
+ rpa = Address.generate_private_address(irk_bytes)
39
+ print(rpa.to_string(with_type_qualifier=False))
40
+
41
+
42
+ @click.command()
43
+ @click.argument("irk", type=str)
44
+ @click.argument("rpa", type=str)
45
+ def verify_rpa(irk: str, rpa: str) -> None:
46
+ address = Address(rpa)
47
+ irk_bytes = bytes.fromhex(irk)
48
+ if verify_rpa_with_irk(address, irk_bytes):
49
+ print(color("Verified", "green"))
50
+ else:
51
+ print(color("Not Verified", "red"))
52
+
53
+
54
+ def main():
55
+ cli.add_command(gen_irk)
56
+ cli.add_command(gen_rpa)
57
+ cli.add_command(verify_rpa)
58
+ cli()
59
+
60
+
61
+ # -----------------------------------------------------------------------------
62
+ if __name__ == '__main__':
63
+ main()