bumble 0.0.207__py3-none-any.whl → 0.0.209__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 +9 -4
- bumble/apps/auracast.py +29 -35
- bumble/apps/bench.py +13 -10
- bumble/apps/console.py +19 -12
- bumble/apps/gg_bridge.py +1 -1
- bumble/att.py +61 -39
- bumble/controller.py +7 -8
- bumble/core.py +306 -159
- bumble/device.py +127 -82
- bumble/gatt.py +25 -228
- bumble/gatt_adapters.py +374 -0
- bumble/gatt_client.py +38 -31
- bumble/gatt_server.py +5 -5
- bumble/hci.py +76 -71
- bumble/host.py +19 -8
- bumble/l2cap.py +2 -2
- bumble/link.py +2 -2
- bumble/pairing.py +5 -5
- bumble/pandora/host.py +19 -23
- bumble/pandora/security.py +2 -3
- bumble/pandora/utils.py +2 -2
- bumble/profiles/aics.py +33 -23
- bumble/profiles/ancs.py +514 -0
- bumble/profiles/ascs.py +2 -1
- bumble/profiles/asha.py +11 -9
- bumble/profiles/bass.py +8 -5
- bumble/profiles/battery_service.py +13 -3
- bumble/profiles/device_information_service.py +16 -14
- bumble/profiles/gap.py +12 -8
- bumble/profiles/gatt_service.py +1 -0
- bumble/profiles/gmap.py +16 -11
- bumble/profiles/hap.py +8 -6
- bumble/profiles/heart_rate_service.py +20 -4
- bumble/profiles/mcp.py +11 -9
- bumble/profiles/pacs.py +37 -24
- bumble/profiles/tmap.py +6 -4
- bumble/profiles/vcs.py +6 -5
- bumble/profiles/vocs.py +49 -41
- bumble/smp.py +3 -3
- bumble/transport/usb.py +1 -3
- bumble/utils.py +10 -0
- {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/METADATA +3 -3
- {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/RECORD +47 -45
- {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/WHEEL +1 -1
- {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/LICENSE +0 -0
- {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/top_level.txt +0 -0
bumble/pandora/host.py
CHANGED
|
@@ -25,7 +25,6 @@ from .config import Config
|
|
|
25
25
|
from bumble.core import (
|
|
26
26
|
BT_BR_EDR_TRANSPORT,
|
|
27
27
|
BT_LE_TRANSPORT,
|
|
28
|
-
BT_PERIPHERAL_ROLE,
|
|
29
28
|
UUID,
|
|
30
29
|
AdvertisingData,
|
|
31
30
|
Appearance,
|
|
@@ -47,6 +46,8 @@ from bumble.hci import (
|
|
|
47
46
|
HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR,
|
|
48
47
|
Address,
|
|
49
48
|
Phy,
|
|
49
|
+
Role,
|
|
50
|
+
OwnAddressType,
|
|
50
51
|
)
|
|
51
52
|
from google.protobuf import any_pb2 # pytype: disable=pyi-error
|
|
52
53
|
from google.protobuf import empty_pb2 # pytype: disable=pyi-error
|
|
@@ -114,11 +115,11 @@ SECONDARY_PHY_TO_BUMBLE_PHY_MAP: Dict[SecondaryPhy, Phy] = {
|
|
|
114
115
|
SECONDARY_CODED: Phy.LE_CODED,
|
|
115
116
|
}
|
|
116
117
|
|
|
117
|
-
OWN_ADDRESS_MAP: Dict[host_pb2.OwnAddressType,
|
|
118
|
-
host_pb2.PUBLIC:
|
|
119
|
-
host_pb2.RANDOM:
|
|
120
|
-
host_pb2.RESOLVABLE_OR_PUBLIC:
|
|
121
|
-
host_pb2.RESOLVABLE_OR_RANDOM:
|
|
118
|
+
OWN_ADDRESS_MAP: Dict[host_pb2.OwnAddressType, OwnAddressType] = {
|
|
119
|
+
host_pb2.PUBLIC: OwnAddressType.PUBLIC,
|
|
120
|
+
host_pb2.RANDOM: OwnAddressType.RANDOM,
|
|
121
|
+
host_pb2.RESOLVABLE_OR_PUBLIC: OwnAddressType.RESOLVABLE_OR_PUBLIC,
|
|
122
|
+
host_pb2.RESOLVABLE_OR_RANDOM: OwnAddressType.RESOLVABLE_OR_RANDOM,
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
|
|
@@ -250,7 +251,7 @@ class HostService(HostServicer):
|
|
|
250
251
|
connection = await self.device.connect(
|
|
251
252
|
address,
|
|
252
253
|
transport=BT_LE_TRANSPORT,
|
|
253
|
-
own_address_type=request.own_address_type,
|
|
254
|
+
own_address_type=OwnAddressType(request.own_address_type),
|
|
254
255
|
)
|
|
255
256
|
except ConnectionError as e:
|
|
256
257
|
if e.error_code == HCI_PAGE_TIMEOUT_ERROR:
|
|
@@ -371,18 +372,16 @@ class HostService(HostServicer):
|
|
|
371
372
|
scan_response_data=scan_response_data,
|
|
372
373
|
)
|
|
373
374
|
|
|
374
|
-
|
|
375
|
-
asyncio.get_running_loop().create_future()
|
|
376
|
-
)
|
|
375
|
+
connections: asyncio.Queue[bumble.device.Connection] = asyncio.Queue()
|
|
377
376
|
|
|
378
377
|
if request.connectable:
|
|
379
378
|
|
|
380
379
|
def on_connection(connection: bumble.device.Connection) -> None:
|
|
381
380
|
if (
|
|
382
381
|
connection.transport == BT_LE_TRANSPORT
|
|
383
|
-
and connection.role ==
|
|
382
|
+
and connection.role == Role.PERIPHERAL
|
|
384
383
|
):
|
|
385
|
-
|
|
384
|
+
connections.put_nowait(connection)
|
|
386
385
|
|
|
387
386
|
self.device.on('connection', on_connection)
|
|
388
387
|
|
|
@@ -397,8 +396,7 @@ class HostService(HostServicer):
|
|
|
397
396
|
await asyncio.sleep(1)
|
|
398
397
|
continue
|
|
399
398
|
|
|
400
|
-
connection = await
|
|
401
|
-
pending_connection = asyncio.get_running_loop().create_future()
|
|
399
|
+
connection = await connections.get()
|
|
402
400
|
|
|
403
401
|
cookie = any_pb2.Any(value=connection.handle.to_bytes(4, 'big'))
|
|
404
402
|
yield AdvertiseResponse(connection=Connection(cookie=cookie))
|
|
@@ -492,14 +490,16 @@ class HostService(HostServicer):
|
|
|
492
490
|
target = Address(target_bytes, Address.RANDOM_DEVICE_ADDRESS)
|
|
493
491
|
advertising_type = AdvertisingType.DIRECTED_CONNECTABLE_LOW_DUTY
|
|
494
492
|
|
|
493
|
+
connections: asyncio.Queue[bumble.device.Connection] = asyncio.Queue()
|
|
494
|
+
|
|
495
495
|
if request.connectable:
|
|
496
496
|
|
|
497
497
|
def on_connection(connection: bumble.device.Connection) -> None:
|
|
498
498
|
if (
|
|
499
499
|
connection.transport == BT_LE_TRANSPORT
|
|
500
|
-
and connection.role ==
|
|
500
|
+
and connection.role == Role.PERIPHERAL
|
|
501
501
|
):
|
|
502
|
-
|
|
502
|
+
connections.put_nowait(connection)
|
|
503
503
|
|
|
504
504
|
self.device.on('connection', on_connection)
|
|
505
505
|
|
|
@@ -510,19 +510,15 @@ class HostService(HostServicer):
|
|
|
510
510
|
await self.device.start_advertising(
|
|
511
511
|
target=target,
|
|
512
512
|
advertising_type=advertising_type,
|
|
513
|
-
own_address_type=request.own_address_type,
|
|
513
|
+
own_address_type=OwnAddressType(request.own_address_type),
|
|
514
514
|
)
|
|
515
515
|
|
|
516
516
|
if not request.connectable:
|
|
517
517
|
await asyncio.sleep(1)
|
|
518
518
|
continue
|
|
519
519
|
|
|
520
|
-
pending_connection: asyncio.Future[bumble.device.Connection] = (
|
|
521
|
-
asyncio.get_running_loop().create_future()
|
|
522
|
-
)
|
|
523
|
-
|
|
524
520
|
self.log.debug('Wait for LE connection...')
|
|
525
|
-
connection = await
|
|
521
|
+
connection = await connections.get()
|
|
526
522
|
|
|
527
523
|
self.log.debug(
|
|
528
524
|
f"Advertise: Connected to {connection.peer_address} (handle={connection.handle})"
|
|
@@ -563,7 +559,7 @@ class HostService(HostServicer):
|
|
|
563
559
|
await self.device.start_scanning(
|
|
564
560
|
legacy=request.legacy,
|
|
565
561
|
active=not request.passive,
|
|
566
|
-
own_address_type=request.own_address_type,
|
|
562
|
+
own_address_type=OwnAddressType(request.own_address_type),
|
|
567
563
|
scan_interval=(
|
|
568
564
|
int(request.interval)
|
|
569
565
|
if request.interval
|
bumble/pandora/security.py
CHANGED
|
@@ -24,11 +24,10 @@ from bumble import hci
|
|
|
24
24
|
from bumble.core import (
|
|
25
25
|
BT_BR_EDR_TRANSPORT,
|
|
26
26
|
BT_LE_TRANSPORT,
|
|
27
|
-
BT_PERIPHERAL_ROLE,
|
|
28
27
|
ProtocolError,
|
|
29
28
|
)
|
|
30
29
|
from bumble.device import Connection as BumbleConnection, Device
|
|
31
|
-
from bumble.hci import HCI_Error
|
|
30
|
+
from bumble.hci import HCI_Error, Role
|
|
32
31
|
from bumble.utils import EventWatcher
|
|
33
32
|
from bumble.pairing import PairingConfig, PairingDelegate as BasePairingDelegate
|
|
34
33
|
from google.protobuf import any_pb2 # pytype: disable=pyi-error
|
|
@@ -318,7 +317,7 @@ class SecurityService(SecurityServicer):
|
|
|
318
317
|
|
|
319
318
|
if (
|
|
320
319
|
connection.transport == BT_LE_TRANSPORT
|
|
321
|
-
and connection.role ==
|
|
320
|
+
and connection.role == Role.PERIPHERAL
|
|
322
321
|
):
|
|
323
322
|
connection.request_pairing()
|
|
324
323
|
else:
|
bumble/pandora/utils.py
CHANGED
|
@@ -20,11 +20,11 @@ import inspect
|
|
|
20
20
|
import logging
|
|
21
21
|
|
|
22
22
|
from bumble.device import Device
|
|
23
|
-
from bumble.hci import Address
|
|
23
|
+
from bumble.hci import Address, AddressType
|
|
24
24
|
from google.protobuf.message import Message # pytype: disable=pyi-error
|
|
25
25
|
from typing import Any, Dict, Generator, MutableMapping, Optional, Tuple
|
|
26
26
|
|
|
27
|
-
ADDRESS_TYPES: Dict[str,
|
|
27
|
+
ADDRESS_TYPES: Dict[str, AddressType] = {
|
|
28
28
|
"public": Address.PUBLIC_DEVICE_ADDRESS,
|
|
29
29
|
"random": Address.RANDOM_DEVICE_ADDRESS,
|
|
30
30
|
"public_identity": Address.PUBLIC_IDENTITY_ADDRESS,
|
bumble/profiles/aics.py
CHANGED
|
@@ -24,16 +24,13 @@ import struct
|
|
|
24
24
|
from dataclasses import dataclass
|
|
25
25
|
from typing import Optional
|
|
26
26
|
|
|
27
|
-
from bumble import gatt
|
|
28
27
|
from bumble.device import Connection
|
|
29
28
|
from bumble.att import ATT_Error
|
|
30
29
|
from bumble.gatt import (
|
|
30
|
+
Attribute,
|
|
31
31
|
Characteristic,
|
|
32
|
-
SerializableCharacteristicAdapter,
|
|
33
|
-
PackedCharacteristicAdapter,
|
|
34
32
|
TemplateService,
|
|
35
33
|
CharacteristicValue,
|
|
36
|
-
UTF8CharacteristicAdapter,
|
|
37
34
|
GATT_AUDIO_INPUT_CONTROL_SERVICE,
|
|
38
35
|
GATT_AUDIO_INPUT_STATE_CHARACTERISTIC,
|
|
39
36
|
GATT_GAIN_SETTINGS_ATTRIBUTE_CHARACTERISTIC,
|
|
@@ -42,6 +39,14 @@ from bumble.gatt import (
|
|
|
42
39
|
GATT_AUDIO_INPUT_CONTROL_POINT_CHARACTERISTIC,
|
|
43
40
|
GATT_AUDIO_INPUT_DESCRIPTION_CHARACTERISTIC,
|
|
44
41
|
)
|
|
42
|
+
from bumble.gatt_adapters import (
|
|
43
|
+
CharacteristicProxy,
|
|
44
|
+
PackedCharacteristicProxyAdapter,
|
|
45
|
+
SerializableCharacteristicAdapter,
|
|
46
|
+
SerializableCharacteristicProxyAdapter,
|
|
47
|
+
UTF8CharacteristicAdapter,
|
|
48
|
+
UTF8CharacteristicProxyAdapter,
|
|
49
|
+
)
|
|
45
50
|
from bumble.gatt_client import ProfileServiceProxy, ServiceProxy
|
|
46
51
|
from bumble.utils import OpenIntEnum
|
|
47
52
|
|
|
@@ -124,7 +129,7 @@ class AudioInputState:
|
|
|
124
129
|
mute: Mute = Mute.NOT_MUTED
|
|
125
130
|
gain_mode: GainMode = GainMode.MANUAL
|
|
126
131
|
change_counter: int = 0
|
|
127
|
-
|
|
132
|
+
attribute: Optional[Attribute] = None
|
|
128
133
|
|
|
129
134
|
def __bytes__(self) -> bytes:
|
|
130
135
|
return bytes(
|
|
@@ -151,10 +156,8 @@ class AudioInputState:
|
|
|
151
156
|
self.change_counter = (self.change_counter + 1) % (CHANGE_COUNTER_MAX_VALUE + 1)
|
|
152
157
|
|
|
153
158
|
async def notify_subscribers_via_connection(self, connection: Connection) -> None:
|
|
154
|
-
assert self.
|
|
155
|
-
await connection.device.notify_subscribers(
|
|
156
|
-
attribute=self.attribute_value, value=bytes(self)
|
|
157
|
-
)
|
|
159
|
+
assert self.attribute is not None
|
|
160
|
+
await connection.device.notify_subscribers(attribute=self.attribute)
|
|
158
161
|
|
|
159
162
|
|
|
160
163
|
@dataclass
|
|
@@ -315,24 +318,28 @@ class AudioInputDescription:
|
|
|
315
318
|
'''
|
|
316
319
|
|
|
317
320
|
audio_input_description: str = "Bluetooth"
|
|
318
|
-
|
|
321
|
+
attribute: Optional[Attribute] = None
|
|
319
322
|
|
|
320
323
|
def on_read(self, _connection: Optional[Connection]) -> str:
|
|
321
324
|
return self.audio_input_description
|
|
322
325
|
|
|
323
326
|
async def on_write(self, connection: Optional[Connection], value: str) -> None:
|
|
324
327
|
assert connection
|
|
325
|
-
assert self.
|
|
328
|
+
assert self.attribute
|
|
326
329
|
|
|
327
330
|
self.audio_input_description = value
|
|
328
|
-
await connection.device.notify_subscribers(
|
|
329
|
-
attribute=self.attribute_value, value=value
|
|
330
|
-
)
|
|
331
|
+
await connection.device.notify_subscribers(attribute=self.attribute)
|
|
331
332
|
|
|
332
333
|
|
|
333
334
|
class AICSService(TemplateService):
|
|
334
335
|
UUID = GATT_AUDIO_INPUT_CONTROL_SERVICE
|
|
335
336
|
|
|
337
|
+
audio_input_state_characteristic: Characteristic[AudioInputState]
|
|
338
|
+
audio_input_type_characteristic: Characteristic[bytes]
|
|
339
|
+
audio_input_status_characteristic: Characteristic[bytes]
|
|
340
|
+
audio_input_control_point_characteristic: Characteristic[bytes]
|
|
341
|
+
gain_settings_properties_characteristic: Characteristic[GainSettingsProperties]
|
|
342
|
+
|
|
336
343
|
def __init__(
|
|
337
344
|
self,
|
|
338
345
|
audio_input_state: Optional[AudioInputState] = None,
|
|
@@ -374,9 +381,7 @@ class AICSService(TemplateService):
|
|
|
374
381
|
),
|
|
375
382
|
AudioInputState,
|
|
376
383
|
)
|
|
377
|
-
self.audio_input_state.
|
|
378
|
-
self.audio_input_state_characteristic.value
|
|
379
|
-
)
|
|
384
|
+
self.audio_input_state.attribute = self.audio_input_state_characteristic
|
|
380
385
|
|
|
381
386
|
self.gain_settings_properties_characteristic = (
|
|
382
387
|
SerializableCharacteristicAdapter(
|
|
@@ -425,8 +430,8 @@ class AICSService(TemplateService):
|
|
|
425
430
|
),
|
|
426
431
|
)
|
|
427
432
|
)
|
|
428
|
-
self.audio_input_description.
|
|
429
|
-
self.audio_input_control_point_characteristic
|
|
433
|
+
self.audio_input_description.attribute = (
|
|
434
|
+
self.audio_input_control_point_characteristic
|
|
430
435
|
)
|
|
431
436
|
|
|
432
437
|
super().__init__(
|
|
@@ -448,24 +453,29 @@ class AICSService(TemplateService):
|
|
|
448
453
|
class AICSServiceProxy(ProfileServiceProxy):
|
|
449
454
|
SERVICE_CLASS = AICSService
|
|
450
455
|
|
|
456
|
+
audio_input_state: CharacteristicProxy[AudioInputState]
|
|
457
|
+
gain_settings_properties: CharacteristicProxy[GainSettingsProperties]
|
|
458
|
+
audio_input_status: CharacteristicProxy[int]
|
|
459
|
+
audio_input_control_point: CharacteristicProxy[bytes]
|
|
460
|
+
|
|
451
461
|
def __init__(self, service_proxy: ServiceProxy) -> None:
|
|
452
462
|
self.service_proxy = service_proxy
|
|
453
463
|
|
|
454
|
-
self.audio_input_state =
|
|
464
|
+
self.audio_input_state = SerializableCharacteristicProxyAdapter(
|
|
455
465
|
service_proxy.get_required_characteristic_by_uuid(
|
|
456
466
|
GATT_AUDIO_INPUT_STATE_CHARACTERISTIC
|
|
457
467
|
),
|
|
458
468
|
AudioInputState,
|
|
459
469
|
)
|
|
460
470
|
|
|
461
|
-
self.gain_settings_properties =
|
|
471
|
+
self.gain_settings_properties = SerializableCharacteristicProxyAdapter(
|
|
462
472
|
service_proxy.get_required_characteristic_by_uuid(
|
|
463
473
|
GATT_GAIN_SETTINGS_ATTRIBUTE_CHARACTERISTIC
|
|
464
474
|
),
|
|
465
475
|
GainSettingsProperties,
|
|
466
476
|
)
|
|
467
477
|
|
|
468
|
-
self.audio_input_status =
|
|
478
|
+
self.audio_input_status = PackedCharacteristicProxyAdapter(
|
|
469
479
|
service_proxy.get_required_characteristic_by_uuid(
|
|
470
480
|
GATT_AUDIO_INPUT_STATUS_CHARACTERISTIC
|
|
471
481
|
),
|
|
@@ -478,7 +488,7 @@ class AICSServiceProxy(ProfileServiceProxy):
|
|
|
478
488
|
)
|
|
479
489
|
)
|
|
480
490
|
|
|
481
|
-
self.audio_input_description =
|
|
491
|
+
self.audio_input_description = UTF8CharacteristicProxyAdapter(
|
|
482
492
|
service_proxy.get_required_characteristic_by_uuid(
|
|
483
493
|
GATT_AUDIO_INPUT_DESCRIPTION_CHARACTERISTIC
|
|
484
494
|
)
|