bumble 0.0.208__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 (77) 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 +12 -5
  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 +13 -15
  17. bumble/core.py +61 -60
  18. bumble/device.py +193 -162
  19. bumble/drivers/__init__.py +2 -2
  20. bumble/gap.py +1 -1
  21. bumble/gatt.py +16 -0
  22. bumble/gatt_adapters.py +3 -3
  23. bumble/gatt_client.py +27 -21
  24. bumble/gatt_server.py +9 -10
  25. bumble/hci.py +109 -90
  26. bumble/hfp.py +3 -3
  27. bumble/hid.py +4 -3
  28. bumble/host.py +30 -19
  29. bumble/keys.py +3 -3
  30. bumble/l2cap.py +21 -19
  31. bumble/link.py +5 -6
  32. bumble/pairing.py +3 -3
  33. bumble/pandora/__init__.py +5 -5
  34. bumble/pandora/host.py +30 -23
  35. bumble/pandora/l2cap.py +2 -2
  36. bumble/pandora/security.py +17 -19
  37. bumble/pandora/utils.py +2 -2
  38. bumble/profiles/aics.py +6 -6
  39. bumble/profiles/ancs.py +513 -0
  40. bumble/profiles/ascs.py +17 -10
  41. bumble/profiles/asha.py +5 -5
  42. bumble/profiles/bass.py +1 -1
  43. bumble/profiles/csip.py +10 -10
  44. bumble/profiles/gatt_service.py +12 -12
  45. bumble/profiles/hap.py +16 -16
  46. bumble/profiles/mcp.py +26 -24
  47. bumble/profiles/pacs.py +6 -6
  48. bumble/profiles/pbp.py +1 -1
  49. bumble/profiles/vcs.py +6 -4
  50. bumble/profiles/vocs.py +3 -3
  51. bumble/rfcomm.py +8 -8
  52. bumble/sdp.py +1 -1
  53. bumble/smp.py +39 -33
  54. bumble/transport/__init__.py +24 -19
  55. bumble/transport/android_emulator.py +8 -4
  56. bumble/transport/android_netsim.py +8 -5
  57. bumble/transport/common.py +5 -1
  58. bumble/transport/file.py +1 -1
  59. bumble/transport/hci_socket.py +1 -1
  60. bumble/transport/pty.py +1 -1
  61. bumble/transport/pyusb.py +3 -3
  62. bumble/transport/serial.py +1 -1
  63. bumble/transport/tcp_client.py +1 -1
  64. bumble/transport/tcp_server.py +1 -1
  65. bumble/transport/udp.py +1 -1
  66. bumble/transport/unix.py +1 -1
  67. bumble/transport/usb.py +1 -3
  68. bumble/transport/vhci.py +2 -2
  69. bumble/transport/ws_client.py +6 -1
  70. bumble/transport/ws_server.py +1 -1
  71. bumble/utils.py +89 -76
  72. {bumble-0.0.208.dist-info → bumble-0.0.210.dist-info}/METADATA +3 -2
  73. {bumble-0.0.208.dist-info → bumble-0.0.210.dist-info}/RECORD +77 -76
  74. {bumble-0.0.208.dist-info → bumble-0.0.210.dist-info}/WHEEL +1 -1
  75. {bumble-0.0.208.dist-info → bumble-0.0.210.dist-info}/entry_points.txt +0 -0
  76. {bumble-0.0.208.dist-info → bumble-0.0.210.dist-info/licenses}/LICENSE +0 -0
  77. {bumble-0.0.208.dist-info → bumble-0.0.210.dist-info}/top_level.txt +0 -0
bumble/device.py CHANGED
@@ -49,18 +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_CENTRAL_ROLE,
62
- BT_LE_TRANSPORT,
63
- BT_PERIPHERAL_ROLE,
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,
64
60
  AdvertisingData,
65
61
  BaseBumbleError,
66
62
  ConnectionParameterUpdateError,
@@ -74,16 +70,8 @@ from .core import (
74
70
  OutOfResourcesError,
75
71
  UnreachableError,
76
72
  )
77
- from .utils import (
78
- AsyncRunner,
79
- CompositeEventEmitter,
80
- EventWatcher,
81
- setup_event_forwarding,
82
- composite_listener,
83
- deprecated,
84
- experimental,
85
- )
86
- from .keys import (
73
+ from bumble import utils
74
+ from bumble.keys import (
87
75
  KeyStore,
88
76
  PairingKeys,
89
77
  )
@@ -98,7 +86,7 @@ from bumble import core
98
86
  from bumble.profiles import gatt_service
99
87
 
100
88
  if TYPE_CHECKING:
101
- from .transport.common import TransportSource, TransportSink
89
+ from bumble.transport.common import TransportSource, TransportSink
102
90
 
103
91
 
104
92
  # -----------------------------------------------------------------------------
@@ -579,7 +567,7 @@ class PeriodicAdvertisingParameters:
579
567
 
580
568
  # -----------------------------------------------------------------------------
581
569
  @dataclass
582
- class AdvertisingSet(EventEmitter):
570
+ class AdvertisingSet(utils.EventEmitter):
583
571
  device: Device
584
572
  advertising_handle: int
585
573
  auto_restart: bool
@@ -796,13 +784,24 @@ class AdvertisingSet(EventEmitter):
796
784
  )
797
785
  del self.device.extended_advertising_sets[self.advertising_handle]
798
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
+
799
798
  def on_termination(self, status: int) -> None:
800
799
  self.enabled = False
801
800
  self.emit('termination', status)
802
801
 
803
802
 
804
803
  # -----------------------------------------------------------------------------
805
- class PeriodicAdvertisingSync(EventEmitter):
804
+ class PeriodicAdvertisingSync(utils.EventEmitter):
806
805
  class State(Enum):
807
806
  INIT = 0
808
807
  PENDING = 1
@@ -931,7 +930,7 @@ class PeriodicAdvertisingSync(EventEmitter):
931
930
  "received established event for cancelled sync, will terminate"
932
931
  )
933
932
  self.state = self.State.ESTABLISHED
934
- AsyncRunner.spawn(self.terminate())
933
+ utils.AsyncRunner.spawn(self.terminate())
935
934
  return
936
935
 
937
936
  if status == hci.HCI_SUCCESS:
@@ -1017,7 +1016,7 @@ class BigParameters:
1017
1016
 
1018
1017
  # -----------------------------------------------------------------------------
1019
1018
  @dataclass
1020
- class Big(EventEmitter):
1019
+ class Big(utils.EventEmitter):
1021
1020
  class State(IntEnum):
1022
1021
  PENDING = 0
1023
1022
  ACTIVE = 1
@@ -1057,7 +1056,7 @@ class Big(EventEmitter):
1057
1056
  logger.error('BIG %d is not active.', self.big_handle)
1058
1057
  return
1059
1058
 
1060
- with closing(EventWatcher()) as watcher:
1059
+ with closing(utils.EventWatcher()) as watcher:
1061
1060
  terminated = asyncio.Event()
1062
1061
  watcher.once(self, Big.Event.TERMINATION, lambda _: terminated.set())
