bumble 0.0.194__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 (54) hide show
  1. bumble/_version.py +2 -2
  2. bumble/apps/auracast.py +692 -0
  3. bumble/apps/bench.py +77 -23
  4. bumble/apps/console.py +5 -20
  5. bumble/apps/controller_info.py +3 -3
  6. bumble/apps/device_info.py +230 -0
  7. bumble/apps/gatt_dump.py +4 -0
  8. bumble/apps/lea_unicast/app.py +16 -17
  9. bumble/at.py +12 -6
  10. bumble/avc.py +8 -5
  11. bumble/avctp.py +3 -2
  12. bumble/avdtp.py +5 -1
  13. bumble/avrcp.py +2 -1
  14. bumble/codecs.py +17 -13
  15. bumble/colors.py +6 -2
  16. bumble/core.py +726 -122
  17. bumble/device.py +817 -117
  18. bumble/drivers/rtk.py +13 -8
  19. bumble/gatt.py +6 -1
  20. bumble/gatt_client.py +10 -4
  21. bumble/hci.py +283 -20
  22. bumble/hid.py +24 -28
  23. bumble/host.py +29 -0
  24. bumble/l2cap.py +24 -17
  25. bumble/link.py +8 -3
  26. bumble/pandora/host.py +3 -2
  27. bumble/profiles/ascs.py +739 -0
  28. bumble/profiles/bap.py +85 -862
  29. bumble/profiles/bass.py +440 -0
  30. bumble/profiles/csip.py +4 -4
  31. bumble/profiles/gap.py +110 -0
  32. bumble/profiles/heart_rate_service.py +4 -3
  33. bumble/profiles/le_audio.py +83 -0
  34. bumble/profiles/mcp.py +448 -0
  35. bumble/profiles/pacs.py +210 -0
  36. bumble/profiles/pbp.py +46 -0
  37. bumble/profiles/tmap.py +89 -0
  38. bumble/rfcomm.py +14 -3
  39. bumble/sdp.py +13 -11
  40. bumble/smp.py +20 -8
  41. bumble/snoop.py +5 -4
  42. bumble/transport/__init__.py +8 -2
  43. bumble/transport/android_emulator.py +9 -3
  44. bumble/transport/android_netsim.py +9 -7
  45. bumble/transport/common.py +46 -18
  46. bumble/transport/pyusb.py +2 -2
  47. bumble/transport/unix.py +56 -0
  48. bumble/transport/usb.py +57 -46
  49. {bumble-0.0.194.dist-info → bumble-0.0.198.dist-info}/METADATA +41 -41
  50. {bumble-0.0.194.dist-info → bumble-0.0.198.dist-info}/RECORD +54 -43
  51. {bumble-0.0.194.dist-info → bumble-0.0.198.dist-info}/WHEEL +1 -1
  52. {bumble-0.0.194.dist-info → bumble-0.0.198.dist-info}/LICENSE +0 -0
  53. {bumble-0.0.194.dist-info → bumble-0.0.198.dist-info}/entry_points.txt +0 -0
  54. {bumble-0.0.194.dist-info → bumble-0.0.198.dist-info}/top_level.txt +0 -0
