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.
Files changed (47) hide show
  1. bumble/_version.py +9 -4
  2. bumble/apps/auracast.py +29 -35
  3. bumble/apps/bench.py +13 -10
  4. bumble/apps/console.py +19 -12
  5. bumble/apps/gg_bridge.py +1 -1
  6. bumble/att.py +61 -39
  7. bumble/controller.py +7 -8
  8. bumble/core.py +306 -159
  9. bumble/device.py +127 -82
  10. bumble/gatt.py +25 -228
  11. bumble/gatt_adapters.py +374 -0
  12. bumble/gatt_client.py +38 -31
  13. bumble/gatt_server.py +5 -5
  14. bumble/hci.py +76 -71
  15. bumble/host.py +19 -8
  16. bumble/l2cap.py +2 -2
  17. bumble/link.py +2 -2
  18. bumble/pairing.py +5 -5
  19. bumble/pandora/host.py +19 -23
  20. bumble/pandora/security.py +2 -3
  21. bumble/pandora/utils.py +2 -2
  22. bumble/profiles/aics.py +33 -23
  23. bumble/profiles/ancs.py +514 -0
  24. bumble/profiles/ascs.py +2 -1
  25. bumble/profiles/asha.py +11 -9
  26. bumble/profiles/bass.py +8 -5
  27. bumble/profiles/battery_service.py +13 -3
  28. bumble/profiles/device_information_service.py +16 -14
  29. bumble/profiles/gap.py +12 -8
  30. bumble/profiles/gatt_service.py +1 -0
  31. bumble/profiles/gmap.py +16 -11
  32. bumble/profiles/hap.py +8 -6
  33. bumble/profiles/heart_rate_service.py +20 -4
  34. bumble/profiles/mcp.py +11 -9
  35. bumble/profiles/pacs.py +37 -24
  36. bumble/profiles/tmap.py +6 -4
  37. bumble/profiles/vcs.py +6 -5
  38. bumble/profiles/vocs.py +49 -41
  39. bumble/smp.py +3 -3
  40. bumble/transport/usb.py +1 -3
  41. bumble/utils.py +10 -0
  42. {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/METADATA +3 -3
  43. {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/RECORD +47 -45
  44. {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/WHEEL +1 -1
  45. {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/LICENSE +0 -0
  46. {bumble-0.0.207.dist-info → bumble-0.0.209.dist-info}/entry_points.txt +0 -0
  47. {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, bumble.hci.OwnAddressType] = {
118
- host_pb2.PUBLIC: bumble.hci.OwnAddressType.PUBLIC,
119
- host_pb2.RANDOM: bumble.hci.OwnAddressType.RANDOM,
120
- host_pb2.RESOLVABLE_OR_PUBLIC: bumble.hci.OwnAddressType.RESOLVABLE_OR_PUBLIC,
121
- host_pb2.RESOLVABLE_OR_RANDOM: bumble.hci.OwnAddressType.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
- pending_connection: asyncio.Future[bumble.device.Connection] = (
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 == BT_PERIPHERAL_ROLE
382
+ and connection.role == Role.PERIPHERAL
384
383
  ):
385
- pending_connection.set_result(connection)
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 pending_connection
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 == BT_PERIPHERAL_ROLE
500
+ and connection.role == Role.PERIPHERAL
501
501
  ):
502
- pending_connection.set_result(connection)
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 pending_connection
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
@@ -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 == BT_PERIPHERAL_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, int] = {
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
- attribute_value: Optional[CharacteristicValue] = None
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.attribute_value is not None
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
- attribute_value: Optional[CharacteristicValue] = None
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.attribute_value
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.attribute_value = (
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.attribute_value = (
429
- self.audio_input_control_point_characteristic.value
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 = SerializableCharacteristicAdapter(
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 = SerializableCharacteristicAdapter(
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 = PackedCharacteristicAdapter(
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 = UTF8CharacteristicAdapter(
491
+ self.audio_input_description = UTF8CharacteristicProxyAdapter(
482
492
  service_proxy.get_required_characteristic_by_uuid(
483
493
  GATT_AUDIO_INPUT_DESCRIPTION_CHARACTERISTIC
484
494
  )