bumble 0.0.211__py3-none-any.whl → 0.0.213__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 +2 -2
- bumble/a2dp.py +6 -0
- bumble/apps/README.md +0 -3
- bumble/apps/auracast.py +11 -9
- bumble/apps/bench.py +482 -31
- bumble/apps/console.py +5 -5
- bumble/apps/controller_info.py +47 -10
- bumble/apps/controller_loopback.py +7 -3
- bumble/apps/controllers.py +2 -2
- bumble/apps/device_info.py +2 -2
- bumble/apps/gatt_dump.py +2 -2
- bumble/apps/gg_bridge.py +2 -2
- bumble/apps/hci_bridge.py +2 -2
- bumble/apps/l2cap_bridge.py +2 -2
- bumble/apps/lea_unicast/app.py +6 -1
- bumble/apps/pair.py +204 -43
- bumble/apps/pandora_server.py +2 -2
- bumble/apps/rfcomm_bridge.py +1 -1
- bumble/apps/scan.py +2 -2
- bumble/apps/show.py +4 -2
- bumble/apps/speaker/speaker.html +1 -0
- bumble/apps/speaker/speaker.js +113 -62
- bumble/apps/speaker/speaker.py +126 -18
- bumble/at.py +4 -4
- bumble/att.py +15 -18
- bumble/avc.py +7 -7
- bumble/avctp.py +5 -5
- bumble/avdtp.py +138 -88
- bumble/avrcp.py +52 -58
- bumble/colors.py +2 -2
- bumble/controller.py +84 -23
- bumble/core.py +13 -7
- bumble/{crypto.py → crypto/__init__.py} +11 -95
- bumble/crypto/builtin.py +652 -0
- bumble/crypto/cryptography.py +84 -0
- bumble/device.py +688 -345
- bumble/drivers/__init__.py +2 -2
- bumble/drivers/common.py +0 -2
- bumble/drivers/intel.py +40 -40
- bumble/drivers/rtk.py +28 -35
- bumble/gatt.py +7 -9
- bumble/gatt_adapters.py +4 -5
- bumble/gatt_client.py +31 -34
- bumble/gatt_server.py +15 -17
- bumble/hci.py +2635 -2878
- bumble/helpers.py +4 -5
- bumble/hfp.py +76 -57
- bumble/hid.py +24 -12
- bumble/host.py +117 -34
- bumble/keys.py +68 -52
- bumble/l2cap.py +329 -403
- bumble/link.py +6 -270
- bumble/pairing.py +23 -20
- bumble/pandora/__init__.py +1 -1
- bumble/pandora/config.py +2 -2
- bumble/pandora/device.py +6 -6
- bumble/pandora/host.py +38 -39
- bumble/pandora/l2cap.py +4 -4
- bumble/pandora/security.py +73 -57
- bumble/pandora/utils.py +3 -3
- bumble/profiles/aics.py +3 -5
- bumble/profiles/ancs.py +3 -1
- bumble/profiles/ascs.py +143 -136
- bumble/profiles/asha.py +13 -8
- bumble/profiles/bap.py +3 -4
- bumble/profiles/csip.py +3 -5
- bumble/profiles/device_information_service.py +2 -2
- bumble/profiles/gap.py +2 -2
- bumble/profiles/gatt_service.py +1 -3
- bumble/profiles/hap.py +42 -58
- bumble/profiles/le_audio.py +4 -4
- bumble/profiles/mcp.py +16 -13
- bumble/profiles/vcs.py +8 -10
- bumble/profiles/vocs.py +6 -9
- bumble/rfcomm.py +27 -18
- bumble/rtp.py +1 -2
- bumble/sdp.py +2 -2
- bumble/smp.py +71 -69
- bumble/tools/rtk_util.py +2 -2
- bumble/transport/__init__.py +2 -16
- bumble/transport/android_netsim.py +5 -5
- bumble/transport/common.py +4 -4
- bumble/transport/pyusb.py +2 -2
- bumble/utils.py +2 -5
- bumble/vendor/android/hci.py +118 -200
- bumble/vendor/zephyr/hci.py +32 -27
- {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/METADATA +5 -5
- {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/RECORD +92 -93
- {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/WHEEL +1 -1
- {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/entry_points.txt +0 -1
- bumble/apps/link_relay/__init__.py +0 -0
- bumble/apps/link_relay/link_relay.py +0 -289
- bumble/apps/link_relay/logging.yml +0 -21
- {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/licenses/LICENSE +0 -0
- {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/top_level.txt +0 -0
bumble/apps/pair.py
CHANGED
|
@@ -18,28 +18,35 @@
|
|
|
18
18
|
import asyncio
|
|
19
19
|
import os
|
|
20
20
|
import logging
|
|
21
|
+
import struct
|
|
22
|
+
|
|
21
23
|
import click
|
|
22
24
|
from prompt_toolkit.shortcuts import PromptSession
|
|
23
25
|
|
|
26
|
+
from bumble.a2dp import make_audio_sink_service_sdp_records
|
|
24
27
|
from bumble.colors import color
|
|
25
28
|
from bumble.device import Device, Peer
|
|
26
|
-
from bumble.transport import
|
|
29
|
+
from bumble.transport import open_transport
|
|
27
30
|
from bumble.pairing import OobData, PairingDelegate, PairingConfig
|
|
28
31
|
from bumble.smp import OobContext, OobLegacyContext
|
|
29
32
|
from bumble.smp import error_name as smp_error_name
|
|
30
33
|
from bumble.keys import JsonKeyStore
|
|
31
34
|
from bumble.core import (
|
|
32
35
|
AdvertisingData,
|
|
36
|
+
Appearance,
|
|
33
37
|
ProtocolError,
|
|
34
38
|
PhysicalTransport,
|
|
39
|
+
UUID,
|
|
35
40
|
)
|
|
36
41
|
from bumble.gatt import (
|
|
37
42
|
GATT_DEVICE_NAME_CHARACTERISTIC,
|
|
38
43
|
GATT_GENERIC_ACCESS_SERVICE,
|
|
44
|
+
GATT_HEART_RATE_SERVICE,
|
|
45
|
+
GATT_HEART_RATE_MEASUREMENT_CHARACTERISTIC,
|
|
39
46
|
Service,
|
|
40
47
|
Characteristic,
|
|
41
|
-
CharacteristicValue,
|
|
42
48
|
)
|
|
49
|
+
from bumble.hci import OwnAddressType
|
|
43
50
|
from bumble.att import (
|
|
44
51
|
ATT_Error,
|
|
45
52
|
ATT_INSUFFICIENT_AUTHENTICATION_ERROR,
|
|
@@ -62,7 +69,7 @@ class Waiter:
|
|
|
62
69
|
self.linger = linger
|
|
63
70
|
|
|
64
71
|
def terminate(self):
|
|
65
|
-
if not self.linger:
|
|
72
|
+
if not self.linger and not self.done.done:
|
|
66
73
|
self.done.set_result(None)
|
|
67
74
|
|
|
68
75
|
async def wait_until_terminated(self):
|
|
@@ -193,7 +200,7 @@ class Delegate(PairingDelegate):
|
|
|
193
200
|
|
|
194
201
|
# -----------------------------------------------------------------------------
|
|
195
202
|
async def get_peer_name(peer, mode):
|
|
196
|
-
if
|
|
203
|
+
if peer.connection.transport == PhysicalTransport.BR_EDR:
|
|
197
204
|
return await peer.request_name()
|
|
198
205
|
|
|
199
206
|
# Try to get the peer name from GATT
|
|
@@ -225,13 +232,14 @@ def read_with_error(connection):
|
|
|
225
232
|
raise ATT_Error(ATT_INSUFFICIENT_AUTHENTICATION_ERROR)
|
|
226
233
|
|
|
227
234
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
+
# -----------------------------------------------------------------------------
|
|
236
|
+
def sdp_records():
|
|
237
|
+
service_record_handle = 0x00010001
|
|
238
|
+
return {
|
|
239
|
+
service_record_handle: make_audio_sink_service_sdp_records(
|
|
240
|
+
service_record_handle
|
|
241
|
+
)
|
|
242
|
+
}
|
|
235
243
|
|
|
236
244
|
|
|
237
245
|
# -----------------------------------------------------------------------------
|
|
@@ -239,15 +247,19 @@ def on_connection(connection, request):
|
|
|
239
247
|
print(color(f'<<< Connection: {connection}', 'green'))
|
|
240
248
|
|
|
241
249
|
# Listen for pairing events
|
|
242
|
-
connection.on(
|
|
243
|
-
connection.on(
|
|
250
|
+
connection.on(connection.EVENT_PAIRING_START, on_pairing_start)
|
|
251
|
+
connection.on(connection.EVENT_PAIRING, lambda keys: on_pairing(connection, keys))
|
|
244
252
|
connection.on(
|
|
245
|
-
|
|
253
|
+
connection.EVENT_CLASSIC_PAIRING, lambda: on_classic_pairing(connection)
|
|
254
|
+
)
|
|
255
|
+
connection.on(
|
|
256
|
+
connection.EVENT_PAIRING_FAILURE,
|
|
257
|
+
lambda reason: on_pairing_failure(connection, reason),
|
|
246
258
|
)
|
|
247
259
|
|
|
248
260
|
# Listen for encryption changes
|
|
249
261
|
connection.on(
|
|
250
|
-
|
|
262
|
+
connection.EVENT_CONNECTION_ENCRYPTION_CHANGE,
|
|
251
263
|
lambda: on_connection_encryption_change(connection),
|
|
252
264
|
)
|
|
253
265
|
|
|
@@ -288,6 +300,20 @@ async def on_pairing(connection, keys):
|
|
|
288
300
|
Waiter.instance.terminate()
|
|
289
301
|
|
|
290
302
|
|
|
303
|
+
# -----------------------------------------------------------------------------
|
|
304
|
+
@AsyncRunner.run_in_task()
|
|
305
|
+
async def on_classic_pairing(connection):
|
|
306
|
+
print(color('***-----------------------------------', 'cyan'))
|
|
307
|
+
print(
|
|
308
|
+
color(
|
|
309
|
+
f'*** Paired [Classic]! (peer identity={connection.peer_address})', 'cyan'
|
|
310
|
+
)
|
|
311
|
+
)
|
|
312
|
+
print(color('***-----------------------------------', 'cyan'))
|
|
313
|
+
await asyncio.sleep(POST_PAIRING_DELAY)
|
|
314
|
+
Waiter.instance.terminate()
|
|
315
|
+
|
|
316
|
+
|
|
291
317
|
# -----------------------------------------------------------------------------
|
|
292
318
|
@AsyncRunner.run_in_task()
|
|
293
319
|
async def on_pairing_failure(connection, reason):
|
|
@@ -305,6 +331,7 @@ async def pair(
|
|
|
305
331
|
mitm,
|
|
306
332
|
bond,
|
|
307
333
|
ctkd,
|
|
334
|
+
advertising_address,
|
|
308
335
|
identity_address,
|
|
309
336
|
linger,
|
|
310
337
|
io,
|
|
@@ -313,6 +340,8 @@ async def pair(
|
|
|
313
340
|
request,
|
|
314
341
|
print_keys,
|
|
315
342
|
keystore_file,
|
|
343
|
+
advertise_service_uuids,
|
|
344
|
+
advertise_appearance,
|
|
316
345
|
device_config,
|
|
317
346
|
hci_transport,
|
|
318
347
|
address_or_name,
|
|
@@ -320,7 +349,7 @@ async def pair(
|
|
|
320
349
|
Waiter.instance = Waiter(linger=linger)
|
|
321
350
|
|
|
322
351
|
print('<<< connecting to HCI...')
|
|
323
|
-
async with await
|
|
352
|
+
async with await open_transport(hci_transport) as (hci_source, hci_sink):
|
|
324
353
|
print('<<< connected')
|
|
325
354
|
|
|
326
355
|
# Create a device to manage the host
|
|
@@ -328,29 +357,33 @@ async def pair(
|
|
|
328
357
|
|
|
329
358
|
# Expose a GATT characteristic that can be used to trigger pairing by
|
|
330
359
|
# responding with an authentication error when read
|
|
331
|
-
if mode
|
|
332
|
-
device.le_enabled = True
|
|
360
|
+
if mode in ('le', 'dual'):
|
|
333
361
|
device.add_service(
|
|
334
362
|
Service(
|
|
335
|
-
|
|
363
|
+
GATT_HEART_RATE_SERVICE,
|
|
336
364
|
[
|
|
337
365
|
Characteristic(
|
|
338
|
-
|
|
339
|
-
Characteristic.Properties.READ
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
CharacteristicValue(
|
|
343
|
-
read=read_with_error, write=write_with_error
|
|
344
|
-
),
|
|
366
|
+
GATT_HEART_RATE_MEASUREMENT_CHARACTERISTIC,
|
|
367
|
+
Characteristic.Properties.READ,
|
|
368
|
+
Characteristic.READ_REQUIRES_AUTHENTICATION,
|
|
369
|
+
bytes(1),
|
|
345
370
|
)
|
|
346
371
|
],
|
|
347
372
|
)
|
|
348
373
|
)
|
|
349
374
|
|
|
350
|
-
#
|
|
351
|
-
if mode
|
|
375
|
+
# LE and Classic support
|
|
376
|
+
if mode in ('classic', 'dual'):
|
|
352
377
|
device.classic_enabled = True
|
|
353
378
|
device.classic_smp_enabled = ctkd
|
|
379
|
+
if mode in ('le', 'dual'):
|
|
380
|
+
device.le_enabled = True
|
|
381
|
+
if mode == 'dual':
|
|
382
|
+
device.le_simultaneous_enabled = True
|
|
383
|
+
|
|
384
|
+
# Setup SDP
|
|
385
|
+
if mode in ('classic', 'dual'):
|
|
386
|
+
device.sdp_service_records = sdp_records()
|
|
354
387
|
|
|
355
388
|
# Get things going
|
|
356
389
|
await device.power_on()
|
|
@@ -369,14 +402,19 @@ async def pair(
|
|
|
369
402
|
# Create an OOB context if needed
|
|
370
403
|
if oob:
|
|
371
404
|
our_oob_context = OobContext()
|
|
372
|
-
|
|
373
|
-
None
|
|
374
|
-
|
|
375
|
-
|
|
405
|
+
if oob == '-':
|
|
406
|
+
shared_data = None
|
|
407
|
+
legacy_context = OobLegacyContext()
|
|
408
|
+
else:
|
|
409
|
+
oob_data = OobData.from_ad(
|
|
376
410
|
AdvertisingData.from_bytes(bytes.fromhex(oob))
|
|
377
|
-
)
|
|
378
|
-
|
|
379
|
-
|
|
411
|
+
)
|
|
412
|
+
shared_data = oob_data.shared_data
|
|
413
|
+
legacy_context = oob_data.legacy_context
|
|
414
|
+
if legacy_context is None and not sc:
|
|
415
|
+
print(color('OOB pairing in legacy mode requires TK', 'red'))
|
|
416
|
+
return
|
|
417
|
+
|
|
380
418
|
oob_contexts = PairingConfig.OobConfig(
|
|
381
419
|
our_context=our_oob_context,
|
|
382
420
|
peer_data=shared_data,
|
|
@@ -386,7 +424,9 @@ async def pair(
|
|
|
386
424
|
print(color('@@@ OOB Data:', 'yellow'))
|
|
387
425
|
if shared_data is None:
|
|
388
426
|
oob_data = OobData(
|
|
389
|
-
address=device.random_address,
|
|
427
|
+
address=device.random_address,
|
|
428
|
+
shared_data=our_oob_context.share(),
|
|
429
|
+
legacy_context=(None if sc else legacy_context),
|
|
390
430
|
)
|
|
391
431
|
print(
|
|
392
432
|
color(
|
|
@@ -394,7 +434,8 @@ async def pair(
|
|
|
394
434
|
'yellow',
|
|
395
435
|
)
|
|
396
436
|
)
|
|
397
|
-
|
|
437
|
+
if legacy_context:
|
|
438
|
+
print(color(f'@@@ TK={legacy_context.tk.hex()}', 'yellow'))
|
|
398
439
|
print(color('@@@-----------------------------------', 'yellow'))
|
|
399
440
|
else:
|
|
400
441
|
oob_contexts = None
|
|
@@ -436,13 +477,109 @@ async def pair(
|
|
|
436
477
|
print(color(f'Pairing failed: {error}', 'red'))
|
|
437
478
|
|
|
438
479
|
else:
|
|
439
|
-
if mode
|
|
440
|
-
# Advertise so that peers can find us and connect
|
|
441
|
-
|
|
442
|
-
|
|
480
|
+
if mode in ('le', 'dual'):
|
|
481
|
+
# Advertise so that peers can find us and connect.
|
|
482
|
+
# Include the heart rate service UUID in the advertisement data
|
|
483
|
+
# so that devices like iPhones can show this device in their
|
|
484
|
+
# Bluetooth selector.
|
|
485
|
+
service_uuids_16 = []
|
|
486
|
+
service_uuids_32 = []
|
|
487
|
+
service_uuids_128 = []
|
|
488
|
+
if advertise_service_uuids:
|
|
489
|
+
for uuid in advertise_service_uuids:
|
|
490
|
+
uuid = uuid.replace("-", "")
|
|
491
|
+
if len(uuid) == 4:
|
|
492
|
+
service_uuids_16.append(UUID(uuid))
|
|
493
|
+
elif len(uuid) == 8:
|
|
494
|
+
service_uuids_32.append(UUID(uuid))
|
|
495
|
+
elif len(uuid) == 32:
|
|
496
|
+
service_uuids_128.append(UUID(uuid))
|
|
497
|
+
else:
|
|
498
|
+
print(color('Invalid UUID format', 'red'))
|
|
499
|
+
return
|
|
500
|
+
else:
|
|
501
|
+
service_uuids_16.append(GATT_HEART_RATE_SERVICE)
|
|
502
|
+
|
|
503
|
+
flags = AdvertisingData.Flags.LE_LIMITED_DISCOVERABLE_MODE
|
|
504
|
+
if mode == 'le':
|
|
505
|
+
flags |= AdvertisingData.Flags.BR_EDR_NOT_SUPPORTED
|
|
506
|
+
if mode == 'dual':
|
|
507
|
+
flags |= AdvertisingData.Flags.SIMULTANEOUS_LE_BR_EDR_CAPABLE
|
|
508
|
+
|
|
509
|
+
ad_structs = [
|
|
510
|
+
(
|
|
511
|
+
AdvertisingData.FLAGS,
|
|
512
|
+
bytes([flags]),
|
|
513
|
+
),
|
|
514
|
+
(AdvertisingData.COMPLETE_LOCAL_NAME, 'Bumble'.encode()),
|
|
515
|
+
]
|
|
516
|
+
if service_uuids_16:
|
|
517
|
+
ad_structs.append(
|
|
518
|
+
(
|
|
519
|
+
AdvertisingData.INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS,
|
|
520
|
+
b"".join(bytes(uuid) for uuid in service_uuids_16),
|
|
521
|
+
)
|
|
522
|
+
)
|
|
523
|
+
if service_uuids_32:
|
|
524
|
+
ad_structs.append(
|
|
525
|
+
(
|
|
526
|
+
AdvertisingData.INCOMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS,
|
|
527
|
+
b"".join(bytes(uuid) for uuid in service_uuids_32),
|
|
528
|
+
)
|
|
529
|
+
)
|
|
530
|
+
if service_uuids_128:
|
|
531
|
+
ad_structs.append(
|
|
532
|
+
(
|
|
533
|
+
AdvertisingData.INCOMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS,
|
|
534
|
+
b"".join(bytes(uuid) for uuid in service_uuids_128),
|
|
535
|
+
)
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
if advertise_appearance:
|
|
539
|
+
advertise_appearance = advertise_appearance.upper()
|
|
540
|
+
try:
|
|
541
|
+
advertise_appearance_int = int(advertise_appearance)
|
|
542
|
+
except ValueError:
|
|
543
|
+
category, subcategory = advertise_appearance.split('/')
|
|
544
|
+
try:
|
|
545
|
+
category_enum = Appearance.Category[category]
|
|
546
|
+
except ValueError:
|
|
547
|
+
print(
|
|
548
|
+
color(f'Invalid appearance category {category}', 'red')
|
|
549
|
+
)
|
|
550
|
+
return
|
|
551
|
+
subcategory_class = Appearance.SUBCATEGORY_CLASSES[
|
|
552
|
+
category_enum
|
|
553
|
+
]
|
|
554
|
+
try:
|
|
555
|
+
subcategory_enum = subcategory_class[subcategory]
|
|
556
|
+
except ValueError:
|
|
557
|
+
print(color(f'Invalid subcategory {subcategory}', 'red'))
|
|
558
|
+
return
|
|
559
|
+
advertise_appearance_int = int(
|
|
560
|
+
Appearance(category_enum, subcategory_enum)
|
|
561
|
+
)
|
|
562
|
+
ad_structs.append(
|
|
563
|
+
(
|
|
564
|
+
AdvertisingData.APPEARANCE,
|
|
565
|
+
struct.pack('<H', advertise_appearance_int),
|
|
566
|
+
)
|
|
567
|
+
)
|
|
568
|
+
device.advertising_data = bytes(AdvertisingData(ad_structs))
|
|
569
|
+
await device.start_advertising(
|
|
570
|
+
auto_restart=True,
|
|
571
|
+
own_address_type=(
|
|
572
|
+
OwnAddressType.PUBLIC
|
|
573
|
+
if advertising_address == 'public'
|
|
574
|
+
else OwnAddressType.RANDOM
|
|
575
|
+
),
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
if mode in ('classic', 'dual'):
|
|
443
579
|
# Become discoverable and connectable
|
|
444
580
|
await device.set_discoverable(True)
|
|
445
581
|
await device.set_connectable(True)
|
|
582
|
+
print(color('Ready for connections on', 'blue'), device.public_address)
|
|
446
583
|
|
|
447
584
|
# Run until the user asks to exit
|
|
448
585
|
await Waiter.instance.wait_until_terminated()
|
|
@@ -462,7 +599,10 @@ class LogHandler(logging.Handler):
|
|
|
462
599
|
# -----------------------------------------------------------------------------
|
|
463
600
|
@click.command()
|
|
464
601
|
@click.option(
|
|
465
|
-
'--mode',
|
|
602
|
+
'--mode',
|
|
603
|
+
type=click.Choice(['le', 'classic', 'dual']),
|
|
604
|
+
default='le',
|
|
605
|
+
show_default=True,
|
|
466
606
|
)
|
|
467
607
|
@click.option(
|
|
468
608
|
'--sc',
|
|
@@ -484,6 +624,10 @@ class LogHandler(logging.Handler):
|
|
|
484
624
|
help='Enable CTKD',
|
|
485
625
|
show_default=True,
|
|
486
626
|
)
|
|
627
|
+
@click.option(
|
|
628
|
+
'--advertising-address',
|
|
629
|
+
type=click.Choice(['random', 'public']),
|
|
630
|
+
)
|
|
487
631
|
@click.option(
|
|
488
632
|
'--identity-address',
|
|
489
633
|
type=click.Choice(['random', 'public']),
|
|
@@ -512,9 +656,20 @@ class LogHandler(logging.Handler):
|
|
|
512
656
|
@click.option('--print-keys', is_flag=True, help='Print the bond keys before pairing')
|
|
513
657
|
@click.option(
|
|
514
658
|
'--keystore-file',
|
|
515
|
-
metavar='
|
|
659
|
+
metavar='FILENAME',
|
|
516
660
|
help='File in which to store the pairing keys',
|
|
517
661
|
)
|
|
662
|
+
@click.option(
|
|
663
|
+
'--advertise-service-uuid',
|
|
664
|
+
metavar="UUID",
|
|
665
|
+
multiple=True,
|
|
666
|
+
help="Advertise a GATT service UUID (may be specified more than once)",
|
|
667
|
+
)
|
|
668
|
+
@click.option(
|
|
669
|
+
'--advertise-appearance',
|
|
670
|
+
metavar='APPEARANCE',
|
|
671
|
+
help='Advertise an Appearance ID (int value or string)',
|
|
672
|
+
)
|
|
518
673
|
@click.argument('device-config')
|
|
519
674
|
@click.argument('hci_transport')
|
|
520
675
|
@click.argument('address-or-name', required=False)
|
|
@@ -524,6 +679,7 @@ def main(
|
|
|
524
679
|
mitm,
|
|
525
680
|
bond,
|
|
526
681
|
ctkd,
|
|
682
|
+
advertising_address,
|
|
527
683
|
identity_address,
|
|
528
684
|
linger,
|
|
529
685
|
io,
|
|
@@ -532,6 +688,8 @@ def main(
|
|
|
532
688
|
request,
|
|
533
689
|
print_keys,
|
|
534
690
|
keystore_file,
|
|
691
|
+
advertise_service_uuid,
|
|
692
|
+
advertise_appearance,
|
|
535
693
|
device_config,
|
|
536
694
|
hci_transport,
|
|
537
695
|
address_or_name,
|
|
@@ -550,6 +708,7 @@ def main(
|
|
|
550
708
|
mitm,
|
|
551
709
|
bond,
|
|
552
710
|
ctkd,
|
|
711
|
+
advertising_address,
|
|
553
712
|
identity_address,
|
|
554
713
|
linger,
|
|
555
714
|
io,
|
|
@@ -558,6 +717,8 @@ def main(
|
|
|
558
717
|
request,
|
|
559
718
|
print_keys,
|
|
560
719
|
keystore_file,
|
|
720
|
+
advertise_service_uuid,
|
|
721
|
+
advertise_appearance,
|
|
561
722
|
device_config,
|
|
562
723
|
hci_transport,
|
|
563
724
|
address_or_name,
|
bumble/apps/pandora_server.py
CHANGED
|
@@ -4,7 +4,7 @@ import logging
|
|
|
4
4
|
import json
|
|
5
5
|
|
|
6
6
|
from bumble.pandora import PandoraDevice, Config, serve
|
|
7
|
-
from typing import
|
|
7
|
+
from typing import Any
|
|
8
8
|
|
|
9
9
|
BUMBLE_SERVER_GRPC_PORT = 7999
|
|
10
10
|
ROOTCANAL_PORT_CUTTLEFISH = 7300
|
|
@@ -39,7 +39,7 @@ def main(grpc_port: int, rootcanal_port: int, transport: str, config: str) -> No
|
|
|
39
39
|
asyncio.run(serve(device, config=server_config, port=grpc_port))
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
def retrieve_config(config: str) ->
|
|
42
|
+
def retrieve_config(config: str) -> dict[str, Any]:
|
|
43
43
|
if not config:
|
|
44
44
|
return {}
|
|
45
45
|
|
bumble/apps/rfcomm_bridge.py
CHANGED
|
@@ -406,7 +406,7 @@ class ClientBridge:
|
|
|
406
406
|
# -----------------------------------------------------------------------------
|
|
407
407
|
async def run(device_config, hci_transport, bridge):
|
|
408
408
|
print("<<< connecting to HCI...")
|
|
409
|
-
async with await transport.
|
|
409
|
+
async with await transport.open_transport(hci_transport) as (
|
|
410
410
|
hci_source,
|
|
411
411
|
hci_sink,
|
|
412
412
|
):
|
bumble/apps/scan.py
CHANGED
|
@@ -22,7 +22,7 @@ import click
|
|
|
22
22
|
|
|
23
23
|
from bumble.colors import color
|
|
24
24
|
from bumble.device import Device
|
|
25
|
-
from bumble.transport import
|
|
25
|
+
from bumble.transport import open_transport
|
|
26
26
|
from bumble.keys import JsonKeyStore
|
|
27
27
|
from bumble.smp import AddressResolver
|
|
28
28
|
from bumble.device import Advertisement
|
|
@@ -127,7 +127,7 @@ async def scan(
|
|
|
127
127
|
transport,
|
|
128
128
|
):
|
|
129
129
|
print('<<< connecting to HCI...')
|
|
130
|
-
async with await
|
|
130
|
+
async with await open_transport(transport) as (hci_source, hci_sink):
|
|
131
131
|
print('<<< connected')
|
|
132
132
|
|
|
133
133
|
if device_config:
|
bumble/apps/show.py
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
# Imports
|
|
17
17
|
# -----------------------------------------------------------------------------
|
|
18
18
|
import datetime
|
|
19
|
+
import importlib
|
|
19
20
|
import logging
|
|
20
21
|
import os
|
|
21
22
|
import struct
|
|
@@ -154,9 +155,10 @@ class Printer:
|
|
|
154
155
|
def main(format, vendor, filename):
|
|
155
156
|
for vendor_name in vendor:
|
|
156
157
|
if vendor_name == 'android':
|
|
157
|
-
|
|
158
|
+
# Prevent being deleted by linter.
|
|
159
|
+
importlib.import_module('bumble.vendor.android.hci')
|
|
158
160
|
elif vendor_name == 'zephyr':
|
|
159
|
-
|
|
161
|
+
importlib.import_module('bumble.vendor.zephyr.hci')
|
|
160
162
|
|
|
161
163
|
input = open(filename, 'rb')
|
|
162
164
|
if format == 'h4':
|
bumble/apps/speaker/speaker.html
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
<tr><td>Codec</td><td><span id="codecText"></span></td></tr>
|
|
16
16
|
<tr><td>Packets</td><td><span id="packetsReceivedText"></span></td></tr>
|
|
17
17
|
<tr><td>Bytes</td><td><span id="bytesReceivedText"></span></td></tr>
|
|
18
|
+
<tr><td>Bitrate</td><td><span id="bitrate"></span></td></tr>
|
|
18
19
|
</table>
|
|
19
20
|
</td>
|
|
20
21
|
<td>
|