bumble/apps/bench.py CHANGED
@@ -40,6 +40,8 @@ from bumble.hci import (
40
40
  HCI_LE_1M_PHY,
41
41
  HCI_LE_2M_PHY,
42
42
  HCI_LE_CODED_PHY,
43
+ HCI_CENTRAL_ROLE,
44
+ HCI_PERIPHERAL_ROLE,
43
45
  HCI_Constant,
44
46
  HCI_Error,
45
47
  HCI_StatusError,
@@ -57,6 +59,7 @@ from bumble.transport import open_transport_or_link
57
59
  import bumble.rfcomm
58
60
  import bumble.core
59
61
  from bumble.utils import AsyncRunner
62
+ from bumble.pairing import PairingConfig
60
63
 
61
64
 
62
65
  # -----------------------------------------------------------------------------
@@ -128,40 +131,34 @@ def le_phy_name(phy_id):
128
131
 
129
132
 
130
133
  def print_connection(connection):
134
+ params = []
131
135
  if connection.transport == BT_LE_TRANSPORT:
132
- phy_state = (
136
+ params.append(
133
137
  'PHY='
134
138
  f'TX:{le_phy_name(connection.phy.tx_phy)}/'
135
139
  f'RX:{le_phy_name(connection.phy.rx_phy)}'
136
140
  )
137
141
 
138
- data_length = (
142
+ params.append(
139
143
  'DL=('
140
144
  f'TX:{connection.data_length[0]}/{connection.data_length[1]},'
141
145
  f'RX:{connection.data_length[2]}/{connection.data_length[3]}'
142
146
  ')'
143
147
  )
144
- connection_parameters = (
148
+
149
+ params.append(
145
150
  'Parameters='
146
151
  f'{connection.parameters.connection_interval * 1.25:.2f}/'
147
152
  f'{connection.parameters.peripheral_latency}/'
148
153
  f'{connection.parameters.supervision_timeout * 10} '
149
154
  )
150
155
 
151
- else:
152
- phy_state = ''
153
- data_length = ''
154
- connection_parameters = ''
156
+ params.append(f'MTU={connection.att_mtu}')
155
157
 
156
- mtu = connection.att_mtu
158
+ else:
159
+ params.append(f'Role={HCI_Constant.role_name(connection.role)}')
157
160
 
158
- logging.info(
159
- f'{color("@@@ Connection:", "yellow")} '
160
- f'{connection_parameters} '
161
- f'{data_length} '
162
- f'{phy_state} '
163
- f'MTU={mtu}'
164
- )
161
+ logging.info(color('@@@ Connection: ', 'yellow') + ' '.join(params))
165
162
 
166
163
 
167
164
  def make_sdp_records(channel):
@@ -214,6 +211,17 @@ def log_stats(title, stats):
214
211
  )
215
212
 
216
213
 
214
+ async def switch_roles(connection, role):
215
+ target_role = HCI_CENTRAL_ROLE if role == "central" else HCI_PERIPHERAL_ROLE
216
+ if connection.role != target_role:
217
+ logging.info(f'{color("### Switching roles to:", "cyan")} {role}')
218
+ try:
219
+ await connection.switch_role(target_role)
220
+ logging.info(color('### Role switch complete', 'cyan'))
221
+ except HCI_Error as error:
222
+ logging.info(f'{color("### Role switch failed:", "red")} {error}')
223
+
224
+
217
225
  class PacketType(enum.IntEnum):
218
226
  RESET = 0
219
227
  SEQUENCE = 1
@@ -1034,6 +1042,10 @@ class RfcommServer(StreamedPacketIO):
1034
1042
 
1035
1043
  def on_dlc(self, dlc):
1036
1044
  logging.info(color(f'*** DLC connected: {dlc}', 'blue'))
1045
+ if self.credits_threshold is not None:
1046
+ dlc.rx_threshold = self.credits_threshold
1047
+ if self.max_credits is not None:
1048
+ dlc.rx_max_credits = self.max_credits
1037
1049
  dlc.sink = self.on_packet
1038
1050
  self.io_sink = dlc.write
1039
1051
  self.dlc = dlc
@@ -1063,6 +1075,7 @@ class Central(Connection.Listener):
1063
1075
  authenticate,
1064
1076
  encrypt,
1065
1077
  extended_data_length,
1078
+ role_switch,
1066
1079
  ):
1067
1080
  super().__init__()
1068
1081
  self.transport = transport
@@ -1073,6 +1086,7 @@ class Central(Connection.Listener):
1073
1086
  self.authenticate = authenticate
1074
1087
  self.encrypt = encrypt or authenticate
1075
1088
  self.extended_data_length = extended_data_length
1089
+ self.role_switch = role_switch
1076
1090
  self.device = None
1077
1091
  self.connection = None
1078
1092
 
@@ -1123,6 +1137,11 @@ class Central(Connection.Listener):
1123
1137
  role = self.role_factory(mode)
1124
1138
  self.device.classic_enabled = self.classic
1125
1139
 
1140
+ # Set up a pairing config factory with minimal requirements.
1141
+ self.device.pairing_config_factory = lambda _: PairingConfig(
1142
+ sc=False, mitm=False, bonding=False
1143
+ )
1144
+
1126
1145
  await self.device.power_on()
1127
1146
 
1128
1147
  if self.classic:
@@ -1151,6 +1170,10 @@ class Central(Connection.Listener):
1151
1170
  self.connection.listener = self
1152
1171
  print_connection(self.connection)
1153
1172
 
1173
+ # Switch roles if needed.
1174
+ if self.role_switch:
1175
+ await switch_roles(self.connection, self.role_switch)
1176
+
1154
1177
  # Wait a bit after the connection, some controllers aren't very good when
1155
1178
  # we start sending data right away while some connection parameters are
1156
1179
  # updated post connection
@@ -1212,20 +1235,30 @@ class Central(Connection.Listener):
1212
1235
  def on_connection_data_length_change(self):
1213
1236
  print_connection(self.connection)
1214
1237
 
1238
+ def on_role_change(self):
1239
+ print_connection(self.connection)
1240
+
1215
1241
 
1216
1242
  # -----------------------------------------------------------------------------
1217
1243
  # Peripheral
1218
1244
  # -----------------------------------------------------------------------------
1219
1245
  class Peripheral(Device.Listener, Connection.Listener):
1220
1246
  def __init__(
1221
- self, transport, classic, extended_data_length, role_factory, mode_factory
1247
+ self,
1248
+ transport,
1249
+ role_factory,
1250
+ mode_factory,
1251
+ classic,
1252
+ extended_data_length,
1253
+ role_switch,
1222
1254
  ):
1223
1255
  self.transport = transport
1224
1256
  self.classic = classic
1225
- self.extended_data_length = extended_data_length
1226
1257
  self.role_factory = role_factory
1227
- self.role = None
1228
1258
  self.mode_factory = mode_factory
1259
+ self.extended_data_length = extended_data_length
1260
+ self.role_switch = role_switch
1261
+ self.role = None
1229
1262
  self.mode = None
1230
1263
  self.device = None
1231
1264
  self.connection = None
@@ -1248,6 +1281,11 @@ class Peripheral(Device.Listener, Connection.Listener):
1248
1281
  self.role = self.role_factory(self.mode)
1249
1282
  self.device.classic_enabled = self.classic
1250
1283
 
1284
+ # Set up a pairing config factory with minimal requirements.
1285
+ self.device.pairing_config_factory = lambda _: PairingConfig(
1286
+ sc=False, mitm=False, bonding=False
1287
+ )
1288
+
1251
1289
  await self.device.power_on()
1252
1290
 
1253
1291
  if self.classic:
@@ -1274,6 +1312,7 @@ class Peripheral(Device.Listener, Connection.Listener):
1274
1312
 
1275
1313
  await self.connected.wait()
1276
1314
  logging.info(color('### Connected', 'cyan'))
1315
+ print_connection(self.connection)
1277
1316
 
1278
1317
  await self.mode.on_connection(self.connection)
1279
1318
  await self.role.run()
@@ -1290,7 +1329,7 @@ class Peripheral(Device.Listener, Connection.Listener):
1290
1329
  AsyncRunner.spawn(self.device.set_connectable(False))
1291
1330
 
1292
1331
  # Request a new data length if needed
1293
- if self.extended_data_length:
1332
+ if not self.classic and self.extended_data_length:
1294
1333
  logging.info("+++ Requesting extended data length")
1295
1334
  AsyncRunner.spawn(
1296
1335
  connection.set_data_length(
@@ -1298,6 +1337,10 @@ class Peripheral(Device.Listener, Connection.Listener):
1298
1337
  )
1299
1338
  )
1300
1339
 
1340
+ # Switch roles if needed.
1341
+ if self.role_switch:
1342
+ AsyncRunner.spawn(switch_roles(connection, self.role_switch))
1343
+
1301
1344
  def on_disconnection(self, reason):
1302
1345
  logging.info(color(f'!!! Disconnection: reason={reason}', 'red'))
1303
1346
  self.connection = None
@@ -1319,6 +1362,9 @@ class Peripheral(Device.Listener, Connection.Listener):
1319
1362
  def on_connection_data_length_change(self):
1320
1363
  print_connection(self.connection)
1321
1364
 
1365
+ def on_role_change(self):
1366
+ print_connection(self.connection)
1367
+
1322
1368
 
1323
1369
  # -----------------------------------------------------------------------------
1324
1370
  def create_mode_factory(ctx, default_mode):
@@ -1448,6 +1494,11 @@ def create_role_factory(ctx, default_role):
1448
1494
  '--extended-data-length',
1449
1495
  help='Request a data length upon connection, specified as tx_octets/tx_time',
1450
1496
  )
1497
+ @click.option(
1498
+ '--role-switch',
1499
+ type=click.Choice(['central', 'peripheral']),
1500
+ help='Request role switch upon connection (central or peripheral)',
1501
+ )
1451
1502
  @click.option(
1452
1503
  '--rfcomm-channel',
1453
1504
  type=int,
@@ -1512,7 +1563,7 @@ def create_role_factory(ctx, default_role):
1512
1563
  '--packet-size',
1513
1564
  '-s',
1514
1565
  metavar='SIZE',
1515
- type=click.IntRange(8, 4096),
1566
+ type=click.IntRange(8, 8192),
1516
1567
  default=500,
1517
1568
  help='Packet size (client or ping role)',
1518
1569
  )
@@ -1572,6 +1623,7 @@ def bench(
1572
1623
  mode,
1573
1624
  att_mtu,
1574
1625
  extended_data_length,
1626
+ role_switch,
1575
1627
  packet_size,
1576
1628
  packet_count,
1577
1629
  start_delay,
@@ -1614,12 +1666,12 @@ def bench(
1614
1666
  ctx.obj['repeat_delay'] = repeat_delay
1615
1667
  ctx.obj['pace'] = pace
1616
1668
  ctx.obj['linger'] = linger
1617
-
1618
1669
  ctx.obj['extended_data_length'] = (
1619
1670
  [int(x) for x in extended_data_length.split('/')]
1620
1671
  if extended_data_length
1621
1672
  else None
1622
1673
  )
1674
+ ctx.obj['role_switch'] = role_switch
1623
1675
  ctx.obj['classic'] = mode in ('rfcomm-client', 'rfcomm-server')
1624
1676
 
1625
1677
 
@@ -1663,6 +1715,7 @@ def central(
1663
1715
  authenticate,
1664
1716
  encrypt or authenticate,
1665
1717
  ctx.obj['extended_data_length'],
1718
+ ctx.obj['role_switch'],
1666
1719
  ).run()
1667
1720
 
1668
1721
  asyncio.run(run_central())
@@ -1679,10 +1732,11 @@ def peripheral(ctx, transport):
1679
1732
  async def run_peripheral():
1680
1733
  await Peripheral(
1681
1734
  transport,
1682
- ctx.obj['classic'],
1683
- ctx.obj['extended_data_length'],
1684
1735
  role_factory,
1685
1736
  mode_factory,
1737
+ ctx.obj['classic'],
1738
+ ctx.obj['extended_data_length'],
1739
+ ctx.obj['role_switch'],
1686
1740
  ).run()
1687
1741
 
1688
1742
  asyncio.run(run_peripheral())
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:
@@ -27,7 +27,7 @@ from bumble.colors import color
27
27
  from bumble.core import name_or_number
28
28
  from bumble.hci import (
29
29
  map_null_terminated_utf8_string,
30
- LeFeatureMask,
30
+ LeFeature,
31
31
  HCI_SUCCESS,
32
32
  HCI_VERSION_NAMES,
33
33
  LMP_VERSION_NAMES,
@@ -140,7 +140,7 @@ async def get_le_info(host: Host) -> None:
140
140
 
141
141
  print(color('LE Features:', 'yellow'))
142
142
  for feature in host.supported_le_features:
143
- print(LeFeatureMask(feature).name)
143
+ print(f' {LeFeature(feature).name}')
144
144
 
145
145
 
146
146
  # -----------------------------------------------------------------------------
@@ -224,7 +224,7 @@ async def async_main(latency_probes, transport):
224
224
  print()
225
225
  print(color('Supported Commands:', 'yellow'))
226
226
  for command in host.supported_commands:
227
- print(' ', HCI_Command.command_name(command))
227
+ print(f' {HCI_Command.command_name(command)}')
228
228
 
229
229
 
230
230
  # -----------------------------------------------------------------------------
@@ -0,0 +1,230 @@
1
+ # Copyright 2021-2022 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
+ # -----------------------------------------------------------------------------
16
+ # Imports
17
+ # -----------------------------------------------------------------------------
18
+ import asyncio
19
+ import os
20
+ import logging
21
+ from typing import Callable, Iterable, Optional
22
+
23
+ import click
24
+
25
+ from bumble.core import ProtocolError
26
+ from bumble.colors import color
27
+ from bumble.device import Device, Peer
28
+ from bumble.gatt import Service
29
+ from bumble.profiles.device_information_service import DeviceInformationServiceProxy
30
+ from bumble.profiles.battery_service import BatteryServiceProxy
31
+ from bumble.profiles.gap import GenericAccessServiceProxy
32
+ from bumble.profiles.tmap import TelephonyAndMediaAudioServiceProxy
33
+ from bumble.transport import open_transport_or_link
34
+
35
+
36
+ # -----------------------------------------------------------------------------
37
+ async def try_show(function: Callable, *args, **kwargs) -> None:
38
+ try:
39
+ await function(*args, **kwargs)
40
+ except ProtocolError as error:
41
+ print(color('ERROR:', 'red'), error)
42
+
43
+
44
+ # -----------------------------------------------------------------------------
45
+ def show_services(services: Iterable[Service]) -> None:
46
+ for service in services:
47
+ print(color(str(service), 'cyan'))
48
+
49
+ for characteristic in service.characteristics:
50
+ print(color(' ' + str(characteristic), 'magenta'))
51
+
52
+
53
+ # -----------------------------------------------------------------------------
54
+ async def show_gap_information(
55
+ gap_service: GenericAccessServiceProxy,
56
+ ):
57
+ print(color('### Generic Access Profile', 'yellow'))
58
+
59
+ if gap_service.device_name:
60
+ print(
61
+ color(' Device Name:', 'green'),
62
+ await gap_service.device_name.read_value(),
63
+ )
64
+
65
+ if gap_service.appearance:
66
+ print(
67
+ color(' Appearance: ', 'green'),
68
+ await gap_service.appearance.read_value(),
69
+ )
70
+
71
+ print()
72
+
73
+
74
+ # -----------------------------------------------------------------------------
75
+ async def show_device_information(
76
+ device_information_service: DeviceInformationServiceProxy,
77
+ ):
78
+ print(color('### Device Information', 'yellow'))
79
+
80
+ if device_information_service.manufacturer_name:
81
+ print(
82
+ color(' Manufacturer Name:', 'green'),
83
+ await device_information_service.manufacturer_name.read_value(),
84
+ )
85
+
86
+ if device_information_service.model_number:
87
+ print(
88
+ color(' Model Number: ', 'green'),
89
+ await device_information_service.model_number.read_value(),
90
+ )
91
+
92
+ if device_information_service.serial_number:
93
+ print(
94
+ color(' Serial Number: ', 'green'),
95
+ await device_information_service.serial_number.read_value(),
96
+ )
97
+
98
+ if device_information_service.firmware_revision:
99
+ print(
100
+ color(' Firmware Revision:', 'green'),
101
+ await device_information_service.firmware_revision.read_value(),
102
+ )
103
+
104
+ print()
105
+
106
+
107
+ # -----------------------------------------------------------------------------
108
+ async def show_battery_level(
109
+ battery_service: BatteryServiceProxy,
110
+ ):
111
+ print(color('### Battery Information', 'yellow'))
112
+
113
+ if battery_service.battery_level:
114
+ print(
115
+ color(' Battery Level:', 'green'),
116
+ await battery_service.battery_level.read_value(),
117
+ )
118
+
119
+ print()
120
+
121
+
122
+ # -----------------------------------------------------------------------------
123
+ async def show_tmas(
124
+ tmas: TelephonyAndMediaAudioServiceProxy,
125
+ ):
126
+ print(color('### Telephony And Media Audio Service', 'yellow'))
127
+
128
+ if tmas.role:
129
+ print(
130
+ color(' Role:', 'green'),
131
+ await tmas.role.read_value(),
132
+ )
133
+
134
+ print()
135
+
136
+
137
+ # -----------------------------------------------------------------------------
138
+ async def show_device_info(peer, done: Optional[asyncio.Future]) -> None:
139
+ try:
140
+ # Discover all services
141
+ print(color('### Discovering Services and Characteristics', 'magenta'))
142
+ await peer.discover_services()
143
+ for service in peer.services:
144
+ await service.discover_characteristics()
145
+
146
+ print(color('=== Services ===', 'yellow'))
147
+ show_services(peer.services)
148
+ print()
149
+
150
+ if gap_service := peer.create_service_proxy(GenericAccessServiceProxy):
151
+ await try_show(show_gap_information, gap_service)
152
+
153
+ if device_information_service := peer.create_service_proxy(
154
+ DeviceInformationServiceProxy
155
+ ):
156
+ await try_show(show_device_information, device_information_service)
157
+
158
+ if battery_service := peer.create_service_proxy(BatteryServiceProxy):
159
+ await try_show(show_battery_level, battery_service)
160
+
161
+ if tmas := peer.create_service_proxy(TelephonyAndMediaAudioServiceProxy):
162
+ await try_show(show_tmas, tmas)
163
+
164
+ if done is not None:
165
+ done.set_result(None)
166
+ except asyncio.CancelledError:
167
+ print(color('!!! Operation canceled', 'red'))
168
+
169
+
170
+ # -----------------------------------------------------------------------------
171
+ async def async_main(device_config, encrypt, transport, address_or_name):
172
+ async with await open_transport_or_link(transport) as (hci_source, hci_sink):
173
+
174
+ # Create a device
175
+ if device_config:
176
+ device = Device.from_config_file_with_hci(
177
+ device_config, hci_source, hci_sink
178
+ )
179
+ else:
180
+ device = Device.with_hci(
181
+ 'Bumble', 'F0:F1:F2:F3:F4:F5', hci_source, hci_sink
182
+ )
183
+ await device.power_on()
184
+
185
+ if address_or_name:
186
+ # Connect to the target peer
187
+ print(color('>>> Connecting...', 'green'))
188
+ connection = await device.connect(address_or_name)
189
+ print(color('>>> Connected', 'green'))
190
+
191
+ # Encrypt the connection if required
192
+ if encrypt:
193
+ print(color('+++ Encrypting connection...', 'blue'))
194
+ await connection.encrypt()
195
+ print(color('+++ Encryption established', 'blue'))
196
+
197
+ await show_device_info(Peer(connection), None)
198
+ else:
199
+ # Wait for a connection
200
+ done = asyncio.get_running_loop().create_future()
201
+ device.on(
202
+ 'connection',
203
+ lambda connection: asyncio.create_task(
204
+ show_device_info(Peer(connection), done)
205
+ ),
206
+ )
207
+ await device.start_advertising(auto_restart=True)
208
+
209
+ print(color('### Waiting for connection...', 'blue'))
210
+ await done
211
+
212
+
213
+ # -----------------------------------------------------------------------------
214
+ @click.command()
215
+ @click.option('--device-config', help='Device configuration', type=click.Path())
216
+ @click.option('--encrypt', help='Encrypt the connection', is_flag=True, default=False)
217
+ @click.argument('transport')
218
+ @click.argument('address-or-name', required=False)
219
+ def main(device_config, encrypt, transport, address_or_name):
220
+ """
221
+ Dump the GATT database on a remote device. If ADDRESS_OR_NAME is not specified,
222
+ wait for an incoming connection.
223
+ """
224
+ logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', 'INFO').upper())
225
+ asyncio.run(async_main(device_config, encrypt, transport, address_or_name))
226
+
227
+
228
+ # -----------------------------------------------------------------------------
229
+ if __name__ == '__main__':
230
+ main()
bumble/apps/gatt_dump.py CHANGED
@@ -75,11 +75,15 @@ async def async_main(device_config, encrypt, transport, address_or_name):
75
75
 
76
76
  if address_or_name:
77
77
  # Connect to the target peer
78
+ print(color('>>> Connecting...', 'green'))
78
79
  connection = await device.connect(address_or_name)
80
+ print(color('>>> Connected', 'green'))
79
81
 
80
82
  # Encrypt the connection if required
81
83
  if encrypt:
84
+ print(color('+++ Encrypting connection...', 'blue'))
82
85
  await connection.encrypt()
86
+ print(color('+++ Encryption established', 'blue'))
83
87
 
84
88
  await dump_gatt_db(Peer(connection), None)
85
89
  else: