bumble 0.0.195__py3-none-any.whl → 0.0.198__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 (50) hide show
  1. bumble/_version.py +2 -2
  2. bumble/apps/auracast.py +351 -66
  3. bumble/apps/console.py +5 -20
  4. bumble/apps/device_info.py +230 -0
  5. bumble/apps/gatt_dump.py +4 -0
  6. bumble/apps/lea_unicast/app.py +16 -17
  7. bumble/at.py +12 -6
  8. bumble/avc.py +8 -5
  9. bumble/avctp.py +3 -2
  10. bumble/avdtp.py +5 -1
  11. bumble/avrcp.py +2 -1
  12. bumble/codecs.py +17 -13
  13. bumble/colors.py +6 -2
  14. bumble/core.py +37 -7
  15. bumble/device.py +382 -111
  16. bumble/drivers/rtk.py +13 -8
  17. bumble/gatt.py +6 -1
  18. bumble/gatt_client.py +10 -4
  19. bumble/hci.py +50 -25
  20. bumble/hid.py +24 -28
  21. bumble/host.py +4 -0
  22. bumble/l2cap.py +24 -17
  23. bumble/link.py +8 -3
  24. bumble/profiles/ascs.py +739 -0
  25. bumble/profiles/bap.py +1 -874
  26. bumble/profiles/bass.py +440 -0
  27. bumble/profiles/csip.py +4 -4
  28. bumble/profiles/gap.py +110 -0
  29. bumble/profiles/heart_rate_service.py +4 -3
  30. bumble/profiles/le_audio.py +43 -9
  31. bumble/profiles/mcp.py +448 -0
  32. bumble/profiles/pacs.py +210 -0
  33. bumble/profiles/tmap.py +89 -0
  34. bumble/rfcomm.py +4 -2
  35. bumble/sdp.py +13 -11
  36. bumble/smp.py +20 -8
  37. bumble/snoop.py +5 -4
  38. bumble/transport/__init__.py +8 -2
  39. bumble/transport/android_emulator.py +9 -3
  40. bumble/transport/android_netsim.py +9 -7
  41. bumble/transport/common.py +46 -18
  42. bumble/transport/pyusb.py +2 -2
  43. bumble/transport/unix.py +56 -0
  44. bumble/transport/usb.py +57 -46
  45. {bumble-0.0.195.dist-info → bumble-0.0.198.dist-info}/METADATA +41 -41
  46. {bumble-0.0.195.dist-info → bumble-0.0.198.dist-info}/RECORD +50 -42
  47. {bumble-0.0.195.dist-info → bumble-0.0.198.dist-info}/WHEEL +1 -1
  48. {bumble-0.0.195.dist-info → bumble-0.0.198.dist-info}/LICENSE +0 -0
  49. {bumble-0.0.195.dist-info → bumble-0.0.198.dist-info}/entry_points.txt +0 -0
  50. {bumble-0.0.195.dist-info → bumble-0.0.198.dist-info}/top_level.txt +0 -0
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.195'
16
- __version_tuple__ = version_tuple = (0, 0, 195)
15
+ __version__ = version = '0.0.198'
16
+ __version_tuple__ = version_tuple = (0, 0, 198)
bumble/apps/auracast.py CHANGED
@@ -17,10 +17,11 @@
17
17
  # -----------------------------------------------------------------------------
18
18
  from __future__ import annotations
19
19
  import asyncio
20
+ import contextlib
20
21
  import dataclasses
21
22
  import logging
22
23
  import os
23
- from typing import cast, Dict, Optional, Tuple
24
+ from typing import cast, Any, AsyncGenerator, Coroutine, Dict, Optional, Tuple
24
25
 
25
26
  import click
26
27
  import pyee
@@ -32,6 +33,7 @@ import bumble.device
32
33
  import bumble.gatt
33
34
  import bumble.hci
34
35
  import bumble.profiles.bap
