bumble 0.0.212__py3-none-any.whl → 0.0.213__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 (86) hide show
  1. bumble/_version.py +2 -2
  2. bumble/a2dp.py +6 -0
  3. bumble/apps/README.md +0 -3
  4. bumble/apps/auracast.py +11 -9
  5. bumble/apps/bench.py +480 -31
  6. bumble/apps/console.py +3 -3
  7. bumble/apps/controller_info.py +47 -10
  8. bumble/apps/controller_loopback.py +7 -3
  9. bumble/apps/controllers.py +2 -2
  10. bumble/apps/device_info.py +2 -2
  11. bumble/apps/gatt_dump.py +2 -2
  12. bumble/apps/gg_bridge.py +2 -2
  13. bumble/apps/hci_bridge.py +2 -2
  14. bumble/apps/l2cap_bridge.py +2 -2
  15. bumble/apps/lea_unicast/app.py +6 -1
  16. bumble/apps/pair.py +19 -11
  17. bumble/apps/pandora_server.py +2 -2
  18. bumble/apps/rfcomm_bridge.py +1 -1
  19. bumble/apps/scan.py +2 -2
  20. bumble/apps/show.py +4 -2
  21. bumble/apps/speaker/speaker.html +1 -0
  22. bumble/apps/speaker/speaker.js +113 -62
  23. bumble/apps/speaker/speaker.py +126 -18
  24. bumble/at.py +4 -4
  25. bumble/att.py +2 -6
  26. bumble/avc.py +7 -7
  27. bumble/avctp.py +3 -3
  28. bumble/avdtp.py +16 -20
  29. bumble/avrcp.py +41 -53
  30. bumble/colors.py +2 -2
  31. bumble/controller.py +84 -23
  32. bumble/device.py +348 -182
  33. bumble/drivers/__init__.py +2 -2
  34. bumble/drivers/common.py +0 -2
  35. bumble/drivers/intel.py +37 -40
  36. bumble/drivers/rtk.py +28 -35
  37. bumble/gatt.py +4 -4
  38. bumble/gatt_adapters.py +4 -5
  39. bumble/gatt_client.py +26 -31
  40. bumble/gatt_server.py +7 -11
  41. bumble/hci.py +2601 -2909
  42. bumble/helpers.py +4 -5
  43. bumble/hfp.py +32 -37
  44. bumble/host.py +94 -35
  45. bumble/keys.py +5 -5
  46. bumble/l2cap.py +310 -394
  47. bumble/link.py +6 -270
  48. bumble/pairing.py +23 -20
  49. bumble/pandora/__init__.py +1 -1
  50. bumble/pandora/config.py +2 -2
  51. bumble/pandora/device.py +6 -6
  52. bumble/pandora/host.py +27 -28
  53. bumble/pandora/l2cap.py +2 -2
  54. bumble/pandora/security.py +6 -6
  55. bumble/pandora/utils.py +3 -3
  56. bumble/profiles/ascs.py +132 -131
  57. bumble/profiles/asha.py +2 -2
  58. bumble/profiles/bap.py +3 -4
  59. bumble/profiles/csip.py +2 -2
  60. bumble/profiles/device_information_service.py +2 -2
  61. bumble/profiles/gap.py +2 -2
  62. bumble/profiles/hap.py +34 -33
  63. bumble/profiles/le_audio.py +4 -4
  64. bumble/profiles/mcp.py +4 -4
  65. bumble/profiles/vcs.py +3 -5
  66. bumble/rfcomm.py +10 -10
  67. bumble/rtp.py +1 -2
  68. bumble/sdp.py +2 -2
  69. bumble/smp.py +57 -61
  70. bumble/tools/rtk_util.py +2 -2
  71. bumble/transport/__init__.py +2 -16
  72. bumble/transport/android_netsim.py +5 -5
  73. bumble/transport/common.py +4 -4
  74. bumble/transport/pyusb.py +2 -2
  75. bumble/utils.py +2 -5
  76. bumble/vendor/android/hci.py +118 -200
  77. bumble/vendor/zephyr/hci.py +32 -27
  78. {bumble-0.0.212.dist-info → bumble-0.0.213.dist-info}/METADATA +2 -2
  79. {bumble-0.0.212.dist-info → bumble-0.0.213.dist-info}/RECORD +83 -86
  80. {bumble-0.0.212.dist-info → bumble-0.0.213.dist-info}/WHEEL +1 -1
  81. {bumble-0.0.212.dist-info → bumble-0.0.213.dist-info}/entry_points.txt +0 -1
  82. bumble/apps/link_relay/__init__.py +0 -0
  83. bumble/apps/link_relay/link_relay.py +0 -289
  84. bumble/apps/link_relay/logging.yml +0 -21
  85. {bumble-0.0.212.dist-info → bumble-0.0.213.dist-info}/licenses/LICENSE +0 -0
  86. {bumble-0.0.212.dist-info → bumble-0.0.213.dist-info}/top_level.txt +0 -0
