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/apps/bench.py CHANGED
@@ -22,7 +22,6 @@ import logging
22
22
  import statistics
23
23
  import struct
24
24
  import time
25
- from typing import Optional
26
25
 
27
26
  import click
28
27
 
@@ -257,8 +256,8 @@ async def pre_power_on(device: Device, classic: bool) -> None:
257
256
 
258
257
  async def post_power_on(
259
258
  device: Device,
260
- le_scan: Optional[tuple[int, int]],
261
- le_advertise: Optional[int],
259
+ le_scan: tuple[int, int] | None,
260
+ le_advertise: int | None,
262
261
  classic_page_scan: bool,
263
262
  classic_inquiry_scan: bool,
264
263
  ) -> None:
@@ -1300,7 +1299,7 @@ class IsoClient(StreamedPacketIO):
1300
1299
  super().__init__()
1301
1300
  self.device = device
1302
1301
  self.ready = asyncio.Event()
1303
- self.cis_link: Optional[CisLink] = None
1302
+ self.cis_link: CisLink | None = None
1304
1303
 
1305
1304
  async def on_connection(
1306
1305
  self, connection: Connection, cis_link: CisLink, sender: bool
@@ -1341,7 +1340,7 @@ class IsoServer(StreamedPacketIO):
1341
1340
  ):
1342
1341
  super().__init__()
1343
1342
  self.device = device
1344
- self.cis_link: Optional[CisLink] = None
1343
+ self.cis_link: CisLink | None = None
1345
1344
  self.ready = asyncio.Event()
1346
1345
 
1347
1346
  logging.info(
bumble/apps/console.py CHANGED
@@ -24,7 +24,6 @@ import logging
24
24
  import os
25
25
  import re
26
26
  from collections import OrderedDict
27
- from typing import Optional, Union
28
27
 
29
28
  import click
30
29
  import humanize
@@ -126,8 +125,8 @@ def parse_phys(phys):
126
125
  # Console App
127
126
  # -----------------------------------------------------------------------------
128
127
  class ConsoleApp:
129
- connected_peer: Optional[Peer]
130
- connection_phy: Optional[ConnectionPHY]
128
+ connected_peer: Peer | None
129
+ connection_phy: ConnectionPHY | None
131
130
 
132
131
  def __init__(self):
133
132
  self.known_addresses = set()
@@ -520,7 +519,7 @@ class ConsoleApp:
520
519
 
521
520
  self.show_attributes(attributes)
522
521
 
523
- def find_remote_characteristic(self, param) -> Optional[CharacteristicProxy]:
522
+ def find_remote_characteristic(self, param) -> CharacteristicProxy | None:
524
523
  if not self.connected_peer:
525
524
  return None
526
525
  parts = param.split('.')
@@ -542,9 +541,7 @@ class ConsoleApp:
542
541
 
543
542
  return None
544
543
 
545
- def find_local_attribute(
546
- self, param
547
- ) -> Optional[Union[Characteristic, Descriptor]]:
544
+ def find_local_attribute(self, param) -> Characteristic | Descriptor | None:
548
545
  parts = param.split('.')
549
546
  if len(parts) == 3:
550
547
  service_uuid = UUID(parts[0])
@@ -1096,9 +1093,7 @@ class DeviceListener(Device.Listener, Connection.Listener):
1096
1093
  if self.app.connected_peer.connection.is_encrypted
1097
1094
  else 'not encrypted'
1098
1095
  )
1099
- self.app.append_to_output(
1100
- 'connection encryption change: ' f'{encryption_state}'
1101
- )
1096
+ self.app.append_to_output(f'connection encryption change: {encryption_state}')
1102
1097
 
1103
1098
  def on_connection_data_length_change(self):
1104
1099
  self.app.append_to_output(
@@ -35,8 +35,6 @@ from bumble.hci import (
35
35
  HCI_READ_BUFFER_SIZE_COMMAND,
36
36
  HCI_READ_LOCAL_NAME_COMMAND,
37
37
  HCI_SUCCESS,
38
- HCI_VERSION_NAMES,
39
- LMP_VERSION_NAMES,
40
38
  CodecID,
41
39
  HCI_Command,
42
40
  HCI_Command_Complete_Event,
@@ -54,6 +52,7 @@ from bumble.hci import (
54
52
  HCI_Read_Local_Supported_Codecs_V2_Command,
55
53
  HCI_Read_Local_Version_Information_Command,
56
54
  LeFeature,
55
+ SpecificationVersion,
57
56
  map_null_terminated_utf8_string,
58
57
  )
59
58
  from bumble.host import Host
@@ -275,7 +274,7 @@ async def async_main(
275
274
  (
276
275
  f'min={min(latencies):.2f}, '
277
276
  f'max={max(latencies):.2f}, '
278
- f'average={sum(latencies)/len(latencies):.2f},'
277
+ f'average={sum(latencies) / len(latencies):.2f},'
279
278
  ),
280
279
  [f'{latency:.4}' for latency in latencies],
281
280
  '\n',
@@ -289,14 +288,20 @@ async def async_main(
289
288
  )
290
289
  print(
291
290
  color(' HCI Version: ', 'green'),
292
- name_or_number(HCI_VERSION_NAMES, host.local_version.hci_version),
291
+ SpecificationVersion(host.local_version.hci_version).name,
292
+ )
293
+ print(
294
+ color(' HCI Subversion:', 'green'),
295
+ f'0x{host.local_version.hci_subversion:04x}',
293
296
  )
294
- print(color(' HCI Subversion:', 'green'), host.local_version.hci_subversion)
295
297
  print(
296
298
  color(' LMP Version: ', 'green'),
297
- name_or_number(LMP_VERSION_NAMES, host.local_version.lmp_version),
299
+ SpecificationVersion(host.local_version.lmp_version).name,
300
+ )
301
+ print(
302
+ color(' LMP Subversion:', 'green'),
303
+ f'0x{host.local_version.lmp_subversion:04x}',
298
304
  )
299
- print(color(' LMP Subversion:', 'green'), host.local_version.lmp_subversion)
300
305
 
301
306
  # Get the Classic info
302
307
  await get_classic_info(host)
@@ -17,7 +17,6 @@
17
17
  # -----------------------------------------------------------------------------
18
18
  import asyncio
19
19
  import time
20
- from typing import Optional
21
20
 
22
21
  import click
23
22
 
@@ -41,7 +40,7 @@ class Loopback:
41
40
  self.transport = transport
42
41
  self.packet_size = packet_size
43
42
  self.packet_count = packet_count
44
- self.connection_handle: Optional[int] = None
43
+ self.connection_handle: int | None = None
45
44
  self.connection_event = asyncio.Event()
46
45
  self.done = asyncio.Event()
47
46
  self.expected_cid = 0
@@ -16,7 +16,7 @@
16
16
  # Imports
17
17
  # -----------------------------------------------------------------------------
18
18
  import asyncio
19
- from typing import Callable, Iterable, Optional
19
+ from collections.abc import Callable, Iterable
20
20
 
21
21
  import click
22
22
 
@@ -174,7 +174,7 @@ async def show_vcs(vcs: VolumeControlServiceProxy) -> None:
174
174
 
175
175
 
176
176
  # -----------------------------------------------------------------------------
177
- async def show_device_info(peer, done: Optional[asyncio.Future]) -> None:
177
+ async def show_device_info(peer, done: asyncio.Future | None) -> None:
178
178
  try:
179
179
  # Discover all services
180
180
  print(color('### Discovering Services and Characteristics', 'magenta'))
@@ -215,7 +215,6 @@ async def show_device_info(peer, done: Optional[asyncio.Future]) -> None:
215
215
  # -----------------------------------------------------------------------------
216
216
  async def async_main(device_config, encrypt, transport, address_or_name):
217
217
  async with await open_transport(transport) as (hci_source, hci_sink):
218
-
219
218
  # Create a device
220
219
  if device_config:
221
220
  device = Device.from_config_file_with_hci(
bumble/apps/gatt_dump.py CHANGED
@@ -61,7 +61,6 @@ async def dump_gatt_db(peer, done):
61
61
  # -----------------------------------------------------------------------------
62
62
  async def async_main(device_config, encrypt, transport, address_or_name):
63
63
  async with await open_transport(transport) as (hci_source, hci_sink):
64
-
65
64
  # Create a device
66
65
  if device_config:
67
66
  device = Device.from_config_file_with_hci(
@@ -268,7 +268,6 @@ class UiServer:
268
268
 
269
269
  # -----------------------------------------------------------------------------
270
270
  class Speaker:
271
-
272
271
  def __init__(
273
272
  self,
274
273
  device_config_path: str | None,
@@ -299,6 +298,7 @@ class Speaker:
299
298
  advertising_interval_max=25,
300
299
  address=Address('F1:F2:F3:F4:F5:F6'),
301
300
  identity_address_type=Address.RANDOM_DEVICE_ADDRESS,
301
+ eatt_enabled=True,
302
302
  )
303
303
 
304
304
  device_config.le_enabled = True
bumble/apps/pair.py CHANGED
@@ -15,10 +15,11 @@
15
15
  # -----------------------------------------------------------------------------
16
16
  # Imports
17
17
  # -----------------------------------------------------------------------------
18
+ from __future__ import annotations
19
+
18
20
  import asyncio
19
21
  import logging
20
22
  import os
21
- import struct
22
23
 
23
24
  import click
24
25
  from prompt_toolkit.shortcuts import PromptSession
@@ -64,7 +65,7 @@ POST_PAIRING_DELAY = 1
64
65
 
65
66
  # -----------------------------------------------------------------------------
66
67
  class Waiter:
67
- instance = None
68
+ instance: Waiter | None = None
68
69
 
69
70
  def __init__(self, linger=False):
70
71
  self.done = asyncio.get_running_loop().create_future()
@@ -328,25 +329,25 @@ async def on_pairing_failure(connection, reason):
328
329
 
329
330
  # -----------------------------------------------------------------------------
330
331
  async def pair(
331
- mode,
332
- sc,
333
- mitm,
334
- bond,
335
- ctkd,
336
- advertising_address,
337
- identity_address,
338
- linger,
339
- io,
340
- oob,
341
- prompt,
342
- request,
343
- print_keys,
344
- keystore_file,
345
- advertise_service_uuids,
346
- advertise_appearance,
347
- device_config,
348
- hci_transport,
349
- address_or_name,
332
+ mode: str,
333
+ sc: bool,
334
+ mitm: bool,
335
+ bond: bool,
336
+ ctkd: bool,
337
+ advertising_address: str,
338
+ identity_address: str,
339
+ linger: bool,
340
+ io: str,
341
+ oob: str,
342
+ prompt: bool,
343
+ request: bool,
344
+ print_keys: bool,
345
+ keystore_file: str,
346
+ advertise_service_uuids: str,
347
+ advertise_appearance: str,
348
+ device_config: str,
349
+ hci_transport: str,
350
+ address_or_name: str,
350
351
  ):
351
352
  Waiter.instance = Waiter(linger=linger)
352
353
 
@@ -404,6 +405,7 @@ async def pair(
404
405
  # Create an OOB context if needed
405
406
  if oob:
406
407
  our_oob_context = OobContext()
408
+ legacy_context: OobLegacyContext | None
407
409
  if oob == '-':
408
410
  shared_data = None
409
411
  legacy_context = OobLegacyContext()
@@ -528,7 +530,9 @@ async def pair(
528
530
  if advertise_appearance:
529
531
  advertise_appearance = advertise_appearance.upper()
530
532
  try:
531
- advertise_appearance_int = int(advertise_appearance)
533
+ appearance = data_types.Appearance.from_int(
534
+ int(advertise_appearance)
535
+ )
532
536
  except ValueError:
533
537
  category, subcategory = advertise_appearance.split('/')
534
538
  try:
@@ -546,12 +550,11 @@ async def pair(
546
550
  except ValueError:
547
551
  print(color(f'Invalid subcategory {subcategory}', 'red'))
548
552
  return
549
- advertise_appearance_int = int(
550
- Appearance(category_enum, subcategory_enum)
553
+ appearance = data_types.Appearance(
554
+ category_enum, subcategory_enum
551
555
  )
552
- advertising_data_types.append(
553
- data_types.Appearance(category_enum, subcategory_enum)
554
- )
556
+
557
+ advertising_data_types.append(appearance)
555
558
  device.advertising_data = bytes(AdvertisingData(advertising_data_types))
556
559
  await device.start_advertising(
557
560
  auto_restart=True,
@@ -661,25 +664,25 @@ class LogHandler(logging.Handler):
661
664
  @click.argument('hci_transport')
662
665
  @click.argument('address-or-name', required=False)
663
666
  def main(
664
- mode,
665
- sc,
666
- mitm,
667
- bond,
668
- ctkd,
669
- advertising_address,
670
- identity_address,
671
- linger,
672
- io,
673
- oob,
674
- prompt,
675
- request,
676
- print_keys,
677
- keystore_file,
678
- advertise_service_uuid,
679
- advertise_appearance,
680
- device_config,
681
- hci_transport,
682
- address_or_name,
667
+ mode: str,
668
+ sc: bool,
669
+ mitm: bool,
670
+ bond: bool,
671
+ ctkd: bool,
672
+ advertising_address: str,
673
+ identity_address: str,
674
+ linger: bool,
675
+ io: str,
676
+ oob: str,
677
+ prompt: bool,
678
+ request: bool,
679
+ print_keys: bool,
680
+ keystore_file: str,
681
+ advertise_service_uuid: str,
682
+ advertise_appearance: str,
683
+ device_config: str,
684
+ hci_transport: str,
685
+ address_or_name: str,
683
686
  ):
684
687
  # Setup logging
685
688
  log_handler = LogHandler()
@@ -19,7 +19,7 @@ ROOTCANAL_PORT_CUTTLEFISH = 7300
19
19
  @click.option(
20
20
  '--transport',
21
21
  help='HCI transport',
22
- default=f'tcp-client:127.0.0.1:<rootcanal-port>',
22
+ default='tcp-client:127.0.0.1:<rootcanal-port>',
23
23
  )
24
24
  @click.option(
25
25
  '--config',
@@ -44,7 +44,7 @@ def retrieve_config(config: str) -> dict[str, Any]:
44
44
  if not config:
45
45
  return {}
46
46
 
47
- with open(config, 'r') as f:
47
+ with open(config) as f:
48
48
  return json.load(f)
49
49
 
50
50
 
@@ -19,7 +19,6 @@ from __future__ import annotations
19
19
 
20
20
  import asyncio
21
21
  import logging
22
- from typing import Optional, Union
23
22
 
24
23
  import click
25
24
 
@@ -47,14 +46,13 @@ from bumble.avdtp import (
47
46
  AVDTP_DELAY_REPORTING_SERVICE_CATEGORY,
48
47
  MediaCodecCapabilities,
49
48
  MediaPacketPump,
49
+ find_avdtp_service_with_connection,
50
50
  )
51
51
  from bumble.avdtp import Protocol as AvdtpProtocol
52
- from bumble.avdtp import find_avdtp_service_with_connection
53
52
  from bumble.avrcp import Protocol as AvrcpProtocol
54
53
  from bumble.colors import color
55
- from bumble.core import AdvertisingData
54
+ from bumble.core import AdvertisingData, DeviceClass, PhysicalTransport
56
55
  from bumble.core import ConnectionError as BumbleConnectionError
57
- from bumble.core import DeviceClass, PhysicalTransport
58
56
  from bumble.device import Connection, Device, DeviceConfiguration
59
57
  from bumble.hci import HCI_CONNECTION_ALREADY_EXISTS_ERROR, Address, HCI_Constant
60
58
  from bumble.pairing import PairingConfig
@@ -191,7 +189,7 @@ class Player:
191
189
  def __init__(
192
190
  self,
193
191
  transport: str,
194
- device_config: Optional[str],
192
+ device_config: str | None,
195
193
  authenticate: bool,
196
194
  encrypt: bool,
197
195
  ) -> None:
@@ -199,8 +197,8 @@ class Player:
199
197
  self.device_config = device_config
200
198
  self.authenticate = authenticate
201
199
  self.encrypt = encrypt
202
- self.avrcp_protocol: Optional[AvrcpProtocol] = None
203
- self.done: Optional[asyncio.Event]
200
+ self.avrcp_protocol: AvrcpProtocol | None = None
201
+ self.done: asyncio.Event | None
204
202
 
205
203
  async def run(self, workload) -> None:
206
204
  self.done = asyncio.Event()
@@ -315,7 +313,7 @@ class Player:
315
313
  codec_type: int,
316
314
  vendor_id: int,
317
315
  codec_id: int,
318
- packet_source: Union[SbcPacketSource, AacPacketSource, OpusPacketSource],
316
+ packet_source: SbcPacketSource | AacPacketSource | OpusPacketSource,
319
317
  codec_capabilities: MediaCodecCapabilities,
320
318
  ):
321
319
  # Discover all endpoints on the remote device
@@ -381,11 +379,11 @@ class Player:
381
379
  print(f">>> {color(address.to_string(False), 'yellow')}:")
382
380
  print(f" Device Class (raw): {class_of_device:06X}")
383
381
  major_class_name = DeviceClass.major_device_class_name(major_device_class)
384
- print(" Device Major Class: " f"{major_class_name}")
382
+ print(f" Device Major Class: {major_class_name}")
385
383
  minor_class_name = DeviceClass.minor_device_class_name(
386
384
  major_device_class, minor_device_class
387
385
  )
388
- print(" Device Minor Class: " f"{minor_class_name}")
386
+ print(f" Device Minor Class: {minor_class_name}")
389
387
  print(
390
388
  " Device Services: "
391
389
  f"{', '.join(DeviceClass.service_class_labels(service_classes))}"
@@ -420,7 +418,7 @@ class Player:
420
418
  async def play(
421
419
  self,
422
420
  device: Device,
423
- address: Optional[str],
421
+ address: str | None,
424
422
  audio_format: str,
425
423
  audio_file: str,
426
424
  ) -> None:
@@ -449,7 +447,7 @@ class Player:
449
447
  return input_file.read(byte_count)
450
448
 
451
449
  # Obtain the codec capabilities from the stream
452
- packet_source: Union[SbcPacketSource, AacPacketSource, OpusPacketSource]
450
+ packet_source: SbcPacketSource | AacPacketSource | OpusPacketSource
453
451
  vendor_id = 0
454
452
  codec_id = 0
455
453
  if audio_format == "sbc":
@@ -17,7 +17,6 @@
17
17
  # -----------------------------------------------------------------------------
18
18
  import asyncio
19
19
  import time
20
- from typing import Optional
21
20
 
22
21
  import click
23
22
 
@@ -82,14 +81,14 @@ class ServerBridge:
82
81
  def __init__(
83
82
  self, channel: int, uuid: str, trace: bool, tcp_host: str, tcp_port: int
84
83
  ) -> None:
85
- self.device: Optional[Device] = None
84
+ self.device: Device | None = None
86
85
  self.channel = channel
87
86
  self.uuid = uuid
88
87
  self.tcp_host = tcp_host
89
88
  self.tcp_port = tcp_port
90
- self.rfcomm_channel: Optional[rfcomm.DLC] = None
91
- self.tcp_tracer: Optional[Tracer]
92
- self.rfcomm_tracer: Optional[Tracer]
89
+ self.rfcomm_channel: rfcomm.DLC | None = None
90
+ self.tcp_tracer: Tracer | None
91
+ self.rfcomm_tracer: Tracer | None
93
92
 
94
93
  if trace:
95
94
  self.tcp_tracer = Tracer(color("RFCOMM->TCP", "cyan"))
@@ -242,14 +241,14 @@ class ClientBridge:
242
241
  self.tcp_port = tcp_port
243
242
  self.authenticate = authenticate
244
243
  self.encrypt = encrypt
245
- self.device: Optional[Device] = None
246
- self.connection: Optional[Connection] = None
247
- self.rfcomm_client: Optional[rfcomm.Client]
248
- self.rfcomm_mux: Optional[rfcomm.Multiplexer]
244
+ self.device: Device | None = None
245
+ self.connection: Connection | None = None
246
+ self.rfcomm_client: rfcomm.Client | None
247
+ self.rfcomm_mux: rfcomm.Multiplexer | None
249
248
  self.tcp_connected: bool = False
250
249
 
251
- self.tcp_tracer: Optional[Tracer]
252
- self.rfcomm_tracer: Optional[Tracer]
250
+ self.tcp_tracer: Tracer | None
251
+ self.rfcomm_tracer: Tracer | None
253
252
 
254
253
  if trace:
255
254
  self.tcp_tracer = Tracer(color("RFCOMM->TCP", "cyan"))
bumble/apps/scan.py CHANGED
@@ -217,9 +217,7 @@ async def scan(
217
217
  @click.option(
218
218
  '--irk',
219
219
  metavar='<IRK_HEX>:<ADDRESS>',
220
- help=(
221
- 'Use this IRK for resolving private addresses ' '(may be used more than once)'
222
- ),
220
+ help=('Use this IRK for resolving private addresses (may be used more than once)'),
223
221
  multiple=True,
224
222
  )
225
223
  @click.option(
@@ -26,7 +26,6 @@ import pathlib
26
26
  import subprocess
27
27
  import weakref
28
28
  from importlib import resources
29
- from typing import Optional
30
29
 
31
30
  import aiohttp
32
31
  import click
@@ -156,7 +155,7 @@ class QueuedOutput(Output):
156
155
 
157
156
  packets: asyncio.Queue
158
157
  extractor: AudioExtractor
159
- packet_pump_task: Optional[asyncio.Task]
158
+ packet_pump_task: asyncio.Task | None
160
159
  started: bool
161
160
 
162
161
  def __init__(self, extractor):
@@ -230,8 +229,8 @@ class WebSocketOutput(QueuedOutput):
230
229
  class FfplayOutput(QueuedOutput):
231
230
  MAX_QUEUE_SIZE = 32768
232
231
 
233
- subprocess: Optional[asyncio.subprocess.Process]
234
- ffplay_task: Optional[asyncio.Task]
232
+ subprocess: asyncio.subprocess.Process | None
233
+ ffplay_task: asyncio.Task | None
235
234
 
236
235
  def __init__(self, codec: str) -> None:
237
236
  super().__init__(AudioExtractor.create(codec))
bumble/at.py CHANGED
@@ -12,7 +12,6 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- from typing import Union
16
15
 
17
16
  from bumble import core
18
17
 
@@ -36,7 +35,7 @@ def tokenize_parameters(buffer: bytes) -> list[bytes]:
36
35
 
37
36
  if in_quotes:
38
37
  token.extend(char)
39
- if char == b'\"':
38
+ if char == b'"':
40
39
  in_quotes = False
41
40
  tokens.append(token[1:-1])
42
41
  token = bytearray()
@@ -63,18 +62,18 @@ def tokenize_parameters(buffer: bytes) -> list[bytes]:
63
62
  return [bytes(token) for token in tokens if len(token) > 0]
64
63
 
65
64
 
66
- def parse_parameters(buffer: bytes) -> list[Union[bytes, list]]:
65
+ def parse_parameters(buffer: bytes) -> list[bytes | list]:
67
66
  """Parse the parameters using the comma and parenthesis separators.
68
67
  Raises AtParsingError in case of invalid input string."""
69
68
 
70
69
  tokens = tokenize_parameters(buffer)
71
70
  accumulator: list[list] = [[]]
72
- current: Union[bytes, list] = bytes()
71
+ current: bytes | list = b''
73
72
 
74
73
  for token in tokens:
75
74
  if token == b',':
76
75
  accumulator[-1].append(current)
77
- current = bytes()
76
+ current = b''
78
77
  elif token == b'(':
79
78
  accumulator.append([])
80
79
  elif token == b')':