bumble 0.0.219__py3-none-any.whl → 0.0.221__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 (104) hide show
  1. bumble/_version.py +2 -2
  2. bumble/a2dp.py +5 -5
  3. bumble/apps/auracast.py +746 -479
  4. bumble/apps/bench.py +4 -5
  5. bumble/apps/console.py +5 -10
  6. bumble/apps/controller_info.py +12 -7
  7. bumble/apps/controller_loopback.py +1 -2
  8. bumble/apps/device_info.py +2 -3
  9. bumble/apps/gatt_dump.py +0 -1
  10. bumble/apps/lea_unicast/app.py +1 -1
  11. bumble/apps/pair.py +49 -46
  12. bumble/apps/pandora_server.py +2 -2
  13. bumble/apps/player/player.py +10 -12
  14. bumble/apps/rfcomm_bridge.py +10 -11
  15. bumble/apps/scan.py +1 -3
  16. bumble/apps/speaker/speaker.py +3 -4
  17. bumble/at.py +4 -5
  18. bumble/att.py +91 -25
  19. bumble/audio/io.py +8 -6
  20. bumble/avc.py +1 -2
  21. bumble/avctp.py +2 -3
  22. bumble/avdtp.py +53 -57
  23. bumble/avrcp.py +25 -27
  24. bumble/codecs.py +15 -15
  25. bumble/colors.py +7 -8
  26. bumble/controller.py +1201 -643
  27. bumble/core.py +41 -49
  28. bumble/crypto/__init__.py +2 -1
  29. bumble/crypto/builtin.py +2 -8
  30. bumble/data_types.py +2 -1
  31. bumble/decoder.py +2 -3
  32. bumble/device.py +278 -325
  33. bumble/drivers/__init__.py +3 -2
  34. bumble/drivers/intel.py +6 -8
  35. bumble/drivers/rtk.py +1 -1
  36. bumble/gatt.py +9 -9
  37. bumble/gatt_adapters.py +6 -6
  38. bumble/gatt_client.py +110 -60
  39. bumble/gatt_server.py +209 -139
  40. bumble/hci.py +87 -74
  41. bumble/helpers.py +5 -5
  42. bumble/hfp.py +27 -26
  43. bumble/hid.py +9 -9
  44. bumble/host.py +44 -50
  45. bumble/keys.py +17 -17
  46. bumble/l2cap.py +1015 -218
  47. bumble/link.py +54 -284
  48. bumble/ll.py +200 -0
  49. bumble/lmp.py +324 -0
  50. bumble/pairing.py +14 -15
  51. bumble/pandora/__init__.py +2 -2
  52. bumble/pandora/device.py +6 -4
  53. bumble/pandora/host.py +19 -10
  54. bumble/pandora/l2cap.py +8 -9
  55. bumble/pandora/security.py +18 -16
  56. bumble/pandora/utils.py +4 -4
  57. bumble/profiles/aics.py +6 -8
  58. bumble/profiles/ams.py +3 -5
  59. bumble/profiles/ancs.py +11 -11
  60. bumble/profiles/ascs.py +5 -5
  61. bumble/profiles/asha.py +10 -9
  62. bumble/profiles/bass.py +9 -3
  63. bumble/profiles/battery_service.py +1 -2
  64. bumble/profiles/csip.py +9 -10
  65. bumble/profiles/device_information_service.py +16 -17
  66. bumble/profiles/gap.py +3 -4
  67. bumble/profiles/gatt_service.py +0 -1
  68. bumble/profiles/gmap.py +12 -13
  69. bumble/profiles/hap.py +3 -3
  70. bumble/profiles/heart_rate_service.py +7 -8
  71. bumble/profiles/le_audio.py +1 -1
  72. bumble/profiles/mcp.py +28 -28
  73. bumble/profiles/pacs.py +13 -17
  74. bumble/profiles/pbp.py +16 -0
  75. bumble/profiles/vcs.py +2 -2
  76. bumble/profiles/vocs.py +6 -9
  77. bumble/rfcomm.py +19 -18
  78. bumble/sdp.py +12 -11
  79. bumble/smp.py +20 -30
  80. bumble/snoop.py +12 -5
  81. bumble/tools/generate_company_id_list.py +1 -1
  82. bumble/tools/intel_util.py +2 -2
  83. bumble/tools/rtk_fw_download.py +1 -1
  84. bumble/tools/rtk_util.py +1 -1
  85. bumble/transport/__init__.py +1 -2
  86. bumble/transport/android_emulator.py +2 -3
  87. bumble/transport/android_netsim.py +49 -40
  88. bumble/transport/common.py +9 -9
  89. bumble/transport/file.py +1 -2
  90. bumble/transport/hci_socket.py +2 -3
  91. bumble/transport/pty.py +3 -5
  92. bumble/transport/pyusb.py +8 -5
  93. bumble/transport/serial.py +1 -2
  94. bumble/transport/vhci.py +1 -2
  95. bumble/transport/ws_server.py +2 -3
  96. bumble/utils.py +23 -14
  97. bumble/vendor/android/hci.py +4 -2
  98. {bumble-0.0.219.dist-info → bumble-0.0.221.dist-info}/METADATA +4 -3
  99. bumble-0.0.221.dist-info/RECORD +185 -0
  100. bumble-0.0.219.dist-info/RECORD +0 -183
  101. {bumble-0.0.219.dist-info → bumble-0.0.221.dist-info}/WHEEL +0 -0
  102. {bumble-0.0.219.dist-info → bumble-0.0.221.dist-info}/entry_points.txt +0 -0
  103. {bumble-0.0.219.dist-info → bumble-0.0.221.dist-info}/licenses/LICENSE +0 -0
  104. {bumble-0.0.219.dist-info → bumble-0.0.221.dist-info}/top_level.txt +0 -0
bumble/hci.py CHANGED
@@ -24,17 +24,13 @@ import functools
24
24
  import logging
25
25
  import secrets
26
26
  import struct
27
- from collections.abc import Sequence
27
+ from collections.abc import Callable, Iterable, Sequence
28
28
  from dataclasses import field
29
29
  from typing import (
30
30
  Any,
31
- Callable,
32
31
  ClassVar,
33
- Iterable,
34
32
  Literal,
35
- Optional,
36
33
  TypeVar,
37
- Union,
38
34
  cast,
39
35
  )
40
36
 
@@ -106,7 +102,7 @@ def map_class_of_device(class_of_device):
106
102
  )
107
103
 
108
104
 
109
- def phy_list_to_bits(phys: Optional[Iterable[Phy]]) -> int:
105
+ def phy_list_to_bits(phys: Iterable[Phy] | None) -> int:
110
106
  if phys is None:
111
107
  return 0
112
108
 
@@ -119,7 +115,6 @@ def phy_list_to_bits(phys: Optional[Iterable[Phy]]) -> int:
119
115
 
120
116
 
121
117
  class SpecableEnum(utils.OpenIntEnum):
122
-
123
118
  @classmethod
124
119
  def type_spec(cls, size: int, byteorder: Literal['little', 'big'] = 'little'):
125
120
  return {
@@ -147,7 +142,6 @@ class SpecableEnum(utils.OpenIntEnum):
147
142
 
148
143
 
149
144
  class SpecableFlag(enum.IntFlag):
150
-
151
145
  @classmethod
152
146
  def type_spec(cls, size: int, byteorder: Literal['little', 'big'] = 'little'):
153
147
  return {
@@ -186,8 +180,8 @@ class SpecableFlag(enum.IntFlag):
186
180
  # - "v" for variable length bytes with a leading length byte
187
181
  # - an integer [1, 4] for 1-byte, 2-byte or 4-byte unsigned little-endian integers
188
182
  # - an integer [-2, -1] for 1-byte, 2-byte signed little-endian integers
189
- FieldSpec = Union[dict[str, Any], Callable[[bytes, int], tuple[int, Any]], str, int]
190
- Fields = Sequence[Union[tuple[str, FieldSpec], 'Fields']]
183
+ FieldSpec = dict[str, Any] | Callable[[bytes, int], tuple[int, Any]] | str | int
184
+ Fields = Sequence['tuple[str, FieldSpec] | Fields']
191
185
 
192
186
 
193
187
  @dataclasses.dataclass
@@ -213,22 +207,44 @@ def metadata(
213
207
 
214
208
  HCI_VENDOR_OGF = 0x3F
215
209
 
216
- # HCI Version
217
- HCI_VERSION_BLUETOOTH_CORE_1_0B = 0
218
- HCI_VERSION_BLUETOOTH_CORE_1_1 = 1
219
- HCI_VERSION_BLUETOOTH_CORE_1_2 = 2
220
- HCI_VERSION_BLUETOOTH_CORE_2_0_EDR = 3
221
- HCI_VERSION_BLUETOOTH_CORE_2_1_EDR = 4
222
- HCI_VERSION_BLUETOOTH_CORE_3_0_HS = 5
223
- HCI_VERSION_BLUETOOTH_CORE_4_0 = 6
224
- HCI_VERSION_BLUETOOTH_CORE_4_1 = 7
225
- HCI_VERSION_BLUETOOTH_CORE_4_2 = 8
226
- HCI_VERSION_BLUETOOTH_CORE_5_0 = 9
227
- HCI_VERSION_BLUETOOTH_CORE_5_1 = 10
228
- HCI_VERSION_BLUETOOTH_CORE_5_2 = 11
229
- HCI_VERSION_BLUETOOTH_CORE_5_3 = 12
230
- HCI_VERSION_BLUETOOTH_CORE_5_4 = 13
231
- HCI_VERSION_BLUETOOTH_CORE_6_0 = 14
210
+ # Specification Version
211
+ class SpecificationVersion(utils.OpenIntEnum):
212
+ BLUETOOTH_CORE_1_0B = 0
213
+ BLUETOOTH_CORE_1_1 = 1
214
+ BLUETOOTH_CORE_1_2 = 2
215
+ BLUETOOTH_CORE_2_0_EDR = 3
216
+ BLUETOOTH_CORE_2_1_EDR = 4
217
+ BLUETOOTH_CORE_3_0_HS = 5
218
+ BLUETOOTH_CORE_4_0 = 6
219
+ BLUETOOTH_CORE_4_1 = 7
220
+ BLUETOOTH_CORE_4_2 = 8
221
+ BLUETOOTH_CORE_5_0 = 9
222
+ BLUETOOTH_CORE_5_1 = 10
223
+ BLUETOOTH_CORE_5_2 = 11
224
+ BLUETOOTH_CORE_5_3 = 12
225
+ BLUETOOTH_CORE_5_4 = 13
226
+ BLUETOOTH_CORE_6_0 = 14
227
+ BLUETOOTH_CORE_6_1 = 15
228
+ BLUETOOTH_CORE_6_2 = 16
229
+
230
+ # For backwards compatibility only
231
+ HCI_VERSION_BLUETOOTH_CORE_1_0B = SpecificationVersion.BLUETOOTH_CORE_1_0B
232
+ HCI_VERSION_BLUETOOTH_CORE_1_1 = SpecificationVersion.BLUETOOTH_CORE_1_1
233
+ HCI_VERSION_BLUETOOTH_CORE_1_2 = SpecificationVersion.BLUETOOTH_CORE_1_2
234
+ HCI_VERSION_BLUETOOTH_CORE_2_0_EDR = SpecificationVersion.BLUETOOTH_CORE_2_0_EDR
235
+ HCI_VERSION_BLUETOOTH_CORE_2_1_EDR = SpecificationVersion.BLUETOOTH_CORE_2_1_EDR
236
+ HCI_VERSION_BLUETOOTH_CORE_3_0_HS = SpecificationVersion.BLUETOOTH_CORE_3_0_HS
237
+ HCI_VERSION_BLUETOOTH_CORE_4_0 = SpecificationVersion.BLUETOOTH_CORE_4_0
238
+ HCI_VERSION_BLUETOOTH_CORE_4_1 = SpecificationVersion.BLUETOOTH_CORE_4_1
239
+ HCI_VERSION_BLUETOOTH_CORE_4_2 = SpecificationVersion.BLUETOOTH_CORE_4_2
240
+ HCI_VERSION_BLUETOOTH_CORE_5_0 = SpecificationVersion.BLUETOOTH_CORE_5_0
241
+ HCI_VERSION_BLUETOOTH_CORE_5_1 = SpecificationVersion.BLUETOOTH_CORE_5_1
242
+ HCI_VERSION_BLUETOOTH_CORE_5_2 = SpecificationVersion.BLUETOOTH_CORE_5_2
243
+ HCI_VERSION_BLUETOOTH_CORE_5_3 = SpecificationVersion.BLUETOOTH_CORE_5_3
244
+ HCI_VERSION_BLUETOOTH_CORE_5_4 = SpecificationVersion.BLUETOOTH_CORE_5_4
245
+ HCI_VERSION_BLUETOOTH_CORE_6_0 = SpecificationVersion.BLUETOOTH_CORE_6_0
246
+ HCI_VERSION_BLUETOOTH_CORE_6_1 = SpecificationVersion.BLUETOOTH_CORE_6_1
247
+ HCI_VERSION_BLUETOOTH_CORE_6_2 = SpecificationVersion.BLUETOOTH_CORE_6_2
232
248
 
233
249
  HCI_VERSION_NAMES = {
234
250
  HCI_VERSION_BLUETOOTH_CORE_1_0B: 'HCI_VERSION_BLUETOOTH_CORE_1_0B',
@@ -246,9 +262,10 @@ HCI_VERSION_NAMES = {
246
262
  HCI_VERSION_BLUETOOTH_CORE_5_3: 'HCI_VERSION_BLUETOOTH_CORE_5_3',
247
263
  HCI_VERSION_BLUETOOTH_CORE_5_4: 'HCI_VERSION_BLUETOOTH_CORE_5_4',
248
264
  HCI_VERSION_BLUETOOTH_CORE_6_0: 'HCI_VERSION_BLUETOOTH_CORE_6_0',
265
+ HCI_VERSION_BLUETOOTH_CORE_6_1: 'HCI_VERSION_BLUETOOTH_CORE_6_1',
266
+ HCI_VERSION_BLUETOOTH_CORE_6_2: 'HCI_VERSION_BLUETOOTH_CORE_6_2',
249
267
  }
250
268
 
251
- # LMP Version
252
269
  LMP_VERSION_NAMES = HCI_VERSION_NAMES
253
270
 
254
271
  # HCI Packet types
@@ -1786,20 +1803,20 @@ class HCI_Object:
1786
1803
 
1787
1804
  @classmethod
1788
1805
  def dict_and_offset_from_bytes(
1789
- cls, data: bytes, offset: int, fields: Fields
1806
+ cls, data: bytes, offset: int, object_fields: Fields
1790
1807
  ) -> tuple[int, collections.OrderedDict[str, Any]]:
1791
1808
  result = collections.OrderedDict[str, Any]()
1792
- for field in fields:
1793
- if isinstance(field, list):
1809
+ for object_field in object_fields:
1810
+ if isinstance(object_field, list):
1794
1811
  # This is an array field, starting with a 1-byte item count.
1795
1812
  item_count = data[offset]
1796
1813
  offset += 1
1797
1814
  # Set fields first, because item_count might be 0.
1798
- for sub_field_name, _ in field:
1815
+ for sub_field_name, _ in object_field:
1799
1816
  result[sub_field_name] = []
1800
1817
 
1801
1818
  for _ in range(item_count):
1802
- for sub_field_name, sub_field_type in field:
1819
+ for sub_field_name, sub_field_type in object_field:
1803
1820
  value, size = HCI_Object.parse_field(
1804
1821
  data, offset, sub_field_type
1805
1822
  )
@@ -1807,7 +1824,7 @@ class HCI_Object:
1807
1824
  offset += size
1808
1825
  continue
1809
1826
 
1810
- field_name, field_type = field
1827
+ field_name, field_type = object_field
1811
1828
  assert isinstance(field_name, str)
1812
1829
  field_value, field_size = HCI_Object.parse_field(
1813
1830
  data, offset, cast(FieldSpec, field_type)
@@ -1890,26 +1907,26 @@ class HCI_Object:
1890
1907
  return field_bytes
1891
1908
 
1892
1909
  @staticmethod
1893
- def dict_to_bytes(hci_object, fields):
1910
+ def dict_to_bytes(hci_object, object_fields):
1894
1911
  result = bytearray()
1895
- for field in fields:
1896
- if isinstance(field, list):
1912
+ for object_field in object_fields:
1913
+ if isinstance(object_field, list):
1897
1914
  # The field is an array. The serialized form starts with a 1-byte
1898
1915
  # item count. We use the length of the first array field as the
1899
1916
  # array count, since all array fields have the same number of items.
1900
- item_count = len(hci_object[field[0][0]])
1917
+ item_count = len(hci_object[object_field[0][0]])
1901
1918
  result += bytes([item_count]) + b''.join(
1902
1919
  b''.join(
1903
1920
  HCI_Object.serialize_field(
1904
1921
  hci_object[sub_field_name][i], sub_field_type
1905
1922
  )
1906
- for sub_field_name, sub_field_type in field
1923
+ for sub_field_name, sub_field_type in object_field
1907
1924
  )
1908
1925
  for i in range(item_count)
1909
1926
  )
1910
1927
  continue
1911
1928
 
1912
- (field_name, field_type) = field
1929
+ (field_name, field_type) = object_field
1913
1930
  result += HCI_Object.serialize_field(hci_object[field_name], field_type)
1914
1931
 
1915
1932
  return bytes(result)
@@ -1967,15 +1984,15 @@ class HCI_Object:
1967
1984
  )
1968
1985
 
1969
1986
  @staticmethod
1970
- def format_fields(hci_object, fields, indentation='', value_mappers=None):
1971
- if not fields:
1987
+ def format_fields(hci_object, object_fields, indentation='', value_mappers=None):
1988
+ if not object_fields:
1972
1989
  return ''
1973
1990
 
1974
1991
  # Build array of formatted key:value pairs
1975
1992
  field_strings = []
1976
- for field in fields:
1977
- if isinstance(field, list):
1978
- for sub_field in field:
1993
+ for object_field in object_fields:
1994
+ if isinstance(object_field, list):
1995
+ for sub_field in object_field:
1979
1996
  sub_field_name, sub_field_type = sub_field
1980
1997
  item_count = len(hci_object[sub_field_name])
1981
1998
  for i in range(item_count):
@@ -1993,7 +2010,7 @@ class HCI_Object:
1993
2010
  )
1994
2011
  continue
1995
2012
 
1996
- field_name, field_type = field
2013
+ field_name, field_type = object_field
1997
2014
  field_value = hci_object[field_name]
1998
2015
  field_strings.append(
1999
2016
  (
@@ -2016,16 +2033,16 @@ class HCI_Object:
2016
2033
  @classmethod
2017
2034
  def fields_from_dataclass(cls, obj: Any) -> list[Any]:
2018
2035
  stack: list[list[Any]] = [[]]
2019
- for field in dataclasses.fields(obj):
2036
+ for object_field in dataclasses.fields(obj):
2020
2037
  # Fields without metadata should be ignored.
2021
2038
  if not isinstance(
2022
- (metadata := field.metadata.get("bumble.hci")), FieldMetadata
2039
+ (metadata := object_field.metadata.get("bumble.hci")), FieldMetadata
2023
2040
  ):
2024
2041
  continue
2025
2042
  if metadata.list_begin:
2026
2043
  stack.append([])
2027
2044
  if metadata.spec:
2028
- stack[-1].append((field.name, metadata.spec))
2045
+ stack[-1].append((object_field.name, metadata.spec))
2029
2046
  if metadata.list_end:
2030
2047
  top = stack.pop()
2031
2048
  stack[-1].append(top)
@@ -2158,7 +2175,7 @@ class Address:
2158
2175
 
2159
2176
  def __init__(
2160
2177
  self,
2161
- address: Union[bytes, str],
2178
+ address: bytes | str,
2162
2179
  address_type: AddressType = RANDOM_DEVICE_ADDRESS,
2163
2180
  ) -> None:
2164
2181
  '''
@@ -2423,9 +2440,9 @@ class HCI_Command(HCI_Packet):
2423
2440
 
2424
2441
  def __init__(
2425
2442
  self,
2426
- parameters: Optional[bytes] = None,
2443
+ parameters: bytes | None = None,
2427
2444
  *,
2428
- op_code: Optional[int] = None,
2445
+ op_code: int | None = None,
2429
2446
  **kwargs,
2430
2447
  ) -> None:
2431
2448
  # op_code should be set in cls.
@@ -5716,7 +5733,7 @@ class HCI_Event(HCI_Packet):
5716
5733
  hci_packet_type = HCI_EVENT_PACKET
5717
5734
  event_names: dict[int, str] = {}
5718
5735
  event_classes: dict[int, type[HCI_Event]] = {}
5719
- vendor_factories: list[Callable[[bytes], Optional[HCI_Event]]] = []
5736
+ vendor_factories: list[Callable[[bytes], HCI_Event | None]] = []
5720
5737
  event_code: int
5721
5738
  fields: Fields = ()
5722
5739
  _parameters: bytes = b''
@@ -5777,14 +5794,12 @@ class HCI_Event(HCI_Packet):
5777
5794
  return event_class
5778
5795
 
5779
5796
  @classmethod
5780
- def add_vendor_factory(
5781
- cls, factory: Callable[[bytes], Optional[HCI_Event]]
5782
- ) -> None:
5797
+ def add_vendor_factory(cls, factory: Callable[[bytes], HCI_Event | None]) -> None:
5783
5798
  cls.vendor_factories.append(factory)
5784
5799
 
5785
5800
  @classmethod
5786
5801
  def remove_vendor_factory(
5787
- cls, factory: Callable[[bytes], Optional[HCI_Event]]
5802
+ cls, factory: Callable[[bytes], HCI_Event | None]
5788
5803
  ) -> None:
5789
5804
  if factory in cls.vendor_factories:
5790
5805
  cls.vendor_factories.remove(factory)
@@ -5797,7 +5812,7 @@ class HCI_Event(HCI_Packet):
5797
5812
  if len(parameters) != length:
5798
5813
  raise InvalidPacketError('invalid packet length')
5799
5814
 
5800
- subclass: Optional[type[HCI_Event]]
5815
+ subclass: type[HCI_Event] | None
5801
5816
  if event_code == HCI_LE_META_EVENT:
5802
5817
  # We do this dispatch here and not in the subclass in order to avoid call
5803
5818
  # loops
@@ -5835,9 +5850,9 @@ class HCI_Event(HCI_Packet):
5835
5850
 
5836
5851
  def __init__(
5837
5852
  self,
5838
- parameters: Optional[bytes] = None,
5853
+ parameters: bytes | None = None,
5839
5854
  *,
5840
- event_code: Optional[int] = None,
5855
+ event_code: int | None = None,
5841
5856
  **kwargs,
5842
5857
  ):
5843
5858
  if event_code is not None:
@@ -5946,9 +5961,7 @@ class HCI_Extended_Event(HCI_Event):
5946
5961
  cls.subevent_names.update(cls.subevent_map(symbols))
5947
5962
 
5948
5963
  @classmethod
5949
- def subclass_from_parameters(
5950
- cls, parameters: bytes
5951
- ) -> Optional[HCI_Extended_Event]:
5964
+ def subclass_from_parameters(cls, parameters: bytes) -> HCI_Extended_Event | None:
5952
5965
  """
5953
5966
  Factory method that parses the subevent code, finds a registered subclass,
5954
5967
  and creates an instance if found.
@@ -5968,9 +5981,9 @@ class HCI_Extended_Event(HCI_Event):
5968
5981
 
5969
5982
  def __init__(
5970
5983
  self,
5971
- parameters: Optional[bytes] = None,
5984
+ parameters: bytes | None = None,
5972
5985
  *,
5973
- subevent_code: Optional[int] = None,
5986
+ subevent_code: int | None = None,
5974
5987
  **kwargs,
5975
5988
  ) -> None:
5976
5989
  if subevent_code is not None:
@@ -6966,7 +6979,7 @@ class HCI_Command_Complete_Event(HCI_Event):
6966
6979
  command_opcode: int = field(
6967
6980
  metadata=metadata({'size': 2, 'mapper': HCI_Command.command_name})
6968
6981
  )
6969
- return_parameters: Union[bytes, HCI_Object, int] = field(metadata=metadata("*"))
6982
+ return_parameters: bytes | HCI_Object | int = field(metadata=metadata("*"))
6970
6983
 
6971
6984
  def map_return_parameters(self, return_parameters):
6972
6985
  '''Map simple 'status' return parameters to their named constant form'''
@@ -7550,20 +7563,20 @@ class HCI_IsoDataPacket(HCI_Packet):
7550
7563
  iso_sdu_fragment: bytes
7551
7564
  pb_flag: int
7552
7565
  ts_flag: int = 0
7553
- time_stamp: Optional[int] = None
7554
- packet_sequence_number: Optional[int] = None
7555
- iso_sdu_length: Optional[int] = None
7556
- packet_status_flag: Optional[int] = None
7566
+ time_stamp: int | None = None
7567
+ packet_sequence_number: int | None = None
7568
+ iso_sdu_length: int | None = None
7569
+ packet_status_flag: int | None = None
7557
7570
 
7558
7571
  def __post_init__(self) -> None:
7559
7572
  self.ts_flag = self.time_stamp is not None
7560
7573
 
7561
7574
  @staticmethod
7562
7575
  def from_bytes(packet: bytes) -> HCI_IsoDataPacket:
7563
- time_stamp: Optional[int] = None
7564
- packet_sequence_number: Optional[int] = None
7565
- iso_sdu_length: Optional[int] = None
7566
- packet_status_flag: Optional[int] = None
7576
+ time_stamp: int | None = None
7577
+ packet_sequence_number: int | None = None
7578
+ iso_sdu_length: int | None = None
7579
+ packet_status_flag: int | None = None
7567
7580
 
7568
7581
  pos = 1
7569
7582
  pdu_info, data_total_length = struct.unpack_from('<HH', packet, pos)
@@ -7646,7 +7659,7 @@ class HCI_IsoDataPacket(HCI_Packet):
7646
7659
 
7647
7660
  # -----------------------------------------------------------------------------
7648
7661
  class HCI_AclDataPacketAssembler:
7649
- current_data: Optional[bytes]
7662
+ current_data: bytes | None
7650
7663
 
7651
7664
  def __init__(self, callback: Callable[[bytes], Any]) -> None:
7652
7665
  self.callback = callback
bumble/helpers.py CHANGED
@@ -20,7 +20,7 @@ from __future__ import annotations
20
20
  import datetime
21
21
  import logging
22
22
  from collections.abc import Callable, MutableMapping
23
- from typing import Any, Optional, cast
23
+ from typing import Any, cast
24
24
 
25
25
  from bumble import avc, avctp, avdtp, avrcp, crypto, rfcomm, sdp
26
26
  from bumble.att import ATT_CID, ATT_PDU
@@ -70,7 +70,7 @@ AVCTP_PID_NAMES = {avrcp.AVRCP_PID: 'AVRCP'}
70
70
  class PacketTracer:
71
71
  class AclStream:
72
72
  psms: MutableMapping[int, int]
73
- peer: Optional[PacketTracer.AclStream]
73
+ peer: PacketTracer.AclStream | None
74
74
  avdtp_assemblers: MutableMapping[int, avdtp.MessageAssembler]
75
75
  avctp_assemblers: MutableMapping[int, avctp.MessageAssembler]
76
76
 
@@ -201,7 +201,7 @@ class PacketTracer:
201
201
  self.label = label
202
202
  self.emit_message = emit_message
203
203
  self.acl_streams = {} # ACL streams, by connection handle
204
- self.packet_timestamp: Optional[datetime.datetime] = None
204
+ self.packet_timestamp: datetime.datetime | None = None
205
205
 
206
206
  def start_acl_stream(self, connection_handle: int) -> PacketTracer.AclStream:
207
207
  logger.info(
@@ -230,7 +230,7 @@ class PacketTracer:
230
230
  self.peer.end_acl_stream(connection_handle)
231
231
 
232
232
  def on_packet(
233
- self, timestamp: Optional[datetime.datetime], packet: HCI_Packet
233
+ self, timestamp: datetime.datetime | None, packet: HCI_Packet
234
234
  ) -> None:
235
235
  self.packet_timestamp = timestamp
236
236
  self.emit(packet)
@@ -262,7 +262,7 @@ class PacketTracer:
262
262
  self,
263
263
  packet: HCI_Packet,
264
264
  direction: int = 0,
265
- timestamp: Optional[datetime.datetime] = None,
265
+ timestamp: datetime.datetime | None = None,
266
266
  ) -> None:
267
267
  if direction == 0:
268
268
  self.host_to_controller_analyzer.on_packet(timestamp, packet)
bumble/hfp.py CHANGED
@@ -25,7 +25,8 @@ import enum
25
25
  import logging
26
26
  import re
27
27
  import traceback
28
- from typing import TYPE_CHECKING, Any, ClassVar, Iterable, Optional, Union
28
+ from collections.abc import Iterable
29
+ from typing import TYPE_CHECKING, Any, ClassVar
29
30
 
30
31
  from typing_extensions import Self
31
32
 
@@ -80,7 +81,7 @@ class HfpProtocol:
80
81
 
81
82
  dlc.sink = self.feed
82
83
 
83
- def feed(self, data: Union[bytes, str]) -> None:
84
+ def feed(self, data: bytes | str) -> None:
84
85
  # Convert the data to a string if needed
85
86
  if isinstance(data, bytes):
86
87
  data = data.decode('utf-8')
@@ -324,8 +325,8 @@ class CallInfo:
324
325
  status: CallInfoStatus
325
326
  mode: CallInfoMode
326
327
  multi_party: CallInfoMultiParty
327
- number: Optional[str] = None
328
- type: Optional[int] = None
328
+ number: str | None = None
329
+ type: int | None = None
329
330
 
330
331
 
331
332
  @dataclasses.dataclass
@@ -353,10 +354,10 @@ class CallLineIdentification:
353
354
 
354
355
  number: str
355
356
  type: int
356
- subaddr: Optional[str] = None
357
- satype: Optional[int] = None
358
- alpha: Optional[str] = None
359
- cli_validity: Optional[int] = None
357
+ subaddr: str | None = None
358
+ satype: int | None = None
359
+ alpha: str | None = None
360
+ cli_validity: int | None = None
360
361
 
361
362
  @classmethod
362
363
  def parse_from(cls, parameters: list[bytes]) -> Self:
@@ -584,7 +585,7 @@ class AgIndicatorState:
584
585
  indicator: AgIndicator
585
586
  supported_values: set[int]
586
587
  current_status: int
587
- index: Optional[int] = None
588
+ index: int | None = None
588
589
  enabled: bool = True
589
590
 
590
591
  @property
@@ -597,7 +598,7 @@ class AgIndicatorState:
597
598
  supported_values_text = (
598
599
  f'({",".join(str(v) for v in self.supported_values)})'
599
600
  )
600
- return f'(\"{self.indicator.value}\",{supported_values_text})'
601
+ return f'("{self.indicator.value}",{supported_values_text})'
601
602
 
602
603
  @classmethod
603
604
  def call(cls: type[Self]) -> Self:
@@ -728,7 +729,7 @@ class HfProtocol(utils.EventEmitter):
728
729
  command_lock: asyncio.Lock
729
730
  if TYPE_CHECKING:
730
731
  response_queue: asyncio.Queue[AtResponse]
731
- unsolicited_queue: asyncio.Queue[Optional[AtResponse]]
732
+ unsolicited_queue: asyncio.Queue[AtResponse | None]
732
733
  else:
733
734
  response_queue: asyncio.Queue
734
735
  unsolicited_queue: asyncio.Queue
@@ -820,7 +821,7 @@ class HfProtocol(utils.EventEmitter):
820
821
  cmd: str,
821
822
  timeout: float = 1.0,
822
823
  response_type: AtResponseType = AtResponseType.NONE,
823
- ) -> Union[None, AtResponse, list[AtResponse]]:
824
+ ) -> None | AtResponse | list[AtResponse]:
824
825
  """
825
826
  Sends an AT command and wait for the peer response.
826
827
  Wait for the AT responses sent by the peer, to the status code.
@@ -1351,7 +1352,7 @@ class AgProtocol(utils.EventEmitter):
1351
1352
  logger.warning(f'AG indicator {indicator} is disabled')
1352
1353
 
1353
1354
  indicator_state.current_status = value
1354
- self.send_response(f'+CIEV: {index+1},{value}')
1355
+ self.send_response(f'+CIEV: {index + 1},{value}')
1355
1356
 
1356
1357
  async def negotiate_codec(self, codec: AudioCodec) -> None:
1357
1358
  """Starts codec negotiation."""
@@ -1411,13 +1412,13 @@ class AgProtocol(utils.EventEmitter):
1411
1412
  self.emit(self.EVENT_VOICE_RECOGNITION, VoiceRecognitionState(int(vrec)))
1412
1413
 
1413
1414
  def _on_chld(self, operation_code: bytes) -> None:
1414
- call_index: Optional[int] = None
1415
+ call_index: int | None = None
1415
1416
  if len(operation_code) > 1:
1416
1417
  call_index = int(operation_code[1:])
1417
1418
  operation_code = operation_code[:1] + b'x'
1418
1419
  try:
1419
1420
  operation = CallHoldOperation(operation_code.decode())
1420
- except:
1421
+ except Exception:
1421
1422
  logger.error(f'Invalid operation: {operation_code.decode()}')
1422
1423
  self.send_cme_error(CmeError.OPERATION_NOT_SUPPORTED)
1423
1424
  return
@@ -1481,8 +1482,8 @@ class AgProtocol(utils.EventEmitter):
1481
1482
  def _on_cmer(
1482
1483
  self,
1483
1484
  mode: bytes,
1484
- keypad: Optional[bytes] = None,
1485
- display: Optional[bytes] = None,
1485
+ keypad: bytes | None = None,
1486
+ display: bytes | None = None,
1486
1487
  indicator: bytes = b'',
1487
1488
  ) -> None:
1488
1489
  if (
@@ -1589,7 +1590,7 @@ class AgProtocol(utils.EventEmitter):
1589
1590
 
1590
1591
  def _on_clcc(self) -> None:
1591
1592
  for call in self.calls:
1592
- number_text = f',\"{call.number}\"' if call.number is not None else ''
1593
+ number_text = f',"{call.number}"' if call.number is not None else ''
1593
1594
  type_text = f',{call.type}' if call.type is not None else ''
1594
1595
  response = (
1595
1596
  f'+CLCC: {call.index}'
@@ -1844,7 +1845,7 @@ def make_ag_sdp_records(
1844
1845
 
1845
1846
  async def find_hf_sdp_record(
1846
1847
  connection: device.Connection,
1847
- ) -> Optional[tuple[int, ProfileVersion, HfSdpFeature]]:
1848
+ ) -> tuple[int, ProfileVersion, HfSdpFeature] | None:
1848
1849
  """Searches a Hands-Free SDP record from remote device.
1849
1850
 
1850
1851
  Args:
@@ -1864,9 +1865,9 @@ async def find_hf_sdp_record(
1864
1865
  ],
1865
1866
  )
1866
1867
  for attribute_lists in search_result:
1867
- channel: Optional[int] = None
1868
- version: Optional[ProfileVersion] = None
1869
- features: Optional[HfSdpFeature] = None
1868
+ channel: int | None = None
1869
+ version: ProfileVersion | None = None
1870
+ features: HfSdpFeature | None = None
1870
1871
  for attribute in attribute_lists:
1871
1872
  # The layout is [[L2CAP_PROTOCOL], [RFCOMM_PROTOCOL, RFCOMM_CHANNEL]].
1872
1873
  if attribute.id == sdp.SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID:
@@ -1896,7 +1897,7 @@ async def find_hf_sdp_record(
1896
1897
 
1897
1898
  async def find_ag_sdp_record(
1898
1899
  connection: device.Connection,
1899
- ) -> Optional[tuple[int, ProfileVersion, AgSdpFeature]]:
1900
+ ) -> tuple[int, ProfileVersion, AgSdpFeature] | None:
1900
1901
  """Searches an Audio-Gateway SDP record from remote device.
1901
1902
 
1902
1903
  Args:
@@ -1915,9 +1916,9 @@ async def find_ag_sdp_record(
1915
1916
  ],
1916
1917
  )
1917
1918
  for attribute_lists in search_result:
1918
- channel: Optional[int] = None
1919
- version: Optional[ProfileVersion] = None
1920
- features: Optional[AgSdpFeature] = None
1919
+ channel: int | None = None
1920
+ version: ProfileVersion | None = None
1921
+ features: AgSdpFeature | None = None
1921
1922
  for attribute in attribute_lists:
1922
1923
  # The layout is [[L2CAP_PROTOCOL], [RFCOMM_PROTOCOL, RFCOMM_CHANNEL]].
1923
1924
  if attribute.id == sdp.SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID:
bumble/hid.py CHANGED
@@ -21,8 +21,8 @@ import enum
21
21
  import logging
22
22
  import struct
23
23
  from abc import ABC, abstractmethod
24
+ from collections.abc import Callable
24
25
  from dataclasses import dataclass
25
- from typing import Callable, Optional
26
26
 
27
27
  from typing_extensions import override
28
28
 
@@ -195,9 +195,9 @@ class SendHandshakeMessage(Message):
195
195
 
196
196
  # -----------------------------------------------------------------------------
197
197
  class HID(ABC, utils.EventEmitter):
198
- l2cap_ctrl_channel: Optional[l2cap.ClassicChannel] = None
199
- l2cap_intr_channel: Optional[l2cap.ClassicChannel] = None
200
- connection: Optional[device.Connection] = None
198
+ l2cap_ctrl_channel: l2cap.ClassicChannel | None = None
199
+ l2cap_intr_channel: l2cap.ClassicChannel | None = None
200
+ connection: device.Connection | None = None
201
201
 
202
202
  EVENT_INTERRUPT_DATA = "interrupt_data"
203
203
  EVENT_CONTROL_DATA = "control_data"
@@ -212,7 +212,7 @@ class HID(ABC, utils.EventEmitter):
212
212
 
213
213
  def __init__(self, device: device.Device, role: Role) -> None:
214
214
  super().__init__()
215
- self.remote_device_bd_address: Optional[Address] = None
215
+ self.remote_device_bd_address: Address | None = None
216
216
  self.device = device
217
217
  self.role = role
218
218
 
@@ -353,10 +353,10 @@ class Device(HID):
353
353
  data: bytes = b''
354
354
  status: int = 0
355
355
 
356
- get_report_cb: Optional[Callable[[int, int, int], GetSetStatus]] = None
357
- set_report_cb: Optional[Callable[[int, int, int, bytes], GetSetStatus]] = None
358
- get_protocol_cb: Optional[Callable[[], GetSetStatus]] = None
359
- set_protocol_cb: Optional[Callable[[int], GetSetStatus]] = None
356
+ get_report_cb: Callable[[int, int, int], GetSetStatus] | None = None
357
+ set_report_cb: Callable[[int, int, int, bytes], GetSetStatus] | None = None
358
+ get_protocol_cb: Callable[[], GetSetStatus] | None = None
359
+ set_protocol_cb: Callable[[int], GetSetStatus] | None = None
360
360
 
361
361
  def __init__(self, device: device.Device) -> None:
362
362
  super().__init__(device, HID.Role.DEVICE)