bumble 0.0.209__py3-none-any.whl → 0.0.210__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 +9 -7
  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 +169 -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 +34 -20
  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.210.dist-info}/METADATA +3 -2
  70. {bumble-0.0.209.dist-info → bumble-0.0.210.dist-info}/RECORD +74 -74
  71. {bumble-0.0.209.dist-info → bumble-0.0.210.dist-info}/WHEEL +1 -1
  72. {bumble-0.0.209.dist-info → bumble-0.0.210.dist-info}/entry_points.txt +0 -0
  73. {bumble-0.0.209.dist-info → bumble-0.0.210.dist-info/licenses}/LICENSE +0 -0
  74. {bumble-0.0.209.dist-info → bumble-0.0.210.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
@@ -4001,7 +4015,19 @@ class Device(CompositeEventEmitter):
4001
4015
  check_result=True,
4002
4016
  )
4003
4017
 
4004
- async def find_peer_by_name(self, name, transport=BT_LE_TRANSPORT):
4018
+ async def transfer_periodic_set_info(
4019
+ self, connection: Connection, advertising_handle: int, service_data: int = 0
4020
+ ) -> None:
4021
+ return await self.send_command(
4022
+ hci.HCI_LE_Periodic_Advertising_Set_Info_Transfer_Command(
4023
+ connection_handle=connection.handle,
4024
+ service_data=service_data,
4025
+ advertising_handle=advertising_handle,
4026
+ ),
4027
+ check_result=True,
4028
+ )
4029
+
4030
+ async def find_peer_by_name(self, name, transport=PhysicalTransport.LE):
4005
4031
  """
4006
4032
  Scan for a peer with a given name and return its address.
4007
4033
  """
@@ -4020,7 +4046,7 @@ class Device(CompositeEventEmitter):
4020
4046
  was_scanning = self.scanning
4021
4047
  was_discovering = self.discovering
4022
4048
  try:
4023
- if transport == BT_LE_TRANSPORT:
4049
+ if transport == PhysicalTransport.LE:
4024
4050
  event_name = 'advertisement'