1063
1062
  await self.device.send_command(
@@ -1080,7 +1079,7 @@ class BigSyncParameters:
1080
1079
 
1081
1080
  # -----------------------------------------------------------------------------
1082
1081
  @dataclass
1083
- class BigSync(EventEmitter):
1082
+ class BigSync(utils.EventEmitter):
1084
1083
  class State(IntEnum):
1085
1084
  PENDING = 0
1086
1085
  ACTIVE = 1
@@ -1115,7 +1114,7 @@ class BigSync(EventEmitter):
1115
1114
  logger.error('BIG Sync %d is not active.', self.big_handle)
1116
1115
  return
1117
1116
 
1118
- with closing(EventWatcher()) as watcher:
1117
+ with closing(utils.EventWatcher()) as watcher:
1119
1118
  terminated = asyncio.Event()
1120
1119
  watcher.once(self, BigSync.Event.TERMINATION, lambda _: terminated.set())
1121
1120
  await self.device.send_command(
@@ -1245,7 +1244,7 @@ class Peer:
1245
1244
  self,
1246
1245
  uuids: Iterable[Union[core.UUID, str]] = (),
1247
1246
  service: Optional[gatt_client.ServiceProxy] = None,
1248
- ) -> list[gatt_client.CharacteristicProxy]:
1247
+ ) -> list[gatt_client.CharacteristicProxy[bytes]]:
1249
1248
  return await self.gatt_client.discover_characteristics(
1250
1249
  uuids=uuids, service=service
1251
1250
  )
@@ -1260,7 +1259,7 @@ class Peer:
1260
1259
  characteristic, start_handle, end_handle
1261
1260
  )
1262
1261
 
1263
- async def discover_attributes(self) -> list[gatt_client.AttributeProxy]:
1262
+ async def discover_attributes(self) -> list[gatt_client.AttributeProxy[bytes]]:
1264
1263
  return await self.gatt_client.discover_attributes()
1265
1264
 
1266
1265
  async def discover_all(self):
@@ -1314,7 +1313,7 @@ class Peer:
1314
1313
  self,
1315
1314
  uuid: core.UUID,
1316
1315
  service: Optional[Union[gatt_client.ServiceProxy, core.UUID]] = None,
1317
- ) -> list[gatt_client.CharacteristicProxy]:
1316
+ ) -> list[gatt_client.CharacteristicProxy[bytes]]:
1318
1317
  if isinstance(service, core.UUID):
1319
1318
  return list(
1320
1319
  itertools.chain(
@@ -1384,7 +1383,7 @@ ConnectionParametersPreferences.default = ConnectionParametersPreferences()
1384
1383
 
1385
1384
  # -----------------------------------------------------------------------------
1386
1385
  @dataclass
1387
- class ScoLink(CompositeEventEmitter):
1386
+ class ScoLink(utils.CompositeEventEmitter):
1388
1387
  device: Device
1389
1388
  acl_connection: Connection
1390
1389
  handle: int
@@ -1475,7 +1474,7 @@ class _IsoLink:
1475
1474
 
1476
1475
  # -----------------------------------------------------------------------------
1477
1476
  @dataclass
1478
- class CisLink(CompositeEventEmitter, _IsoLink):
1477
+ class CisLink(utils.EventEmitter, _IsoLink):
1479
1478
  class State(IntEnum):
1480
1479
  PENDING = 0
1481
1480
  ESTABLISHED = 1
@@ -1552,16 +1551,16 @@ class IsoPacketStream:
1552
1551
 
1553
1552
 
1554
1553
  # -----------------------------------------------------------------------------
1555
- class Connection(CompositeEventEmitter):
1554
+ class Connection(utils.CompositeEventEmitter):
1556
1555
  device: Device
1557
1556
  handle: int
1558
- transport: int
1557
+ transport: core.PhysicalTransport
1559
1558
  self_address: hci.Address
1560
1559
  self_resolvable_address: Optional[hci.Address]
1561
1560
  peer_address: hci.Address
1562
1561
  peer_resolvable_address: Optional[hci.Address]
1563
1562
  peer_le_features: Optional[hci.LeFeatureMask]
1564
- role: int
1563
+ role: hci.Role
1565
1564
  encryption: int
1566
1565
  authenticated: bool
1567
1566
  sc: bool
@@ -1572,7 +1571,7 @@ class Connection(CompositeEventEmitter):
1572
1571
  cs_configs: dict[int, ChannelSoundingConfig] # Config ID to Configuration
1573
1572
  cs_procedures: dict[int, ChannelSoundingProcedure] # Config ID to Procedures
1574
1573
 
1575
- @composite_listener
1574
+ @utils.composite_listener
1576
1575
  class Listener:
1577
1576
  def on_disconnection(self, reason):
1578
1577
  pass
@@ -1651,7 +1650,7 @@ class Connection(CompositeEventEmitter):
1651
1650
  return cls(
1652
1651
  device,
1653
1652
  None,
1654
- BT_BR_EDR_TRANSPORT,
1653
+ PhysicalTransport.BR_EDR,
1655
1654
  device.public_address,
1656
1655
  None,
1657
1656
  peer_address,
@@ -1666,7 +1665,7 @@ class Connection(CompositeEventEmitter):
1666
1665
  Finish an incomplete connection upon completion.
1667
1666
  """
1668
1667
  assert self.handle is None
1669
- assert self.transport == BT_BR_EDR_TRANSPORT
1668
+ assert self.transport == PhysicalTransport.BR_EDR
1670
1669
  self.handle = handle
1671
1670
  self.parameters = parameters
1672
1671
 
@@ -1674,9 +1673,9 @@ class Connection(CompositeEventEmitter):
1674
1673
  def role_name(self):
1675
1674
  if self.role is None:
1676
1675
  return 'NOT-SET'
1677
- if self.role == BT_CENTRAL_ROLE:
1676
+ if self.role == hci.Role.CENTRAL:
1678
1677
  return 'CENTRAL'
1679
- if self.role == BT_PERIPHERAL_ROLE:
1678
+ if self.role == hci.Role.PERIPHERAL:
1680
1679
  return 'PERIPHERAL'
1681
1680
  return f'UNKNOWN[{self.role}]'
1682
1681
 
@@ -1691,7 +1690,7 @@ class Connection(CompositeEventEmitter):
1691
1690
  def send_l2cap_pdu(self, cid: int, pdu: bytes) -> None:
1692
1691
  self.device.send_l2cap_pdu(self.handle, cid, pdu)
1693
1692
 
1694
- @deprecated("Please use create_l2cap_channel()")
1693
+ @utils.deprecated("Please use create_l2cap_channel()")
1695
1694
  async def open_l2cap_channel(
1696
1695
  self,
1697
1696
  psm,
@@ -1734,7 +1733,7 @@ class Connection(CompositeEventEmitter):
1734
1733
  async def encrypt(self, enable: bool = True) -> None:
1735
1734
  return await self.device.encrypt(self, enable)
1736
1735
 
1737
- async def switch_role(self, role: int) -> None:
1736
+ async def switch_role(self, role: hci.Role) -> None:
1738
1737
  return await self.device.switch_role(self, role)
1739
1738
 
1740
1739
  async def sustain(self, timeout: Optional[float] = None) -> None:
@@ -1745,7 +1744,9 @@ class Connection(CompositeEventEmitter):
1745
1744
  self.on('disconnection_failure', abort.set_exception)
1746
1745
 
1747
1746
  try:
1748
- 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
+ )
1749
1750
  finally:
1750
1751
  self.remove_listener('disconnection', abort.set_result)
1751
1752
  self.remove_listener('disconnection_failure', abort.set_exception)
@@ -1784,6 +1785,13 @@ class Connection(CompositeEventEmitter):
1784
1785
  ) -> None:
1785
1786
  await self.device.transfer_periodic_sync(self, sync_handle, service_data)
1786
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
+
1787
1795
  # [Classic only]
1788
1796
  async def request_remote_name(self):
1789
1797
  return await self.device.request_remote_name(self)
@@ -1814,7 +1822,7 @@ class Connection(CompositeEventEmitter):
1814
1822
  raise
1815
1823
 
1816
1824
  def __str__(self):
1817
- if self.transport == BT_LE_TRANSPORT:
1825
+ if self.transport == PhysicalTransport.LE:
1818
1826
  return (
1819
1827
  f'Connection(transport=LE, handle=0x{self.handle:04X}, '
1820
1828
  f'role={self.role_name}, '
@@ -2022,7 +2030,7 @@ device_host_event_handlers: list[str] = []
2022
2030
 
2023
2031
 
2024
2032
  # -----------------------------------------------------------------------------
2025
- class Device(CompositeEventEmitter):
2033
+ class Device(utils.CompositeEventEmitter):
2026
2034
  # Incomplete list of fields.
2027
2035
  random_address: hci.Address # Random private address that may change periodically
2028
2036
  public_address: (
@@ -2054,7 +2062,7 @@ class Device(CompositeEventEmitter):
2054
2062
  _pending_cis: Dict[int, tuple[int, int]]
2055
2063
  gatt_service: gatt_service.GenericAttributeProfileService | None = None
2056
2064
 
2057
- @composite_listener
2065
+ @utils.composite_listener
2058
2066
  class Listener:
2059
2067
  def on_advertisement(self, advertisement):
2060
2068
  pass
@@ -2275,7 +2283,9 @@ class Device(CompositeEventEmitter):
2275
2283
  self.l2cap_channel_manager.register_fixed_channel(ATT_CID, self.on_gatt_pdu)
2276
2284
 
2277
2285
  # Forward some events
2278
- setup_event_forwarding(self.gatt_server, self, 'characteristic_subscription')
2286
+ utils.setup_event_forwarding(
2287
+ self.gatt_server, self, 'characteristic_subscription'
2288
+ )
2279
2289
 
2280
2290
  # Set the initial host
2281
2291
  if host:
@@ -2364,11 +2374,11 @@ class Device(CompositeEventEmitter):
2364
2374
  None,
2365
2375
  )
2366
2376
 
2367
- @deprecated("Please use create_l2cap_server()")
2377
+ @utils.deprecated("Please use create_l2cap_server()")
2368
2378
  def register_l2cap_server(self, psm, server) -> int:
2369
2379
  return self.l2cap_channel_manager.register_server(psm, server)
2370
2380
 
2371
- @deprecated("Please use create_l2cap_server()")
2381
+ @utils.deprecated("Please use create_l2cap_server()")
2372
2382
  def register_l2cap_channel_server(
2373
2383
  self,
2374
2384
  psm,
@@ -2381,7 +2391,7 @@ class Device(CompositeEventEmitter):
2381
2391
  psm, server, max_credits, mtu, mps
2382
2392
  )
2383
2393
 
2384
- @deprecated("Please use create_l2cap_channel()")
2394
+ @utils.deprecated("Please use create_l2cap_channel()")
2385
2395
  async def open_l2cap_channel(
2386
2396
  self,
2387
2397
  connection,
@@ -2713,7 +2723,7 @@ class Device(CompositeEventEmitter):
2713
2723
  if phy == hci.HCI_LE_1M_PHY:
2714
2724
  return True
2715
2725
 
2716
- feature_map = {
2726
+ feature_map: dict[int, hci.LeFeatureMask] = {
2717
2727
  hci.HCI_LE_2M_PHY: hci.LeFeatureMask.LE_2M_PHY,
2718
2728
  hci.HCI_LE_CODED_PHY: hci.LeFeatureMask.LE_CODED_PHY,
2719
2729
  }
@@ -2734,7 +2744,7 @@ class Device(CompositeEventEmitter):
2734
2744
  self,
2735
2745
  advertising_type: AdvertisingType = AdvertisingType.UNDIRECTED_CONNECTABLE_SCANNABLE,
2736
2746
  target: Optional[hci.Address] = None,
2737
- own_address_type: int = hci.OwnAddressType.RANDOM,
2747
+ own_address_type: hci.OwnAddressType = hci.OwnAddressType.RANDOM,
2738
2748
  auto_restart: bool = False,
2739
2749
  advertising_data: Optional[bytes] = None,
2740
2750
  scan_response_data: Optional[bytes] = None,
@@ -3015,7 +3025,7 @@ class Device(CompositeEventEmitter):
3015
3025
  active: bool = True,
3016
3026
  scan_interval: float = DEVICE_DEFAULT_SCAN_INTERVAL, # Scan interval in ms
3017
3027
  scan_window: float = DEVICE_DEFAULT_SCAN_WINDOW, # Scan window in ms
3018
- own_address_type: int = hci.OwnAddressType.RANDOM,
3028
+ own_address_type: hci.OwnAddressType = hci.OwnAddressType.RANDOM,
3019
3029
  filter_duplicates: bool = False,
3020
3030
  scanning_phys: Sequence[int] = (hci.HCI_LE_1M_PHY, hci.HCI_LE_CODED_PHY),
3021
3031
  ) -> None:
@@ -3091,7 +3101,7 @@ class Device(CompositeEventEmitter):
3091
3101
  # pylint: disable=line-too-long
3092
3102
  hci.HCI_LE_Set_Scan_Parameters_Command(
3093
3103
  le_scan_type=scan_type,
3094
- le_scan_interval=int(scan_window / 0.625),
3104
+ le_scan_interval=int(scan_interval / 0.625),
3095
3105
  le_scan_window=int(scan_window / 0.625),
3096
3106
  own_address_type=own_address_type,
3097
3107
  scanning_filter_policy=hci.HCI_LE_Set_Scan_Parameters_Command.BASIC_UNFILTERED_POLICY,
@@ -3222,7 +3232,7 @@ class Device(CompositeEventEmitter):
3222
3232
  advertiser_clock_accuracy,
3223
3233
  )
3224
3234
 
3225
- AsyncRunner.spawn(self._update_periodic_advertising_syncs())
3235
+ utils.AsyncRunner.spawn(self._update_periodic_advertising_syncs())
3226
3236
 
3227
3237
  return
3228
3238
 
@@ -3381,11 +3391,11 @@ class Device(CompositeEventEmitter):
3381
3391
  async def connect(
3382
3392
  self,
3383
3393
  peer_address: Union[hci.Address, str],
3384
- transport: int = BT_LE_TRANSPORT,
3394
+ transport: core.PhysicalTransport = PhysicalTransport.LE,
3385
3395
  connection_parameters_preferences: Optional[
3386
- Dict[int, ConnectionParametersPreferences]
3396
+ dict[hci.Phy, ConnectionParametersPreferences]
3387
3397
  ] = None,
3388
- own_address_type: int = hci.OwnAddressType.RANDOM,
3398
+ own_address_type: hci.OwnAddressType = hci.OwnAddressType.RANDOM,
3389
3399
  timeout: Optional[float] = DEVICE_DEFAULT_CONNECT_TIMEOUT,
3390
3400
  always_resolve: bool = False,
3391
3401
  ) -> Connection:
@@ -3431,22 +3441,23 @@ class Device(CompositeEventEmitter):
3431
3441
  '''
3432
3442
 
3433
3443
  # Check parameters
3434
- if transport not in (BT_LE_TRANSPORT, BT_BR_EDR_TRANSPORT):
3444
+ if transport not in (PhysicalTransport.LE, PhysicalTransport.BR_EDR):
3435
3445
  raise InvalidArgumentError('invalid transport')
3446
+ transport = core.PhysicalTransport(transport)
3436
3447
 
3437
3448
  # Adjust the transport automatically if we need to
3438
- if transport == BT_LE_TRANSPORT and not self.le_enabled:
3439
- transport = BT_BR_EDR_TRANSPORT
3440
- elif transport == BT_BR_EDR_TRANSPORT and not self.classic_enabled:
3441
- 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
3442
3453
 
3443
3454
  # Check that there isn't already a pending connection
3444
- if transport == BT_LE_TRANSPORT and self.is_le_connecting:
3455
+ if transport == PhysicalTransport.LE and self.is_le_connecting:
3445
3456
  raise InvalidStateError('connection already pending')
3446
3457
 
3447
3458
  if isinstance(peer_address, str):
3448
3459
  try:
3449
- if transport == BT_LE_TRANSPORT and peer_address.endswith('@'):
3460
+ if transport == PhysicalTransport.LE and peer_address.endswith('@'):
3450
3461
  peer_address = hci.Address.from_string_for_transport(
3451
3462
  peer_address[:-1], transport
3452
3463
  )
@@ -3466,21 +3477,21 @@ class Device(CompositeEventEmitter):
3466
3477
  else:
3467
3478
  # All BR/EDR addresses should be public addresses
3468
3479
  if (
3469
- transport == BT_BR_EDR_TRANSPORT
3480
+ transport == PhysicalTransport.BR_EDR
3470
3481
  and peer_address.address_type != hci.Address.PUBLIC_DEVICE_ADDRESS
3471
3482
  ):
3472
3483
  raise InvalidArgumentError('BR/EDR addresses must be PUBLIC')
3473
3484
 
3474
3485
  assert isinstance(peer_address, hci.Address)
3475
3486
 
3476
- if transport == BT_LE_TRANSPORT and always_resolve:
3487
+ if transport == PhysicalTransport.LE and always_resolve:
3477
3488
  logger.debug('resolving address')
3478
3489
  peer_address = await self.find_peer_by_identity_address(
3479
3490
  peer_address
3480
3491
  ) # TODO: timeout
3481
3492
 
3482
3493
  def on_connection(connection):
3483
- if transport == BT_LE_TRANSPORT or (
3494
+ if transport == PhysicalTransport.LE or (
3484
3495
  # match BR/EDR connection event against peer address
3485
3496
  connection.transport == transport
3486
3497
  and connection.peer_address == peer_address
@@ -3488,7 +3499,7 @@ class Device(CompositeEventEmitter):
3488
3499
  pending_connection.set_result(connection)
3489
3500
 
3490
3501
  def on_connection_failure(error):
3491
- if transport == BT_LE_TRANSPORT or (
3502
+ if transport == PhysicalTransport.LE or (
3492
3503
  # match BR/EDR connection failure event against peer address
3493
3504
  error.transport == transport
3494
3505
  and error.peer_address == peer_address
@@ -3502,7 +3513,7 @@ class Device(CompositeEventEmitter):
3502
3513
 
3503
3514
  try:
3504
3515
  # Tell the controller to connect
3505
- if transport == BT_LE_TRANSPORT:
3516
+ if transport == PhysicalTransport.LE:
3506
3517
  if connection_parameters_preferences is None:
3507
3518
  if connection_parameters_preferences is None:
3508
3519
  connection_parameters_preferences = {
@@ -3628,7 +3639,7 @@ class Device(CompositeEventEmitter):
3628
3639
  else:
3629
3640
  # Save pending connection
3630
3641
  self.pending_connections[peer_address] = Connection.incomplete(
3631
- self, peer_address, BT_CENTRAL_ROLE
3642
+ self, peer_address, hci.Role.CENTRAL
3632
3643
  )
3633
3644
 
3634
3645
  # TODO: allow passing other settings
@@ -3647,18 +3658,18 @@ class Device(CompositeEventEmitter):
3647
3658
  raise hci.HCI_StatusError(result)
3648
3659
 
3649
3660
  # Wait for the connection process to complete
3650
- if transport == BT_LE_TRANSPORT:
3661
+ if transport == PhysicalTransport.LE:
3651
3662
  self.le_connecting = True
3652
3663
 
3653
3664
  if timeout is None:
3654
- return await self.abort_on('flush', pending_connection)
3665
+ return await utils.cancel_on_event(self, 'flush', pending_connection)
3655
3666
 
3656
3667
  try:
3657
3668
  return await asyncio.wait_for(
3658
3669
  asyncio.shield(pending_connection), timeout
3659
3670
  )
3660
3671
  except asyncio.TimeoutError:
3661
- if transport == BT_LE_TRANSPORT:
3672
+ if transport == PhysicalTransport.LE:
3662
3673
  await self.send_command(
3663
3674
  hci.HCI_LE_Create_Connection_Cancel_Command()
3664
3675
  )
@@ -3668,13 +3679,15 @@ class Device(CompositeEventEmitter):
3668
3679
  )
3669
3680
 
3670
3681
  try:
3671
- return await self.abort_on('flush', pending_connection)
3682
+ return await utils.cancel_on_event(
3683
+ self, 'flush', pending_connection
3684
+ )
3672
3685
  except core.ConnectionError as error:
3673
3686
  raise core.TimeoutError() from error
3674
3687
  finally:
3675
3688
  self.remove_listener('connection', on_connection)
3676
3689
  self.remove_listener('connection_failure', on_connection_failure)
3677
- if transport == BT_LE_TRANSPORT:
3690
+ if transport == PhysicalTransport.LE:
3678
3691
  self.le_connecting = False
3679
3692
  self.connect_own_address_type = None
3680
3693
  else:
@@ -3683,7 +3696,7 @@ class Device(CompositeEventEmitter):
3683
3696
  async def accept(
3684
3697
  self,
3685
3698
  peer_address: Union[hci.Address, str] = hci.Address.ANY,
3686
- role: int = BT_PERIPHERAL_ROLE,
3699
+ role: hci.Role = hci.Role.PERIPHERAL,
3687
3700
  timeout: Optional[float] = DEVICE_DEFAULT_CONNECT_TIMEOUT,
3688
3701
  ) -> Connection:
3689
3702
  '''
@@ -3704,7 +3717,7 @@ class Device(CompositeEventEmitter):
3704
3717
  # If the address is not parsable, assume it is a name instead
3705
3718
  logger.debug('looking for peer by name')
3706
3719
  peer_address = await self.find_peer_by_name(
3707
- peer_address, BT_BR_EDR_TRANSPORT
3720
+ peer_address, PhysicalTransport.BR_EDR
3708
3721
  ) # TODO: timeout
3709
3722
 
3710
3723
  assert isinstance(peer_address, hci.Address)
@@ -3724,7 +3737,7 @@ class Device(CompositeEventEmitter):
3724
3737
 
3725
3738
  try:
3726
3739
  # Wait for a request or a completed connection
3727
- pending_request = self.abort_on('flush', pending_request_fut)
3740
+ pending_request = utils.cancel_on_event(self, 'flush', pending_request_fut)
3728
3741
  result = await (
3729
3742
  asyncio.wait_for(pending_request, timeout)
3730
3743
  if timeout
@@ -3754,14 +3767,14 @@ class Device(CompositeEventEmitter):
3754
3767
 
3755
3768
  def on_connection(connection):
3756
3769
  if (
3757
- connection.transport == BT_BR_EDR_TRANSPORT
3770
+ connection.transport == PhysicalTransport.BR_EDR
3758
3771
  and connection.peer_address == peer_address
3759
3772
  ):
3760
3773
  pending_connection.set_result(connection)
3761
3774
 
3762
3775
  def on_connection_failure(error):
3763
3776
  if (
3764
- error.transport == BT_BR_EDR_TRANSPORT
3777
+ error.transport == PhysicalTransport.BR_EDR
3765
3778
  and error.peer_address == peer_address
3766
3779
  ):
3767
3780
  pending_connection.set_exception(error)
@@ -3769,12 +3782,12 @@ class Device(CompositeEventEmitter):
3769
3782
  self.on('connection', on_connection)
3770
3783
  self.on('connection_failure', on_connection_failure)
3771
3784
 
3772
- # Save pending connection, with the Peripheral role.
3785
+ # Save pending connection, with the Peripheral hci.role.
3773
3786
  # Even if we requested a role switch in the hci.HCI_Accept_Connection_Request
3774
3787
  # command, this connection is still considered Peripheral until an eventual
3775
3788
  # role change event.
3776
3789
  self.pending_connections[peer_address] = Connection.incomplete(
3777
- self, peer_address, BT_PERIPHERAL_ROLE
3790
+ self, peer_address, hci.Role.PERIPHERAL
3778
3791
  )
3779
3792
 
3780
3793
  try:
@@ -3786,7 +3799,7 @@ class Device(CompositeEventEmitter):
3786
3799
  )
3787
3800
 
3788
3801
  # Wait for connection complete
3789
- return await self.abort_on('flush', pending_connection)
3802
+ return await utils.cancel_on_event(self, 'flush', pending_connection)
3790
3803
 
3791
3804
  finally:
3792
3805
  self.remove_listener('connection', on_connection)
@@ -3831,7 +3844,7 @@ class Device(CompositeEventEmitter):
3831
3844
  # If the address is not parsable, assume it is a name instead
3832
3845
  logger.debug('looking for peer by name')
3833
3846
  peer_address = await self.find_peer_by_name(
3834
- peer_address, BT_BR_EDR_TRANSPORT
3847
+ peer_address, PhysicalTransport.BR_EDR
3835
3848
  ) # TODO: timeout
3836
3849
 
3837
3850
  await self.send_command(
@@ -3860,7 +3873,7 @@ class Device(CompositeEventEmitter):
3860
3873
 
3861
3874
  # Wait for the disconnection process to complete
3862
3875
  self.disconnecting = True
3863
- return await self.abort_on('flush', pending_disconnection)
3876
+ return await utils.cancel_on_event(self, 'flush', pending_disconnection)
3864
3877
  finally:
3865
3878
  connection.remove_listener(
3866
3879
  'disconnection', pending_disconnection.set_result
@@ -3903,7 +3916,7 @@ class Device(CompositeEventEmitter):
3903
3916
  '''
3904
3917
 
3905
3918
  if use_l2cap:
3906
- if connection.role != BT_PERIPHERAL_ROLE:
3919
+ if connection.role != hci.Role.PERIPHERAL:
3907
3920
  raise InvalidStateError(
3908
3921
  'only peripheral can update connection parameters with l2cap'
3909
3922
  )
@@ -4002,7 +4015,19 @@ class Device(CompositeEventEmitter):
4002
4015
  check_result=True,
4003
4016
  )
4004
4017
 
4005
- 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):
4006
4031
  """
4007
4032
  Scan for a peer with a given name and return its address.
4008
4033
  """
@@ -4021,7 +4046,7 @@ class Device(CompositeEventEmitter):
4021
4046
  was_scanning = self.scanning
4022
4047
  was_discovering = self.discovering
4023
4048
  try:
4024
- if transport == BT_LE_TRANSPORT:
4049
+ if transport == PhysicalTransport.LE:
4025
4050
  event_name = 'advertisement'
4026
4051
  listener = self.on(
4027
4052
  event_name,
@@ -4033,7 +4058,7 @@ class Device(CompositeEventEmitter):
4033
4058
  if not self.scanning:
4034
4059
  await self.start_scanning(filter_duplicates=True)
4035
4060
 
4036
- elif transport == BT_BR_EDR_TRANSPORT:
4061
+ elif transport == PhysicalTransport.BR_EDR:
4037
4062
  event_name = 'inquiry_result'
4038
4063
  listener = self.on(
4039
4064
  event_name,
@@ -4047,14 +4072,14 @@ class Device(CompositeEventEmitter):
4047
4072
  else:
4048
4073
  return None
4049
4074
 
4050
- return await self.abort_on('flush', peer_address)
4075
+ return await utils.cancel_on_event(self, 'flush', peer_address)
4051
4076
  finally:
4052
4077
  if listener is not None:
4053
4078
  self.remove_listener(event_name, listener)
4054
4079
 
4055
- if transport == BT_LE_TRANSPORT and not was_scanning:
4080
+ if transport == PhysicalTransport.LE and not was_scanning:
4056
4081
  await self.stop_scanning()
4057
- elif transport == BT_BR_EDR_TRANSPORT and not was_discovering:
4082
+ elif transport == PhysicalTransport.BR_EDR and not was_discovering:
4058
4083
  await self.stop_discovery()
4059
4084
 
4060
4085
  async def find_peer_by_identity_address(
@@ -4097,7 +4122,7 @@ class Device(CompositeEventEmitter):
4097
4122
  if not self.scanning:
4098
4123
  await self.start_scanning(filter_duplicates=True)
4099
4124
 
4100
- return await self.abort_on('flush', peer_address)
4125
+ return await utils.cancel_on_event(self, 'flush', peer_address)
4101
4126
  finally:
4102
4127
  if listener is not None:
4103
4128
  self.remove_listener(event_name, listener)
@@ -4148,10 +4173,10 @@ class Device(CompositeEventEmitter):
4148
4173
  if keys.ltk:
4149
4174
  return keys.ltk.value
4150
4175
 
4151
- if connection.role == BT_CENTRAL_ROLE and keys.ltk_central:
4176
+ if connection.role == hci.Role.CENTRAL and keys.ltk_central:
4152
4177
  return keys.ltk_central.value
4153
4178
 
4154
- if connection.role == BT_PERIPHERAL_ROLE and keys.ltk_peripheral:
4179
+ if connection.role == hci.Role.PERIPHERAL and keys.ltk_peripheral:
4155
4180
  return keys.ltk_peripheral.value
4156
4181
  return None
4157
4182
 
@@ -4201,7 +4226,9 @@ class Device(CompositeEventEmitter):
4201
4226
  raise hci.HCI_StatusError(result)
4202
4227
 
4203
4228
  # Wait for the authentication to complete
4204
- await connection.abort_on('disconnection', pending_authentication)
4229
+ await utils.cancel_on_event(
4230
+ connection, 'disconnection', pending_authentication
4231
+ )
4205
4232
  finally:
4206
4233
  connection.remove_listener('connection_authentication', on_authentication)
4207
4234
  connection.remove_listener(
@@ -4209,7 +4236,7 @@ class Device(CompositeEventEmitter):
4209
4236
  )
4210
4237
 
4211
4238
  async def encrypt(self, connection, enable=True):
4212
- if not enable and connection.transport == BT_LE_TRANSPORT:
4239
+ if not enable and connection.transport == PhysicalTransport.LE:
4213
4240
  raise InvalidArgumentError('`enable` parameter is classic only.')
4214
4241
 
4215
4242
  # Set up event handlers
@@ -4226,7 +4253,7 @@ class Device(CompositeEventEmitter):
4226
4253
 
4227
4254
  # Request the encryption
4228
4255
  try:
4229
- if connection.transport == BT_LE_TRANSPORT:
4256
+ if connection.transport == PhysicalTransport.LE:
4230
4257
  # Look for a key in the key store
4231
4258
  if self.keystore is None:
4232
4259
  raise InvalidOperationError('no key store')
@@ -4247,7 +4274,7 @@ class Device(CompositeEventEmitter):
4247
4274
  else:
4248
4275
  raise InvalidOperationError('no LTK found for peer')
4249
4276
 
4250
- if connection.role != hci.HCI_CENTRAL_ROLE:
4277
+ if connection.role != hci.Role.CENTRAL:
4251
4278
  raise InvalidStateError('only centrals can start encryption')
4252
4279
 
4253
4280
  result = await self.send_command(
@@ -4281,7 +4308,7 @@ class Device(CompositeEventEmitter):
4281
4308
  raise hci.HCI_StatusError(result)
4282
4309
 
4283
4310
  # Wait for the result
4284
- await connection.abort_on('disconnection', pending_encryption)
4311
+ await utils.cancel_on_event(connection, 'disconnection', pending_encryption)
4285
4312
  finally:
4286
4313
  connection.remove_listener(
4287
4314
  'connection_encryption_change', on_encryption_change
@@ -4303,7 +4330,7 @@ class Device(CompositeEventEmitter):
4303
4330
  self.emit('key_store_update')
4304
4331
 
4305
4332
  # [Classic only]
4306
- async def switch_role(self, connection: Connection, role: int):
4333
+ async def switch_role(self, connection: Connection, role: hci.Role):
4307
4334
  pending_role_change = asyncio.get_running_loop().create_future()
4308
4335
 
4309
4336
  def on_role_change(new_role):
@@ -4325,7 +4352,9 @@ class Device(CompositeEventEmitter):
4325
4352
  f'{hci.HCI_Constant.error_name(result.status)}'
4326
4353
  )
4327
4354
  raise hci.HCI_StatusError(result)
4328
- await connection.abort_on('disconnection', pending_role_change)
4355
+ await utils.cancel_on_event(
4356
+ connection, 'disconnection', pending_role_change
4357
+ )
4329
4358
  finally:
4330
4359
  connection.remove_listener('role_change', on_role_change)
4331
4360
  connection.remove_listener('role_change_failure', on_role_change_failure)
@@ -4374,13 +4403,13 @@ class Device(CompositeEventEmitter):
4374
4403
  raise hci.HCI_StatusError(result)
4375
4404
 
4376
4405
  # Wait for the result
4377
- return await self.abort_on('flush', pending_name)
4406
+ return await utils.cancel_on_event(self, 'flush', pending_name)
4378
4407
  finally:
4379
4408
  self.remove_listener('remote_name', handler)
4380
4409
  self.remove_listener('remote_name_failure', failure_handler)
4381
4410
 
4382
4411
  # [LE only]
4383
- @experimental('Only for testing.')
4412
+ @utils.experimental('Only for testing.')
4384
4413
  async def setup_cig(
4385
4414
  self,
4386
4415
  cig_id: int,
@@ -4438,7 +4467,7 @@ class Device(CompositeEventEmitter):
4438
4467
  return cis_handles
4439
4468
 
4440
4469
  # [LE only]
4441
- @experimental('Only for testing.')
4470
+ @utils.experimental('Only for testing.')
4442
4471
  async def create_cis(
4443
4472
  self, cis_acl_pairs: Sequence[tuple[int, int]]
4444
4473
  ) -> list[CisLink]:
@@ -4454,7 +4483,7 @@ class Device(CompositeEventEmitter):
4454
4483
  cig_id=cig_id,
4455
4484
  )
4456
4485
 
4457
- with closing(EventWatcher()) as watcher:
4486
+ with closing(utils.EventWatcher()) as watcher:
4458
4487
  pending_cis_establishments = {
4459
4488
  cis_handle: asyncio.get_running_loop().create_future()
4460
4489
  for cis_handle, _ in cis_acl_pairs
@@ -4481,7 +4510,7 @@ class Device(CompositeEventEmitter):
4481
4510
  return await asyncio.gather(*pending_cis_establishments.values())
4482
4511
 
4483
4512
  # [LE only]
4484
- @experimental('Only for testing.')
4513
+ @utils.experimental('Only for testing.')
4485
4514
  async def accept_cis_request(self, handle: int) -> CisLink:
4486
4515
  """[LE Only] Accepts an incoming CIS request.
4487
4516
 
@@ -4503,7 +4532,7 @@ class Device(CompositeEventEmitter):
4503
4532
  if cis_link.state == CisLink.State.ESTABLISHED:
4504
4533
  return cis_link
4505
4534
 
4506
- with closing(EventWatcher()) as watcher:
4535
+ with closing(utils.EventWatcher()) as watcher:
4507
4536
  pending_establishment = asyncio.get_running_loop().create_future()
4508
4537
 
4509
4538
  def on_establishment() -> None:
@@ -4527,7 +4556,7 @@ class Device(CompositeEventEmitter):
4527
4556
  raise UnreachableError()
4528
4557
 
4529
4558
  # [LE only]
4530
- @experimental('Only for testing.')
4559
+ @utils.experimental('Only for testing.')
4531
4560
  async def reject_cis_request(
4532
4561
  self,
4533
4562
  handle: int,
@@ -4541,14 +4570,14 @@ class Device(CompositeEventEmitter):
4541
4570
  )
4542
4571
 
4543
4572
  # [LE only]
4544
- @experimental('Only for testing.')
4573
+ @utils.experimental('Only for testing.')
4545
4574
  async def create_big(
4546
4575
  self, advertising_set: AdvertisingSet, parameters: BigParameters
4547
4576
  ) -> Big:
4548
4577
  if (big_handle := self.next_big_handle()) is None:
4549
4578
  raise core.OutOfResourcesError("All valid BIG handles already in use")
4550
4579
 
4551
- with closing(EventWatcher()) as watcher:
4580
+ with closing(utils.EventWatcher()) as watcher:
4552
4581
  big = Big(
4553
4582
  big_handle=big_handle,
4554
4583
  parameters=parameters,
@@ -4591,7 +4620,7 @@ class Device(CompositeEventEmitter):
4591
4620
  return big
4592
4621
 
4593
4622
  # [LE only]
4594
- @experimental('Only for testing.')
4623
+ @utils.experimental('Only for testing.')
4595
4624
  async def create_big_sync(
4596
4625
  self, pa_sync: PeriodicAdvertisingSync, parameters: BigSyncParameters
4597
4626
  ) -> BigSync:
@@ -4601,7 +4630,7 @@ class Device(CompositeEventEmitter):
4601
4630
  if (pa_sync_handle := pa_sync.sync_handle) is None:
4602
4631
  raise core.InvalidStateError("PA Sync is not established")
4603
4632
 
4604
- with closing(EventWatcher()) as watcher:
4633
+ with closing(utils.EventWatcher()) as watcher:
4605
4634
  big_sync = BigSync(
4606
4635
  big_handle=big_handle,
4607
4636
  parameters=parameters,
@@ -4649,7 +4678,7 @@ class Device(CompositeEventEmitter):
4649
4678
  Returns:
4650
4679
  LE features supported by the remote device.
4651
4680
  """
4652
- with closing(EventWatcher()) as watcher:
4681
+ with closing(utils.EventWatcher()) as watcher:
4653
4682
  read_feature_future: asyncio.Future[hci.LeFeatureMask] = (
4654
4683
  asyncio.get_running_loop().create_future()
4655
4684
  )
@@ -4672,7 +4701,7 @@ class Device(CompositeEventEmitter):
4672
4701
  )
4673
4702
  return await read_feature_future
4674
4703
 
4675
- @experimental('Only for testing.')
4704
+ @utils.experimental('Only for testing.')
4676
4705
  async def get_remote_cs_capabilities(
4677
4706
  self, connection: Connection
4678
4707
  ) -> ChannelSoundingCapabilities:
@@ -4680,7 +4709,7 @@ class Device(CompositeEventEmitter):
4680
4709
  asyncio.get_running_loop().create_future()
4681
4710
  )
4682
4711
 
4683
- with closing(EventWatcher()) as watcher:
4712
+ with closing(utils.EventWatcher()) as watcher:
4684
4713
  watcher.once(
4685
4714
  connection, 'channel_sounding_capabilities', complete_future.set_result
4686
4715
  )
@@ -4697,7 +4726,7 @@ class Device(CompositeEventEmitter):
4697
4726
  )
4698
4727
  return await complete_future
4699
4728
 
4700
- @experimental('Only for testing.')
4729
+ @utils.experimental('Only for testing.')
4701
4730
  async def set_default_cs_settings(
4702
4731
  self,
4703
4732
  connection: Connection,
@@ -4717,7 +4746,7 @@ class Device(CompositeEventEmitter):
4717
4746
  check_result=True,
4718
4747
  )
4719
4748
 
4720
- @experimental('Only for testing.')
4749
+ @utils.experimental('Only for testing.')
4721
4750
  async def create_cs_config(
4722
4751
  self,
4723
4752
  connection: Connection,
@@ -4754,7 +4783,7 @@ class Device(CompositeEventEmitter):
4754
4783
  if config_id is None:
4755
4784
  raise OutOfResourcesError("No available config ID on this connection!")
4756
4785
 
4757
- with closing(EventWatcher()) as watcher:
4786
+ with closing(utils.EventWatcher()) as watcher:
4758
4787
  watcher.once(
4759
4788
  connection, 'channel_sounding_config', complete_future.set_result
4760
4789
  )
@@ -4788,12 +4817,12 @@ class Device(CompositeEventEmitter):
4788
4817
  )
4789
4818
  return await complete_future
4790
4819
 
4791
- @experimental('Only for testing.')
4820
+ @utils.experimental('Only for testing.')
4792
4821
  async def enable_cs_security(self, connection: Connection) -> None:
4793
4822
  complete_future: asyncio.Future[None] = (
4794
4823
  asyncio.get_running_loop().create_future()
4795
4824
  )
4796
- with closing(EventWatcher()) as watcher:
4825
+ with closing(utils.EventWatcher()) as watcher:
4797
4826
 
4798
4827
  def on_event(event: hci.HCI_LE_CS_Security_Enable_Complete_Event) -> None:
4799
4828
  if event.connection_handle != connection.handle:
@@ -4812,7 +4841,7 @@ class Device(CompositeEventEmitter):
4812
4841
  )
4813
4842
  return await complete_future
4814
4843
 
4815
- @experimental('Only for testing.')
4844
+ @utils.experimental('Only for testing.')
4816
4845
  async def set_cs_procedure_parameters(
4817
4846
  self,
4818
4847
  connection: Connection,
@@ -4850,7 +4879,7 @@ class Device(CompositeEventEmitter):
4850
4879
  check_result=True,
4851
4880
  )
4852
4881
 
4853
- @experimental('Only for testing.')
4882
+ @utils.experimental('Only for testing.')
4854
4883
  async def enable_cs_procedure(
4855
4884
  self,
4856
4885
  connection: Connection,
@@ -4860,7 +4889,7 @@ class Device(CompositeEventEmitter):
4860
4889
  complete_future: asyncio.Future[ChannelSoundingProcedure] = (
4861
4890
  asyncio.get_running_loop().create_future()
4862
4891
  )
4863
- with closing(EventWatcher()) as watcher:
4892
+ with closing(utils.EventWatcher()) as watcher:
4864
4893
  watcher.once(
4865
4894
  connection, 'channel_sounding_procedure', complete_future.set_result
4866
4895
  )
@@ -4900,10 +4929,12 @@ class Device(CompositeEventEmitter):
4900
4929
  value=link_key, authenticated=authenticated
4901
4930
  )
4902
4931
 
4903
- 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
+ )
4904
4935
 
4905
4936
  if connection := self.find_connection_by_bd_addr(
4906
- bd_addr, transport=BT_BR_EDR_TRANSPORT
4937
+ bd_addr, transport=PhysicalTransport.BR_EDR
4907
4938
  ):
4908
4939
  connection.link_key_type = key_type
4909
4940
 
@@ -5169,7 +5200,7 @@ class Device(CompositeEventEmitter):
5169
5200
  if advertising_set.auto_restart:
5170
5201
  connection.once(
5171
5202
  'disconnection',
5172
- lambda _: self.abort_on('flush', advertising_set.start()),
5203
+ lambda _: utils.cancel_on_event(self, 'flush', advertising_set.start()),
5173
5204
  )
5174
5205
 
5175
5206
  self.emit('connection', connection)
@@ -5178,11 +5209,11 @@ class Device(CompositeEventEmitter):
5178
5209
  def on_connection(
5179
5210
  self,
5180
5211
  connection_handle: int,
5181
- transport: int,
5212
+ transport: core.PhysicalTransport,
5182
5213
  peer_address: hci.Address,
5183
5214
  self_resolvable_address: Optional[hci.Address],
5184
5215
  peer_resolvable_address: Optional[hci.Address],
5185
- role: int,
5216
+ role: hci.Role,
5186
5217
  connection_parameters: ConnectionParameters,
5187
5218
  ) -> None:
5188
5219
  # Convert all-zeros addresses into None.
@@ -5203,7 +5234,7 @@ class Device(CompositeEventEmitter):
5203
5234
  'new connection reuses the same handle as a previous connection'
5204
5235
  )
5205
5236
 
5206
- if transport == BT_BR_EDR_TRANSPORT:
5237
+ if transport == PhysicalTransport.BR_EDR:
5207
5238
  # Create a new connection
5208
5239
  connection = self.pending_connections.pop(peer_address)
5209
5240
  connection.complete(connection_handle, connection_parameters)
@@ -5225,8 +5256,8 @@ class Device(CompositeEventEmitter):
5225
5256
  peer_address = resolved_address
5226
5257
 
5227
5258
  self_address = None
5228
- own_address_type: Optional[int] = None
5229
- if role == hci.HCI_CENTRAL_ROLE:
5259
+ own_address_type: Optional[hci.OwnAddressType] = None
5260
+ if role == hci.Role.CENTRAL:
5230
5261
  own_address_type = self.connect_own_address_type
5231
5262
  assert own_address_type is not None
5232
5263
  else:
@@ -5273,22 +5304,22 @@ class Device(CompositeEventEmitter):
5273
5304
  )
5274
5305
  self.connections[connection_handle] = connection
5275
5306
 
5276
- if role == hci.HCI_PERIPHERAL_ROLE and self.legacy_advertiser:
5307
+ if role == hci.Role.PERIPHERAL and self.legacy_advertiser:
5277
5308
  if self.legacy_advertiser.auto_restart:
5278
5309
  advertiser = self.legacy_advertiser
5279
5310
  connection.once(
5280
5311
  'disconnection',
5281
- lambda _: self.abort_on('flush', advertiser.start()),
5312
+ lambda _: utils.cancel_on_event(self, 'flush', advertiser.start()),
5282
5313
  )
5283
5314
  else:
5284
5315
  self.legacy_advertiser = None
5285
5316
 
5286
- 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:
5287
5318
  # We can emit now, we have all the info we need
5288
5319
  self.emit('connection', connection)
5289
5320
  return
5290
5321
 
5291
- if role == hci.HCI_PERIPHERAL_ROLE and self.supports_le_extended_advertising:
5322
+ if role == hci.Role.PERIPHERAL and self.supports_le_extended_advertising:
5292
5323
  if advertising_set := self.connecting_extended_advertising_sets.pop(
5293
5324
  connection_handle, None
5294
5325
  ):
@@ -5305,7 +5336,7 @@ class Device(CompositeEventEmitter):
5305
5336
 
5306
5337
  # For directed advertising, this means a timeout
5307
5338
  if (
5308
- transport == BT_LE_TRANSPORT
5339
+ transport == PhysicalTransport.LE
5309
5340
  and self.legacy_advertiser
5310
5341
  and self.legacy_advertiser.advertising_type.is_directed
5311
5342
  ):
@@ -5332,7 +5363,7 @@ class Device(CompositeEventEmitter):
5332
5363
  hci.HCI_Connection_Complete_Event.ESCO_LINK_TYPE,
5333
5364
  ):
5334
5365
  if connection := self.find_connection_by_bd_addr(
5335
- bd_addr, transport=BT_BR_EDR_TRANSPORT
5366
+ bd_addr, transport=PhysicalTransport.BR_EDR
5336
5367
  ):
5337
5368
  self.emit('sco_request', connection, link_type)
5338
5369
  else:
@@ -5353,7 +5384,7 @@ class Device(CompositeEventEmitter):
5353
5384
  elif self.classic_accept_any:
5354
5385
  # Save pending connection
5355
5386
  self.pending_connections[bd_addr] = Connection.incomplete(
5356
- self, bd_addr, BT_PERIPHERAL_ROLE
5387
+ self, bd_addr, hci.Role.PERIPHERAL
5357
5388
  )
5358
5389
 
5359
5390
  self.host.send_command_sync(
@@ -5405,7 +5436,7 @@ class Device(CompositeEventEmitter):
5405
5436
  connection.emit('disconnection_failure', error)
5406
5437
 
5407
5438
  @host_event_handler
5408
- @AsyncRunner.run_in_task()
5439
+ @utils.AsyncRunner.run_in_task()
5409
5440
  async def on_inquiry_complete(self):
5410
5441
  if self.auto_restart_inquiry:
5411
5442
  # Inquire again
@@ -5537,7 +5568,7 @@ class Device(CompositeEventEmitter):
5537
5568
 
5538
5569
  async def reply() -> None:
5539
5570
  try:
5540
- if await connection.abort_on('disconnection', method()):
5571
+ if await utils.cancel_on_event(connection, 'disconnection', method()):
5541
5572
  await self.host.send_command(
5542
5573
  hci.HCI_User_Confirmation_Request_Reply_Command(
5543
5574
  bd_addr=connection.peer_address
@@ -5553,7 +5584,7 @@ class Device(CompositeEventEmitter):
5553
5584
  )
5554
5585
  )
5555
5586
 
5556
- AsyncRunner.spawn(reply())
5587
+ utils.AsyncRunner.spawn(reply())
5557
5588
 
5558
5589
  # [Classic only]
5559
5590
  @host_event_handler
@@ -5564,8 +5595,8 @@ class Device(CompositeEventEmitter):
5564
5595
 
5565
5596
  async def reply() -> None:
5566
5597
  try:
5567
- number = await connection.abort_on(
5568
- 'disconnection', pairing_config.delegate.get_number()
5598
+ number = await utils.cancel_on_event(
5599
+ connection, 'disconnection', pairing_config.delegate.get_number()
5569
5600
  )
5570
5601
  if number is not None:
5571
5602
  await self.host.send_command(
@@ -5583,7 +5614,7 @@ class Device(CompositeEventEmitter):
5583
5614
  )
5584
5615
  )
5585
5616
 
5586
- AsyncRunner.spawn(reply())
5617
+ utils.AsyncRunner.spawn(reply())
5587
5618
 
5588
5619
  # [Classic only]
5589
5620
  @host_event_handler
@@ -5598,8 +5629,8 @@ class Device(CompositeEventEmitter):
5598
5629
  if io_capability == hci.HCI_KEYBOARD_ONLY_IO_CAPABILITY:
5599
5630
  # Ask the user to enter a string
5600
5631
  async def get_pin_code():
5601
- pin_code = await connection.abort_on(
5602
- 'disconnection', pairing_config.delegate.get_string(16)
5632
+ pin_code = await utils.cancel_on_event(
5633
+ connection, 'disconnection', pairing_config.delegate.get_string(16)
5603
5634
  )
5604
5635
 
5605
5636
  if pin_code is not None:
@@ -5637,8 +5668,8 @@ class Device(CompositeEventEmitter):
5637
5668
  pairing_config = self.pairing_config_factory(connection)
5638
5669
 
5639
5670
  # Show the passkey to the user
5640
- connection.abort_on(
5641
- 'disconnection', pairing_config.delegate.display_number(passkey)
5671
+ utils.cancel_on_event(
5672
+ connection, 'disconnection', pairing_config.delegate.display_number(passkey)
5642
5673
  )
5643
5674
 
5644
5675
  # [Classic only]
@@ -5670,7 +5701,7 @@ class Device(CompositeEventEmitter):
5670
5701
  # [Classic only]
5671
5702
  @host_event_handler
5672
5703
  @with_connection_from_address
5673
- @experimental('Only for testing.')
5704
+ @utils.experimental('Only for testing.')
5674
5705
  def on_sco_connection(
5675
5706
  self, acl_connection: Connection, sco_handle: int, link_type: int
5676
5707
  ) -> None:
@@ -5690,7 +5721,7 @@ class Device(CompositeEventEmitter):
5690
5721
  # [Classic only]
5691
5722
  @host_event_handler
5692
5723
  @with_connection_from_address
5693
- @experimental('Only for testing.')
5724
+ @utils.experimental('Only for testing.')
5694
5725
  def on_sco_connection_failure(
5695
5726
  self, acl_connection: Connection, status: int
5696
5727
  ) -> None:
@@ -5699,7 +5730,7 @@ class Device(CompositeEventEmitter):
5699
5730
 
5700
5731
  # [Classic only]
5701
5732
  @host_event_handler
5702
- @experimental('Only for testing')
5733
+ @utils.experimental('Only for testing')
5703
5734
  def on_sco_packet(
5704
5735
  self, sco_handle: int, packet: hci.HCI_SynchronousDataPacket
5705
5736
  ) -> None:
@@ -5709,7 +5740,7 @@ class Device(CompositeEventEmitter):
5709
5740
  # [LE only]
5710
5741
  @host_event_handler
5711
5742
  @with_connection_from_handle
5712
- @experimental('Only for testing')
5743
+ @utils.experimental('Only for testing')
5713
5744
  def on_cis_request(
5714
5745
  self,
5715
5746
  acl_connection: Connection,
@@ -5736,7 +5767,7 @@ class Device(CompositeEventEmitter):
5736
5767
 
5737
5768
  # [LE only]
5738
5769
  @host_event_handler
5739
- @experimental('Only for testing')
5770
+ @utils.experimental('Only for testing')
5740
5771
  def on_cis_establishment(self, cis_handle: int) -> None:
5741
5772
  cis_link = self.cis_links[cis_handle]
5742
5773
  cis_link.state = CisLink.State.ESTABLISHED
@@ -5756,7 +5787,7 @@ class Device(CompositeEventEmitter):
5756
5787
 
5757
5788
  # [LE only]
5758
5789
  @host_event_handler
5759
- @experimental('Only for testing')
5790
+ @utils.experimental('Only for testing')
5760
5791
  def on_cis_establishment_failure(self, cis_handle: int, status: int) -> None:
5761
5792
  logger.debug(f'*** CIS Establishment Failure: cis=[0x{cis_handle:04X}] ***')
5762
5793
  if cis_link := self.cis_links.pop(cis_handle):
@@ -5765,7 +5796,7 @@ class Device(CompositeEventEmitter):
5765
5796
 
5766
5797
  # [LE only]
5767
5798
  @host_event_handler
5768
- @experimental('Only for testing')
5799
+ @utils.experimental('Only for testing')
5769
5800
  def on_iso_packet(self, handle: int, packet: hci.HCI_IsoDataPacket) -> None:
5770
5801
  if (cis_link := self.cis_links.get(handle)) and cis_link.sink:
5771
5802
  cis_link.sink(packet)
@@ -5783,14 +5814,14 @@ class Device(CompositeEventEmitter):
5783
5814
  connection.encryption = encryption
5784
5815
  if (
5785
5816
  not connection.authenticated
5786
- and connection.transport == BT_BR_EDR_TRANSPORT
5817
+ and connection.transport == PhysicalTransport.BR_EDR
5787
5818
  and encryption == hci.HCI_Encryption_Change_Event.AES_CCM
5788
5819
  ):
5789
5820
  connection.authenticated = True
5790
5821
  connection.sc = True
5791
5822
  if (
5792
5823
  not connection.authenticated
5793
- and connection.transport == BT_LE_TRANSPORT
5824
+ and connection.transport == PhysicalTransport.LE
5794
5825
  and encryption == hci.HCI_Encryption_Change_Event.E0_OR_AES_CCM
5795
5826
  ):
5796
5827
  connection.authenticated = True