bumble/link.py CHANGED
@@ -17,26 +17,20 @@
17
17
  # -----------------------------------------------------------------------------
18
18
  import logging
19
19
  import asyncio
20
- from functools import partial
21
20
 
22
- from bumble.core import (
23
- PhysicalTransport,
24
- InvalidStateError,
25
- )
26
- from bumble.colors import color
21
+ from bumble import core
27
22
  from bumble.hci import (
28
23
  Address,
29
24
  Role,
30
25
  HCI_SUCCESS,
31
26
  HCI_CONNECTION_ACCEPT_TIMEOUT_ERROR,
32
- HCI_CONNECTION_TIMEOUT_ERROR,
33
27
  HCI_UNKNOWN_CONNECTION_IDENTIFIER_ERROR,
34
28
  HCI_PAGE_TIMEOUT_ERROR,
35
29
  HCI_Connection_Complete_Event,
36
30
  )
37
31
  from bumble import controller
38
32
 
39
- from typing import Optional, Set
33
+ from typing import Optional
40
34
 
41
35
  # -----------------------------------------------------------------------------
42
36
  # Logging
@@ -65,7 +59,7 @@ class LocalLink:
65
59
  Link bus for controllers to communicate with each other
66
60
  '''
67
61
 
68
- controllers: Set[controller.Controller]
62
+ controllers: set[controller.Controller]
69
63
 
70
64
  def __init__(self):
71
65
  self.controllers = set()
@@ -115,10 +109,10 @@ class LocalLink:
115
109
 
116
110
  def send_acl_data(self, sender_controller, destination_address, transport, data):
117
111
  # Send the data to the first controller with a matching address
118
- if transport == PhysicalTransport.LE:
112
+ if transport == core.PhysicalTransport.LE:
119
113
  destination_controller = self.find_controller(destination_address)
120
114
  source_address = sender_controller.random_address
121
- elif transport == PhysicalTransport.BR_EDR:
115
+ elif transport == core.PhysicalTransport.BR_EDR:
122
116
  destination_controller = self.find_classic_controller(destination_address)
123
117
  source_address = sender_controller.public_address
124
118
  else:
@@ -274,7 +268,7 @@ class LocalLink:
274
268
 
275
269
  responder_controller.on_classic_connection_request(
276
270
  initiator_controller.public_address,
277
- HCI_Connection_Complete_Event.ACL_LINK_TYPE,
271
+ HCI_Connection_Complete_Event.LinkType.ACL,
278
272
  )
279
273
 
280
274
  def classic_accept_connection(
@@ -384,261 +378,3 @@ class LocalLink:
384
378
  responder_controller.on_classic_sco_connection_complete(
385
379
  initiator_controller.public_address, HCI_SUCCESS, link_type
386
380
  )
387
-
388
-
389
- # -----------------------------------------------------------------------------
390
- class RemoteLink:
391
- '''
392
- A Link implementation that communicates with other virtual controllers via a
393
- WebSocket relay
394
- '''
395
-
396
- def __init__(self, uri):
397
- self.controller = None
398
- self.uri = uri
399
- self.execution_queue = asyncio.Queue()
400
- self.websocket = asyncio.get_running_loop().create_future()
401
- self.rpc_result = None
402
- self.pending_connection = None
403
- self.central_connections = set() # List of addresses that we have connected to
404
- self.peripheral_connections = (
405
- set()
406
- ) # List of addresses that have connected to us
407
-
408
- # Connect and run asynchronously
409
- asyncio.create_task(self.run_connection())
410
- asyncio.create_task(self.run_executor_loop())
411
-
412
- def add_controller(self, controller):
413
- if self.controller:
414
- raise InvalidStateError('controller already set')
415
- self.controller = controller
416
-
417
- def remove_controller(self, controller):
418
- if self.controller != controller:
419
- raise InvalidStateError('controller mismatch')
420
- self.controller = None
421
-
422
- def get_pending_connection(self):
423
- return self.pending_connection
424
-
425
- def get_pending_classic_connection(self):
426
- return self.pending_classic_connection
427
-
428
- async def wait_until_connected(self):
429
- await self.websocket
430
-
431
- def execute(self, async_function):
432
- self.execution_queue.put_nowait(async_function())
433
-
434
- async def run_executor_loop(self):
435
- logger.debug('executor loop starting')
436
- while True:
437
- item = await self.execution_queue.get()
438
- try:
439
- await item
440
- except Exception as error:
441
- logger.warning(
442
- f'{color("!!! Exception in async handler:", "red")} {error}'
443
- )
444
-
445
- async def run_connection(self):
446
- import websockets # lazy import
447
-
448
- # Connect to the relay
449
- logger.debug(f'connecting to {self.uri}')
450
- # pylint: disable-next=no-member
451
- websocket = await websockets.connect(self.uri)
452
- self.websocket.set_result(websocket)
453
- logger.debug(f'connected to {self.uri}')
454
-
455
- while True:
456
- message = await websocket.recv()
457
- logger.debug(f'received message: {message}')
458
- keyword, *payload = message.split(':', 1)
459
-
460
- handler_name = f'on_{keyword}_received'
461
- handler = getattr(self, handler_name, None)
462
- if handler:
463
- await handler(payload[0] if payload else None)
464
-
465
- def close(self):
466
- if self.websocket.done():
467
- logger.debug('closing websocket')
468
- websocket = self.websocket.result()
469
- asyncio.create_task(websocket.close())
470
-
471
- async def on_result_received(self, result):
472
- if self.rpc_result:
473
- self.rpc_result.set_result(result)
474
-
475
- async def on_left_received(self, address):
476
- if address in self.central_connections:
477
- self.controller.on_link_peripheral_disconnected(Address(address))
478
- self.central_connections.remove(address)
479
-
480
- if address in self.peripheral_connections:
481
- self.controller.on_link_central_disconnected(
482
- address, HCI_CONNECTION_TIMEOUT_ERROR
483
- )
484
- self.peripheral_connections.remove(address)
485
-
486
- async def on_unreachable_received(self, target):
487
- await self.on_left_received(target)
488
-
489
- async def on_message_received(self, message):
490
- sender, *payload = message.split('/', 1)
491
- if payload:
492
- keyword, *payload = payload[0].split(':', 1)
493
- handler_name = f'on_{keyword}_message_received'
494
- handler = getattr(self, handler_name, None)
495
- if handler:
496
- await handler(sender, payload[0] if payload else None)
497
-
498
- async def on_advertisement_message_received(self, sender, advertisement):
499
- try:
500
- self.controller.on_link_advertising_data(
501
- Address(sender), bytes.fromhex(advertisement)
502
- )
503
- except Exception:
504
- logger.exception('exception')
505
-
506
- async def on_acl_message_received(self, sender, acl_data):
507
- try:
508
- self.controller.on_link_acl_data(Address(sender), bytes.fromhex(acl_data))
509
- except Exception:
510
- logger.exception('exception')
511
-
512
- async def on_connect_message_received(self, sender, _):
513
- # Remember the connection
514
- self.peripheral_connections.add(sender)
515
-
516
- # Notify the controller
517
- logger.debug(f'connection from central {sender}')
518
- self.controller.on_link_central_connected(Address(sender))
519
-
520
- # Accept the connection by responding to it
521
- await self.send_targeted_message(sender, 'connected')
522
-
523
- async def on_connected_message_received(self, sender, _):
524
- if not self.pending_connection:
525
- logger.warning('received a connection ack, but no connection is pending')
526
- return
527
-
528
- # Remember the connection
529
- self.central_connections.add(sender)
530
-
531
- # Notify the controller
532
- logger.debug(f'connected to peripheral {self.pending_connection.peer_address}')
533
- self.controller.on_link_peripheral_connection_complete(
534
- self.pending_connection, HCI_SUCCESS
535
- )
536
-
537
- async def on_disconnect_message_received(self, sender, message):
538
- # Notify the controller
539
- params = parse_parameters(message)
540
- reason = int(params.get('reason', str(HCI_CONNECTION_TIMEOUT_ERROR)))
541
- self.controller.on_link_central_disconnected(Address(sender), reason)
542
-
543
- # Forget the connection
544
- if sender in self.peripheral_connections:
545
- self.peripheral_connections.remove(sender)
546
-
547
- async def on_encrypted_message_received(self, sender, _):
548
- # TODO parse params to get real args
549
- self.controller.on_link_encrypted(Address(sender), bytes(8), 0, bytes(16))
550
-
551
- async def send_rpc_command(self, command):
552
- # Ensure we have a connection
553
- websocket = await self.websocket
554
-
555
- # Create a future value to hold the eventual result
556
- assert self.rpc_result is None
557
- self.rpc_result = asyncio.get_running_loop().create_future()
558
-
559
- # Send the command
560
- await websocket.send(command)
561
-
562
- # Wait for the result
563
- rpc_result = await self.rpc_result
564
- self.rpc_result = None
565
- logger.debug(f'rpc_result: {rpc_result}')
566
-
567
- # TODO: parse the result
568
-
569
- async def send_targeted_message(self, target, message):
570
- # Ensure we have a connection
571
- websocket = await self.websocket
572
-
573
- # Send the message
574
- await websocket.send(f'@{target} {message}')
575
-
576
- async def notify_address_changed(self):
577
- await self.send_rpc_command(f'/set-address {self.controller.random_address}')
578
-
579
- def on_address_changed(self, controller):
580
- logger.info(f'address changed for {controller}: {controller.random_address}')
581
-
582
- # Notify the relay of the change
583
- self.execute(self.notify_address_changed)
584
-
585
- async def send_advertising_data_to_relay(self, data):
586
- await self.send_targeted_message('*', f'advertisement:{data.hex()}')
587
-
588
- def send_advertising_data(self, _, data):
589
- self.execute(partial(self.send_advertising_data_to_relay, data))
590
-
591
- async def send_acl_data_to_relay(self, peer_address, data):
592
- await self.send_targeted_message(peer_address, f'acl:{data.hex()}')
593
-
594
- def send_acl_data(self, _, peer_address, _transport, data):
595
- # TODO: handle different transport
596
- self.execute(partial(self.send_acl_data_to_relay, peer_address, data))
597
-
598
- async def send_connection_request_to_relay(self, peer_address):
599
- await self.send_targeted_message(peer_address, 'connect')
600
-
601
- def connect(self, _, le_create_connection_command):
602
- if self.pending_connection:
603
- logger.warning('connection already pending')
604
- return
605
- self.pending_connection = le_create_connection_command
606
- self.execute(
607
- partial(
608
- self.send_connection_request_to_relay,
609
- str(le_create_connection_command.peer_address),
610
- )
611
- )
612
-
613
- def on_disconnection_complete(self, disconnect_command):
614
- self.controller.on_link_peripheral_disconnection_complete(
615
- disconnect_command, HCI_SUCCESS
616
- )
617
-
618
- def disconnect(self, central_address, peripheral_address, disconnect_command):
619
- logger.debug(
620
- f'disconnect {central_address} -> '
621
- f'{peripheral_address}: reason = {disconnect_command.reason}'
622
- )
623
- self.execute(
624
- partial(
625
- self.send_targeted_message,
626
- peripheral_address,
627
- f'disconnect:reason={disconnect_command.reason}',
628
- )
629
- )
630
- asyncio.get_running_loop().call_soon(
631
- self.on_disconnection_complete, disconnect_command
632
- )
633
-
634
- def on_connection_encrypted(self, _, peripheral_address, rand, ediv, ltk):
635
- asyncio.get_running_loop().call_soon(
636
- self.controller.on_link_encrypted, peripheral_address, rand, ediv, ltk
637
- )
638
- self.execute(
639
- partial(
640
- self.send_targeted_message,
641
- peripheral_address,
642
- f'encrypted:ltk={ltk.hex()}',
643
- )
644
- )
bumble/pairing.py CHANGED
@@ -18,15 +18,10 @@
18
18
  from __future__ import annotations
19
19
  import enum
20
20
  from dataclasses import dataclass
21
- from typing import Optional, Tuple
22
-
23
- from bumble.hci import (
24
- Address,
25
- HCI_NO_INPUT_NO_OUTPUT_IO_CAPABILITY,
26
- HCI_DISPLAY_ONLY_IO_CAPABILITY,
27
- HCI_DISPLAY_YES_NO_IO_CAPABILITY,
28
- HCI_KEYBOARD_ONLY_IO_CAPABILITY,
29
- )
21
+ import secrets
22
+ from typing import Optional
23
+
24
+ from bumble import hci
30
25
  from bumble.smp import (
31
26
  SMP_NO_INPUT_NO_OUTPUT_IO_CAPABILITY,
32
27
  SMP_KEYBOARD_ONLY_IO_CAPABILITY,
@@ -49,7 +44,7 @@ from bumble.core import AdvertisingData, LeRole
49
44
  class OobData:
50
45
  """OOB data that can be sent from one device to another."""
51
46
 
52
- address: Optional[Address] = None
47
+ address: Optional[hci.Address] = None
53
48
  role: Optional[LeRole] = None
54
49
  shared_data: Optional[OobSharedData] = None
55
50
  legacy_context: Optional[OobLegacyContext] = None
@@ -61,7 +56,7 @@ class OobData:
61
56
  shared_data_r: Optional[bytes] = None
62
57
  for ad_type, ad_data in ad.ad_structures:
63
58
  if ad_type == AdvertisingData.LE_BLUETOOTH_DEVICE_ADDRESS:
64
- instance.address = Address(ad_data)
59
+ instance.address = hci.Address(ad_data)
65
60
  elif ad_type == AdvertisingData.LE_ROLE:
66
61
  instance.role = LeRole(ad_data[0])
67
62
  elif ad_type == AdvertisingData.LE_SECURE_CONNECTIONS_CONFIRMATION_VALUE:
@@ -129,11 +124,11 @@ class PairingDelegate:
129
124
  # Default mapping from abstract to Classic I/O capabilities.
130
125
  # Subclasses may override this if they prefer a different mapping.
131
126
  CLASSIC_IO_CAPABILITIES_MAP = {
132
- NO_OUTPUT_NO_INPUT: HCI_NO_INPUT_NO_OUTPUT_IO_CAPABILITY,
133
- KEYBOARD_INPUT_ONLY: HCI_KEYBOARD_ONLY_IO_CAPABILITY,
134
- DISPLAY_OUTPUT_ONLY: HCI_DISPLAY_ONLY_IO_CAPABILITY,
135
- DISPLAY_OUTPUT_AND_YES_NO_INPUT: HCI_DISPLAY_YES_NO_IO_CAPABILITY,
136
- DISPLAY_OUTPUT_AND_KEYBOARD_INPUT: HCI_DISPLAY_YES_NO_IO_CAPABILITY,
127
+ NO_OUTPUT_NO_INPUT: hci.IoCapability.NO_INPUT_NO_OUTPUT,
128
+ KEYBOARD_INPUT_ONLY: hci.IoCapability.KEYBOARD_ONLY,
129
+ DISPLAY_OUTPUT_ONLY: hci.IoCapability.DISPLAY_ONLY,
130
+ DISPLAY_OUTPUT_AND_YES_NO_INPUT: hci.IoCapability.DISPLAY_YES_NO,
131
+ DISPLAY_OUTPUT_AND_KEYBOARD_INPUT: hci.IoCapability.DISPLAY_YES_NO,
137
132
  }
138
133
 
139
134
  io_capability: IoCapability
@@ -159,7 +154,7 @@ class PairingDelegate:
159
154
 
160
155
  # pylint: disable=line-too-long
161
156
  return self.CLASSIC_IO_CAPABILITIES_MAP.get(
162
- self.io_capability, HCI_NO_INPUT_NO_OUTPUT_IO_CAPABILITY
157
+ self.io_capability, hci.IoCapability.NO_INPUT_NO_OUTPUT
163
158
  )
164
159
 
165
160
  @property
@@ -205,7 +200,7 @@ class PairingDelegate:
205
200
  # [LE only]
206
201
  async def key_distribution_response(
207
202
  self, peer_initiator_key_distribution: int, peer_responder_key_distribution: int
208
- ) -> Tuple[int, int]:
203
+ ) -> tuple[int, int]:
209
204
  """
210
205
  Return the key distribution response in an SMP protocol context.
211
206
 
@@ -222,14 +217,22 @@ class PairingDelegate:
222
217
  ),
223
218
  )
224
219
 
220
+ async def generate_passkey(self) -> int:
221
+ """
222
+ Return a passkey value between 0 and 999999 (inclusive).
223
+ """
224
+
225
+ # By default, generate a random passkey.
226
+ return secrets.randbelow(1000000)
227
+
225
228
 
226
229
  # -----------------------------------------------------------------------------
227
230
  class PairingConfig:
228
231
  """Configuration for the Pairing protocol."""
229
232
 
230
233
  class AddressType(enum.IntEnum):
231
- PUBLIC = Address.PUBLIC_DEVICE_ADDRESS
232
- RANDOM = Address.RANDOM_DEVICE_ADDRESS
234
+ PUBLIC = hci.Address.PUBLIC_DEVICE_ADDRESS
235
+ RANDOM = hci.Address.RANDOM_DEVICE_ADDRESS
233
236
 
234
237
  @dataclass
235
238
  class OobConfig:
@@ -45,7 +45,7 @@ __all__ = [
45
45
 
46
46
 
47
47
  # Add servicers hooks.
48
- _SERVICERS_HOOKS: List[Callable[[PandoraDevice, Config, grpc.aio.Server], None]] = []
48
+ _SERVICERS_HOOKS: list[Callable[[PandoraDevice, Config, grpc.aio.Server], None]] = []
49
49
 
50
50
 
51
51
  def register_servicer_hook(
bumble/pandora/config.py CHANGED
@@ -15,7 +15,7 @@
15
15
  from __future__ import annotations
16
16
  from bumble.pairing import PairingConfig, PairingDelegate
17
17
  from dataclasses import dataclass
18
- from typing import Any, Dict
18
+ from typing import Any
19
19
 
20
20
 
21
21
  @dataclass
@@ -32,7 +32,7 @@ class Config:
32
32
  PairingDelegate.DEFAULT_KEY_DISTRIBUTION
33
33
  )
34
34
 
35
- def load_from_dict(self, config: Dict[str, Any]) -> None:
35
+ def load_from_dict(self, config: dict[str, Any]) -> None:
36
36
  io_capability_name: str = config.get(
37
37
  'io_capability', 'no_output_no_input'
38
38
  ).upper()
bumble/pandora/device.py CHANGED
@@ -32,7 +32,7 @@ from bumble.sdp import (
32
32
  DataElement,
33
33
  ServiceAttribute,
34
34
  )
35
- from typing import Any, Dict, List, Optional
35
+ from typing import Any, Optional
36
36
 
37
37
 
38
38
  # Default rootcanal HCI TCP address
@@ -49,13 +49,13 @@ class PandoraDevice:
49
49
 
50
50
  # Bumble device instance & configuration.
51
51
  device: Device
52
- config: Dict[str, Any]
52
+ config: dict[str, Any]
53
53
 
54
54
  # HCI transport name & instance.
55
55
  _hci_name: str
56
56
  _hci: Optional[transport.Transport] # type: ignore[name-defined]
57
57
 
58
- def __init__(self, config: Dict[str, Any]) -> None:
58
+ def __init__(self, config: dict[str, Any]) -> None:
59
59
  self.config = config
60
60
  self.device = _make_device(config)
61
61
  self._hci_name = config.get(
@@ -95,14 +95,14 @@ class PandoraDevice:
95
95
  await self.close()
96
96
  await self.open()
97
97
 
98
- def info(self) -> Optional[Dict[str, str]]:
98
+ def info(self) -> Optional[dict[str, str]]:
99
99
  return {
100
100
  'public_bd_address': str(self.device.public_address),
101
101
  'random_address': str(self.device.random_address),
102
102
  }
103
103
 
104
104
 
105
- def _make_device(config: Dict[str, Any]) -> Device:
105
+ def _make_device(config: dict[str, Any]) -> Device:
106
106
  """Initialize an idle Bumble device instance."""
107
107
 
108
108
  # initialize bumble device.
@@ -117,7 +117,7 @@ def _make_device(config: Dict[str, Any]) -> Device:
117
117
 
118
118
 
119
119
  # TODO(b/267540823): remove when Pandora A2dp is supported
120
- def _make_sdp_records(rfcomm_channel: int) -> Dict[int, List[ServiceAttribute]]:
120
+ def _make_sdp_records(rfcomm_channel: int) -> dict[int, list[ServiceAttribute]]:
121
121
  return {
122
122
  0x00010001: [
123
123
  ServiceAttribute(
bumble/pandora/host.py CHANGED
@@ -73,7 +73,6 @@ from pandora.host_pb2 import (
73
73
  ConnectResponse,
74
74
  DataTypes,
75
75
  DisconnectRequest,
76
- DiscoverabilityMode,
77
76
  InquiryResponse,
78
77
  PrimaryPhy,
79
78
  ReadLocalAddressResponse,
@@ -86,9 +85,9 @@ from pandora.host_pb2 import (
86
85
  WaitConnectionResponse,
87
86
  WaitDisconnectionRequest,
88
87
  )
89
- from typing import AsyncGenerator, Dict, List, Optional, Set, Tuple, cast
88
+ from typing import AsyncGenerator, Optional, cast
90
89
 
91
- PRIMARY_PHY_MAP: Dict[int, PrimaryPhy] = {
90
+ PRIMARY_PHY_MAP: dict[int, PrimaryPhy] = {
92
91
  # Default value reported by Bumble for legacy Advertising reports.
93
92
  # FIXME(uael): `None` might be a better value, but Bumble need to change accordingly.
94
93
  0: PRIMARY_1M,
@@ -96,26 +95,26 @@ PRIMARY_PHY_MAP: Dict[int, PrimaryPhy] = {
96
95
  3: PRIMARY_CODED,
97
96
  }
98
97
 
99
- SECONDARY_PHY_MAP: Dict[int, SecondaryPhy] = {
98
+ SECONDARY_PHY_MAP: dict[int, SecondaryPhy] = {
100
99
  0: SECONDARY_NONE,
101
100
  1: SECONDARY_1M,
102
101
  2: SECONDARY_2M,
103
102
  3: SECONDARY_CODED,
104
103
  }
105
104
 
106
- PRIMARY_PHY_TO_BUMBLE_PHY_MAP: Dict[PrimaryPhy, Phy] = {
105
+ PRIMARY_PHY_TO_BUMBLE_PHY_MAP: dict[PrimaryPhy, Phy] = {
107
106
  PRIMARY_1M: Phy.LE_1M,
108
107
  PRIMARY_CODED: Phy.LE_CODED,
109
108
  }
110
109
 
111
- SECONDARY_PHY_TO_BUMBLE_PHY_MAP: Dict[SecondaryPhy, Phy] = {
110
+ SECONDARY_PHY_TO_BUMBLE_PHY_MAP: dict[SecondaryPhy, Phy] = {
112
111
  SECONDARY_NONE: Phy.LE_1M,
113
112
  SECONDARY_1M: Phy.LE_1M,
114
113
  SECONDARY_2M: Phy.LE_2M,
115
114
  SECONDARY_CODED: Phy.LE_CODED,
116
115
  }
117
116
 
118
- OWN_ADDRESS_MAP: Dict[host_pb2.OwnAddressType, OwnAddressType] = {
117
+ OWN_ADDRESS_MAP: dict[host_pb2.OwnAddressType, OwnAddressType] = {
119
118
  host_pb2.PUBLIC: OwnAddressType.PUBLIC,
120
119
  host_pb2.RANDOM: OwnAddressType.RANDOM,
121
120
  host_pb2.RESOLVABLE_OR_PUBLIC: OwnAddressType.RESOLVABLE_OR_PUBLIC,
@@ -124,7 +123,7 @@ OWN_ADDRESS_MAP: Dict[host_pb2.OwnAddressType, OwnAddressType] = {
124
123
 
125
124
 
126
125
  class HostService(HostServicer):
127
- waited_connections: Set[int]
126
+ waited_connections: set[int]
128
127
 
129
128
  def __init__(
130
129
  self, grpc_server: grpc.aio.Server, device: Device, config: Config
@@ -618,7 +617,7 @@ class HostService(HostServicer):
618
617
  self.log.debug('Inquiry')
619
618
 
620
619
  inquiry_queue: asyncio.Queue[
621
- Optional[Tuple[Address, int, AdvertisingData, int]]
620
+ Optional[tuple[Address, int, AdvertisingData, int]]
622
621
  ] = asyncio.Queue()
623
622
  complete_handler = self.device.on(
624
623
  self.device.EVENT_INQUIRY_COMPLETE, lambda: inquiry_queue.put_nowait(None)
@@ -670,10 +669,10 @@ class HostService(HostServicer):
670
669
  return empty_pb2.Empty()
671
670
 
672
671
  def unpack_data_types(self, dt: DataTypes) -> AdvertisingData:
673
- ad_structures: List[Tuple[int, bytes]] = []
672
+ ad_structures: list[tuple[int, bytes]] = []
674
673
 
675
- uuids: List[str]
676
- datas: Dict[str, bytes]
674
+ uuids: list[str]
675
+ datas: dict[str, bytes]
677
676
 
678
677
  def uuid128_from_str(uuid: str) -> bytes:
679
678
  """Decode a 128-bit uuid encoded as XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
@@ -887,50 +886,50 @@ class HostService(HostServicer):
887
886
 
888
887
  def pack_data_types(self, ad: AdvertisingData) -> DataTypes:
889
888
  dt = DataTypes()
890
- uuids: List[UUID]
889
+ uuids: list[UUID]
891
890
  s: str
892
891
  i: int
893
- ij: Tuple[int, int]
894
- uuid_data: Tuple[UUID, bytes]
892
+ ij: tuple[int, int]
893
+ uuid_data: tuple[UUID, bytes]
895
894
  data: bytes
896
895
 
897
896
  if uuids := cast(
898
- List[UUID],
897
+ list[UUID],
899
898
  ad.get(AdvertisingData.INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS),
900
899
  ):
901
900
  dt.incomplete_service_class_uuids16.extend(
902
901
  list(map(lambda x: x.to_hex_str('-'), uuids))
903
902
  )
904
903
  if uuids := cast(
905
- List[UUID],
904
+ list[UUID],
906
905
  ad.get(AdvertisingData.COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS),
907
906
  ):
908
907
  dt.complete_service_class_uuids16.extend(
909
908
  list(map(lambda x: x.to_hex_str('-'), uuids))
910
909
  )
911
910
  if uuids := cast(
912
- List[UUID],
911
+ list[UUID],
913
912
  ad.get(AdvertisingData.INCOMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS),
914
913
  ):
915
914
  dt.incomplete_service_class_uuids32.extend(
916
915
  list(map(lambda x: x.to_hex_str('-'), uuids))
917
916
  )
918
917
  if uuids := cast(
919
- List[UUID],
918
+ list[UUID],
920
919
  ad.get(AdvertisingData.COMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS),
921
920
  ):
922
921
  dt.complete_service_class_uuids32.extend(
923
922
  list(map(lambda x: x.to_hex_str('-'), uuids))
924
923
  )
925
924
  if uuids := cast(
926
- List[UUID],
925
+ list[UUID],
927
926
  ad.get(AdvertisingData.INCOMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS),
928
927
  ):
929
928
  dt.incomplete_service_class_uuids128.extend(
930
929
  list(map(lambda x: x.to_hex_str('-'), uuids))
931
930
  )
932
931
  if uuids := cast(
933
- List[UUID],
932
+ list[UUID],
934
933
  ad.get(AdvertisingData.COMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS),
935
934
  ):
936
935
  dt.complete_service_class_uuids128.extend(
@@ -945,42 +944,42 @@ class HostService(HostServicer):
945
944
  if i := cast(int, ad.get(AdvertisingData.CLASS_OF_DEVICE)):
946
945
  dt.class_of_device = i
947
946
  if ij := cast(
948
- Tuple[int, int],
947
+ tuple[int, int],
949
948
  ad.get(AdvertisingData.PERIPHERAL_CONNECTION_INTERVAL_RANGE),
950
949
  ):
951
950
  dt.peripheral_connection_interval_min = ij[0]
952
951
  dt.peripheral_connection_interval_max = ij[1]
953
952
  if uuids := cast(
954
- List[UUID],
953
+ list[UUID],
955
954
  ad.get(AdvertisingData.LIST_OF_16_BIT_SERVICE_SOLICITATION_UUIDS),
956
955
  ):
957
956
  dt.service_solicitation_uuids16.extend(
958
957
  list(map(lambda x: x.to_hex_str('-'), uuids))
959
958
  )
960
959
  if uuids := cast(
961
- List[UUID],
960
+ list[UUID],
962
961
  ad.get(AdvertisingData.LIST_OF_32_BIT_SERVICE_SOLICITATION_UUIDS),
963
962
  ):
964
963
  dt.service_solicitation_uuids32.extend(
965
964
  list(map(lambda x: x.to_hex_str('-'), uuids))
966
965
  )
967
966
  if uuids := cast(
968
- List[UUID],
967
+ list[UUID],
969
968
  ad.get(AdvertisingData.LIST_OF_128_BIT_SERVICE_SOLICITATION_UUIDS),
970
969
  ):
971
970
  dt.service_solicitation_uuids128.extend(
972
971
  list(map(lambda x: x.to_hex_str('-'), uuids))
973
972
  )
974
973
  if uuid_data := cast(
975
- Tuple[UUID, bytes], ad.get(AdvertisingData.SERVICE_DATA_16_BIT_UUID)
974
+ tuple[UUID, bytes], ad.get(AdvertisingData.SERVICE_DATA_16_BIT_UUID)
976
975
  ):
977
976
  dt.service_data_uuid16[uuid_data[0].to_hex_str('-')] = uuid_data[1]
978
977
  if uuid_data := cast(
979
- Tuple[UUID, bytes], ad.get(AdvertisingData.SERVICE_DATA_32_BIT_UUID)
978
+ tuple[UUID, bytes], ad.get(AdvertisingData.SERVICE_DATA_32_BIT_UUID)
980
979
  ):
981
980
  dt.service_data_uuid32[uuid_data[0].to_hex_str('-')] = uuid_data[1]
982
981
  if uuid_data := cast(
983
- Tuple[UUID, bytes], ad.get(AdvertisingData.SERVICE_DATA_128_BIT_UUID)
982
+ tuple[UUID, bytes], ad.get(AdvertisingData.SERVICE_DATA_128_BIT_UUID)
984
983
  ):
985
984
  dt.service_data_uuid128[uuid_data[0].to_hex_str('-')] = uuid_data[1]
986
985
  if data := cast(bytes, ad.get(AdvertisingData.PUBLIC_TARGET_ADDRESS, raw=True)):