bumble 0.0.209__py3-none-any.whl → 0.0.211__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 (74) hide show
  1. bumble/_version.py +2 -2
  2. bumble/a2dp.py +7 -7
  3. bumble/apps/auracast.py +37 -29
  4. bumble/apps/bench.py +13 -9
  5. bumble/apps/console.py +1 -1
  6. bumble/apps/lea_unicast/app.py +6 -2
  7. bumble/apps/pair.py +4 -3
  8. bumble/apps/player/player.py +3 -3
  9. bumble/apps/rfcomm_bridge.py +1 -1
  10. bumble/apps/speaker/speaker.py +4 -2
  11. bumble/att.py +2 -3
  12. bumble/avc.py +5 -5
  13. bumble/avdtp.py +9 -10
  14. bumble/avrcp.py +18 -19
  15. bumble/bridge.py +2 -2
  16. bumble/controller.py +6 -7
  17. bumble/core.py +56 -56
  18. bumble/device.py +172 -137
  19. bumble/drivers/__init__.py +2 -2
  20. bumble/gap.py +1 -1
  21. bumble/gatt_adapters.py +3 -3
  22. bumble/gatt_client.py +27 -21
  23. bumble/gatt_server.py +9 -10
  24. bumble/hci.py +48 -22
  25. bumble/hfp.py +3 -3
  26. bumble/hid.py +4 -3
  27. bumble/host.py +22 -16
  28. bumble/keys.py +3 -3
  29. bumble/l2cap.py +19 -17
  30. bumble/link.py +3 -4
  31. bumble/pairing.py +3 -3
  32. bumble/pandora/__init__.py +5 -5
  33. bumble/pandora/host.py +18 -12
  34. bumble/pandora/l2cap.py +2 -2
  35. bumble/pandora/security.py +15 -16
  36. bumble/profiles/aics.py +6 -6
  37. bumble/profiles/ancs.py +9 -10
  38. bumble/profiles/ascs.py +17 -10
  39. bumble/profiles/asha.py +5 -5
  40. bumble/profiles/bass.py +1 -1
  41. bumble/profiles/csip.py +10 -10
  42. bumble/profiles/gatt_service.py +12 -12
  43. bumble/profiles/hap.py +16 -16
  44. bumble/profiles/mcp.py +26 -24
  45. bumble/profiles/pacs.py +6 -6
  46. bumble/profiles/pbp.py +1 -1
  47. bumble/profiles/vcs.py +6 -4
  48. bumble/profiles/vocs.py +3 -3
  49. bumble/rfcomm.py +8 -8
  50. bumble/sdp.py +1 -1
  51. bumble/smp.py +36 -30
  52. bumble/transport/__init__.py +24 -19
  53. bumble/transport/android_emulator.py +8 -4
  54. bumble/transport/android_netsim.py +8 -5
  55. bumble/transport/common.py +5 -1
  56. bumble/transport/file.py +1 -1
  57. bumble/transport/hci_socket.py +1 -1
  58. bumble/transport/pty.py +1 -1
  59. bumble/transport/pyusb.py +3 -3
  60. bumble/transport/serial.py +1 -1
  61. bumble/transport/tcp_client.py +1 -1
  62. bumble/transport/tcp_server.py +1 -1
  63. bumble/transport/udp.py +1 -1
  64. bumble/transport/unix.py +1 -1
  65. bumble/transport/vhci.py +2 -2
  66. bumble/transport/ws_client.py +6 -1
  67. bumble/transport/ws_server.py +1 -1
  68. bumble/utils.py +89 -76
  69. {bumble-0.0.209.dist-info → bumble-0.0.211.dist-info}/METADATA +3 -2
  70. {bumble-0.0.209.dist-info → bumble-0.0.211.dist-info}/RECORD +74 -74
  71. {bumble-0.0.209.dist-info → bumble-0.0.211.dist-info}/WHEEL +1 -1
  72. {bumble-0.0.209.dist-info → bumble-0.0.211.dist-info}/entry_points.txt +0 -0
  73. {bumble-0.0.209.dist-info → bumble-0.0.211.dist-info/licenses}/LICENSE +0 -0
  74. {bumble-0.0.209.dist-info → bumble-0.0.211.dist-info}/top_level.txt +0 -0
bumble/device.py CHANGED
@@ -49,16 +49,14 @@ from typing import (
49
49
  )
50
50
  from typing_extensions import Self
51
51
 
52
- from pyee import EventEmitter
53
-
54
- from .colors import color
55
- from .att import ATT_CID, ATT_DEFAULT_MTU, ATT_PDU
56
- from .gatt import Attribute, Characteristic, Descriptor, Service
57
- from .host import DataPacketQueue, Host
58
- from .profiles.gap import GenericAccessService
59
- from .core import (
60
- BT_BR_EDR_TRANSPORT,
61
- BT_LE_TRANSPORT,
52
+
53
+ from bumble.colors import color
54
+ from bumble.att import ATT_CID, ATT_DEFAULT_MTU, ATT_PDU
55
+ from bumble.gatt import Attribute, Characteristic, Descriptor, Service
56
+ from bumble.host import DataPacketQueue, Host
57
+ from bumble.profiles.gap import GenericAccessService
58
+ from bumble.core import (
59
+ PhysicalTransport,
62
60
  AdvertisingData,
63
61
  BaseBumbleError,
64
62
  ConnectionParameterUpdateError,
@@ -72,16 +70,8 @@ from .core import (
72
70
  OutOfResourcesError,
73
71
  UnreachableError,
74
72
  )
75
- from .utils import (
76
- AsyncRunner,
77
- CompositeEventEmitter,
78
- EventWatcher,
79
- setup_event_forwarding,
80
- composite_listener,
81
- deprecated,
82
- experimental,
83
- )
84
- from .keys import (
73
+ from bumble import utils
74
+ from bumble.keys import (
85
75
  KeyStore,
86
76
  PairingKeys,
87
77
  )
@@ -96,7 +86,7 @@ from bumble import core
96
86
  from bumble.profiles import gatt_service
97
87
 
98
88
  if TYPE_CHECKING:
99
- from .transport.common import TransportSource, TransportSink
89
+ from bumble.transport.common import TransportSource, TransportSink
100
90
 
101
91
 
102
92
  # -----------------------------------------------------------------------------
@@ -577,7 +567,7 @@ class PeriodicAdvertisingParameters:
577
567
 
578
568
  # -----------------------------------------------------------------------------
579
569
  @dataclass
580
- class AdvertisingSet(EventEmitter):
570
+ class AdvertisingSet(utils.EventEmitter):
581
571
  device: Device
582
572
  advertising_handle: int
583
573
  auto_restart: bool
@@ -794,13 +784,24 @@ class AdvertisingSet(EventEmitter):
794
784
  )
795
785
  del self.device.extended_advertising_sets[self.advertising_handle]
796
786
 
787
+ async def transfer_periodic_info(
788
+ self, connection: Connection, service_data: int = 0
789
+ ) -> None:
790
+ if not self.periodic_enabled:
791
+ raise core.InvalidStateError(
792
+ f"Periodic Advertising is not enabled on Advertising Set 0x{self.advertising_handle:02X}"
793
+ )
794
+ await connection.transfer_periodic_set_info(
795
+ self.advertising_handle, service_data
796
+ )
797
+
797
798
  def on_termination(self, status: int) -> None:
798
799
  self.enabled = False
799
800
  self.emit('termination', status)
800
801
 
801
802
 
802
803
  # -----------------------------------------------------------------------------
803
- class PeriodicAdvertisingSync(EventEmitter):
804
+ class PeriodicAdvertisingSync(utils.EventEmitter):
804
805
  class State(Enum):
805
806
  INIT = 0
806
807
  PENDING = 1
@@ -929,7 +930,7 @@ class PeriodicAdvertisingSync(EventEmitter):
929
930
  "received established event for cancelled sync, will terminate"
930
931
  )
931
932
  self.state = self.State.ESTABLISHED
932
- AsyncRunner.spawn(self.terminate())
933
+ utils.AsyncRunner.spawn(self.terminate())
933
934
  return
934
935
 
935
936
  if status == hci.HCI_SUCCESS:
@@ -1015,7 +1016,7 @@ class BigParameters:
1015
1016
 
1016
1017
  # -----------------------------------------------------------------------------
1017
1018
  @dataclass
1018
- class Big(EventEmitter):
1019
+ class Big(utils.EventEmitter):
1019
1020
  class State(IntEnum):
1020
1021
  PENDING = 0
1021
1022
  ACTIVE = 1
@@ -1055,7 +1056,7 @@ class Big(EventEmitter):
1055
1056
  logger.error('BIG %d is not active.', self.big_handle)
1056
1057
  return
1057
1058
 
1058
- with closing(EventWatcher()) as watcher:
1059
+ with closing(utils.EventWatcher()) as watcher:
1059
1060
  terminated = asyncio.Event()
1060
1061
  watcher.once(self, Big.Event.TERMINATION, lambda _: terminated.set())
1061
1062
  await self.device.send_command(
@@ -1078,7 +1079,7 @@ class BigSyncParameters:
1078
1079
 
1079
1080
  # -----------------------------------------------------------------------------
1080
1081
  @dataclass
1081
- class BigSync(EventEmitter):
1082
+ class BigSync(utils.EventEmitter):
1082
1083
  class State(IntEnum):
1083
1084
  PENDING = 0
1084
1085
  ACTIVE = 1
@@ -1113,7 +1114,7 @@ class BigSync(EventEmitter):
1113
1114
  logger.error('BIG Sync %d is not active.', self.big_handle)
1114
1115
  return
1115
1116
 
1116
- with closing(EventWatcher()) as watcher:
1117
+ with closing(utils.EventWatcher()) as watcher:
1117
1118
  terminated = asyncio.Event()
1118
1119
  watcher.once(self, BigSync.Event.TERMINATION, lambda _: terminated.set())
1119
1120
  await self.device.send_command(
@@ -1243,7 +1244,7 @@ class Peer:
1243
1244
  self,
1244
1245
  uuids: Iterable[Union[core.UUID, str]] = (),
1245
1246
  service: Optional[gatt_client.ServiceProxy] = None,
1246
- ) -> list[gatt_client.CharacteristicProxy]:
1247
+ ) -> list[gatt_client.CharacteristicProxy[bytes]]:
1247
1248
  return await self.gatt_client.discover_characteristics(
1248
1249
  uuids=uuids, service=service
1249
1250
  )
@@ -1258,7 +1259,7 @@ class Peer:
1258
1259
  characteristic, start_handle, end_handle
1259
1260
  )
1260
1261
 
1261
- async def discover_attributes(self) -> list[gatt_client.AttributeProxy]:
1262
+ async def discover_attributes(self) -> list[gatt_client.AttributeProxy[bytes]]:
1262
1263
  return await self.gatt_client.discover_attributes()
1263
1264
 
1264
1265
  async def discover_all(self):
@@ -1312,7 +1313,7 @@ class Peer:
1312
1313
  self,
1313
1314
  uuid: core.UUID,
1314
1315
  service: Optional[Union[gatt_client.ServiceProxy, core.UUID]] = None,
1315
- ) -> list[gatt_client.CharacteristicProxy]:
1316
+ ) -> list[gatt_client.CharacteristicProxy[bytes]]:
1316
1317
  if isinstance(service, core.UUID):
1317
1318
  return list(
1318
1319
  itertools.chain(
@@ -1382,7 +1383,7 @@ ConnectionParametersPreferences.default = ConnectionParametersPreferences()
1382
1383
 
1383
1384
  # -----------------------------------------------------------------------------
1384
1385
  @dataclass
1385
- class ScoLink(CompositeEventEmitter):
1386
+ class ScoLink(utils.CompositeEventEmitter):
1386
1387
  device: Device
1387
1388
  acl_connection: Connection
1388
1389
  handle: int
@@ -1473,7 +1474,7 @@ class _IsoLink:
1473
1474
 
1474
1475
  # -----------------------------------------------------------------------------
1475
1476
  @dataclass
1476
- class CisLink(CompositeEventEmitter, _IsoLink):
1477
+ class CisLink(utils.EventEmitter, _IsoLink):
1477
1478
  class State(IntEnum):
1478
1479
  PENDING = 0
1479
1480
  ESTABLISHED = 1
@@ -1550,7 +1551,7 @@ class IsoPacketStream:
1550
1551
 
1551
1552
 
1552
1553
  # -----------------------------------------------------------------------------
1553
- class Connection(CompositeEventEmitter):
1554
+ class Connection(utils.CompositeEventEmitter):
1554
1555
  device: Device
1555
1556
  handle: int
1556
1557
  transport: core.PhysicalTransport
@@ -1570,7 +1571,7 @@ class Connection(CompositeEventEmitter):
1570
1571
  cs_configs: dict[int, ChannelSoundingConfig] # Config ID to Configuration
1571
1572
  cs_procedures: dict[int, ChannelSoundingProcedure] # Config ID to Procedures
1572
1573
 
1573
- @composite_listener
1574
+ @utils.composite_listener
1574
1575
  class Listener:
1575
1576
  def on_disconnection(self, reason):
1576
1577
  pass
@@ -1649,7 +1650,7 @@ class Connection(CompositeEventEmitter):
1649
1650
  return cls(
1650
1651
  device,
1651
1652
  None,
1652
- BT_BR_EDR_TRANSPORT,
1653
+ PhysicalTransport.BR_EDR,
1653
1654
  device.public_address,
1654
1655
  None,
1655
1656
  peer_address,
@@ -1664,7 +1665,7 @@ class Connection(CompositeEventEmitter):
1664
1665
  Finish an incomplete connection upon completion.
1665
1666
  """
1666
1667
  assert self.handle is None
1667
- assert self.transport == BT_BR_EDR_TRANSPORT
1668
+ assert self.transport == PhysicalTransport.BR_EDR
1668
1669
  self.handle = handle
1669
1670
  self.parameters = parameters
1670
1671
 
@@ -1689,7 +1690,7 @@ class Connection(CompositeEventEmitter):
1689
1690
  def send_l2cap_pdu(self, cid: int, pdu: bytes) -> None:
1690
1691
  self.device.send_l2cap_pdu(self.handle, cid, pdu)
1691
1692
 
1692
- @deprecated("Please use create_l2cap_channel()")
1693
+ @utils.deprecated("Please use create_l2cap_channel()")
1693
1694
  async def open_l2cap_channel(
1694
1695
  self,
1695
1696
  psm,
@@ -1743,7 +1744,9 @@ class Connection(CompositeEventEmitter):
1743
1744
  self.on('disconnection_failure', abort.set_exception)
1744
1745
 
1745
1746
  try:
1746
- await asyncio.wait_for(self.device.abort_on('flush', abort), timeout)
1747
+ await asyncio.wait_for(
1748
+ utils.cancel_on_event(self.device, 'flush', abort), timeout
1749
+ )
1747
1750
  finally:
1748
1751
  self.remove_listener('disconnection', abort.set_result)
1749
1752
  self.remove_listener('disconnection_failure', abort.set_exception)
@@ -1782,6 +1785,13 @@ class Connection(CompositeEventEmitter):
1782
1785
  ) -> None:
1783
1786
  await self.device.transfer_periodic_sync(self, sync_handle, service_data)
1784
1787
 
1788
+ async def transfer_periodic_set_info(
1789
+ self, advertising_handle: int, service_data: int = 0
1790
+ ) -> None:
1791
+ await self.device.transfer_periodic_set_info(
1792
+ self, advertising_handle, service_data
1793
+ )
1794
+
1785
1795
  # [Classic only]
1786
1796
  async def request_remote_name(self):
1787
1797
  return await self.device.request_remote_name(self)
@@ -1812,7 +1822,7 @@ class Connection(CompositeEventEmitter):
1812
1822
  raise
1813
1823
 
1814
1824
  def __str__(self):
1815
- if self.transport == BT_LE_TRANSPORT:
1825
+ if self.transport == PhysicalTransport.LE:
1816
1826
  return (
1817
1827
  f'Connection(transport=LE, handle=0x{self.handle:04X}, '
1818
1828
  f'role={self.role_name}, '
@@ -2020,7 +2030,7 @@ device_host_event_handlers: list[str] = []
2020
2030
 
2021
2031
 
2022
2032
  # -----------------------------------------------------------------------------
2023
- class Device(CompositeEventEmitter):
2033
+ class Device(utils.CompositeEventEmitter):
2024
2034
  # Incomplete list of fields.
2025
2035
  random_address: hci.Address # Random private address that may change periodically
2026
2036
  public_address: (
@@ -2052,7 +2062,7 @@ class Device(CompositeEventEmitter):
2052
2062
  _pending_cis: Dict[int, tuple[int, int]]
2053
2063
  gatt_service: gatt_service.GenericAttributeProfileService | None = None
2054
2064
 
2055
- @composite_listener
2065
+ @utils.composite_listener
2056
2066
  class Listener:
2057
2067
  def on_advertisement(self, advertisement):
2058
2068
  pass
@@ -2273,7 +2283,9 @@ class Device(CompositeEventEmitter):
2273
2283
  self.l2cap_channel_manager.register_fixed_channel(ATT_CID, self.on_gatt_pdu)
2274
2284
 
2275
2285
  # Forward some events
2276
- setup_event_forwarding(self.gatt_server, self, 'characteristic_subscription')
2286
+ utils.setup_event_forwarding(
2287
+ self.gatt_server, self, 'characteristic_subscription'
2288
+ )
2277
2289
 
2278
2290
  # Set the initial host
2279
2291
  if host:
@@ -2362,11 +2374,11 @@ class Device(CompositeEventEmitter):
2362
2374
  None,
2363
2375
  )
2364
2376
 
2365
- @deprecated("Please use create_l2cap_server()")
2377
+ @utils.deprecated("Please use create_l2cap_server()")
2366
2378
  def register_l2cap_server(self, psm, server) -> int:
2367
2379
  return self.l2cap_channel_manager.register_server(psm, server)
2368
2380
 
2369
- @deprecated("Please use create_l2cap_server()")
2381
+ @utils.deprecated("Please use create_l2cap_server()")
2370
2382
  def register_l2cap_channel_server(
2371
2383
  self,
2372
2384
  psm,
@@ -2379,7 +2391,7 @@ class Device(CompositeEventEmitter):
2379
2391
  psm, server, max_credits, mtu, mps
2380
2392
  )
2381
2393
 
2382
- @deprecated("Please use create_l2cap_channel()")
2394
+ @utils.deprecated("Please use create_l2cap_channel()")
2383
2395
  async def open_l2cap_channel(
2384
2396
  self,
2385
2397
  connection,
@@ -3220,7 +3232,7 @@ class Device(CompositeEventEmitter):
3220
3232
  advertiser_clock_accuracy,
3221
3233
  )
3222
3234
 
3223
- AsyncRunner.spawn(self._update_periodic_advertising_syncs())
3235
+ utils.AsyncRunner.spawn(self._update_periodic_advertising_syncs())
3224
3236
 
3225
3237
  return
3226
3238
 
@@ -3379,7 +3391,7 @@ class Device(CompositeEventEmitter):
3379
3391
  async def connect(
3380
3392
  self,
3381
3393
  peer_address: Union[hci.Address, str],
3382
- transport: core.PhysicalTransport = BT_LE_TRANSPORT,
3394
+ transport: core.PhysicalTransport = PhysicalTransport.LE,
3383
3395
  connection_parameters_preferences: Optional[
3384
3396
  dict[hci.Phy, ConnectionParametersPreferences]
3385
3397
  ] = None,
@@ -3429,23 +3441,23 @@ class Device(CompositeEventEmitter):
3429
3441
  '''
3430
3442
 
3431
3443
  # Check parameters
3432
- if transport not in (BT_LE_TRANSPORT, BT_BR_EDR_TRANSPORT):
3444
+ if transport not in (PhysicalTransport.LE, PhysicalTransport.BR_EDR):
3433
3445
  raise InvalidArgumentError('invalid transport')
3434
3446
  transport = core.PhysicalTransport(transport)
3435
3447
 
3436
3448
  # Adjust the transport automatically if we need to
3437
- if transport == BT_LE_TRANSPORT and not self.le_enabled:
3438
- transport = BT_BR_EDR_TRANSPORT
3439
- elif transport == BT_BR_EDR_TRANSPORT and not self.classic_enabled:
3440
- transport = BT_LE_TRANSPORT
3449
+ if transport == PhysicalTransport.LE and not self.le_enabled:
3450
+ transport = PhysicalTransport.BR_EDR
3451
+ elif transport == PhysicalTransport.BR_EDR and not self.classic_enabled:
3452
+ transport = PhysicalTransport.LE
3441
3453
 
3442
3454
  # Check that there isn't already a pending connection
3443
- if transport == BT_LE_TRANSPORT and self.is_le_connecting:
3455
+ if transport == PhysicalTransport.LE and self.is_le_connecting:
3444
3456
  raise InvalidStateError('connection already pending')
3445
3457
 
3446
3458
  if isinstance(peer_address, str):
3447
3459
  try:
3448
- if transport == BT_LE_TRANSPORT and peer_address.endswith('@'):
3460
+ if transport == PhysicalTransport.LE and peer_address.endswith('@'):
3449
3461
  peer_address = hci.Address.from_string_for_transport(
3450
3462
  peer_address[:-1], transport
3451
3463
  )
@@ -3465,21 +3477,21 @@ class Device(CompositeEventEmitter):
3465
3477
  else:
3466
3478
  # All BR/EDR addresses should be public addresses
3467
3479
  if (
3468
- transport == BT_BR_EDR_TRANSPORT
3480
+ transport == PhysicalTransport.BR_EDR
3469
3481
  and peer_address.address_type != hci.Address.PUBLIC_DEVICE_ADDRESS
3470
3482
  ):
3471
3483
  raise InvalidArgumentError('BR/EDR addresses must be PUBLIC')
3472
3484
 
3473
3485
  assert isinstance(peer_address, hci.Address)
3474
3486
 
3475
- if transport == BT_LE_TRANSPORT and always_resolve:
3487
+ if transport == PhysicalTransport.LE and always_resolve:
3476
3488
  logger.debug('resolving address')
3477
3489
  peer_address = await self.find_peer_by_identity_address(
3478
3490
  peer_address
3479
3491
  ) # TODO: timeout
3480
3492
 
3481
3493
  def on_connection(connection):
3482
- if transport == BT_LE_TRANSPORT or (
3494
+ if transport == PhysicalTransport.LE or (
3483
3495
  # match BR/EDR connection event against peer address
3484
3496
  connection.transport == transport
3485
3497
  and connection.peer_address == peer_address
@@ -3487,7 +3499,7 @@ class Device(CompositeEventEmitter):
3487
3499
  pending_connection.set_result(connection)
3488
3500
 
3489
3501
  def on_connection_failure(error):
3490
- if transport == BT_LE_TRANSPORT or (
3502
+ if transport == PhysicalTransport.LE or (
3491
3503
  # match BR/EDR connection failure event against peer address
3492
3504
  error.transport == transport
3493
3505
  and error.peer_address == peer_address
@@ -3501,7 +3513,7 @@ class Device(CompositeEventEmitter):
3501
3513
 
3502
3514
  try:
3503
3515
  # Tell the controller to connect
3504
- if transport == BT_LE_TRANSPORT:
3516
+ if transport == PhysicalTransport.LE:
3505
3517
  if connection_parameters_preferences is None:
3506
3518
  if connection_parameters_preferences is None:
3507
3519
  connection_parameters_preferences = {
@@ -3646,18 +3658,18 @@ class Device(CompositeEventEmitter):
3646
3658
  raise hci.HCI_StatusError(result)
3647
3659
 
3648
3660
  # Wait for the connection process to complete
3649
- if transport == BT_LE_TRANSPORT:
3661
+ if transport == PhysicalTransport.LE:
3650
3662
  self.le_connecting = True
3651
3663
 
3652
3664
  if timeout is None:
3653
- return await self.abort_on('flush', pending_connection)
3665
+ return await utils.cancel_on_event(self, 'flush', pending_connection)
3654
3666
 
3655
3667
  try:
3656
3668
  return await asyncio.wait_for(
3657
3669
  asyncio.shield(pending_connection), timeout
3658
3670
  )
3659
3671
  except asyncio.TimeoutError:
3660
- if transport == BT_LE_TRANSPORT:
3672
+ if transport == PhysicalTransport.LE:
3661
3673
  await self.send_command(
3662
3674
  hci.HCI_LE_Create_Connection_Cancel_Command()
3663
3675
  )
@@ -3667,13 +3679,15 @@ class Device(CompositeEventEmitter):
3667
3679
  )
3668
3680
 
3669
3681
  try:
3670
- return await self.abort_on('flush', pending_connection)
3682
+ return await utils.cancel_on_event(
3683
+ self, 'flush', pending_connection
3684
+ )
3671
3685
  except core.ConnectionError as error:
3672
3686
  raise core.TimeoutError() from error
3673
3687
  finally:
3674
3688
  self.remove_listener('connection', on_connection)
3675
3689
  self.remove_listener('connection_failure', on_connection_failure)
3676
- if transport == BT_LE_TRANSPORT:
3690
+ if transport == PhysicalTransport.LE:
3677
3691
  self.le_connecting = False
3678
3692
  self.connect_own_address_type = None
3679
3693
  else:
@@ -3703,7 +3717,7 @@ class Device(CompositeEventEmitter):
3703
3717
  # If the address is not parsable, assume it is a name instead
3704
3718
  logger.debug('looking for peer by name')
3705
3719
  peer_address = await self.find_peer_by_name(
3706
- peer_address, BT_BR_EDR_TRANSPORT
3720
+ peer_address, PhysicalTransport.BR_EDR
3707
3721
  ) # TODO: timeout
3708
3722
 
3709
3723
  assert isinstance(peer_address, hci.Address)
@@ -3723,7 +3737,7 @@ class Device(CompositeEventEmitter):
3723
3737
 
3724
3738
  try:
3725
3739
  # Wait for a request or a completed connection
3726
- pending_request = self.abort_on('flush', pending_request_fut)
3740
+ pending_request = utils.cancel_on_event(self, 'flush', pending_request_fut)
3727
3741
  result = await (
3728
3742
  asyncio.wait_for(pending_request, timeout)
3729
3743
  if timeout
@@ -3753,14 +3767,14 @@ class Device(CompositeEventEmitter):
3753
3767
 
3754
3768
  def on_connection(connection):
3755
3769
  if (
3756
- connection.transport == BT_BR_EDR_TRANSPORT
3770
+ connection.transport == PhysicalTransport.BR_EDR
3757
3771
  and connection.peer_address == peer_address
3758
3772
  ):
3759
3773
  pending_connection.set_result(connection)
3760
3774
 
3761
3775
  def on_connection_failure(error):
3762
3776
  if (
3763
- error.transport == BT_BR_EDR_TRANSPORT
3777
+ error.transport == PhysicalTransport.BR_EDR
3764
3778
  and error.peer_address == peer_address
3765
3779
  ):
3766
3780
  pending_connection.set_exception(error)
@@ -3785,7 +3799,7 @@ class Device(CompositeEventEmitter):
3785
3799
  )
3786
3800
 
3787
3801
  # Wait for connection complete
3788
- return await self.abort_on('flush', pending_connection)
3802
+ return await utils.cancel_on_event(self, 'flush', pending_connection)
3789
3803
 
3790
3804
  finally:
3791
3805
  self.remove_listener('connection', on_connection)
@@ -3830,7 +3844,7 @@ class Device(CompositeEventEmitter):
3830
3844
  # If the address is not parsable, assume it is a name instead
3831
3845
  logger.debug('looking for peer by name')
3832
3846
  peer_address = await self.find_peer_by_name(
3833
- peer_address, BT_BR_EDR_TRANSPORT
3847
+ peer_address, PhysicalTransport.BR_EDR
3834
3848
  ) # TODO: timeout
3835
3849
 
3836
3850
  await self.send_command(
@@ -3859,7 +3873,7 @@ class Device(CompositeEventEmitter):
3859
3873
 
3860
3874
  # Wait for the disconnection process to complete
3861
3875
  self.disconnecting = True
3862
- return await self.abort_on('flush', pending_disconnection)
3876
+ return await utils.cancel_on_event(self, 'flush', pending_disconnection)
3863
3877
  finally:
3864
3878
  connection.remove_listener(
3865
3879
  'disconnection', pending_disconnection.set_result
@@ -3939,6 +3953,9 @@ class Device(CompositeEventEmitter):
3939
3953
  return result.return_parameters.rssi
3940
3954
 
3941
3955
  async def get_connection_phy(self, connection: Connection) -> ConnectionPHY:
3956
+ if not self.host.supports_command(hci.HCI_LE_READ_PHY_COMMAND):
3957
+ return ConnectionPHY(hci.Phy.LE_1M, hci.Phy.LE_1M)
3958
+
3942
3959
  result = await self.send_command(
3943
3960
  hci.HCI_LE_Read_PHY_Command(connection_handle=connection.handle),
3944
3961
  check_result=True,
@@ -4001,7 +4018,19 @@ class Device(CompositeEventEmitter):
4001
4018
  check_result=True,
4002
4019
  )
4003
4020
 
4004
- async def find_peer_by_name(self, name, transport=BT_LE_TRANSPORT):
4021
+ async def transfer_periodic_set_info(
4022
+ self, connection: Connection, advertising_handle: int, service_data: int = 0
4023
+ ) -> None:
4024
+ return await self.send_command(
4025
+ hci.HCI_LE_Periodic_Advertising_Set_Info_Transfer_Command(
4026
+ connection_handle=connection.handle,
4027
+ service_data=service_data,
4028
+ advertising_handle=advertising_handle,
4029
+ ),
4030
+ check_result=True,
4031
+ )
4032
+
4033
+ async def find_peer_by_name(self, name, transport=PhysicalTransport.LE):
4005
4034
  """
4006
4035
  Scan for a peer with a given name and return its address.
4007
4036
  """
@@ -4020,7 +4049,7 @@ class Device(CompositeEventEmitter):
4020
4049
  was_scanning = self.scanning
4021
4050
  was_discovering = self.discovering
4022
4051
  try:
4023
- if transport == BT_LE_TRANSPORT:
4052
+ if transport == PhysicalTransport.LE:
4024
4053
  event_name = 'advertisement'
4025
4054
  listener = self.on(
4026
4055
  event_name,
@@ -4032,7 +4061,7 @@ class Device(CompositeEventEmitter):
4032
4061
  if not self.scanning:
4033
4062
  await self.start_scanning(filter_duplicates=True)
4034
4063
 
4035
- elif transport == BT_BR_EDR_TRANSPORT:
4064
+ elif transport == PhysicalTransport.BR_EDR:
4036
4065
  event_name = 'inquiry_result'
4037
4066
  listener = self.on(
4038
4067
  event_name,
@@ -4046,14 +4075,14 @@ class Device(CompositeEventEmitter):
4046
4075
  else:
4047
4076
  return None
4048
4077
 
4049
- return await self.abort_on('flush', peer_address)
4078
+ return await utils.cancel_on_event(self, 'flush', peer_address)
4050
4079
  finally:
4051
4080
  if listener is not None:
4052
4081
  self.remove_listener(event_name, listener)
4053
4082
 
4054
- if transport == BT_LE_TRANSPORT and not was_scanning:
4083
+ if transport == PhysicalTransport.LE and not was_scanning:
4055
4084
  await self.stop_scanning()
4056
- elif transport == BT_BR_EDR_TRANSPORT and not was_discovering:
4085
+ elif transport == PhysicalTransport.BR_EDR and not was_discovering:
4057
4086
  await self.stop_discovery()
4058
4087
 
4059
4088
  async def find_peer_by_identity_address(
@@ -4096,7 +4125,7 @@ class Device(CompositeEventEmitter):
4096
4125
  if not self.scanning:
4097
4126
  await self.start_scanning(filter_duplicates=True)
4098
4127
 
4099
- return await self.abort_on('flush', peer_address)
4128
+ return await utils.cancel_on_event(self, 'flush', peer_address)
4100
4129
  finally:
4101
4130
  if listener is not None:
4102
4131
  self.remove_listener(event_name, listener)
@@ -4200,7 +4229,9 @@ class Device(CompositeEventEmitter):
4200
4229
  raise hci.HCI_StatusError(result)
4201
4230
 
4202
4231
  # Wait for the authentication to complete
4203
- await connection.abort_on('disconnection', pending_authentication)
4232
+ await utils.cancel_on_event(
4233
+ connection, 'disconnection', pending_authentication
4234
+ )
4204
4235
  finally:
4205
4236
  connection.remove_listener('connection_authentication', on_authentication)
4206
4237
  connection.remove_listener(
@@ -4208,7 +4239,7 @@ class Device(CompositeEventEmitter):
4208
4239
  )
4209
4240
 
4210
4241
  async def encrypt(self, connection, enable=True):
4211
- if not enable and connection.transport == BT_LE_TRANSPORT:
4242
+ if not enable and connection.transport == PhysicalTransport.LE:
4212
4243
  raise InvalidArgumentError('`enable` parameter is classic only.')
4213
4244
 
4214
4245
  # Set up event handlers
@@ -4225,7 +4256,7 @@ class Device(CompositeEventEmitter):
4225
4256
 
4226
4257
  # Request the encryption
4227
4258
  try:
4228
- if connection.transport == BT_LE_TRANSPORT:
4259
+ if connection.transport == PhysicalTransport.LE:
4229
4260
  # Look for a key in the key store
4230
4261
  if self.keystore is None:
4231
4262
  raise InvalidOperationError('no key store')
@@ -4246,7 +4277,7 @@ class Device(CompositeEventEmitter):
4246
4277
  else:
4247
4278
  raise InvalidOperationError('no LTK found for peer')
4248
4279
 
4249
- if connection.role != hci.HCI_CENTRAL_ROLE:
4280
+ if connection.role != hci.Role.CENTRAL:
4250
4281
  raise InvalidStateError('only centrals can start encryption')
4251
4282
 
4252
4283
  result = await self.send_command(
@@ -4280,7 +4311,7 @@ class Device(CompositeEventEmitter):
4280
4311
  raise hci.HCI_StatusError(result)
4281
4312
 
4282
4313
  # Wait for the result
4283
- await connection.abort_on('disconnection', pending_encryption)
4314
+ await utils.cancel_on_event(connection, 'disconnection', pending_encryption)
4284
4315
  finally:
4285
4316
  connection.remove_listener(
4286
4317
  'connection_encryption_change', on_encryption_change
@@ -4324,7 +4355,9 @@ class Device(CompositeEventEmitter):
4324
4355
  f'{hci.HCI_Constant.error_name(result.status)}'
4325
4356
  )
4326
4357
  raise hci.HCI_StatusError(result)
4327
- await connection.abort_on('disconnection', pending_role_change)
4358
+ await utils.cancel_on_event(
4359
+ connection, 'disconnection', pending_role_change
4360
+ )
4328
4361
  finally:
4329
4362
  connection.remove_listener('role_change', on_role_change)
4330
4363
  connection.remove_listener('role_change_failure', on_role_change_failure)
@@ -4373,13 +4406,13 @@ class Device(CompositeEventEmitter):
4373
4406
  raise hci.HCI_StatusError(result)
4374
4407
 
4375
4408
  # Wait for the result
4376
- return await self.abort_on('flush', pending_name)
4409
+ return await utils.cancel_on_event(self, 'flush', pending_name)
4377
4410
  finally:
4378
4411
  self.remove_listener('remote_name', handler)
4379
4412
  self.remove_listener('remote_name_failure', failure_handler)
4380
4413
 
4381
4414
  # [LE only]
4382
- @experimental('Only for testing.')
4415
+ @utils.experimental('Only for testing.')
4383
4416
  async def setup_cig(
4384
4417
  self,
4385
4418
  cig_id: int,
@@ -4437,7 +4470,7 @@ class Device(CompositeEventEmitter):
4437
4470
  return cis_handles
4438
4471
 
4439
4472
  # [LE only]
4440
- @experimental('Only for testing.')
4473
+ @utils.experimental('Only for testing.')
4441
4474
  async def create_cis(
4442
4475
  self, cis_acl_pairs: Sequence[tuple[int, int]]
4443
4476
  ) -> list[CisLink]:
@@ -4453,7 +4486,7 @@ class Device(CompositeEventEmitter):
4453
4486
  cig_id=cig_id,
4454
4487
  )
4455
4488
 
4456
- with closing(EventWatcher()) as watcher:
4489
+ with closing(utils.EventWatcher()) as watcher:
4457
4490
  pending_cis_establishments = {
4458
4491
  cis_handle: asyncio.get_running_loop().create_future()
4459
4492
  for cis_handle, _ in cis_acl_pairs
@@ -4480,7 +4513,7 @@ class Device(CompositeEventEmitter):
4480
4513
  return await asyncio.gather(*pending_cis_establishments.values())
4481
4514
 
4482
4515
  # [LE only]
4483
- @experimental('Only for testing.')
4516
+ @utils.experimental('Only for testing.')
4484
4517
  async def accept_cis_request(self, handle: int) -> CisLink:
4485
4518
  """[LE Only] Accepts an incoming CIS request.
4486
4519
 
@@ -4502,7 +4535,7 @@ class Device(CompositeEventEmitter):
4502
4535
  if cis_link.state == CisLink.State.ESTABLISHED:
4503
4536
  return cis_link
4504
4537
 
4505
- with closing(EventWatcher()) as watcher:
4538
+ with closing(utils.EventWatcher()) as watcher:
4506
4539
  pending_establishment = asyncio.get_running_loop().create_future()
4507
4540
 
4508
4541
  def on_establishment() -> None:
@@ -4526,7 +4559,7 @@ class Device(CompositeEventEmitter):
4526
4559
  raise UnreachableError()
4527
4560
 
4528
4561
  # [LE only]
4529
- @experimental('Only for testing.')
4562
+ @utils.experimental('Only for testing.')
4530
4563
  async def reject_cis_request(
4531
4564
  self,
4532
4565
  handle: int,
@@ -4540,14 +4573,14 @@ class Device(CompositeEventEmitter):
4540
4573
  )
4541
4574
 
4542
4575
  # [LE only]
4543
- @experimental('Only for testing.')
4576
+ @utils.experimental('Only for testing.')
4544
4577
  async def create_big(
4545
4578
  self, advertising_set: AdvertisingSet, parameters: BigParameters
4546
4579
  ) -> Big:
4547
4580
  if (big_handle := self.next_big_handle()) is None:
4548
4581
  raise core.OutOfResourcesError("All valid BIG handles already in use")
4549
4582
 
4550
- with closing(EventWatcher()) as watcher:
4583
+ with closing(utils.EventWatcher()) as watcher:
4551
4584
  big = Big(
4552
4585
  big_handle=big_handle,
4553
4586
  parameters=parameters,
@@ -4590,7 +4623,7 @@ class Device(CompositeEventEmitter):
4590
4623
  return big
4591
4624
 
4592
4625
  # [LE only]
4593
- @experimental('Only for testing.')
4626
+ @utils.experimental('Only for testing.')
4594
4627
  async def create_big_sync(
4595
4628
  self, pa_sync: PeriodicAdvertisingSync, parameters: BigSyncParameters
4596
4629
  ) -> BigSync:
@@ -4600,7 +4633,7 @@ class Device(CompositeEventEmitter):
4600
4633
  if (pa_sync_handle := pa_sync.sync_handle) is None:
4601
4634
  raise core.InvalidStateError("PA Sync is not established")
4602
4635
 
4603
- with closing(EventWatcher()) as watcher:
4636
+ with closing(utils.EventWatcher()) as watcher:
4604
4637
  big_sync = BigSync(
4605
4638
  big_handle=big_handle,
4606
4639
  parameters=parameters,
@@ -4648,7 +4681,7 @@ class Device(CompositeEventEmitter):
4648
4681
  Returns:
4649
4682
  LE features supported by the remote device.
4650
4683
  """
4651
- with closing(EventWatcher()) as watcher:
4684
+ with closing(utils.EventWatcher()) as watcher:
4652
4685
  read_feature_future: asyncio.Future[hci.LeFeatureMask] = (
4653
4686
  asyncio.get_running_loop().create_future()
4654
4687
  )
@@ -4671,7 +4704,7 @@ class Device(CompositeEventEmitter):
4671
4704
  )
4672
4705
  return await read_feature_future
4673
4706
 
4674
- @experimental('Only for testing.')
4707
+ @utils.experimental('Only for testing.')
4675
4708
  async def get_remote_cs_capabilities(
4676
4709
  self, connection: Connection
4677
4710
  ) -> ChannelSoundingCapabilities:
@@ -4679,7 +4712,7 @@ class Device(CompositeEventEmitter):
4679
4712
  asyncio.get_running_loop().create_future()
4680
4713
  )
4681
4714
 
4682
- with closing(EventWatcher()) as watcher:
4715
+ with closing(utils.EventWatcher()) as watcher:
4683
4716
  watcher.once(
4684
4717
  connection, 'channel_sounding_capabilities', complete_future.set_result
4685
4718
  )
@@ -4696,7 +4729,7 @@ class Device(CompositeEventEmitter):
4696
4729
  )
4697
4730
  return await complete_future
4698
4731
 
4699
- @experimental('Only for testing.')
4732
+ @utils.experimental('Only for testing.')
4700
4733
  async def set_default_cs_settings(
4701
4734
  self,
4702
4735
  connection: Connection,
@@ -4716,7 +4749,7 @@ class Device(CompositeEventEmitter):
4716
4749
  check_result=True,
4717
4750
  )
4718
4751
 
4719
- @experimental('Only for testing.')
4752
+ @utils.experimental('Only for testing.')
4720
4753
  async def create_cs_config(
4721
4754
  self,
4722
4755
  connection: Connection,
@@ -4753,7 +4786,7 @@ class Device(CompositeEventEmitter):
4753
4786
  if config_id is None:
4754
4787
  raise OutOfResourcesError("No available config ID on this connection!")
4755
4788
 
4756
- with closing(EventWatcher()) as watcher:
4789
+ with closing(utils.EventWatcher()) as watcher:
4757
4790
  watcher.once(
4758
4791
  connection, 'channel_sounding_config', complete_future.set_result
4759
4792
  )
@@ -4787,12 +4820,12 @@ class Device(CompositeEventEmitter):
4787
4820
  )
4788
4821
  return await complete_future
4789
4822
 
4790
- @experimental('Only for testing.')
4823
+ @utils.experimental('Only for testing.')
4791
4824
  async def enable_cs_security(self, connection: Connection) -> None:
4792
4825
  complete_future: asyncio.Future[None] = (
4793
4826
  asyncio.get_running_loop().create_future()
4794
4827
  )
4795
- with closing(EventWatcher()) as watcher:
4828
+ with closing(utils.EventWatcher()) as watcher:
4796
4829
 
4797
4830
  def on_event(event: hci.HCI_LE_CS_Security_Enable_Complete_Event) -> None:
4798
4831
  if event.connection_handle != connection.handle:
@@ -4811,7 +4844,7 @@ class Device(CompositeEventEmitter):
4811
4844
  )
4812
4845
  return await complete_future
4813
4846
 
4814
- @experimental('Only for testing.')
4847
+ @utils.experimental('Only for testing.')
4815
4848
  async def set_cs_procedure_parameters(
4816
4849
  self,
4817
4850
  connection: Connection,
@@ -4849,7 +4882,7 @@ class Device(CompositeEventEmitter):
4849
4882
  check_result=True,
4850
4883
  )
4851
4884
 
4852
- @experimental('Only for testing.')
4885
+ @utils.experimental('Only for testing.')
4853
4886
  async def enable_cs_procedure(
4854
4887
  self,
4855
4888
  connection: Connection,
@@ -4859,7 +4892,7 @@ class Device(CompositeEventEmitter):
4859
4892
  complete_future: asyncio.Future[ChannelSoundingProcedure] = (
4860
4893
  asyncio.get_running_loop().create_future()
4861
4894
  )
4862
- with closing(EventWatcher()) as watcher:
4895
+ with closing(utils.EventWatcher()) as watcher:
4863
4896
  watcher.once(
4864
4897
  connection, 'channel_sounding_procedure', complete_future.set_result
4865
4898
  )
@@ -4899,10 +4932,12 @@ class Device(CompositeEventEmitter):
4899
4932
  value=link_key, authenticated=authenticated
4900
4933
  )
4901
4934
 
4902
- self.abort_on('flush', self.update_keys(str(bd_addr), pairing_keys))
4935
+ utils.cancel_on_event(
4936
+ self, 'flush', self.update_keys(str(bd_addr), pairing_keys)
4937
+ )
4903
4938
 
4904
4939
  if connection := self.find_connection_by_bd_addr(
4905
- bd_addr, transport=BT_BR_EDR_TRANSPORT
4940
+ bd_addr, transport=PhysicalTransport.BR_EDR
4906
4941
  ):
4907
4942
  connection.link_key_type = key_type
4908
4943
 
@@ -5168,7 +5203,7 @@ class Device(CompositeEventEmitter):
5168
5203
  if advertising_set.auto_restart:
5169
5204
  connection.once(
5170
5205
  'disconnection',
5171
- lambda _: self.abort_on('flush', advertising_set.start()),
5206
+ lambda _: utils.cancel_on_event(self, 'flush', advertising_set.start()),
5172
5207
  )
5173
5208
 
5174
5209
  self.emit('connection', connection)
@@ -5202,7 +5237,7 @@ class Device(CompositeEventEmitter):
5202
5237
  'new connection reuses the same handle as a previous connection'
5203
5238
  )
5204
5239
 
5205
- if transport == BT_BR_EDR_TRANSPORT:
5240
+ if transport == PhysicalTransport.BR_EDR:
5206
5241
  # Create a new connection
5207
5242
  connection = self.pending_connections.pop(peer_address)
5208
5243
  connection.complete(connection_handle, connection_parameters)
@@ -5225,7 +5260,7 @@ class Device(CompositeEventEmitter):
5225
5260
 
5226
5261
  self_address = None
5227
5262
  own_address_type: Optional[hci.OwnAddressType] = None
5228
- if role == hci.HCI_CENTRAL_ROLE:
5263
+ if role == hci.Role.CENTRAL:
5229
5264
  own_address_type = self.connect_own_address_type
5230
5265
  assert own_address_type is not None
5231
5266
  else:
@@ -5272,22 +5307,22 @@ class Device(CompositeEventEmitter):
5272
5307
  )
5273
5308
  self.connections[connection_handle] = connection
5274
5309
 
5275
- if role == hci.HCI_PERIPHERAL_ROLE and self.legacy_advertiser:
5310
+ if role == hci.Role.PERIPHERAL and self.legacy_advertiser:
5276
5311
  if self.legacy_advertiser.auto_restart:
5277
5312
  advertiser = self.legacy_advertiser
5278
5313
  connection.once(
5279
5314
  'disconnection',
5280
- lambda _: self.abort_on('flush', advertiser.start()),
5315
+ lambda _: utils.cancel_on_event(self, 'flush', advertiser.start()),
5281
5316
  )
5282
5317
  else:
5283
5318
  self.legacy_advertiser = None
5284
5319
 
5285
- if role == hci.HCI_CENTRAL_ROLE or not self.supports_le_extended_advertising:
5320
+ if role == hci.Role.CENTRAL or not self.supports_le_extended_advertising:
5286
5321
  # We can emit now, we have all the info we need
5287
5322
  self.emit('connection', connection)
5288
5323
  return
5289
5324
 
5290
- if role == hci.HCI_PERIPHERAL_ROLE and self.supports_le_extended_advertising:
5325
+ if role == hci.Role.PERIPHERAL and self.supports_le_extended_advertising:
5291
5326
  if advertising_set := self.connecting_extended_advertising_sets.pop(
5292
5327
  connection_handle, None
5293
5328
  ):
@@ -5304,7 +5339,7 @@ class Device(CompositeEventEmitter):
5304
5339
 
5305
5340
  # For directed advertising, this means a timeout
5306
5341
  if (
5307
- transport == BT_LE_TRANSPORT
5342
+ transport == PhysicalTransport.LE
5308
5343
  and self.legacy_advertiser
5309
5344
  and self.legacy_advertiser.advertising_type.is_directed
5310
5345
  ):
@@ -5331,7 +5366,7 @@ class Device(CompositeEventEmitter):
5331
5366
  hci.HCI_Connection_Complete_Event.ESCO_LINK_TYPE,
5332
5367
  ):
5333
5368
  if connection := self.find_connection_by_bd_addr(
5334
- bd_addr, transport=BT_BR_EDR_TRANSPORT
5369
+ bd_addr, transport=PhysicalTransport.BR_EDR
5335
5370
  ):
5336
5371
  self.emit('sco_request', connection, link_type)
5337
5372
  else:
@@ -5404,7 +5439,7 @@ class Device(CompositeEventEmitter):
5404
5439
  connection.emit('disconnection_failure', error)
5405
5440
 
5406
5441
  @host_event_handler
5407
- @AsyncRunner.run_in_task()
5442
+ @utils.AsyncRunner.run_in_task()
5408
5443
  async def on_inquiry_complete(self):
5409
5444
  if self.auto_restart_inquiry:
5410
5445
  # Inquire again
@@ -5536,7 +5571,7 @@ class Device(CompositeEventEmitter):
5536
5571
 
5537
5572
  async def reply() -> None:
5538
5573
  try:
5539
- if await connection.abort_on('disconnection', method()):
5574
+ if await utils.cancel_on_event(connection, 'disconnection', method()):
5540
5575
  await self.host.send_command(
5541
5576
  hci.HCI_User_Confirmation_Request_Reply_Command(
5542
5577
  bd_addr=connection.peer_address
@@ -5552,7 +5587,7 @@ class Device(CompositeEventEmitter):
5552
5587
  )
5553
5588
  )
5554
5589
 
5555
- AsyncRunner.spawn(reply())
5590
+ utils.AsyncRunner.spawn(reply())
5556
5591
 
5557
5592
  # [Classic only]
5558
5593
  @host_event_handler
@@ -5563,8 +5598,8 @@ class Device(CompositeEventEmitter):
5563
5598
 
5564
5599
  async def reply() -> None:
5565
5600
  try:
5566
- number = await connection.abort_on(
5567
- 'disconnection', pairing_config.delegate.get_number()
5601
+ number = await utils.cancel_on_event(
5602
+ connection, 'disconnection', pairing_config.delegate.get_number()
5568
5603
  )
5569
5604
  if number is not None:
5570
5605
  await self.host.send_command(
@@ -5582,7 +5617,7 @@ class Device(CompositeEventEmitter):
5582
5617
  )
5583
5618
  )
5584
5619
 
5585
- AsyncRunner.spawn(reply())
5620
+ utils.AsyncRunner.spawn(reply())
5586
5621
 
5587
5622
  # [Classic only]
5588
5623
  @host_event_handler
@@ -5597,8 +5632,8 @@ class Device(CompositeEventEmitter):
5597
5632
  if io_capability == hci.HCI_KEYBOARD_ONLY_IO_CAPABILITY:
5598
5633
  # Ask the user to enter a string
5599
5634
  async def get_pin_code():
5600
- pin_code = await connection.abort_on(
5601
- 'disconnection', pairing_config.delegate.get_string(16)
5635
+ pin_code = await utils.cancel_on_event(
5636
+ connection, 'disconnection', pairing_config.delegate.get_string(16)
5602
5637
  )
5603
5638
 
5604
5639
  if pin_code is not None:
@@ -5636,8 +5671,8 @@ class Device(CompositeEventEmitter):
5636
5671
  pairing_config = self.pairing_config_factory(connection)
5637
5672
 
5638
5673
  # Show the passkey to the user
5639
- connection.abort_on(
5640
- 'disconnection', pairing_config.delegate.display_number(passkey)
5674
+ utils.cancel_on_event(
5675
+ connection, 'disconnection', pairing_config.delegate.display_number(passkey)
5641
5676
  )
5642
5677
 
5643
5678
  # [Classic only]
@@ -5669,7 +5704,7 @@ class Device(CompositeEventEmitter):
5669
5704
  # [Classic only]
5670
5705
  @host_event_handler
5671
5706
  @with_connection_from_address
5672
- @experimental('Only for testing.')
5707
+ @utils.experimental('Only for testing.')
5673
5708
  def on_sco_connection(
5674
5709
  self, acl_connection: Connection, sco_handle: int, link_type: int
5675
5710
  ) -> None:
@@ -5689,7 +5724,7 @@ class Device(CompositeEventEmitter):
5689
5724
  # [Classic only]
5690
5725
  @host_event_handler
5691
5726
  @with_connection_from_address
5692
- @experimental('Only for testing.')
5727
+ @utils.experimental('Only for testing.')
5693
5728
  def on_sco_connection_failure(
5694
5729
  self, acl_connection: Connection, status: int
5695
5730
  ) -> None:
@@ -5698,7 +5733,7 @@ class Device(CompositeEventEmitter):
5698
5733
 
5699
5734
  # [Classic only]
5700
5735
  @host_event_handler
5701
- @experimental('Only for testing')
5736
+ @utils.experimental('Only for testing')
5702
5737
  def on_sco_packet(
5703
5738
  self, sco_handle: int, packet: hci.HCI_SynchronousDataPacket
5704
5739
  ) -> None:
@@ -5708,7 +5743,7 @@ class Device(CompositeEventEmitter):
5708
5743
  # [LE only]
5709
5744
  @host_event_handler
5710
5745
  @with_connection_from_handle
5711
- @experimental('Only for testing')
5746
+ @utils.experimental('Only for testing')
5712
5747
  def on_cis_request(
5713
5748
  self,
5714
5749
  acl_connection: Connection,
@@ -5735,7 +5770,7 @@ class Device(CompositeEventEmitter):
5735
5770
 
5736
5771
  # [LE only]
5737
5772
  @host_event_handler
5738
- @experimental('Only for testing')
5773
+ @utils.experimental('Only for testing')
5739
5774
  def on_cis_establishment(self, cis_handle: int) -> None:
5740
5775
  cis_link = self.cis_links[cis_handle]
5741
5776
  cis_link.state = CisLink.State.ESTABLISHED
@@ -5755,7 +5790,7 @@ class Device(CompositeEventEmitter):
5755
5790
 
5756
5791
  # [LE only]
5757
5792
  @host_event_handler
5758
- @experimental('Only for testing')
5793
+ @utils.experimental('Only for testing')
5759
5794
  def on_cis_establishment_failure(self, cis_handle: int, status: int) -> None:
5760
5795
  logger.debug(f'*** CIS Establishment Failure: cis=[0x{cis_handle:04X}] ***')
5761
5796
  if cis_link := self.cis_links.pop(cis_handle):
@@ -5764,7 +5799,7 @@ class Device(CompositeEventEmitter):
5764
5799
 
5765
5800
  # [LE only]
5766
5801
  @host_event_handler
5767
- @experimental('Only for testing')
5802
+ @utils.experimental('Only for testing')
5768
5803
  def on_iso_packet(self, handle: int, packet: hci.HCI_IsoDataPacket) -> None:
5769
5804
  if (cis_link := self.cis_links.get(handle)) and cis_link.sink:
5770
5805
  cis_link.sink(packet)
@@ -5782,14 +5817,14 @@ class Device(CompositeEventEmitter):
5782
5817
  connection.encryption = encryption
5783
5818
  if (
5784
5819
  not connection.authenticated
5785
- and connection.transport == BT_BR_EDR_TRANSPORT
5820
+ and connection.transport == PhysicalTransport.BR_EDR
5786
5821
  and encryption == hci.HCI_Encryption_Change_Event.AES_CCM
5787
5822
  ):
5788
5823
  connection.authenticated = True
5789
5824
  connection.sc = True
5790
5825
  if (
5791
5826
  not connection.authenticated
5792
- and connection.transport == BT_LE_TRANSPORT
5827
+ and connection.transport == PhysicalTransport.LE
5793
5828
  and encryption == hci.HCI_Encryption_Change_Event.E0_OR_AES_CCM
5794
5829
  ):
5795
5830
  connection.authenticated = True