36
+ import bumble.profiles.bass
35
37
  import bumble.profiles.pbp
36
38
  import bumble.transport
37
39
  import bumble.utils
@@ -46,14 +48,16 @@ logger = logging.getLogger(__name__)
46
48
  # -----------------------------------------------------------------------------
47
49
  # Constants
48
50
  # -----------------------------------------------------------------------------
49
- AURACAST_DEFAULT_DEVICE_NAME = "Bumble Auracast"
50
- AURACAST_DEFAULT_DEVICE_ADDRESS = bumble.hci.Address("F0:F1:F2:F3:F4:F5")
51
+ AURACAST_DEFAULT_DEVICE_NAME = 'Bumble Auracast'
52
+ AURACAST_DEFAULT_DEVICE_ADDRESS = bumble.hci.Address('F0:F1:F2:F3:F4:F5')
53
+ AURACAST_DEFAULT_SYNC_TIMEOUT = 5.0
54
+ AURACAST_DEFAULT_ATT_MTU = 256
51
55
 
52
56
 
53
57
  # -----------------------------------------------------------------------------
54
- # Discover Broadcasts
58
+ # Scan For Broadcasts
55
59
  # -----------------------------------------------------------------------------
56
- class BroadcastDiscoverer:
60
+ class BroadcastScanner(pyee.EventEmitter):
57
61
  @dataclasses.dataclass
58
62
  class Broadcast(pyee.EventEmitter):
59
63
  name: str
@@ -79,22 +83,6 @@ class BroadcastDiscoverer:
79
83
  self.sync.on('periodic_advertisement', self.on_periodic_advertisement)
80
84
  self.sync.on('biginfo_advertisement', self.on_biginfo_advertisement)
81
85
 
82
- self.establishment_timeout_task = asyncio.create_task(
83
- self.wait_for_establishment()
84
- )
85
-
86
- async def wait_for_establishment(self) -> None:
87
- await asyncio.sleep(5.0)
88
- if self.sync.state == bumble.device.PeriodicAdvertisingSync.State.PENDING:
89
- print(
90
- color(
91
- '!!! Periodic advertisement sync not established in time, '
92
- 'canceling',
93
- 'red',
94
- )
95
- )
96
- await self.sync.terminate()
97
-
98
86
  def update(self, advertisement: bumble.device.Advertisement) -> None:
99
87
  self.rssi = advertisement.rssi
100
88
  for service_data in advertisement.data.get_all(
@@ -139,6 +127,8 @@ class BroadcastDiscoverer:
139
127
  data,
140
128
  )
141
129
 
130
+ self.emit('update')
131
+
142
132
  def print(self) -> None:
143
133
  print(
144
134
  color('Broadcast:', 'yellow'),
@@ -227,13 +217,12 @@ class BroadcastDiscoverer:
227
217
  )
228
218
 
229
219
  def on_sync_establishment(self) -> None:
230
- self.establishment_timeout_task.cancel()
231
- self.emit('change')
220
+ self.emit('sync_establishment')
232
221
 
233
222
  def on_sync_loss(self) -> None:
234
223
  self.basic_audio_announcement = None
235
224
  self.biginfo = None
236
- self.emit('change')
225
+ self.emit('sync_loss')
237
226
 
238
227
  def on_periodic_advertisement(
239
228
  self, advertisement: bumble.device.PeriodicAdvertisement
@@ -268,37 +257,21 @@ class BroadcastDiscoverer:
268
257
  filter_duplicates: bool,
269
258
  sync_timeout: float,
270
259
  ):
260
+ super().__init__()
271
261
  self.device = device
272
262
  self.filter_duplicates = filter_duplicates
273
263
  self.sync_timeout = sync_timeout
274
- self.broadcasts: Dict[bumble.hci.Address, BroadcastDiscoverer.Broadcast] = {}
275
- self.status_message = ''
264
+ self.broadcasts: Dict[bumble.hci.Address, BroadcastScanner.Broadcast] = {}
276
265
  device.on('advertisement', self.on_advertisement)
