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/gatt_client.py CHANGED
@@ -29,16 +29,18 @@ import logging
29
29
  import struct
30
30
  from datetime import datetime
31
31
  from typing import (
32
+ Any,
33
+ Callable,
34
+ Dict,
35
+ Generic,
36
+ Iterable,
32
37
  List,
33
38
  Optional,
34
- Dict,
39
+ Set,
35
40
  Tuple,
36
- Callable,
37
41
  Union,
38
- Any,
39
- Iterable,
40
42
  Type,
41
- Set,
43
+ TypeVar,
42
44
  TYPE_CHECKING,
43
45
  )
44
46
 
@@ -82,9 +84,14 @@ from .gatt import (
82
84
  TemplateService,
83
85
  )
84
86
 
87
+ # -----------------------------------------------------------------------------
88
+ # Typing
89
+ # -----------------------------------------------------------------------------
85
90
  if TYPE_CHECKING:
86
91
  from bumble.device import Connection
87
92
 
93
+ _T = TypeVar('_T')
94
+
88
95
  # -----------------------------------------------------------------------------
89
96
  # Logging
90
97
  # -----------------------------------------------------------------------------
@@ -110,7 +117,7 @@ def show_services(services: Iterable[ServiceProxy]) -> None:
110
117
  # -----------------------------------------------------------------------------
111
118
  # Proxies
112
119
  # -----------------------------------------------------------------------------
113
- class AttributeProxy(EventEmitter):
120
+ class AttributeProxy(EventEmitter, Generic[_T]):
114
121
  def __init__(
115
122
  self, client: Client, handle: int, end_group_handle: int, attribute_type: UUID
116
123
  ) -> None:
@@ -120,21 +127,21 @@ class AttributeProxy(EventEmitter):
120
127
  self.end_group_handle = end_group_handle
121
128
  self.type = attribute_type
122
129
 
123
- async def read_value(self, no_long_read: bool = False) -> bytes:
130
+ async def read_value(self, no_long_read: bool = False) -> _T:
124
131
  return self.decode_value(
125
132
  await self.client.read_value(self.handle, no_long_read)
126
133
  )
127
134
 
128
- async def write_value(self, value, with_response=False):
135
+ async def write_value(self, value: _T, with_response=False):
129
136
  return await self.client.write_value(
130
137
  self.handle, self.encode_value(value), with_response
131
138
  )
132
139
 
133
- def encode_value(self, value: Any) -> bytes:
134
- return value
140
+ def encode_value(self, value: _T) -> bytes:
141
+ return value # type: ignore
135
142
 
136
- def decode_value(self, value_bytes: bytes) -> Any:
137
- return value_bytes
143
+ def decode_value(self, value: bytes) -> _T:
144
+ return value # type: ignore
138
145
 
139
146
  def __str__(self) -> str:
140
147
  return f'Attribute(handle=0x{self.handle:04X}, type={self.type})'
@@ -184,19 +191,19 @@ class ServiceProxy(AttributeProxy):
184
191
  return f'Service(handle=0x{self.handle:04X}, uuid={self.uuid})'
185
192
 
186
193
 
187
- class CharacteristicProxy(AttributeProxy):
194
+ class CharacteristicProxy(AttributeProxy[_T]):
188
195
  properties: Characteristic.Properties
189
196
  descriptors: List[DescriptorProxy]
190
- subscribers: Dict[Any, Callable[[bytes], Any]]
197
+ subscribers: Dict[Any, Callable[[_T], Any]]
191
198
 
192
199
  def __init__(
193
200
  self,
194
- client,
195
- handle,
196
- end_group_handle,
197
- uuid,
201
+ client: Client,
202
+ handle: int,
203
+ end_group_handle: int,
204
+ uuid: UUID,
198
205
  properties: int,
199
- ):
206
+ ) -> None:
200
207
  super().__init__(client, handle, end_group_handle, uuid)
201
208
  self.uuid = uuid
202
209
  self.properties = Characteristic.Properties(properties)
@@ -204,21 +211,21 @@ class CharacteristicProxy(AttributeProxy):
204
211
  self.descriptors_discovered = False
205
212
  self.subscribers = {} # Map from subscriber to proxy subscriber
206
213
 
207
- def get_descriptor(self, descriptor_type):
214
+ def get_descriptor(self, descriptor_type: UUID) -> Optional[DescriptorProxy]:
208
215
  for descriptor in self.descriptors:
209
216
  if descriptor.type == descriptor_type:
210
217
  return descriptor
211
218
 
212
219
  return None
213
220
 
214
- async def discover_descriptors(self):
221
+ async def discover_descriptors(self) -> list[DescriptorProxy]:
215
222
  return await self.client.discover_descriptors(self)
216
223
 
217
224
  async def subscribe(
218
225
  self,
219
- subscriber: Optional[Callable[[bytes], Any]] = None,
226
+ subscriber: Optional[Callable[[_T], Any]] = None,
220
227
  prefer_notify: bool = True,
221
- ):
228
+ ) -> None:
222
229
  if subscriber is not None:
223
230
  if subscriber in self.subscribers:
224
231
  # We already have a proxy subscriber
@@ -233,13 +240,13 @@ class CharacteristicProxy(AttributeProxy):
233
240
  self.subscribers[subscriber] = on_change
234
241
  subscriber = on_change
235
242
 
236
- return await self.client.subscribe(self, subscriber, prefer_notify)
243
+ await self.client.subscribe(self, subscriber, prefer_notify)
237
244
 
238
- async def unsubscribe(self, subscriber=None, force=False):
245
+ async def unsubscribe(self, subscriber=None, force=False) -> None:
239
246
  if subscriber in self.subscribers:
240
247
  subscriber = self.subscribers.pop(subscriber)
241
248
 
242
- return await self.client.unsubscribe(self, subscriber, force)
249
+ await self.client.unsubscribe(self, subscriber, force)
243
250
 
244
251
  def __str__(self) -> str:
245
252
  return (
@@ -250,7 +257,7 @@ class CharacteristicProxy(AttributeProxy):
250
257
 
251
258
 
252
259
  class DescriptorProxy(AttributeProxy):
253
- def __init__(self, client, handle, descriptor_type):
260
+ def __init__(self, client: Client, handle: int, descriptor_type: UUID) -> None:
254
261
  super().__init__(client, handle, 0, descriptor_type)
255
262
 
256
263
  def __str__(self) -> str:
@@ -679,7 +686,7 @@ class Client:
679
686
 
680
687
  properties, handle = struct.unpack_from('<BH', attribute_value)
681
688
  characteristic_uuid = UUID.from_bytes(attribute_value[3:])
682
- characteristic = CharacteristicProxy(
689
+ characteristic: CharacteristicProxy = CharacteristicProxy(
683
690
  self, handle, 0, characteristic_uuid, properties
684
691
  )
685
692
 
@@ -805,7 +812,7 @@ class Client:
805
812
  logger.warning(f'bogus handle value: {attribute_handle}')
806
813
  return []
807
814
 
808
- attribute = AttributeProxy(
815
+ attribute: AttributeProxy = AttributeProxy(
809
816
  self, attribute_handle, 0, UUID.from_bytes(attribute_uuid)
810
817
  )
811
818
  attributes.append(attribute)
@@ -818,7 +825,7 @@ class Client:
818
825
  async def subscribe(
819
826
  self,
820
827
  characteristic: CharacteristicProxy,
821
- subscriber: Optional[Callable[[bytes], Any]] = None,
828
+ subscriber: Optional[Callable[[Any], Any]] = None,
822
829
  prefer_notify: bool = True,
823
830
  ) -> None:
824
831
  # If we haven't already discovered the descriptors for this characteristic,
@@ -868,7 +875,7 @@ class Client:
868
875
  async def unsubscribe(
869
876
  self,
870
877
  characteristic: CharacteristicProxy,
871
- subscriber: Optional[Callable[[bytes], Any]] = None,
878
+ subscriber: Optional[Callable[[Any], Any]] = None,
872
879
  force: bool = False,
873
880
  ) -> None:
874
881
  '''
bumble/gatt_server.py CHANGED
@@ -36,7 +36,6 @@ from typing import (
36
36
  Tuple,
37
37
  TypeVar,
38
38
  Type,
39
- Union,
40
39
  TYPE_CHECKING,
41
40
  )
42
41
  from pyee import EventEmitter
@@ -78,7 +77,6 @@ from bumble.gatt import (
78
77
  GATT_REQUEST_TIMEOUT,
79
78
  GATT_SECONDARY_SERVICE_ATTRIBUTE_TYPE,
80
79
  Characteristic,
81
- CharacteristicAdapter,
82
80
  CharacteristicDeclaration,
83
81
  CharacteristicValue,
84
82
  IncludedServiceDeclaration,
@@ -469,7 +467,7 @@ class Server(EventEmitter):
469
467
  finally:
470
468
  self.pending_confirmations[connection.handle] = None
471
469
 
472
- async def notify_or_indicate_subscribers(
470
+ async def _notify_or_indicate_subscribers(
473
471
  self,
474
472
  indicate: bool,
475
473
  attribute: Attribute,
@@ -503,7 +501,9 @@ class Server(EventEmitter):
503
501
  value: Optional[bytes] = None,
504
502
  force: bool = False,
505
503
  ):
506
- return await self.notify_or_indicate_subscribers(False, attribute, value, force)
504
+ return await self._notify_or_indicate_subscribers(
505
+ False, attribute, value, force
506
+ )
507
507
 
508
508
  async def indicate_subscribers(
509
509
  self,
@@ -511,7 +511,7 @@ class Server(EventEmitter):
511
511
  value: Optional[bytes] = None,
512
512
  force: bool = False,
513
513
  ):
514
- return await self.notify_or_indicate_subscribers(True, attribute, value, force)
514
+ return await self._notify_or_indicate_subscribers(True, attribute, value, force)
515
515
 
516
516
  def on_disconnection(self, connection: Connection) -> None:
517
517
  if connection.handle in self.subscribers:
bumble/hci.py CHANGED
@@ -24,6 +24,7 @@ import logging
24
24
  import secrets
25
25
  import struct
26
26
  from typing import Any, Callable, Dict, Iterable, List, Optional, Type, Union, ClassVar
27
+ from typing_extensions import Self
27
28
 
28
29
  from bumble import crypto
29
30
  from bumble.colors import color
@@ -34,6 +35,7 @@ from bumble.core import (
34
35
  InvalidArgumentError,
35
36
  InvalidPacketError,
36
37
  ProtocolError,
38
+ PhysicalTransport,
37
39
  bit_flags_to_strings,
38
40
  name_or_number,
39
41
  padded_bytes,
@@ -94,7 +96,7 @@ def map_class_of_device(class_of_device):
94
96
  )
95
97
 
96
98
 
97
- def phy_list_to_bits(phys: Optional[Iterable[int]]) -> int:
99
+ def phy_list_to_bits(phys: Optional[Iterable[Phy]]) -> int:
98
100
  if phys is None:
99
101
  return 0
100
102
 
@@ -700,30 +702,22 @@ HCI_ERROR_NAMES[HCI_SUCCESS] = 'HCI_SUCCESS'
700
702
  HCI_COMMAND_STATUS_PENDING = 0
701
703
 
702
704
 
705
+ class Phy(enum.IntEnum):
706
+ LE_1M = 1
707
+ LE_2M = 2
708
+ LE_CODED = 3
709
+
710
+
703
711
  # ACL
704
712
  HCI_ACL_PB_FIRST_NON_FLUSHABLE = 0
705
713
  HCI_ACL_PB_CONTINUATION = 1
706
714
  HCI_ACL_PB_FIRST_FLUSHABLE = 2
707
715
  HCI_ACK_PB_COMPLETE_L2CAP = 3
708
716
 
709
- # Roles
710
- HCI_CENTRAL_ROLE = 0
711
- HCI_PERIPHERAL_ROLE = 1
712
-
713
- HCI_ROLE_NAMES = {
714
- HCI_CENTRAL_ROLE: 'CENTRAL',
715
- HCI_PERIPHERAL_ROLE: 'PERIPHERAL'
716
- }
717
-
718
- # LE PHY Types
719
- HCI_LE_1M_PHY = 1
720
- HCI_LE_2M_PHY = 2
721
- HCI_LE_CODED_PHY = 3
722
-
723
- HCI_LE_PHY_NAMES = {
724
- HCI_LE_1M_PHY: 'LE 1M',
725
- HCI_LE_2M_PHY: 'LE 2M',
726
- HCI_LE_CODED_PHY: 'LE Coded'
717
+ HCI_LE_PHY_NAMES: dict[int,str] = {
718
+ Phy.LE_1M: 'LE 1M',
719
+ Phy.LE_2M: 'LE 2M',
720
+ Phy.LE_CODED: 'LE Coded'
727
721
  }
728
722
 
729
723
  HCI_LE_1M_PHY_BIT = 0
@@ -732,19 +726,13 @@ HCI_LE_CODED_PHY_BIT = 2
732
726
 
733
727
  HCI_LE_PHY_BIT_NAMES = ['LE_1M_PHY', 'LE_2M_PHY', 'LE_CODED_PHY']
734
728
 
735
- HCI_LE_PHY_TYPE_TO_BIT = {
736
- HCI_LE_1M_PHY: HCI_LE_1M_PHY_BIT,
737
- HCI_LE_2M_PHY: HCI_LE_2M_PHY_BIT,
738
- HCI_LE_CODED_PHY: HCI_LE_CODED_PHY_BIT
729
+ HCI_LE_PHY_TYPE_TO_BIT: dict[Phy, int] = {
730
+ Phy.LE_1M: HCI_LE_1M_PHY_BIT,
731
+ Phy.LE_2M: HCI_LE_2M_PHY_BIT,
732
+ Phy.LE_CODED: HCI_LE_CODED_PHY_BIT,
739
733
  }
740
734
 
741
735
 
742
- class Phy(enum.IntEnum):
743
- LE_1M = HCI_LE_1M_PHY
744
- LE_2M = HCI_LE_2M_PHY
745
- LE_CODED = HCI_LE_CODED_PHY
746
-
747
-
748
736
  class PhyBit(enum.IntFlag):
749
737
  LE_1M = 1 << HCI_LE_1M_PHY_BIT
750
738
  LE_2M = 1 << HCI_LE_2M_PHY_BIT
@@ -811,6 +799,19 @@ class CsSubeventAbortReason(OpenIntEnum):
811
799
  SCHEDULING_CONFLICT_OR_LIMITED_RESOURCES = 0x03
812
800
  UNSPECIFIED = 0x0F
813
801
 
802
+ class Role(enum.IntEnum):
803
+ CENTRAL = 0
804
+ PERIPHERAL = 1
805
+
806
+ # For Backward Compatibility.
807
+ HCI_CENTRAL_ROLE = Role.CENTRAL
808
+ HCI_PERIPHERAL_ROLE = Role.PERIPHERAL
809
+
810
+
811
+ HCI_LE_1M_PHY = Phy.LE_1M
812
+ HCI_LE_2M_PHY = Phy.LE_2M
813
+ HCI_LE_CODED_PHY = Phy.LE_CODED
814
+
814
815
 
815
816
  # Connection Parameters
816
817
  HCI_CONNECTION_INTERVAL_MS_PER_UNIT = 1.25
@@ -889,10 +890,15 @@ HCI_LINK_TYPE_NAMES = {
889
890
  }
890
891
 
891
892
  # Address types
892
- HCI_PUBLIC_DEVICE_ADDRESS_TYPE = 0x00
893
- HCI_RANDOM_DEVICE_ADDRESS_TYPE = 0x01
894
- HCI_PUBLIC_IDENTITY_ADDRESS_TYPE = 0x02
895
- HCI_RANDOM_IDENTITY_ADDRESS_TYPE = 0x03
893
+ class AddressType(OpenIntEnum):
894
+ PUBLIC_DEVICE = 0x00
895
+ RANDOM_DEVICE = 0x01
896
+ PUBLIC_IDENTITY = 0x02
897
+ RANDOM_IDENTITY = 0x03
898
+ # (Directed Only) Address is RPA, but controller cannot resolve.
899
+ UNABLE_TO_RESOLVE = 0xFE
900
+ # (Extended Only) No address.
901
+ ANONYMOUS = 0xFF
896
902
 
897
903
  # Supported Commands Masks
898
904
  # See Bluetooth spec @ 6.27 SUPPORTED COMMANDS
@@ -1582,8 +1588,8 @@ class HCI_Constant:
1582
1588
  return HCI_ERROR_NAMES.get(status, f'0x{status:02X}')
1583
1589
 
1584
1590
  @staticmethod
1585
- def role_name(role):
1586
- return HCI_ROLE_NAMES.get(role, str(role))
1591
+ def role_name(role: int) -> str:
1592
+ return Role(role).name
1587
1593
 
1588
1594
  @staticmethod
1589
1595
  def le_phy_name(phy):
@@ -1949,17 +1955,10 @@ class Address:
1949
1955
  address[0] is the LSB of the address, address[5] is the MSB.
1950
1956
  '''
1951
1957
 
1952
- PUBLIC_DEVICE_ADDRESS = 0x00
1953
- RANDOM_DEVICE_ADDRESS = 0x01
1954
- PUBLIC_IDENTITY_ADDRESS = 0x02
1955
- RANDOM_IDENTITY_ADDRESS = 0x03
1956
-
1957
- ADDRESS_TYPE_NAMES = {
1958
- PUBLIC_DEVICE_ADDRESS: 'PUBLIC_DEVICE_ADDRESS',
1959
- RANDOM_DEVICE_ADDRESS: 'RANDOM_DEVICE_ADDRESS',
1960
- PUBLIC_IDENTITY_ADDRESS: 'PUBLIC_IDENTITY_ADDRESS',
1961
- RANDOM_IDENTITY_ADDRESS: 'RANDOM_IDENTITY_ADDRESS',
1962
- }
1958
+ PUBLIC_DEVICE_ADDRESS = AddressType.PUBLIC_DEVICE
1959
+ RANDOM_DEVICE_ADDRESS = AddressType.RANDOM_DEVICE
1960
+ PUBLIC_IDENTITY_ADDRESS = AddressType.PUBLIC_IDENTITY
1961
+ RANDOM_IDENTITY_ADDRESS = AddressType.RANDOM_IDENTITY
1963
1962
 
1964
1963
  # Type declarations
1965
1964
  NIL: Address
@@ -1969,40 +1968,44 @@ class Address:
1969
1968
  # pylint: disable-next=unnecessary-lambda
1970
1969
  ADDRESS_TYPE_SPEC = {'size': 1, 'mapper': lambda x: Address.address_type_name(x)}
1971
1970
 
1972
- @staticmethod
1973
- def address_type_name(address_type):
1974
- return name_or_number(Address.ADDRESS_TYPE_NAMES, address_type)
1971
+ @classmethod
1972
+ def address_type_name(cls: type[Self], address_type: int) -> str:
1973
+ return AddressType(address_type).name
1975
1974
 
1976
- @staticmethod
1977
- def from_string_for_transport(string, transport):
1975
+ @classmethod
1976
+ def from_string_for_transport(
1977
+ cls: type[Self], string: str, transport: PhysicalTransport
1978
+ ) -> Self:
1978
1979
  if transport == BT_BR_EDR_TRANSPORT:
1979
1980
  address_type = Address.PUBLIC_DEVICE_ADDRESS
1980
1981
  else:
1981
1982
  address_type = Address.RANDOM_DEVICE_ADDRESS
1982
- return Address(string, address_type)
1983
+ return cls(string, address_type)
1983
1984
 
1984
- @staticmethod
1985
- def parse_address(data, offset):
1985
+ @classmethod
1986
+ def parse_address(cls: type[Self], data: bytes, offset: int) -> tuple[int, Self]:
1986
1987
  # Fix the type to a default value. This is used for parsing type-less Classic
1987
1988
  # addresses
1988
- return Address.parse_address_with_type(
1989
- data, offset, Address.PUBLIC_DEVICE_ADDRESS
1990
- )
1989
+ return cls.parse_address_with_type(data, offset, Address.PUBLIC_DEVICE_ADDRESS)
1991
1990
 
1992
- @staticmethod
1993
- def parse_random_address(data, offset):
1994
- return Address.parse_address_with_type(
1995
- data, offset, Address.RANDOM_DEVICE_ADDRESS
1996
- )
1991
+ @classmethod
1992
+ def parse_random_address(
1993
+ cls: type[Self], data: bytes, offset: int
1994
+ ) -> tuple[int, Self]:
1995
+ return cls.parse_address_with_type(data, offset, Address.RANDOM_DEVICE_ADDRESS)
1997
1996
 
1998
- @staticmethod
1999
- def parse_address_with_type(data, offset, address_type):
2000
- return offset + 6, Address(data[offset : offset + 6], address_type)
1997
+ @classmethod
1998
+ def parse_address_with_type(
1999
+ cls: type[Self], data: bytes, offset: int, address_type: AddressType
2000
+ ) -> tuple[int, Self]:
2001
+ return offset + 6, cls(data[offset : offset + 6], address_type)
2001
2002
 
2002
- @staticmethod
2003
- def parse_address_preceded_by_type(data, offset):
2004
- address_type = data[offset - 1]
2005
- return Address.parse_address_with_type(data, offset, address_type)
2003
+ @classmethod
2004
+ def parse_address_preceded_by_type(
2005
+ cls: type[Self], data: bytes, offset: int
2006
+ ) -> tuple[int, Self]:
2007
+ address_type = AddressType(data[offset - 1])
2008
+ return cls.parse_address_with_type(data, offset, address_type)
2006
2009
 
2007
2010
  @classmethod
2008
2011
  def generate_static_address(cls) -> Address:
@@ -2042,8 +2045,10 @@ class Address:
2042
2045
  )
2043
2046
 
2044
2047
  def __init__(
2045
- self, address: Union[bytes, str], address_type: int = RANDOM_DEVICE_ADDRESS
2046
- ):
2048
+ self,
2049
+ address: Union[bytes, str],
2050
+ address_type: AddressType = RANDOM_DEVICE_ADDRESS,
2051
+ ) -> None:
2047
2052
  '''
2048
2053
  Initialize an instance. `address` may be a byte array in little-endian
2049
2054
  format, or a hex string in big-endian format (with optional ':'
bumble/host.py CHANGED
@@ -44,6 +44,7 @@ from bumble import hci
44
44
  from bumble.core import (
45
45
  BT_BR_EDR_TRANSPORT,
46
46
  BT_LE_TRANSPORT,
47
+ PhysicalTransport,
47
48
  ConnectionPHY,
48
49
  ConnectionParameters,
49
50
  )
@@ -186,7 +187,11 @@ class DataPacketQueue(pyee.EventEmitter):
186
187
  # -----------------------------------------------------------------------------
187
188
  class Connection:
188
189
  def __init__(
189
- self, host: Host, handle: int, peer_address: hci.Address, transport: int
190
+ self,
191
+ host: Host,
192
+ handle: int,
193
+ peer_address: hci.Address,
194
+ transport: PhysicalTransport,
190
195
  ):
191
196
  self.host = host
192
197
  self.handle = handle
@@ -235,7 +240,7 @@ class Host(AbortableEventEmitter):
235
240
  cis_links: Dict[int, IsoLink]
236
241
  bis_links: Dict[int, IsoLink]
237
242
  sco_links: Dict[int, ScoLink]
238
- bigs: dict[int, set[int]] = {} # BIG Handle to BIS Handles
243
+ bigs: dict[int, set[int]]
239
244
  acl_packet_queue: Optional[DataPacketQueue] = None
240
245
  le_acl_packet_queue: Optional[DataPacketQueue] = None
241
246
  iso_packet_queue: Optional[DataPacketQueue] = None
@@ -259,6 +264,7 @@ class Host(AbortableEventEmitter):
259
264
  self.cis_links = {} # CIS links, by connection handle
260
265
  self.bis_links = {} # BIS links, by connection handle
261
266
  self.sco_links = {} # SCO links, by connection handle
267
+ self.bigs = {} # BIG Handle to BIS Handles
262
268
  self.pending_command = None
263
269
  self.pending_response: Optional[asyncio.Future[Any]] = None
264
270
  self.number_of_supported_advertising_sets = 0
@@ -978,7 +984,7 @@ class Host(AbortableEventEmitter):
978
984
  event.peer_address,
979
985
  getattr(event, 'local_resolvable_private_address', None),
980
986
  getattr(event, 'peer_resolvable_private_address', None),
981
- event.role,
987
+ hci.Role(event.role),
982
988
  connection_parameters,
983
989
  )
984
990
  else:
@@ -1061,8 +1067,10 @@ class Host(AbortableEventEmitter):
1061
1067
  )
1062
1068
 
1063
1069
  # Flush the data queues
1064
- self.acl_packet_queue.flush(handle)
1065
- self.le_acl_packet_queue.flush(handle)
1070
+ if self.acl_packet_queue:
1071
+ self.acl_packet_queue.flush(handle)
1072
+ if self.le_acl_packet_queue:
1073
+ self.le_acl_packet_queue.flush(handle)
1066
1074
  if self.iso_packet_queue:
1067
1075
  self.iso_packet_queue.flush(handle)
1068
1076
  else:
@@ -1098,8 +1106,11 @@ class Host(AbortableEventEmitter):
1098
1106
 
1099
1107
  # Notify the client
1100
1108
  if event.status == hci.HCI_SUCCESS:
1101
- connection_phy = ConnectionPHY(event.tx_phy, event.rx_phy)
1102
- self.emit('connection_phy_update', connection.handle, connection_phy)
1109
+ self.emit(
1110
+ 'connection_phy_update',
1111
+ connection.handle,
1112
+ ConnectionPHY(event.tx_phy, event.rx_phy),
1113
+ )
1103
1114
  else:
1104
1115
  self.emit('connection_phy_update_failure', connection.handle, event.status)
1105
1116
 
@@ -1331,7 +1342,7 @@ class Host(AbortableEventEmitter):
1331
1342
  f'role change for {event.bd_addr}: '
1332
1343
  f'{hci.HCI_Constant.role_name(event.new_role)}'
1333
1344
  )
1334
- self.emit('role_change', event.bd_addr, event.new_role)
1345
+ self.emit('role_change', event.bd_addr, hci.Role(event.new_role))
1335
1346
  else:
1336
1347
  logger.debug(
1337
1348
  f'role change for {event.bd_addr} failed: '
bumble/l2cap.py CHANGED
@@ -42,7 +42,6 @@ from typing import (
42
42
  from .utils import deprecated
43
43
  from .colors import color
44
44
  from .core import (
45
- BT_CENTRAL_ROLE,
46
45
  InvalidStateError,
47
46
  InvalidArgumentError,
48
47
  InvalidPacketError,
@@ -52,6 +51,7 @@ from .core import (
52
51
  from .hci import (
53
52
  HCI_LE_Connection_Update_Command,
54
53
  HCI_Object,
54
+ Role,
55
55
  key_with_value,
56
56
  name_or_number,
57
57
  )
@@ -1908,7 +1908,7 @@ class ChannelManager:
1908
1908
  def on_l2cap_connection_parameter_update_request(
1909
1909
  self, connection: Connection, cid: int, request
1910
1910
  ):
1911
- if connection.role == BT_CENTRAL_ROLE:
1911
+ if connection.role == Role.CENTRAL:
1912
1912
  self.send_control_frame(
1913
1913
  connection,
1914
1914
  cid,
bumble/link.py CHANGED
@@ -20,7 +20,6 @@ import asyncio
20
20
  from functools import partial
21
21
 
22
22
  from bumble.core import (
23
- BT_PERIPHERAL_ROLE,
24
23
  BT_BR_EDR_TRANSPORT,
25
24
  BT_LE_TRANSPORT,
26
25
  InvalidStateError,
@@ -28,6 +27,7 @@ from bumble.core import (
28
27
  from bumble.colors import color
29
28
  from bumble.hci import (
30
29
  Address,
30
+ Role,
31
31
  HCI_SUCCESS,
32
32
  HCI_CONNECTION_ACCEPT_TIMEOUT_ERROR,
33
33
  HCI_CONNECTION_TIMEOUT_ERROR,
@@ -292,7 +292,7 @@ class LocalLink:
292
292
  return
293
293
 
294
294
  async def task():
295
- if responder_role != BT_PERIPHERAL_ROLE:
295
+ if responder_role != Role.PERIPHERAL:
296
296
  initiator_controller.on_classic_role_change(
297
297
  responder_controller.public_address, int(not (responder_role))
298
298
  )
bumble/pairing.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2021-2023 Google LLC
1
+ # Copyright 2021-2025 Google LLC
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -76,18 +76,18 @@ class OobData:
76
76
  return instance
77
77
 
78
78
  def to_ad(self) -> AdvertisingData:
79
- ad_structures = []
79
+ ad_structures: list[tuple[int, bytes]] = []
80
80
  if self.address is not None:
81
81
  ad_structures.append(
82
- (AdvertisingData.LE_BLUETOOTH_DEVICE_ADDRESS, bytes(self.address))
82
+ (AdvertisingData.Type.LE_BLUETOOTH_DEVICE_ADDRESS, bytes(self.address))
83
83
  )
84
84
  if self.role is not None:
85
- ad_structures.append((AdvertisingData.LE_ROLE, bytes([self.role])))
85
+ ad_structures.append((AdvertisingData.Type.LE_ROLE, bytes([self.role])))
86
86
  if self.shared_data is not None:
87
87
  ad_structures.extend(self.shared_data.to_ad().ad_structures)
88
88
  if self.legacy_context is not None:
89
89
  ad_structures.append(
90
- (AdvertisingData.SECURITY_MANAGER_TK_VALUE, self.legacy_context.tk)
90
+ (AdvertisingData.Type.SECURITY_MANAGER_TK_VALUE, self.legacy_context.tk)
91
91
  )
92
92
 
93
93
  return AdvertisingData(ad_structures)