4025
4051
  listener = self.on(
4026
4052
  event_name,
@@ -4032,7 +4058,7 @@ class Device(CompositeEventEmitter):
4032
4058
  if not self.scanning:
4033
4059
  await self.start_scanning(filter_duplicates=True)
4034
4060
 
4035
- elif transport == BT_BR_EDR_TRANSPORT:
4061
+ elif transport == PhysicalTransport.BR_EDR:
4036
4062
  event_name = 'inquiry_result'
4037
4063
  listener = self.on(
4038
4064
  event_name,
@@ -4046,14 +4072,14 @@ class Device(CompositeEventEmitter):
4046
4072
  else:
4047
4073
  return None
4048
4074
 
4049
- return await self.abort_on('flush', peer_address)
4075
+ return await utils.cancel_on_event(self, 'flush', peer_address)
4050
4076
  finally:
4051
4077
  if listener is not None:
4052
4078
  self.remove_listener(event_name, listener)
4053
4079
 
4054
- if transport == BT_LE_TRANSPORT and not was_scanning:
4080
+ if transport == PhysicalTransport.LE and not was_scanning:
4055
4081
  await self.stop_scanning()
4056
- elif transport == BT_BR_EDR_TRANSPORT and not was_discovering:
4082
+ elif transport == PhysicalTransport.BR_EDR and not was_discovering:
4057
4083
  await self.stop_discovery()
4058
4084
 
4059
4085
  async def find_peer_by_identity_address(
@@ -4096,7 +4122,7 @@ class Device(CompositeEventEmitter):
4096
4122
  if not self.scanning:
4097
4123
  await self.start_scanning(filter_duplicates=True)
4098
4124
 
4099
- return await self.abort_on('flush', peer_address)
4125
+ return await utils.cancel_on_event(self, 'flush', peer_address)
4100
4126
  finally:
4101
4127
  if listener is not None:
4102
4128
  self.remove_listener(event_name, listener)
@@ -4200,7 +4226,9 @@ class Device(CompositeEventEmitter):
4200
4226
  raise hci.HCI_StatusError(result)
4201
4227
 
4202
4228
  # Wait for the authentication to complete
4203
- await connection.abort_on('disconnection', pending_authentication)
4229
+ await utils.cancel_on_event(
4230
+ connection, 'disconnection', pending_authentication
4231
+ )
4204
4232
  finally:
4205
4233
  connection.remove_listener('connection_authentication', on_authentication)
4206
4234
  connection.remove_listener(
@@ -4208,7 +4236,7 @@ class Device(CompositeEventEmitter):
4208
4236
  )
4209
4237
 
4210
4238
  async def encrypt(self, connection, enable=True):
4211
- if not enable and connection.transport == BT_LE_TRANSPORT:
4239
+ if not enable and connection.transport == PhysicalTransport.LE:
4212
4240
  raise InvalidArgumentError('`enable` parameter is classic only.')
4213
4241
 
4214
4242
  # Set up event handlers
@@ -4225,7 +4253,7 @@ class Device(CompositeEventEmitter):
4225
4253
 
4226
4254
  # Request the encryption
4227
4255
  try:
4228
- if connection.transport == BT_LE_TRANSPORT:
4256
+ if connection.transport == PhysicalTransport.LE:
4229
4257
  # Look for a key in the key store
4230
4258
  if self.keystore is None:
4231
4259
  raise InvalidOperationError('no key store')
@@ -4246,7 +4274,7 @@ class Device(CompositeEventEmitter):
4246
4274
  else:
4247
4275
  raise InvalidOperationError('no LTK found for peer')
4248
4276
 
4249
- if connection.role != hci.HCI_CENTRAL_ROLE:
4277
+ if connection.role != hci.Role.CENTRAL:
4250
4278
  raise InvalidStateError('only centrals can start encryption')
4251
4279
 
4252
4280
  result = await self.send_command(
@@ -4280,7 +4308,7 @@ class Device(CompositeEventEmitter):
4280
4308
  raise hci.HCI_StatusError(result)
4281
4309
 
4282
4310
  # Wait for the result
4283
- await connection.abort_on('disconnection', pending_encryption)
4311
+ await utils.cancel_on_event(connection, 'disconnection', pending_encryption)
4284
4312
  finally:
4285
4313
  connection.remove_listener(
4286
4314
  'connection_encryption_change', on_encryption_change
@@ -4324,7 +4352,9 @@ class Device(CompositeEventEmitter):
4324
4352
  f'{hci.HCI_Constant.error_name(result.status)}'
4325
4353
  )
4326
4354
  raise hci.HCI_StatusError(result)
4327
- await connection.abort_on('disconnection', pending_role_change)
4355
+ await utils.cancel_on_event(
4356
+ connection, 'disconnection', pending_role_change
4357
+ )
4328
4358
  finally:
4329
4359
  connection.remove_listener('role_change', on_role_change)
4330
4360
  connection.remove_listener('role_change_failure', on_role_change_failure)
@@ -4373,13 +4403,13 @@ class Device(CompositeEventEmitter):
4373
4403
  raise hci.HCI_StatusError(result)
4374
4404
 
4375
4405
  # Wait for the result
4376
- return await self.abort_on('flush', pending_name)
4406
+ return await utils.cancel_on_event(self, 'flush', pending_name)
4377
4407
  finally:
4378
4408
  self.remove_listener('remote_name', handler)
4379
4409
  self.remove_listener('remote_name_failure', failure_handler)
4380
4410
 
4381
4411
  # [LE only]
4382
- @experimental('Only for testing.')
4412
+ @utils.experimental('Only for testing.')
4383
4413
  async def setup_cig(
4384
4414
  self,
4385
4415
  cig_id: int,
@@ -4437,7 +4467,7 @@ class Device(CompositeEventEmitter):
4437
4467
  return cis_handles
4438
4468
 
4439
4469
  # [LE only]
4440
- @experimental('Only for testing.')
4470
+ @utils.experimental('Only for testing.')
4441
4471
  async def create_cis(
4442
4472
  self, cis_acl_pairs: Sequence[tuple[int, int]]
4443
4473
  ) -> list[CisLink]:
@@ -4453,7 +4483,7 @@ class Device(CompositeEventEmitter):
4453
4483
  cig_id=cig_id,
4454
4484
  )
4455
4485
 
4456
- with closing(EventWatcher()) as watcher:
4486
+ with closing(utils.EventWatcher()) as watcher:
4457
4487
  pending_cis_establishments = {
4458
4488
  cis_handle: asyncio.get_running_loop().create_future()
4459
4489
  for cis_handle, _ in cis_acl_pairs
@@ -4480,7 +4510,7 @@ class Device(CompositeEventEmitter):
4480
4510
  return await asyncio.gather(*pending_cis_establishments.values())
4481
4511
 
4482
4512
  # [LE only]
4483
- @experimental('Only for testing.')
4513
+ @utils.experimental('Only for testing.')
4484
4514
  async def accept_cis_request(self, handle: int) -> CisLink:
4485
4515
  """[LE Only] Accepts an incoming CIS request.
4486
4516
 
@@ -4502,7 +4532,7 @@ class Device(CompositeEventEmitter):
4502
4532
  if cis_link.state == CisLink.State.ESTABLISHED:
4503
4533
  return cis_link
4504
4534
 
4505
- with closing(EventWatcher()) as watcher:
4535
+ with closing(utils.EventWatcher()) as watcher:
4506
4536
  pending_establishment = asyncio.get_running_loop().create_future()
4507
4537
 
4508
4538
  def on_establishment() -> None:
@@ -4526,7 +4556,7 @@ class Device(CompositeEventEmitter):
4526
4556
  raise UnreachableError()
4527
4557
 
4528
4558
  # [LE only]
4529
- @experimental('Only for testing.')
4559
+ @utils.experimental('Only for testing.')
4530
4560
  async def reject_cis_request(
4531
4561
  self,
4532
4562
  handle: int,
@@ -4540,14 +4570,14 @@ class Device(CompositeEventEmitter):
4540
4570
  )
4541
4571
 
4542
4572
  # [LE only]
4543
- @experimental('Only for testing.')
4573
+ @utils.experimental('Only for testing.')
4544
4574
  async def create_big(
4545
4575
  self, advertising_set: AdvertisingSet, parameters: BigParameters
4546
4576
  ) -> Big:
4547
4577
  if (big_handle := self.next_big_handle()) is None:
4548
4578
  raise core.OutOfResourcesError("All valid BIG handles already in use")
4549
4579
 
4550
- with closing(EventWatcher()) as watcher:
4580
+ with closing(utils.EventWatcher()) as watcher:
4551
4581
  big = Big(
4552
4582
  big_handle=big_handle,
4553
4583
  parameters=parameters,
@@ -4590,7 +4620,7 @@ class Device(CompositeEventEmitter):
4590
4620
  return big
4591
4621
 
4592
4622
  # [LE only]
4593
- @experimental('Only for testing.')
4623
+ @utils.experimental('Only for testing.')
4594
4624
  async def create_big_sync(
4595
4625
  self, pa_sync: PeriodicAdvertisingSync, parameters: BigSyncParameters
4596
4626
  ) -> BigSync:
@@ -4600,7 +4630,7 @@ class Device(CompositeEventEmitter):
4600
4630
  if (pa_sync_handle := pa_sync.sync_handle) is None:
4601
4631
  raise core.InvalidStateError("PA Sync is not established")
4602
4632
 
4603
- with closing(EventWatcher()) as watcher:
4633
+ with closing(utils.EventWatcher()) as watcher:
4604
4634
  big_sync = BigSync(
4605
4635
  big_handle=big_handle,
4606
4636
  parameters=parameters,
@@ -4648,7 +4678,7 @@ class Device(CompositeEventEmitter):
4648
4678
  Returns:
4649
4679
  LE features supported by the remote device.
4650
4680
  """
4651
- with closing(EventWatcher()) as watcher:
4681
+ with closing(utils.EventWatcher()) as watcher:
4652
4682
  read_feature_future: asyncio.Future[hci.LeFeatureMask] = (
4653
4683
  asyncio.get_running_loop().create_future()
4654
4684
  )
@@ -4671,7 +4701,7 @@ class Device(CompositeEventEmitter):
4671
4701
  )
4672
4702
  return await read_feature_future
4673
4703
 
4674
- @experimental('Only for testing.')
4704
+ @utils.experimental('Only for testing.')
4675
4705
  async def get_remote_cs_capabilities(
4676
4706
  self, connection: Connection
4677
4707
  ) -> ChannelSoundingCapabilities:
@@ -4679,7 +4709,7 @@ class Device(CompositeEventEmitter):
4679
4709
  asyncio.get_running_loop().create_future()
4680
4710
  )
4681
4711
 
4682
- with closing(EventWatcher()) as watcher:
4712
+ with closing(utils.EventWatcher()) as watcher:
4683
4713
  watcher.once(
4684
4714
  connection, 'channel_sounding_capabilities', complete_future.set_result
4685
4715
  )
@@ -4696,7 +4726,7 @@ class Device(CompositeEventEmitter):
4696
4726
  )
4697
4727
  return await complete_future
4698
4728
 
4699
- @experimental('Only for testing.')
4729
+ @utils.experimental('Only for testing.')
4700
4730
  async def set_default_cs_settings(
4701
4731
  self,
4702
4732
  connection: Connection,
@@ -4716,7 +4746,7 @@ class Device(CompositeEventEmitter):
4716
4746
  check_result=True,
4717
4747
  )
4718
4748
 
4719
- @experimental('Only for testing.')
4749
+ @utils.experimental('Only for testing.')
4720
4750
  async def create_cs_config(
4721
4751
  self,
4722
4752
  connection: Connection,
@@ -4753,7 +4783,7 @@ class Device(CompositeEventEmitter):
4753
4783
  if config_id is None:
4754
4784
  raise OutOfResourcesError("No available config ID on this connection!")
4755
4785
 
4756
- with closing(EventWatcher()) as watcher:
4786
+ with closing(utils.EventWatcher()) as watcher:
4757
4787
  watcher.once(
4758
4788
  connection, 'channel_sounding_config', complete_future.set_result
4759
4789
  )
@@ -4787,12 +4817,12 @@ class Device(CompositeEventEmitter):
4787
4817
  )
4788
4818
  return await complete_future
4789
4819
 
4790
- @experimental('Only for testing.')
4820
+ @utils.experimental('Only for testing.')
4791
4821
  async def enable_cs_security(self, connection: Connection) -> None:
4792
4822
  complete_future: asyncio.Future[None] = (
4793
4823
  asyncio.get_running_loop().create_future()
4794
4824
  )
4795
- with closing(EventWatcher()) as watcher:
4825
+ with closing(utils.EventWatcher()) as watcher:
4796
4826
 
4797
4827
  def on_event(event: hci.HCI_LE_CS_Security_Enable_Complete_Event) -> None:
4798
4828
  if event.connection_handle != connection.handle:
@@ -4811,7 +4841,7 @@ class Device(CompositeEventEmitter):
4811
4841
  )
4812
4842
  return await complete_future
4813
4843
 
4814
- @experimental('Only for testing.')
4844
+ @utils.experimental('Only for testing.')
4815
4845
  async def set_cs_procedure_parameters(
4816
4846
  self,
4817
4847
  connection: Connection,
@@ -4849,7 +4879,7 @@ class Device(CompositeEventEmitter):
4849
4879
  check_result=True,
4850
4880
  )
4851
4881
 
4852
- @experimental('Only for testing.')
4882
+ @utils.experimental('Only for testing.')
4853
4883
  async def enable_cs_procedure(
4854
4884
  self,
4855
4885
  connection: Connection,
@@ -4859,7 +4889,7 @@ class Device(CompositeEventEmitter):
4859
4889
  complete_future: asyncio.Future[ChannelSoundingProcedure] = (
4860
4890
  asyncio.get_running_loop().create_future()
4861
4891
  )
4862
- with closing(EventWatcher()) as watcher:
4892
+ with closing(utils.EventWatcher()) as watcher:
4863
4893
  watcher.once(
4864
4894
  connection, 'channel_sounding_procedure', complete_future.set_result
4865
4895
  )
@@ -4899,10 +4929,12 @@ class Device(CompositeEventEmitter):
4899
4929
  value=link_key, authenticated=authenticated
4900
4930
  )
4901
4931
 
4902
- self.abort_on('flush', self.update_keys(str(bd_addr), pairing_keys))
4932
+ utils.cancel_on_event(
4933
+ self, 'flush', self.update_keys(str(bd_addr), pairing_keys)
4934
+ )
4903
4935
 
4904
4936
  if connection := self.find_connection_by_bd_addr(
4905
- bd_addr, transport=BT_BR_EDR_TRANSPORT
4937
+ bd_addr, transport=PhysicalTransport.BR_EDR
4906
4938
  ):
4907
4939
  connection.link_key_type = key_type
4908
4940
 
@@ -5168,7 +5200,7 @@ class Device(CompositeEventEmitter):
5168
5200
  if advertising_set.auto_restart:
5169
5201
  connection.once(
5170
5202
  'disconnection',
5171
- lambda _: self.abort_on('flush', advertising_set.start()),
5203
+ lambda _: utils.cancel_on_event(self, 'flush', advertising_set.start()),
5172
5204
  )
5173
5205
 
5174
5206
  self.emit('connection', connection)
@@ -5202,7 +5234,7 @@ class Device(CompositeEventEmitter):
5202
5234
  'new connection reuses the same handle as a previous connection'
5203
5235
  )
5204
5236
 
5205
- if transport == BT_BR_EDR_TRANSPORT:
5237
+ if transport == PhysicalTransport.BR_EDR:
5206
5238
  # Create a new connection
5207
5239
  connection = self.pending_connections.pop(peer_address)
5208
5240
  connection.complete(connection_handle, connection_parameters)
@@ -5225,7 +5257,7 @@ class Device(CompositeEventEmitter):
5225
5257
 
5226
5258
  self_address = None
5227
5259
  own_address_type: Optional[hci.OwnAddressType] = None
5228
- if role == hci.HCI_CENTRAL_ROLE:
5260
+ if role == hci.Role.CENTRAL:
5229
5261
  own_address_type = self.connect_own_address_type
5230
5262
  assert own_address_type is not None
5231
5263
  else:
@@ -5272,22 +5304,22 @@ class Device(CompositeEventEmitter):
5272
5304
  )
5273
5305
  self.connections[connection_handle] = connection
5274
5306
 
5275
- if role == hci.HCI_PERIPHERAL_ROLE and self.legacy_advertiser:
5307
+ if role == hci.Role.PERIPHERAL and self.legacy_advertiser:
5276
5308
  if self.legacy_advertiser.auto_restart:
5277
5309
  advertiser = self.legacy_advertiser
5278
5310
  connection.once(
5279
5311
  'disconnection',
5280
- lambda _: self.abort_on('flush', advertiser.start()),
5312
+ lambda _: utils.cancel_on_event(self, 'flush', advertiser.start()),
5281
5313
  )
5282
5314
  else:
5283
5315
  self.legacy_advertiser = None
5284
5316
 
5285
- if role == hci.HCI_CENTRAL_ROLE or not self.supports_le_extended_advertising:
5317
+ if role == hci.Role.CENTRAL or not self.supports_le_extended_advertising:
5286
5318
  # We can emit now, we have all the info we need
5287
5319
  self.emit('connection', connection)
5288
5320
  return
5289
5321
 
5290
- if role == hci.HCI_PERIPHERAL_ROLE and self.supports_le_extended_advertising:
5322
+ if role == hci.Role.PERIPHERAL and self.supports_le_extended_advertising:
5291
5323
  if advertising_set := self.connecting_extended_advertising_sets.pop(
5292
5324
  connection_handle, None
5293
5325
  ):
@@ -5304,7 +5336,7 @@ class Device(CompositeEventEmitter):
5304
5336
 
5305
5337
  # For directed advertising, this means a timeout
5306
5338
  if (
5307
- transport == BT_LE_TRANSPORT
5339
+ transport == PhysicalTransport.LE
5308
5340
  and self.legacy_advertiser
5309
5341
  and self.legacy_advertiser.advertising_type.is_directed
5310
5342
  ):
@@ -5331,7 +5363,7 @@ class Device(CompositeEventEmitter):
5331
5363
  hci.HCI_Connection_Complete_Event.ESCO_LINK_TYPE,
5332
5364
  ):
5333
5365
  if connection := self.find_connection_by_bd_addr(
5334
- bd_addr, transport=BT_BR_EDR_TRANSPORT
5366
+ bd_addr, transport=PhysicalTransport.BR_EDR
5335
5367
  ):
5336
5368
  self.emit('sco_request', connection, link_type)
5337
5369
  else:
@@ -5404,7 +5436,7 @@ class Device(CompositeEventEmitter):
5404
5436
  connection.emit('disconnection_failure', error)
5405
5437
 
5406
5438
  @host_event_handler
5407
- @AsyncRunner.run_in_task()
5439
+ @utils.AsyncRunner.run_in_task()
5408
5440
  async def on_inquiry_complete(self):
5409
5441
  if self.auto_restart_inquiry:
5410
5442
  # Inquire again
@@ -5536,7 +5568,7 @@ class Device(CompositeEventEmitter):
5536
5568
 
5537
5569
  async def reply() -> None:
5538
5570
  try:
5539
- if await connection.abort_on('disconnection', method()):
5571
+ if await utils.cancel_on_event(connection, 'disconnection', method()):
5540
5572
  await self.host.send_command(
5541
5573
  hci.HCI_User_Confirmation_Request_Reply_Command(
5542
5574
  bd_addr=connection.peer_address
@@ -5552,7 +5584,7 @@ class Device(CompositeEventEmitter):
5552
5584
  )
5553
5585
  )
5554
5586
 
5555
- AsyncRunner.spawn(reply())
5587
+ utils.AsyncRunner.spawn(reply())
5556
5588
 
5557
5589
  # [Classic only]
5558
5590
  @host_event_handler
@@ -5563,8 +5595,8 @@ class Device(CompositeEventEmitter):
5563
5595
 
5564
5596
  async def reply() -> None:
5565
5597
  try:
5566
- number = await connection.abort_on(
5567
- 'disconnection', pairing_config.delegate.get_number()
5598
+ number = await utils.cancel_on_event(
5599
+ connection, 'disconnection', pairing_config.delegate.get_number()
5568
5600
  )
5569
5601
  if number is not None:
5570
5602
  await self.host.send_command(
@@ -5582,7 +5614,7 @@ class Device(CompositeEventEmitter):
5582
5614
  )
5583
5615
  )
5584
5616
 
5585
- AsyncRunner.spawn(reply())
5617
+ utils.AsyncRunner.spawn(reply())
5586
5618
 
5587
5619
  # [Classic only]
5588
5620
  @host_event_handler
@@ -5597,8 +5629,8 @@ class Device(CompositeEventEmitter):
5597
5629
  if io_capability == hci.HCI_KEYBOARD_ONLY_IO_CAPABILITY:
5598
5630
  # Ask the user to enter a string
5599
5631
  async def get_pin_code():
5600
- pin_code = await connection.abort_on(
5601
- 'disconnection', pairing_config.delegate.get_string(16)
5632
+ pin_code = await utils.cancel_on_event(
5633
+ connection, 'disconnection', pairing_config.delegate.get_string(16)
5602
5634
  )
5603
5635
 
5604
5636
  if pin_code is not None:
@@ -5636,8 +5668,8 @@ class Device(CompositeEventEmitter):
5636
5668
  pairing_config = self.pairing_config_factory(connection)
5637
5669
 
5638
5670
  # Show the passkey to the user
5639
- connection.abort_on(
5640
- 'disconnection', pairing_config.delegate.display_number(passkey)
5671
+ utils.cancel_on_event(
5672
+ connection, 'disconnection', pairing_config.delegate.display_number(passkey)
5641
5673
  )
5642
5674
 
5643
5675
  # [Classic only]
@@ -5669,7 +5701,7 @@ class Device(CompositeEventEmitter):
5669
5701
  # [Classic only]
5670
5702
  @host_event_handler
5671
5703
  @with_connection_from_address
5672
- @experimental('Only for testing.')
5704
+ @utils.experimental('Only for testing.')
5673
5705
  def on_sco_connection(
5674
5706
  self, acl_connection: Connection, sco_handle: int, link_type: int
5675
5707
  ) -> None:
@@ -5689,7 +5721,7 @@ class Device(CompositeEventEmitter):
5689
5721
  # [Classic only]
5690
5722
  @host_event_handler
5691
5723
  @with_connection_from_address
5692
- @experimental('Only for testing.')
5724
+ @utils.experimental('Only for testing.')
5693
5725
  def on_sco_connection_failure(
5694
5726
  self, acl_connection: Connection, status: int
5695
5727
  ) -> None:
@@ -5698,7 +5730,7 @@ class Device(CompositeEventEmitter):
5698
5730
 
5699
5731
  # [Classic only]
5700
5732
  @host_event_handler
5701
- @experimental('Only for testing')
5733
+ @utils.experimental('Only for testing')
5702
5734
  def on_sco_packet(
5703
5735
  self, sco_handle: int, packet: hci.HCI_SynchronousDataPacket
5704
5736
  ) -> None:
@@ -5708,7 +5740,7 @@ class Device(CompositeEventEmitter):
5708
5740
  # [LE only]
5709
5741
  @host_event_handler
5710
5742
  @with_connection_from_handle
5711
- @experimental('Only for testing')
5743
+ @utils.experimental('Only for testing')
5712
5744
  def on_cis_request(
5713
5745
  self,
5714
5746
  acl_connection: Connection,
@@ -5735,7 +5767,7 @@ class Device(CompositeEventEmitter):
5735
5767
 
5736
5768
  # [LE only]
5737
5769
  @host_event_handler
5738
- @experimental('Only for testing')
5770
+ @utils.experimental('Only for testing')
5739
5771
  def on_cis_establishment(self, cis_handle: int) -> None:
5740
5772
  cis_link = self.cis_links[cis_handle]
5741
5773
  cis_link.state = CisLink.State.ESTABLISHED
@@ -5755,7 +5787,7 @@ class Device(CompositeEventEmitter):
5755
5787
 
5756
5788
  # [LE only]
5757
5789
  @host_event_handler
5758
- @experimental('Only for testing')
5790
+ @utils.experimental('Only for testing')
5759
5791
  def on_cis_establishment_failure(self, cis_handle: int, status: int) -> None:
5760
5792
  logger.debug(f'*** CIS Establishment Failure: cis=[0x{cis_handle:04X}] ***')
5761
5793
  if cis_link := self.cis_links.pop(cis_handle):
@@ -5764,7 +5796,7 @@ class Device(CompositeEventEmitter):
5764
5796
 
5765
5797
  # [LE only]
5766
5798
  @host_event_handler
5767
- @experimental('Only for testing')
5799
+ @utils.experimental('Only for testing')
5768
5800
  def on_iso_packet(self, handle: int, packet: hci.HCI_IsoDataPacket) -> None:
5769
5801
  if (cis_link := self.cis_links.get(handle)) and cis_link.sink:
5770
5802
  cis_link.sink(packet)
@@ -5782,14 +5814,14 @@ class Device(CompositeEventEmitter):
5782
5814
  connection.encryption = encryption
5783
5815
  if (
5784
5816
  not connection.authenticated
5785
- and connection.transport == BT_BR_EDR_TRANSPORT
5817
+ and connection.transport == PhysicalTransport.BR_EDR
5786
5818
  and encryption == hci.HCI_Encryption_Change_Event.AES_CCM
5787
5819
  ):
5788
5820
  connection.authenticated = True
5789
5821
  connection.sc = True
5790
5822
  if (
5791
5823
  not connection.authenticated
5792
- and connection.transport == BT_LE_TRANSPORT
5824
+ and connection.transport == PhysicalTransport.LE
5793
5825
  and encryption == hci.HCI_Encryption_Change_Event.E0_OR_AES_CCM
5794
5826
  ):
5795
5827
  connection.authenticated = True