277
266
 
278
- async def run(self) -> None:
279
- self.status_message = color('Scanning...', 'green')
267
+ async def start(self) -> None:
280
268
  await self.device.start_scanning(
281
269
  active=False,
282
270
  filter_duplicates=False,
283
271
  )
284
272
 
285
- def refresh(self) -> None:
286
- # Clear the screen from the top
287
- print('\033[H')
288
- print('\033[0J')
289
- print('\033[H')
290
-
291
- # Print the status message
292
- print(self.status_message)
293
- print("==========================================")
294
-
295
- # Print all broadcasts
296
- for broadcast in self.broadcasts.values():
297
- broadcast.print()
298
- print('------------------------------------------')
299
-
300
- # Clear the screen to the bottom
301
- print('\033[0J')
273
+ async def stop(self) -> None:
274
+ await self.device.stop_scanning()
302
275
 
303
276
  def on_advertisement(self, advertisement: bumble.device.Advertisement) -> None:
304
277
  if (
@@ -311,7 +284,6 @@ class BroadcastDiscoverer:
311
284
 
312
285
  if broadcast := self.broadcasts.get(advertisement.address):
313
286
  broadcast.update(advertisement)
314
- self.refresh()
315
287
  return
316
288
 
317
289
  bumble.utils.AsyncRunner.spawn(
@@ -331,41 +303,318 @@ class BroadcastDiscoverer:
331
303
  name,
332
304
  periodic_advertising_sync,
333
305
  )
334
- broadcast.on('change', self.refresh)
335
306
  broadcast.update(advertisement)
336
307
  self.broadcasts[advertisement.address] = broadcast
337
308
  periodic_advertising_sync.on('loss', lambda: self.on_broadcast_loss(broadcast))
338
- self.status_message = color(
339
- f'+Found {len(self.broadcasts)} broadcasts', 'green'
340
- )
341
- self.refresh()
309
+ self.emit('new_broadcast', broadcast)
342
310
 
343
311
  def on_broadcast_loss(self, broadcast: Broadcast) -> None:
344
312
  del self.broadcasts[broadcast.sync.advertiser_address]
345
313
  bumble.utils.AsyncRunner.spawn(broadcast.sync.terminate())
314
+ self.emit('broadcast_loss', broadcast)
315
+
316
+
317
+ class PrintingBroadcastScanner:
318
+ def __init__(
319
+ self, device: bumble.device.Device, filter_duplicates: bool, sync_timeout: float
320
+ ) -> None:
321
+ self.scanner = BroadcastScanner(device, filter_duplicates, sync_timeout)
322
+ self.scanner.on('new_broadcast', self.on_new_broadcast)
323
+ self.scanner.on('broadcast_loss', self.on_broadcast_loss)
324
+ self.scanner.on('update', self.refresh)
325
+ self.status_message = ''
326
+
327
+ async def start(self) -> None:
328
+ self.status_message = color('Scanning...', 'green')
329
+ await self.scanner.start()
330
+
331
+ def on_new_broadcast(self, broadcast: BroadcastScanner.Broadcast) -> None:
346
332
  self.status_message = color(
347
- f'-Found {len(self.broadcasts)} broadcasts', 'green'
333
+ f'+Found {len(self.scanner.broadcasts)} broadcasts', 'green'
348
334
  )
335
+ broadcast.on('change', self.refresh)
336
+ broadcast.on('update', self.refresh)
349
337
  self.refresh()
350
338
 
339
+ def on_broadcast_loss(self, broadcast: BroadcastScanner.Broadcast) -> None:
340
+ self.status_message = color(
341
+ f'-Found {len(self.scanner.broadcasts)} broadcasts', 'green'
342
+ )
343
+ self.refresh()
351
344
 
352
- async def run_discover_broadcasts(
353
- filter_duplicates: bool, sync_timeout: float, transport: str
354
- ) -> None:
345
+ def refresh(self) -> None:
346
+ # Clear the screen from the top
347
+ print('\033[H')
348
+ print('\033[0J')
349
+ print('\033[H')
350
+
351
+ # Print the status message
352
+ print(self.status_message)
353
+ print("==========================================")
354
+
355
+ # Print all broadcasts
356
+ for broadcast in self.scanner.broadcasts.values():
357
+ broadcast.print()
358
+ print('------------------------------------------')
359
+
360
+ # Clear the screen to the bottom
361
+ print('\033[0J')
362
+
363
+
364
+ @contextlib.asynccontextmanager
365
+ async def create_device(transport: str) -> AsyncGenerator[bumble.device.Device, Any]:
355
366
  async with await bumble.transport.open_transport(transport) as (
356
367
  hci_source,
357
368
  hci_sink,
358
369
  ):
359
- device = bumble.device.Device.with_hci(
360
- AURACAST_DEFAULT_DEVICE_NAME,
361
- AURACAST_DEFAULT_DEVICE_ADDRESS,
370
+ device_config = bumble.device.DeviceConfiguration(
371
+ name=AURACAST_DEFAULT_DEVICE_NAME,
372
+ address=AURACAST_DEFAULT_DEVICE_ADDRESS,
373
+ keystore='JsonKeyStore',
374
+ )
375
+
376
+ device = bumble.device.Device.from_config_with_hci(
377
+ device_config,
362
378
  hci_source,
363
379
  hci_sink,
364
380
  )
365
381
  await device.power_on()
366
- discoverer = BroadcastDiscoverer(device, filter_duplicates, sync_timeout)
367
- await discoverer.run()
368
- await hci_source.terminated
382
+
383
+ yield device
384
+
385
+
386
+ async def find_broadcast_by_name(
387
+ device: bumble.device.Device, name: Optional[str]
388
+ ) -> BroadcastScanner.Broadcast:
389
+ result = asyncio.get_running_loop().create_future()
390
+
391
+ def on_broadcast_change(broadcast: BroadcastScanner.Broadcast) -> None:
392
+ if broadcast.basic_audio_announcement and not result.done():
393
+ print(color('Broadcast basic audio announcement received', 'green'))
394
+ result.set_result(broadcast)
395
+
396
+ def on_new_broadcast(broadcast: BroadcastScanner.Broadcast) -> None:
397
+ if name is None or broadcast.name == name:
398
+ print(color('Broadcast found:', 'green'), broadcast.name)
399
+ broadcast.on('change', lambda: on_broadcast_change(broadcast))
400
+ return
401
+
402
+ print(color(f'Skipping broadcast {broadcast.name}'))
403
+
404
+ scanner = BroadcastScanner(device, False, AURACAST_DEFAULT_SYNC_TIMEOUT)
405
+ scanner.on('new_broadcast', on_new_broadcast)
406
+ await scanner.start()
407
+
408
+ broadcast = await result
409
+ await scanner.stop()
410
+
411
+ return broadcast
412
+
413
+
414
+ async def run_scan(
415
+ filter_duplicates: bool, sync_timeout: float, transport: str
416
+ ) -> None:
417
+ async with create_device(transport) as device:
418
+ if not device.supports_le_periodic_advertising:
419
+ print(color('Periodic advertising not supported', 'red'))
420
+ return
421
+
422
+ scanner = PrintingBroadcastScanner(device, filter_duplicates, sync_timeout)
423
+ await scanner.start()
424
+ await asyncio.get_running_loop().create_future()
425
+
426
+
427
+ async def run_assist(
428
+ broadcast_name: Optional[str],
429
+ source_id: Optional[int],
430
+ command: str,
431
+ transport: str,
432
+ address: str,
433
+ ) -> None:
434
+ async with create_device(transport) as device:
435
+ if not device.supports_le_periodic_advertising:
436
+ print(color('Periodic advertising not supported', 'red'))
437
+ return
438
+
439
+ # Connect to the server
440
+ print(f'=== Connecting to {address}...')
441
+ connection = await device.connect(address)
442
+ peer = bumble.device.Peer(connection)
443
+ print(f'=== Connected to {peer}')
444
+
445
+ print("+++ Encrypting connection...")
446
+ await peer.connection.encrypt()
447
+ print("+++ Connection encrypted")
448
+
449
+ # Request a larger MTU
450
+ mtu = AURACAST_DEFAULT_ATT_MTU
451
+ print(color(f'$$$ Requesting MTU={mtu}', 'yellow'))
452
+ await peer.request_mtu(mtu)
453
+
454
+ # Get the BASS service
455
+ bass = await peer.discover_service_and_create_proxy(
456
+ bumble.profiles.bass.BroadcastAudioScanServiceProxy
457
+ )
458
+
459
+ # Check that the service was found
460
+ if not bass:
461
+ print(color('!!! Broadcast Audio Scan Service not found', 'red'))
462
+ return
463
+
464
+ # Subscribe to and read the broadcast receive state characteristics
465
+ for i, broadcast_receive_state in enumerate(bass.broadcast_receive_states):
466
+ try:
467
+ await broadcast_receive_state.subscribe(
468
+ lambda value, i=i: print(
469
+ f"{color(f'Broadcast Receive State Update [{i}]:', 'green')} {value}"
470
+ )
471
+ )
472
+ except bumble.core.ProtocolError as error:
473
+ print(
474
+ color(
475
+ f'!!! Failed to subscribe to Broadcast Receive State characteristic:',
476
+ 'red',
477
+ ),
478
+ error,
479
+ )
480
+ value = await broadcast_receive_state.read_value()
481
+ print(
482
+ f'{color(f"Initial Broadcast Receive State [{i}]:", "green")} {value}'
483
+ )
484
+
485
+ if command == 'monitor-state':
486
+ await peer.sustain()
487
+ return
488
+
489
+ if command == 'add-source':
490
+ # Find the requested broadcast
491
+ await bass.remote_scan_started()
492
+ if broadcast_name:
493
+ print(color('Scanning for broadcast:', 'cyan'), broadcast_name)
494
+ else:
495
+ print(color('Scanning for any broadcast', 'cyan'))
496
+ broadcast = await find_broadcast_by_name(device, broadcast_name)
497
+
498
+ if broadcast.broadcast_audio_announcement is None:
499
+ print(color('No broadcast audio announcement found', 'red'))
500
+ return
501
+
502
+ if (
503
+ broadcast.basic_audio_announcement is None
504
+ or not broadcast.basic_audio_announcement.subgroups
505
+ ):
506
+ print(color('No subgroups found', 'red'))
507
+ return
508
+
509
+ # Add the source
510
+ print(color('Adding source:', 'blue'), broadcast.sync.advertiser_address)
511
+ await bass.add_source(
512
+ broadcast.sync.advertiser_address,
513
+ broadcast.sync.sid,
514
+ broadcast.broadcast_audio_announcement.broadcast_id,
515
+ bumble.profiles.bass.PeriodicAdvertisingSyncParams.SYNCHRONIZE_TO_PA_PAST_AVAILABLE,
516
+ 0xFFFF,
517
+ [
518
+ bumble.profiles.bass.SubgroupInfo(
519
+ bumble.profiles.bass.SubgroupInfo.ANY_BIS,
520
+ bytes(broadcast.basic_audio_announcement.subgroups[0].metadata),
521
+ )
522
+ ],
523
+ )
524
+
525
+ # Initiate a PA Sync Transfer
526
+ await broadcast.sync.transfer(peer.connection)
527
+
528
+ # Notify the sink that we're done scanning.
529
+ await bass.remote_scan_stopped()
530
+
531
+ await peer.sustain()
532
+ return
533
+
534
+ if command == 'modify-source':
535
+ if source_id is None:
536
+ print(color('!!! modify-source requires --source-id'))
537
+ return
538
+
539
+ # Find the requested broadcast
540
+ await bass.remote_scan_started()
541
+ if broadcast_name:
542
+ print(color('Scanning for broadcast:', 'cyan'), broadcast_name)
543
+ else:
544
+ print(color('Scanning for any broadcast', 'cyan'))
545
+ broadcast = await find_broadcast_by_name(device, broadcast_name)
546
+
547
+ if broadcast.broadcast_audio_announcement is None:
548
+ print(color('No broadcast audio announcement found', 'red'))
549
+ return
550
+
551
+ if (
552
+ broadcast.basic_audio_announcement is None
553
+ or not broadcast.basic_audio_announcement.subgroups
554
+ ):
555
+ print(color('No subgroups found', 'red'))
556
+ return
557
+
558
+ # Modify the source
559
+ print(
560
+ color('Modifying source:', 'blue'),
561
+ source_id,
562
+ )
563
+ await bass.modify_source(
564
+ source_id,
565
+ bumble.profiles.bass.PeriodicAdvertisingSyncParams.SYNCHRONIZE_TO_PA_PAST_NOT_AVAILABLE,
566
+ 0xFFFF,
567
+ [
568
+ bumble.profiles.bass.SubgroupInfo(
569
+ bumble.profiles.bass.SubgroupInfo.ANY_BIS,
570
+ bytes(broadcast.basic_audio_announcement.subgroups[0].metadata),
571
+ )
572
+ ],
573
+ )
574
+ await peer.sustain()
575
+ return
576
+
577
+ if command == 'remove-source':
578
+ if source_id is None:
579
+ print(color('!!! remove-source requires --source-id'))
580
+ return
581
+
582
+ # Remove the source
583
+ print(color('Removing source:', 'blue'), source_id)
584
+ await bass.remove_source(source_id)
585
+ await peer.sustain()
586
+ return
587
+
588
+ print(color(f'!!! invalid command {command}'))
589
+
590
+
591
+ async def run_pair(transport: str, address: str) -> None:
592
+ async with create_device(transport) as device:
593
+
594
+ # Connect to the server
595
+ print(f'=== Connecting to {address}...')
596
+ async with device.connect_as_gatt(address) as peer:
597
+ print(f'=== Connected to {peer}')
598
+
599
+ print("+++ Initiating pairing...")
600
+ await peer.connection.pair()
601
+ print("+++ Paired")
602
+
603
+
604
+ def run_async(async_command: Coroutine) -> None:
605
+ try:
606
+ asyncio.run(async_command)
607
+ except bumble.core.ProtocolError as error:
608
+ if error.error_namespace == 'att' and error.error_code in list(
609
+ bumble.profiles.bass.ApplicationError
610
+ ):
611
+ message = bumble.profiles.bass.ApplicationError(error.error_code).name
612
+ else:
613
+ message = str(error)
614
+
615
+ print(
616
+ color('!!! An error occurred while executing the command:', 'red'), message
617
+ )
369
618
 
370
619
 
371
620
  # -----------------------------------------------------------------------------
@@ -379,7 +628,7 @@ def auracast(
379
628
  ctx.ensure_object(dict)
380
629
 
381
630
 
382
- @auracast.command('discover-broadcasts')
631
+ @auracast.command('scan')
383
632
  @click.option(
384
633
  '--filter-duplicates', is_flag=True, default=False, help='Filter duplicates'
385
634
  )
@@ -387,14 +636,50 @@ def auracast(
387
636
  '--sync-timeout',
388
637
  metavar='SYNC_TIMEOUT',
389
638
  type=float,
390
- default=5.0,
639
+ default=AURACAST_DEFAULT_SYNC_TIMEOUT,
391
640
  help='Sync timeout (in seconds)',
392
641
  )
393
642
  @click.argument('transport')
394
643
  @click.pass_context
395
- def discover_broadcasts(ctx, filter_duplicates, sync_timeout, transport):
396
- """Discover public broadcasts"""
397
- asyncio.run(run_discover_broadcasts(filter_duplicates, sync_timeout, transport))
644
+ def scan(ctx, filter_duplicates, sync_timeout, transport):
645
+ """Scan for public broadcasts"""
646
+ run_async(run_scan(filter_duplicates, sync_timeout, transport))
647
+
648
+
649
+ @auracast.command('assist')
650
+ @click.option(
651
+ '--broadcast-name',
652
+ metavar='BROADCAST_NAME',
653
+ help='Broadcast Name to tune to',
654
+ )
655
+ @click.option(
656
+ '--source-id',
657
+ metavar='SOURCE_ID',
658
+ type=int,
659
+ help='Source ID (for remove-source command)',
660
+ )
661
+ @click.option(
662
+ '--command',
663
+ type=click.Choice(
664
+ ['monitor-state', 'add-source', 'modify-source', 'remove-source']
665
+ ),
666
+ required=True,
667
+ )
668
+ @click.argument('transport')
669
+ @click.argument('address')
670
+ @click.pass_context
671
+ def assist(ctx, broadcast_name, source_id, command, transport, address):
672
+ """Scan for broadcasts on behalf of a audio server"""
673
+ run_async(run_assist(broadcast_name, source_id, command, transport, address))
674
+
675
+
676
+ @auracast.command('pair')
677
+ @click.argument('transport')
678
+ @click.argument('address')
679
+ @click.pass_context
680
+ def pair(ctx, transport, address):
681
+ """Pair with an audio server"""
682
+ run_async(run_pair(transport, address))
398
683
 
399
684
 
400
685
  def main():
bumble/apps/console.py CHANGED
@@ -63,6 +63,7 @@ from bumble.transport import open_transport_or_link
63
63
  from bumble.gatt import Characteristic, Service, CharacteristicDeclaration, Descriptor
64
64
  from bumble.gatt_client import CharacteristicProxy
65
65
  from bumble.hci import (
66
+ Address,
66
67
  HCI_Constant,
67
68
  HCI_LE_1M_PHY,
68
69
  HCI_LE_2M_PHY,
@@ -289,11 +290,7 @@ class ConsoleApp:
289
290
  device_config, hci_source, hci_sink
290
291
  )
291
292
  else:
292
- random_address = (
293
- f"{random.randint(192,255):02X}" # address is static random
294
- )
295
- for random_byte in random.sample(range(255), 5):
296
- random_address += f":{random_byte:02X}"
293
+ random_address = Address.generate_static_address()
297
294
  self.append_to_log(f"Setting random address: {random_address}")
298
295
  self.device = Device.with_hci(
299
296
  'Bumble', random_address, hci_source, hci_sink
@@ -503,21 +500,9 @@ class ConsoleApp:
503
500
  self.show_error('not connected')
504
501
  return
505
502
 
506
- # Discover all services, characteristics and descriptors
507
- self.append_to_output('discovering services...')
508
- await self.connected_peer.discover_services()
509
- self.append_to_output(
510
- f'found {len(self.connected_peer.services)} services,'
511
- ' discovering characteristics...'
512
- )
513
- await self.connected_peer.discover_characteristics()
514
- self.append_to_output('found characteristics, discovering descriptors...')
515
- for service in self.connected_peer.services:
516
- for characteristic in service.characteristics:
517
- await self.connected_peer.discover_descriptors(characteristic)
518
- self.append_to_output('discovery completed')
519
-
520
- self.show_remote_services(self.connected_peer.services)
503
+ self.append_to_output('Service Discovery starting...')
504
+ await self.connected_peer.discover_all()
505
+ self.append_to_output('Service Discovery done!')
521
506
 
522
507
  async def discover_attributes(self):
523
508
  if not self.connected_peer: