bumble 0.0.213__py3-none-any.whl → 0.0.215__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 (123) hide show
  1. bumble/_version.py +16 -3
  2. bumble/a2dp.py +15 -16
  3. bumble/apps/auracast.py +14 -38
  4. bumble/apps/bench.py +10 -15
  5. bumble/apps/ble_rpa_tool.py +1 -0
  6. bumble/apps/console.py +22 -25
  7. bumble/apps/controller_info.py +20 -25
  8. bumble/apps/controller_loopback.py +6 -10
  9. bumble/apps/controllers.py +2 -3
  10. bumble/apps/device_info.py +4 -5
  11. bumble/apps/gatt_dump.py +3 -3
  12. bumble/apps/gg_bridge.py +7 -8
  13. bumble/apps/hci_bridge.py +4 -3
  14. bumble/apps/l2cap_bridge.py +5 -5
  15. bumble/apps/lea_unicast/app.py +16 -26
  16. bumble/apps/pair.py +30 -43
  17. bumble/apps/pandora_server.py +5 -4
  18. bumble/apps/player/player.py +20 -24
  19. bumble/apps/rfcomm_bridge.py +4 -10
  20. bumble/apps/scan.py +17 -8
  21. bumble/apps/show.py +4 -5
  22. bumble/apps/speaker/speaker.py +23 -27
  23. bumble/apps/unbond.py +3 -3
  24. bumble/apps/usb_probe.py +2 -4
  25. bumble/att.py +241 -246
  26. bumble/audio/io.py +5 -9
  27. bumble/avc.py +2 -2
  28. bumble/avctp.py +6 -7
  29. bumble/avdtp.py +19 -22
  30. bumble/avrcp.py +1097 -589
  31. bumble/codecs.py +2 -0
  32. bumble/controller.py +142 -35
  33. bumble/core.py +567 -248
  34. bumble/crypto/__init__.py +2 -2
  35. bumble/crypto/builtin.py +1 -1
  36. bumble/crypto/cryptography.py +2 -4
  37. bumble/data_types.py +1025 -0
  38. bumble/device.py +319 -267
  39. bumble/drivers/__init__.py +3 -2
  40. bumble/drivers/intel.py +3 -4
  41. bumble/drivers/rtk.py +26 -9
  42. bumble/gap.py +4 -4
  43. bumble/gatt.py +3 -2
  44. bumble/gatt_adapters.py +3 -11
  45. bumble/gatt_client.py +69 -81
  46. bumble/gatt_server.py +124 -124
  47. bumble/hci.py +114 -18
  48. bumble/helpers.py +19 -26
  49. bumble/hfp.py +10 -21
  50. bumble/hid.py +22 -16
  51. bumble/host.py +191 -103
  52. bumble/keys.py +5 -3
  53. bumble/l2cap.py +138 -104
  54. bumble/link.py +18 -19
  55. bumble/logging.py +65 -0
  56. bumble/pairing.py +7 -6
  57. bumble/pandora/__init__.py +9 -8
  58. bumble/pandora/config.py +3 -1
  59. bumble/pandora/device.py +3 -2
  60. bumble/pandora/host.py +38 -36
  61. bumble/pandora/l2cap.py +22 -21
  62. bumble/pandora/security.py +15 -15
  63. bumble/pandora/utils.py +5 -3
  64. bumble/profiles/aics.py +11 -11
  65. bumble/profiles/ams.py +403 -0
  66. bumble/profiles/ancs.py +6 -7
  67. bumble/profiles/ascs.py +14 -9
  68. bumble/profiles/asha.py +8 -12
  69. bumble/profiles/bap.py +11 -23
  70. bumble/profiles/bass.py +2 -7
  71. bumble/profiles/battery_service.py +3 -4
  72. bumble/profiles/cap.py +1 -2
  73. bumble/profiles/csip.py +2 -6
  74. bumble/profiles/device_information_service.py +2 -2
  75. bumble/profiles/gap.py +4 -4
  76. bumble/profiles/gatt_service.py +1 -4
  77. bumble/profiles/gmap.py +5 -5
  78. bumble/profiles/hap.py +62 -59
  79. bumble/profiles/heart_rate_service.py +5 -4
  80. bumble/profiles/le_audio.py +3 -1
  81. bumble/profiles/mcp.py +3 -7
  82. bumble/profiles/pacs.py +3 -6
  83. bumble/profiles/pbp.py +2 -0
  84. bumble/profiles/tmap.py +2 -3
  85. bumble/profiles/vcs.py +2 -8
  86. bumble/profiles/vocs.py +8 -8
  87. bumble/rfcomm.py +11 -14
  88. bumble/rtp.py +1 -0
  89. bumble/sdp.py +10 -8
  90. bumble/smp.py +151 -159
  91. bumble/snoop.py +5 -5
  92. bumble/tools/generate_company_id_list.py +1 -0
  93. bumble/tools/intel_fw_download.py +3 -3
  94. bumble/tools/intel_util.py +5 -4
  95. bumble/tools/rtk_fw_download.py +6 -3
  96. bumble/tools/rtk_util.py +26 -8
  97. bumble/transport/__init__.py +19 -15
  98. bumble/transport/android_emulator.py +8 -13
  99. bumble/transport/android_netsim.py +19 -18
  100. bumble/transport/common.py +12 -15
  101. bumble/transport/file.py +1 -1
  102. bumble/transport/hci_socket.py +4 -6
  103. bumble/transport/pty.py +5 -6
  104. bumble/transport/pyusb.py +7 -10
  105. bumble/transport/serial.py +2 -1
  106. bumble/transport/tcp_client.py +2 -2
  107. bumble/transport/tcp_server.py +11 -14
  108. bumble/transport/udp.py +3 -3
  109. bumble/transport/unix.py +67 -1
  110. bumble/transport/usb.py +6 -6
  111. bumble/transport/vhci.py +0 -1
  112. bumble/transport/ws_client.py +2 -1
  113. bumble/transport/ws_server.py +3 -2
  114. bumble/utils.py +20 -5
  115. bumble/vendor/android/hci.py +1 -2
  116. bumble/vendor/zephyr/hci.py +0 -1
  117. {bumble-0.0.213.dist-info → bumble-0.0.215.dist-info}/METADATA +4 -2
  118. bumble-0.0.215.dist-info/RECORD +183 -0
  119. bumble-0.0.213.dist-info/RECORD +0 -180
  120. {bumble-0.0.213.dist-info → bumble-0.0.215.dist-info}/WHEEL +0 -0
  121. {bumble-0.0.213.dist-info → bumble-0.0.215.dist-info}/entry_points.txt +0 -0
  122. {bumble-0.0.213.dist-info → bumble-0.0.215.dist-info}/licenses/LICENSE +0 -0
  123. {bumble-0.0.213.dist-info → bumble-0.0.215.dist-info}/top_level.txt +0 -0
bumble/device.py CHANGED
@@ -16,24 +16,22 @@
16
16
  # Imports
17
17
  # -----------------------------------------------------------------------------
18
18
  from __future__ import annotations
19
+
19
20
  import asyncio
20
21
  import collections
21
- from collections.abc import Iterable, Sequence
22
- from contextlib import (
23
- asynccontextmanager,
24
- AsyncExitStack,
25
- closing,
26
- )
27
22
  import copy
28
- from dataclasses import dataclass, field
29
- from enum import Enum, IntEnum
30
23
  import functools
31
24
  import itertools
32
25
  import json
33
26
  import logging
34
27
  import secrets
35
28
  import sys
29
+ from collections.abc import Iterable, Sequence
30
+ from contextlib import AsyncExitStack, asynccontextmanager, closing
31
+ from dataclasses import dataclass, field
32
+ from enum import Enum, IntEnum
36
33
  from typing import (
34
+ TYPE_CHECKING,
37
35
  Any,
38
36
  Awaitable,
39
37
  Callable,
@@ -43,47 +41,47 @@ from typing import (
43
41
  Union,
44
42
  cast,
45
43
  overload,
46
- TYPE_CHECKING,
47
44
  )
48
- from typing_extensions import Self
49
45
 
46
+ from typing_extensions import Self
50
47
 
51
- from bumble.colors import color
48
+ from bumble import (
49
+ core,
50
+ data_types,
51
+ gatt,
52
+ gatt_client,
53
+ gatt_server,
54
+ hci,
55
+ l2cap,
56
+ pairing,
57
+ sdp,
58
+ smp,
59
+ utils,
60
+ )
52
61
  from bumble.att import ATT_CID, ATT_DEFAULT_MTU, ATT_PDU
53
- from bumble.gatt import Attribute, Characteristic, Descriptor, Service
54
- from bumble.host import DataPacketQueue, Host
55
- from bumble.profiles.gap import GenericAccessService
62
+ from bumble.colors import color
56
63
  from bumble.core import (
57
- PhysicalTransport,
58
64
  AdvertisingData,
59
65
  BaseBumbleError,
60
- ConnectionParameterUpdateError,
61
66
  CommandTimeoutError,
67
+ ConnectionParameterUpdateError,
62
68
  ConnectionPHY,
63
69
  InvalidArgumentError,
64
70
  InvalidOperationError,
65
71
  InvalidStateError,
66
72
  NotSupportedError,
67
73
  OutOfResourcesError,
74
+ PhysicalTransport,
68
75
  UnreachableError,
69
76
  )
70
- from bumble import utils
71
- from bumble.keys import (
72
- KeyStore,
73
- PairingKeys,
74
- )
75
- from bumble import hci
76
- from bumble import pairing
77
- from bumble import gatt_client
78
- from bumble import gatt_server
79
- from bumble import smp
80
- from bumble import sdp
81
- from bumble import l2cap
82
- from bumble import core
77
+ from bumble.gatt import Attribute, Characteristic, Descriptor, Service
78
+ from bumble.host import DataPacketQueue, Host
79
+ from bumble.keys import KeyStore, PairingKeys
83
80
  from bumble.profiles import gatt_service
81
+ from bumble.profiles.gap import GenericAccessService
84
82
 
85
83
  if TYPE_CHECKING:
86
- from bumble.transport.common import TransportSource, TransportSink
84
+ from bumble.transport.common import TransportSink, TransportSource
87
85
 
88
86
  _T = TypeVar('_T')
89
87
 
@@ -267,7 +265,7 @@ class ExtendedAdvertisement(Advertisement):
267
265
 
268
266
  # -----------------------------------------------------------------------------
269
267
  class AdvertisementDataAccumulator:
270
- def __init__(self, passive=False):
268
+ def __init__(self, passive: bool = False):
271
269
  self.passive = passive
272
270
  self.last_advertisement = None
273
271
  self.last_data = b''
@@ -1166,14 +1164,11 @@ class BigSync(utils.EventEmitter):
1166
1164
  logger.error('BIG Sync %d is not active.', self.big_handle)
1167
1165
  return
1168
1166
 
1169
- with closing(utils.EventWatcher()) as watcher:
1170
- terminated = asyncio.Event()
1171
- watcher.once(self, BigSync.Event.TERMINATION, lambda _: terminated.set())
1172
- await self.device.send_command(
1173
- hci.HCI_LE_BIG_Terminate_Sync_Command(big_handle=self.big_handle),
1174
- check_result=True,
1175
- )
1176
- await terminated.wait()
1167
+ await self.device.send_command(
1168
+ hci.HCI_LE_BIG_Terminate_Sync_Command(big_handle=self.big_handle),
1169
+ check_result=True,
1170
+ )
1171
+ self.state = BigSync.State.TERMINATED
1177
1172
 
1178
1173
 
1179
1174
  # -----------------------------------------------------------------------------
@@ -1249,7 +1244,7 @@ class LePhyOptions:
1249
1244
  PREFER_S_2_CODED_PHY = 1
1250
1245
  PREFER_S_8_CODED_PHY = 2
1251
1246
 
1252
- def __init__(self, coded_phy_preference=0):
1247
+ def __init__(self, coded_phy_preference: int = 0):
1253
1248
  self.coded_phy_preference = coded_phy_preference
1254
1249
 
1255
1250
  def __int__(self):
@@ -1458,6 +1453,8 @@ class _IsoLink:
1458
1453
  handle: int
1459
1454
  device: Device
1460
1455
  sink: Callable[[hci.HCI_IsoDataPacket], Any] | None = None
1456
+ data_paths: set[_IsoLink.Direction]
1457
+ _data_path_lock: asyncio.Lock
1461
1458
 
1462
1459
  class Direction(IntEnum):
1463
1460
  HOST_TO_CONTROLLER = (
@@ -1467,6 +1464,10 @@ class _IsoLink:
1467
1464
  hci.HCI_LE_Setup_ISO_Data_Path_Command.Direction.CONTROLLER_TO_HOST
1468
1465
  )
1469
1466
 
1467
+ def __init__(self) -> None:
1468
+ self._data_path_lock = asyncio.Lock()
1469
+ self.data_paths = set()
1470
+
1470
1471
  async def setup_data_path(
1471
1472
  self,
1472
1473
  direction: _IsoLink.Direction,
@@ -1487,37 +1488,45 @@ class _IsoLink:
1487
1488
  Raises:
1488
1489
  HCI_Error: When command complete status is not HCI_SUCCESS.
1489
1490
  """
1490
- await self.device.send_command(
1491
- hci.HCI_LE_Setup_ISO_Data_Path_Command(
1492
- connection_handle=self.handle,
1493
- data_path_direction=direction,
1494
- data_path_id=data_path_id,
1495
- codec_id=codec_id or hci.CodingFormat(hci.CodecID.TRANSPARENT),
1496
- controller_delay=controller_delay,
1497
- codec_configuration=codec_configuration,
1498
- ),
1499
- check_result=True,
1500
- )
1491
+ async with self._data_path_lock:
1492
+ if direction in self.data_paths:
1493
+ return
1494
+ await self.device.send_command(
1495
+ hci.HCI_LE_Setup_ISO_Data_Path_Command(
1496
+ connection_handle=self.handle,
1497
+ data_path_direction=direction,
1498
+ data_path_id=data_path_id,
1499
+ codec_id=codec_id or hci.CodingFormat(hci.CodecID.TRANSPARENT),
1500
+ controller_delay=controller_delay,
1501
+ codec_configuration=codec_configuration,
1502
+ ),
1503
+ check_result=True,
1504
+ )
1505
+ self.data_paths.add(direction)
1501
1506
 
1502
- async def remove_data_path(self, directions: Iterable[_IsoLink.Direction]) -> int:
1507
+ async def remove_data_path(self, directions: Iterable[_IsoLink.Direction]) -> None:
1503
1508
  """Remove a data path with controller on given direction.
1504
1509
 
1505
1510
  Args:
1506
1511
  direction: Direction of data path.
1507
1512
 
1508
- Returns:
1509
- Command status.
1513
+ Raises:
1514
+ HCI_Error: When command complete status is not HCI_SUCCESS.
1510
1515
  """
1511
- response = await self.device.send_command(
1512
- hci.HCI_LE_Remove_ISO_Data_Path_Command(
1513
- connection_handle=self.handle,
1514
- data_path_direction=sum(
1515
- 1 << direction for direction in set(directions)
1516
+ async with self._data_path_lock:
1517
+ directions_to_remove = set(directions).intersection(self.data_paths)
1518
+ if not directions_to_remove:
1519
+ return
1520
+ await self.device.send_command(
1521
+ hci.HCI_LE_Remove_ISO_Data_Path_Command(
1522
+ connection_handle=self.handle,
1523
+ data_path_direction=sum(
1524
+ 1 << direction for direction in directions_to_remove
1525
+ ),
1516
1526
  ),
1517
- ),
1518
- check_result=False,
1519
- )
1520
- return response.return_parameters.status
1527
+ check_result=True,
1528
+ )
1529
+ self.data_paths.difference_update(directions_to_remove)
1521
1530
 
1522
1531
  def write(self, sdu: bytes) -> None:
1523
1532
  """Write an ISO SDU."""
@@ -1627,7 +1636,8 @@ class CisLink(utils.EventEmitter, _IsoLink):
1627
1636
  EVENT_ESTABLISHMENT_FAILURE: ClassVar[str] = "establishment_failure"
1628
1637
 
1629
1638
  def __post_init__(self) -> None:
1630
- super().__init__()
1639
+ utils.EventEmitter.__init__(self)
1640
+ _IsoLink.__init__(self)
1631
1641
 
1632
1642
  async def disconnect(
1633
1643
  self, reason: int = hci.HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR
@@ -1643,6 +1653,7 @@ class BisLink(_IsoLink):
1643
1653
  sink: Callable[[hci.HCI_IsoDataPacket], Any] | None = None
1644
1654
 
1645
1655
  def __post_init__(self) -> None:
1656
+ super().__init__()
1646
1657
  self.device = self.big.device
1647
1658
 
1648
1659
 
@@ -1697,6 +1708,7 @@ class Connection(utils.CompositeEventEmitter):
1697
1708
  self_address: hci.Address
1698
1709
  self_resolvable_address: Optional[hci.Address]
1699
1710
  peer_address: hci.Address
1711
+ peer_name: Optional[str]
1700
1712
  peer_resolvable_address: Optional[hci.Address]
1701
1713
  peer_le_features: Optional[hci.LeFeatureMask]
1702
1714
  role: hci.Role
@@ -1752,6 +1764,8 @@ class Connection(utils.CompositeEventEmitter):
1752
1764
  EVENT_CIS_REQUEST = "cis_request"
1753
1765
  EVENT_CIS_ESTABLISHMENT = "cis_establishment"
1754
1766
  EVENT_CIS_ESTABLISHMENT_FAILURE = "cis_establishment_failure"
1767
+ EVENT_LE_SUBRATE_CHANGE = "le_subrate_change"
1768
+ EVENT_LE_SUBRATE_CHANGE_FAILURE = "le_subrate_change_failure"
1755
1769
 
1756
1770
  @utils.composite_listener
1757
1771
  class Listener:
@@ -1784,9 +1798,22 @@ class Connection(utils.CompositeEventEmitter):
1784
1798
 
1785
1799
  @dataclass
1786
1800
  class Parameters:
1787
- connection_interval: float # Connection interval, in milliseconds. [LE only]
1788
- peripheral_latency: int # Peripheral latency, in number of intervals. [LE only]
1789
- supervision_timeout: float # Supervision timeout, in milliseconds.
1801
+ """
1802
+ LE connection parameters.
1803
+
1804
+ Attributes:
1805
+ connection_interval: Connection interval, in milliseconds.
1806
+ peripheral_latency: Peripheral latency, in number of intervals.
1807
+ supervision_timeout: Supervision timeout, in milliseconds.
1808
+ subrate_factor: See Bluetooth spec Vol 6, Part B - 4.5.1 Connection events
1809
+ continuation_number: See Bluetooth spec Vol 6, Part B - 4.5.1 Connection events
1810
+ """
1811
+
1812
+ connection_interval: float
1813
+ peripheral_latency: int
1814
+ supervision_timeout: float
1815
+ subrate_factor: int = 1
1816
+ continuation_number: int = 0
1790
1817
 
1791
1818
  def __init__(
1792
1819
  self,
@@ -1827,36 +1854,6 @@ class Connection(utils.CompositeEventEmitter):
1827
1854
  self.cs_configs = {}
1828
1855
  self.cs_procedures = {}
1829
1856
 
1830
- # [Classic only]
1831
- @classmethod
1832
- def incomplete(cls, device, peer_address, role):
1833
- """
1834
- Instantiate an incomplete connection (ie. one waiting for a HCI Connection
1835
- Complete event).
1836
- Once received it shall be completed using the `.complete` method.
1837
- """
1838
- return cls(
1839
- device,
1840
- None,
1841
- PhysicalTransport.BR_EDR,
1842
- device.public_address,
1843
- None,
1844
- peer_address,
1845
- None,
1846
- role,
1847
- None,
1848
- )
1849
-
1850
- # [Classic only]
1851
- def complete(self, handle, parameters):
1852
- """
1853
- Finish an incomplete connection upon completion.
1854
- """
1855
- assert self.handle is None
1856
- assert self.transport == PhysicalTransport.BR_EDR
1857
- self.handle = handle
1858
- self.parameters = parameters
1859
-
1860
1857
  @property
1861
1858
  def role_name(self):
1862
1859
  if self.role is None:
@@ -1868,7 +1865,7 @@ class Connection(utils.CompositeEventEmitter):
1868
1865
  return f'UNKNOWN[{self.role}]'
1869
1866
 
1870
1867
  @property
1871
- def is_encrypted(self):
1868
+ def is_encrypted(self) -> bool:
1872
1869
  return self.encryption != 0
1873
1870
 
1874
1871
  @property
@@ -1878,16 +1875,6 @@ class Connection(utils.CompositeEventEmitter):
1878
1875
  def send_l2cap_pdu(self, cid: int, pdu: bytes) -> None:
1879
1876
  self.device.send_l2cap_pdu(self.handle, cid, pdu)
1880
1877
 
1881
- @utils.deprecated("Please use create_l2cap_channel()")
1882
- async def open_l2cap_channel(
1883
- self,
1884
- psm,
1885
- max_credits=DEVICE_DEFAULT_L2CAP_COC_MAX_CREDITS,
1886
- mtu=DEVICE_DEFAULT_L2CAP_COC_MTU,
1887
- mps=DEVICE_DEFAULT_L2CAP_COC_MPS,
1888
- ):
1889
- return await self.device.open_l2cap_channel(self, psm, max_credits, mtu, mps)
1890
-
1891
1878
  @overload
1892
1879
  async def create_l2cap_channel(
1893
1880
  self, spec: l2cap.ClassicChannelSpec
@@ -1939,7 +1926,7 @@ class Connection(utils.CompositeEventEmitter):
1939
1926
  self.remove_listener(self.EVENT_DISCONNECTION, abort.set_result)
1940
1927
  self.remove_listener(self.EVENT_DISCONNECTION_FAILURE, abort.set_exception)
1941
1928
 
1942
- async def set_data_length(self, tx_octets, tx_time) -> None:
1929
+ async def set_data_length(self, tx_octets: int, tx_time: int) -> None:
1943
1930
  return await self.device.set_data_length(self, tx_octets, tx_time)
1944
1931
 
1945
1932
  async def update_parameters(
@@ -1969,7 +1956,12 @@ class Connection(utils.CompositeEventEmitter):
1969
1956
  use_l2cap=use_l2cap,
1970
1957
  )
1971
1958
 
1972
- async def set_phy(self, tx_phys=None, rx_phys=None, phy_options=None):
1959
+ async def set_phy(
1960
+ self,
1961
+ tx_phys: Optional[Iterable[hci.Phy]] = None,
1962
+ rx_phys: Optional[Iterable[hci.Phy]] = None,
1963
+ phy_options: int = 0,
1964
+ ):
1973
1965
  return await self.device.set_connection_phy(self, tx_phys, rx_phys, phy_options)
1974
1966
 
1975
1967
  async def get_phy(self) -> ConnectionPHY:
@@ -2058,6 +2050,7 @@ class DeviceConfiguration:
2058
2050
  le_simultaneous_enabled: bool = False
2059
2051
  le_privacy_enabled: bool = False
2060
2052
  le_rpa_timeout: int = DEVICE_DEFAULT_LE_RPA_TIMEOUT
2053
+ le_subrate_enabled: bool = False
2061
2054
  classic_enabled: bool = False
2062
2055
  classic_sc_enabled: bool = True
2063
2056
  classic_ssp_enabled: bool = True
@@ -2067,9 +2060,7 @@ class DeviceConfiguration:
2067
2060
  connectable: bool = True
2068
2061
  discoverable: bool = True
2069
2062
  advertising_data: bytes = bytes(
2070
- AdvertisingData(
2071
- [(AdvertisingData.COMPLETE_LOCAL_NAME, bytes(DEVICE_DEFAULT_NAME, 'utf-8'))]
2072
- )
2063
+ AdvertisingData([data_types.CompleteLocalName(DEVICE_DEFAULT_NAME)])
2073
2064
  )
2074
2065
  irk: bytes = bytes(16) # This really must be changed for any level of security
2075
2066
  keystore: Optional[str] = None
@@ -2113,9 +2104,7 @@ class DeviceConfiguration:
2113
2104
  self.advertising_data = bytes.fromhex(advertising_data)
2114
2105
  elif name is not None:
2115
2106
  self.advertising_data = bytes(
2116
- AdvertisingData(
2117
- [(AdvertisingData.COMPLETE_LOCAL_NAME, bytes(self.name, 'utf-8'))]
2118
- )
2107
+ AdvertisingData([data_types.CompleteLocalName(self.name)])
2119
2108
  )
2120
2109
 
2121
2110
  # Load scan response data
@@ -2167,7 +2156,7 @@ class DeviceConfiguration:
2167
2156
  # Decorator that converts the first argument from a connection handle to a connection
2168
2157
  def with_connection_from_handle(function):
2169
2158
  @functools.wraps(function)
2170
- def wrapper(self, connection_handle, *args, **kwargs):
2159
+ def wrapper(self, connection_handle: int, *args, **kwargs):
2171
2160
  if (connection := self.lookup_connection(connection_handle)) is None:
2172
2161
  raise ObjectLookupError(
2173
2162
  f'no connection for handle: 0x{connection_handle:04x}'
@@ -2180,9 +2169,7 @@ def with_connection_from_handle(function):
2180
2169
  # Decorator that converts the first argument from a bluetooth address to a connection
2181
2170
  def with_connection_from_address(function):
2182
2171
  @functools.wraps(function)
2183
- def wrapper(self, address, *args, **kwargs):
2184
- if connection := self.pending_connections.get(address, False):
2185
- return function(self, connection, *args, **kwargs)
2172
+ def wrapper(self, address: hci.Address, *args, **kwargs):
2186
2173
  for connection in self.connections.values():
2187
2174
  if connection.peer_address == address:
2188
2175
  return function(self, connection, *args, **kwargs)
@@ -2196,8 +2183,6 @@ def with_connection_from_address(function):
2196
2183
  def try_with_connection_from_address(function):
2197
2184
  @functools.wraps(function)
2198
2185
  def wrapper(self, address, *args, **kwargs):
2199
- if connection := self.pending_connections.get(address, False):
2200
- return function(self, connection, address, *args, **kwargs)
2201
2186
  for connection in self.connections.values():
2202
2187
  if connection.peer_address == address:
2203
2188
  return function(self, connection, address, *args, **kwargs)
@@ -2249,7 +2234,7 @@ class Device(utils.CompositeEventEmitter):
2249
2234
  scan_response_data: bytes
2250
2235
  cs_capabilities: ChannelSoundingCapabilities | None = None
2251
2236
  connections: dict[int, Connection]
2252
- pending_connections: dict[hci.Address, Connection]
2237
+ connection_roles: dict[hci.Address, hci.Role]
2253
2238
  classic_pending_accepts: dict[
2254
2239
  hci.Address,
2255
2240
  list[asyncio.Future[Union[Connection, tuple[hci.Address, int, int]]]],
@@ -2371,7 +2356,9 @@ class Device(utils.CompositeEventEmitter):
2371
2356
  self.le_connecting = False
2372
2357
  self.disconnecting = False
2373
2358
  self.connections = {} # Connections, by connection handle
2374
- self.pending_connections = {} # Connections, by BD address (BR/EDR only)
2359
+ self.connection_roles = (
2360
+ {}
2361
+ ) # Local connection roles, by BD address (BR/EDR only)
2375
2362
  self.sco_links = {} # ScoLinks, by connection handle (BR/EDR only)
2376
2363
  self.cis_links = {} # CisLinks, by connection handle (LE only)
2377
2364
  self._pending_cis = {} # (CIS_ID, CIG_ID), by CIS_handle
@@ -2410,6 +2397,7 @@ class Device(utils.CompositeEventEmitter):
2410
2397
  self.le_privacy_enabled = config.le_privacy_enabled
2411
2398
  self.le_rpa_timeout = config.le_rpa_timeout
2412
2399
  self.le_rpa_periodic_update_task: Optional[asyncio.Task] = None
2400
+ self.le_subrate_enabled = config.le_subrate_enabled
2413
2401
  self.classic_enabled = config.classic_enabled
2414
2402
  self.cis_enabled = config.cis_enabled
2415
2403
  self.classic_sc_enabled = config.classic_sc_enabled
@@ -2598,36 +2586,6 @@ class Device(utils.CompositeEventEmitter):
2598
2586
  None,
2599
2587
  )
2600
2588
 
2601
- @utils.deprecated("Please use create_l2cap_server()")
2602
- def register_l2cap_server(self, psm, server) -> int:
2603
- return self.l2cap_channel_manager.register_server(psm, server)
2604
-
2605
- @utils.deprecated("Please use create_l2cap_server()")
2606
- def register_l2cap_channel_server(
2607
- self,
2608
- psm,
2609
- server,
2610
- max_credits=DEVICE_DEFAULT_L2CAP_COC_MAX_CREDITS,
2611
- mtu=DEVICE_DEFAULT_L2CAP_COC_MTU,
2612
- mps=DEVICE_DEFAULT_L2CAP_COC_MPS,
2613
- ):
2614
- return self.l2cap_channel_manager.register_le_coc_server(
2615
- psm, server, max_credits, mtu, mps
2616
- )
2617
-
2618
- @utils.deprecated("Please use create_l2cap_channel()")
2619
- async def open_l2cap_channel(
2620
- self,
2621
- connection,
2622
- psm,
2623
- max_credits=DEVICE_DEFAULT_L2CAP_COC_MAX_CREDITS,
2624
- mtu=DEVICE_DEFAULT_L2CAP_COC_MTU,
2625
- mps=DEVICE_DEFAULT_L2CAP_COC_MPS,
2626
- ):
2627
- return await self.l2cap_channel_manager.open_le_coc(
2628
- connection, psm, max_credits, mtu, mps
2629
- )
2630
-
2631
2589
  @overload
2632
2590
  async def create_l2cap_channel(
2633
2591
  self,
@@ -2695,7 +2653,7 @@ class Device(utils.CompositeEventEmitter):
2695
2653
  def send_l2cap_pdu(self, connection_handle: int, cid: int, pdu: bytes) -> None:
2696
2654
  self.host.send_l2cap_pdu(connection_handle, cid, pdu)
2697
2655
 
2698
- async def send_command(self, command, check_result=False):
2656
+ async def send_command(self, command: hci.HCI_Command, check_result: bool = False):
2699
2657
  try:
2700
2658
  return await asyncio.wait_for(
2701
2659
  self.host.send_command(command, check_result), self.command_timeout
@@ -2789,6 +2747,15 @@ class Device(utils.CompositeEventEmitter):
2789
2747
  check_result=True,
2790
2748
  )
2791
2749
 
2750
+ if self.le_subrate_enabled:
2751
+ await self.send_command(
2752
+ hci.HCI_LE_Set_Host_Feature_Command(
2753
+ bit_number=hci.LeFeature.CONNECTION_SUBRATING_HOST_SUPPORT,
2754
+ bit_value=1,
2755
+ ),
2756
+ check_result=True,
2757
+ )
2758
+
2792
2759
  if self.config.channel_sounding_enabled:
2793
2760
  await self.send_command(
2794
2761
  hci.HCI_LE_Set_Host_Feature_Command(
@@ -2943,13 +2910,13 @@ class Device(utils.CompositeEventEmitter):
2943
2910
  def supports_le_features(self, feature: hci.LeFeatureMask) -> bool:
2944
2911
  return self.host.supports_le_features(feature)
2945
2912
 
2946
- def supports_le_phy(self, phy: int) -> bool:
2947
- if phy == hci.HCI_LE_1M_PHY:
2913
+ def supports_le_phy(self, phy: hci.Phy) -> bool:
2914
+ if phy == hci.Phy.LE_1M:
2948
2915
  return True
2949
2916
 
2950
- feature_map: dict[int, hci.LeFeatureMask] = {
2951
- hci.HCI_LE_2M_PHY: hci.LeFeatureMask.LE_2M_PHY,
2952
- hci.HCI_LE_CODED_PHY: hci.LeFeatureMask.LE_CODED_PHY,
2917
+ feature_map: dict[hci.Phy, hci.LeFeatureMask] = {
2918
+ hci.Phy.LE_2M: hci.LeFeatureMask.LE_2M_PHY,
2919
+ hci.Phy.LE_CODED: hci.LeFeatureMask.LE_CODED_PHY,
2953
2920
  }
2954
2921
  if phy not in feature_map:
2955
2922
  raise InvalidArgumentError('invalid PHY')
@@ -3226,8 +3193,8 @@ class Device(utils.CompositeEventEmitter):
3226
3193
  else 0
3227
3194
  )
3228
3195
  await advertising_set.start(duration=duration)
3229
- except Exception as error:
3230
- logger.exception(f'failed to start advertising set: {error}')
3196
+ except Exception:
3197
+ logger.exception('failed to start advertising set')
3231
3198
  await advertising_set.remove()
3232
3199
  raise
3233
3200
 
@@ -3553,7 +3520,9 @@ class Device(utils.CompositeEventEmitter):
3553
3520
  self.discovering = False
3554
3521
 
3555
3522
  @host_event_handler
3556
- def on_inquiry_result(self, address, class_of_device, data, rssi):
3523
+ def on_inquiry_result(
3524
+ self, address: hci.Address, class_of_device: int, data: bytes, rssi: int
3525
+ ):
3557
3526
  self.emit(
3558
3527
  self.EVENT_INQUIRY_RESULT,
3559
3528
  address,
@@ -3562,7 +3531,9 @@ class Device(utils.CompositeEventEmitter):
3562
3531
  rssi,
3563
3532
  )
3564
3533
 
3565
- async def set_scan_enable(self, inquiry_scan_enabled, page_scan_enabled):
3534
+ async def set_scan_enable(
3535
+ self, inquiry_scan_enabled: bool, page_scan_enabled: bool
3536
+ ):
3566
3537
  if inquiry_scan_enabled and page_scan_enabled:
3567
3538
  scan_enable = 0x03
3568
3539
  elif page_scan_enabled:
@@ -3582,14 +3553,7 @@ class Device(utils.CompositeEventEmitter):
3582
3553
  # Synthesize an inquiry response if none is set already
3583
3554
  if self.inquiry_response is None:
3584
3555
  self.inquiry_response = bytes(
3585
- AdvertisingData(
3586
- [
3587
- (
3588
- AdvertisingData.COMPLETE_LOCAL_NAME,
3589
- bytes(self.name, 'utf-8'),
3590
- )
3591
- ]
3592
- )
3556
+ AdvertisingData([data_types.CompleteLocalName(self.name)])
3593
3557
  )
3594
3558
 
3595
3559
  # Update the controller
@@ -3695,6 +3659,7 @@ class Device(utils.CompositeEventEmitter):
3695
3659
  # If the address is not parsable, assume it is a name instead
3696
3660
  always_resolve = False
3697
3661
  logger.debug('looking for peer by name')
3662
+ assert isinstance(peer_address, str)
3698
3663
  peer_address = await self.find_peer_by_name(
3699
3664
  peer_address, transport
3700
3665
  ) # TODO: timeout
@@ -3722,7 +3687,7 @@ class Device(utils.CompositeEventEmitter):
3722
3687
  ):
3723
3688
  pending_connection.set_result(connection)
3724
3689
 
3725
- def on_connection_failure(error):
3690
+ def on_connection_failure(error: core.ConnectionError):
3726
3691
  if transport == PhysicalTransport.LE or (
3727
3692
  # match BR/EDR connection failure event against peer address
3728
3693
  error.transport == transport
@@ -3862,9 +3827,7 @@ class Device(utils.CompositeEventEmitter):
3862
3827
  )
3863
3828
  else:
3864
3829
  # Save pending connection
3865
- self.pending_connections[peer_address] = Connection.incomplete(
3866
- self, peer_address, hci.Role.CENTRAL
3867
- )
3830
+ self.connection_roles[peer_address] = hci.Role.CENTRAL
3868
3831
 
3869
3832
  # TODO: allow passing other settings
3870
3833
  result = await self.send_command(
@@ -3917,7 +3880,7 @@ class Device(utils.CompositeEventEmitter):
3917
3880
  self.le_connecting = False
3918
3881
  self.connect_own_address_type = None
3919
3882
  else:
3920
- self.pending_connections.pop(peer_address, None)
3883
+ self.connection_roles.pop(peer_address, None)
3921
3884
 
3922
3885
  async def accept(
3923
3886
  self,
@@ -3942,6 +3905,7 @@ class Device(utils.CompositeEventEmitter):
3942
3905
  except InvalidArgumentError:
3943
3906
  # If the address is not parsable, assume it is a name instead
3944
3907
  logger.debug('looking for peer by name')
3908
+ assert isinstance(peer_address, str)
3945
3909
  peer_address = await self.find_peer_by_name(
3946
3910
  peer_address, PhysicalTransport.BR_EDR
3947
3911
  ) # TODO: timeout
@@ -4000,7 +3964,7 @@ class Device(utils.CompositeEventEmitter):
4000
3964
  ):
4001
3965
  pending_connection.set_result(connection)
4002
3966
 
4003
- def on_connection_failure(error):
3967
+ def on_connection_failure(error: core.ConnectionError):
4004
3968
  if (
4005
3969
  error.transport == PhysicalTransport.BR_EDR
4006
3970
  and error.peer_address == peer_address
@@ -4010,13 +3974,11 @@ class Device(utils.CompositeEventEmitter):
4010
3974
  self.on(self.EVENT_CONNECTION, on_connection)
4011
3975
  self.on(self.EVENT_CONNECTION_FAILURE, on_connection_failure)
4012
3976
 
4013
- # Save pending connection, with the Peripheral hci.role.
3977
+ # Save Peripheral hci.role.
4014
3978
  # Even if we requested a role switch in the hci.HCI_Accept_Connection_Request
4015
3979
  # command, this connection is still considered Peripheral until an eventual
4016
3980
  # role change event.
4017
- self.pending_connections[peer_address] = Connection.incomplete(
4018
- self, peer_address, hci.Role.PERIPHERAL
4019
- )
3981
+ self.connection_roles[peer_address] = hci.Role.PERIPHERAL
4020
3982
 
4021
3983
  try:
4022
3984
  # Accept connection request
@@ -4034,10 +3996,10 @@ class Device(utils.CompositeEventEmitter):
4034
3996
  finally:
4035
3997
  self.remove_listener(self.EVENT_CONNECTION, on_connection)
4036
3998
  self.remove_listener(self.EVENT_CONNECTION_FAILURE, on_connection_failure)
4037
- self.pending_connections.pop(peer_address, None)
3999
+ self.connection_roles.pop(peer_address, None)
4038
4000
 
4039
4001
  @asynccontextmanager
4040
- async def connect_as_gatt(self, peer_address):
4002
+ async def connect_as_gatt(self, peer_address: Union[hci.Address, str]):
4041
4003
  async with AsyncExitStack() as stack:
4042
4004
  connection = await stack.enter_async_context(
4043
4005
  await self.connect(peer_address)
@@ -4073,6 +4035,7 @@ class Device(utils.CompositeEventEmitter):
4073
4035
  except InvalidArgumentError:
4074
4036
  # If the address is not parsable, assume it is a name instead
4075
4037
  logger.debug('looking for peer by name')
4038
+ assert isinstance(peer_address, str)
4076
4039
  peer_address = await self.find_peer_by_name(
4077
4040
  peer_address, PhysicalTransport.BR_EDR
4078
4041
  ) # TODO: timeout
@@ -4118,7 +4081,9 @@ class Device(utils.CompositeEventEmitter):
4118
4081
  )
4119
4082
  self.disconnecting = False
4120
4083
 
4121
- async def set_data_length(self, connection, tx_octets, tx_time) -> None:
4084
+ async def set_data_length(
4085
+ self, connection: Connection, tx_octets: int, tx_time: int
4086
+ ) -> None:
4122
4087
  if tx_octets < 0x001B or tx_octets > 0x00FB:
4123
4088
  raise InvalidArgumentError('tx_octets must be between 0x001B and 0x00FB')
4124
4089
 
@@ -4221,7 +4186,11 @@ class Device(utils.CompositeEventEmitter):
4221
4186
  )
4222
4187
 
4223
4188
  async def set_connection_phy(
4224
- self, connection, tx_phys=None, rx_phys=None, phy_options=None
4189
+ self,
4190
+ connection: Connection,
4191
+ tx_phys: Optional[Iterable[hci.Phy]] = None,
4192
+ rx_phys: Optional[Iterable[hci.Phy]] = None,
4193
+ phy_options: int = 0,
4225
4194
  ):
4226
4195
  if not self.host.supports_command(hci.HCI_LE_SET_PHY_COMMAND):
4227
4196
  logger.warning('ignoring request, command not supported')
@@ -4237,7 +4206,7 @@ class Device(utils.CompositeEventEmitter):
4237
4206
  all_phys=all_phys_bits,
4238
4207
  tx_phys=hci.phy_list_to_bits(tx_phys),
4239
4208
  rx_phys=hci.phy_list_to_bits(rx_phys),
4240
- phy_options=0 if phy_options is None else int(phy_options),
4209
+ phy_options=phy_options,
4241
4210
  )
4242
4211
  )
4243
4212
 
@@ -4248,7 +4217,11 @@ class Device(utils.CompositeEventEmitter):
4248
4217
  )
4249
4218
  raise hci.HCI_StatusError(result)
4250
4219
 
4251
- async def set_default_phy(self, tx_phys=None, rx_phys=None):
4220
+ async def set_default_phy(
4221
+ self,
4222
+ tx_phys: Optional[Iterable[hci.Phy]] = None,
4223
+ rx_phys: Optional[Iterable[hci.Phy]] = None,
4224
+ ):
4252
4225
  all_phys_bits = (1 if tx_phys is None else 0) | (
4253
4226
  (1 if rx_phys is None else 0) << 1
4254
4227
  )
@@ -4286,7 +4259,7 @@ class Device(utils.CompositeEventEmitter):
4286
4259
  check_result=True,
4287
4260
  )
4288
4261
 
4289
- async def find_peer_by_name(self, name, transport=PhysicalTransport.LE):
4262
+ async def find_peer_by_name(self, name: str, transport=PhysicalTransport.LE):
4290
4263
  """
4291
4264
  Scan for a peer with a given name and return its address.
4292
4265
  """
@@ -4301,7 +4274,7 @@ class Device(utils.CompositeEventEmitter):
4301
4274
  if local_name == name:
4302
4275
  peer_address.set_result(address)
4303
4276
 
4304
- listener = None
4277
+ listener: Optional[Callable[..., None]] = None
4305
4278
  was_scanning = self.scanning
4306
4279
  was_discovering = self.discovering
4307
4280
  try:
@@ -4407,10 +4380,10 @@ class Device(utils.CompositeEventEmitter):
4407
4380
  def smp_session_proxy(self, session_proxy: type[smp.Session]) -> None:
4408
4381
  self.smp_manager.session_proxy = session_proxy
4409
4382
 
4410
- async def pair(self, connection):
4383
+ async def pair(self, connection: Connection):
4411
4384
  return await self.smp_manager.pair(connection)
4412
4385
 
4413
- def request_pairing(self, connection):
4386
+ def request_pairing(self, connection: Connection):
4414
4387
  return self.smp_manager.request_pairing(connection)
4415
4388
 
4416
4389
  async def get_long_term_key(
@@ -4498,7 +4471,7 @@ class Device(utils.CompositeEventEmitter):
4498
4471
  on_authentication_failure,
4499
4472
  )
4500
4473
 
4501
- async def encrypt(self, connection, enable=True):
4474
+ async def encrypt(self, connection: Connection, enable: bool = True):
4502
4475
  if not enable and connection.transport == PhysicalTransport.LE:
4503
4476
  raise InvalidArgumentError('`enable` parameter is classic only.')
4504
4477
 
@@ -4508,7 +4481,7 @@ class Device(utils.CompositeEventEmitter):
4508
4481
  def on_encryption_change():
4509
4482
  pending_encryption.set_result(None)
4510
4483
 
4511
- def on_encryption_failure(error_code):
4484
+ def on_encryption_failure(error_code: int):
4512
4485
  pending_encryption.set_exception(hci.HCI_Error(error_code))
4513
4486
 
4514
4487
  connection.on(
@@ -4591,8 +4564,8 @@ class Device(utils.CompositeEventEmitter):
4591
4564
  try:
4592
4565
  await self.keystore.update(address, keys)
4593
4566
  await self.refresh_resolving_list()
4594
- except Exception as error:
4595
- logger.warning(f'!!! error while storing keys: {error}')
4567
+ except Exception:
4568
+ logger.exception('!!! error while storing keys')
4596
4569
  else:
4597
4570
  self.emit(self.EVENT_KEY_STORE_UPDATE)
4598
4571
 
@@ -4600,10 +4573,10 @@ class Device(utils.CompositeEventEmitter):
4600
4573
  async def switch_role(self, connection: Connection, role: hci.Role):
4601
4574
  pending_role_change = asyncio.get_running_loop().create_future()
4602
4575
 
4603
- def on_role_change(new_role):
4576
+ def on_role_change(new_role: hci.Role):
4604
4577
  pending_role_change.set_result(new_role)
4605
4578
 
4606
- def on_role_change_failure(error_code):
4579
+ def on_role_change_failure(error_code: int):
4607
4580
  pending_role_change.set_exception(hci.HCI_Error(error_code))
4608
4581
 
4609
4582
  connection.on(connection.EVENT_ROLE_CHANGE, on_role_change)
@@ -5193,10 +5166,10 @@ class Device(utils.CompositeEventEmitter):
5193
5166
  ):
5194
5167
  connection.emit(connection.EVENT_LINK_KEY)
5195
5168
 
5196
- def add_service(self, service):
5169
+ def add_service(self, service: gatt.Service):
5197
5170
  self.gatt_server.add_service(service)
5198
5171
 
5199
- def add_services(self, services):
5172
+ def add_services(self, services: Iterable[gatt.Service]):
5200
5173
  self.gatt_server.add_services(services)
5201
5174
 
5202
5175
  def add_default_services(
@@ -5292,10 +5265,10 @@ class Device(utils.CompositeEventEmitter):
5292
5265
  @host_event_handler
5293
5266
  def on_advertising_set_termination(
5294
5267
  self,
5295
- status,
5296
- advertising_handle,
5297
- connection_handle,
5298
- number_of_completed_extended_advertising_events,
5268
+ status: int,
5269
+ advertising_handle: int,
5270
+ connection_handle: int,
5271
+ number_of_completed_extended_advertising_events: int,
5299
5272
  ):
5300
5273
  # Legacy advertising set is also one of extended advertising sets.
5301
5274
  if not (
@@ -5463,15 +5436,49 @@ class Device(utils.CompositeEventEmitter):
5463
5436
  self.emit(self.EVENT_CONNECTION, connection)
5464
5437
 
5465
5438
  @host_event_handler
5466
- def on_connection(
5439
+ def on_classic_connection(
5440
+ self,
5441
+ connection_handle: int,
5442
+ peer_address: hci.Address,
5443
+ ) -> None:
5444
+ connection_role = self.connection_roles.pop(peer_address, hci.Role.PERIPHERAL)
5445
+
5446
+ logger.debug(
5447
+ f'*** Connection: [0x{connection_handle:04X}] '
5448
+ f'{peer_address} {hci.HCI_Constant.role_name(connection_role)}'
5449
+ )
5450
+ if connection_handle in self.connections:
5451
+ logger.warning(
5452
+ 'new connection reuses the same handle as a previous connection'
5453
+ )
5454
+
5455
+ # Create a new connection
5456
+ connection = Connection(
5457
+ device=self,
5458
+ handle=connection_handle,
5459
+ transport=PhysicalTransport.BR_EDR,
5460
+ self_address=self.public_address,
5461
+ self_resolvable_address=None,
5462
+ peer_address=peer_address,
5463
+ peer_resolvable_address=None,
5464
+ role=connection_role,
5465
+ parameters=Connection.Parameters(0.0, 0, 0.0),
5466
+ )
5467
+ self.connections[connection_handle] = connection
5468
+
5469
+ self.emit(self.EVENT_CONNECTION, connection)
5470
+
5471
+ @host_event_handler
5472
+ def on_le_connection(
5467
5473
  self,
5468
5474
  connection_handle: int,
5469
- transport: core.PhysicalTransport,
5470
5475
  peer_address: hci.Address,
5471
5476
  self_resolvable_address: Optional[hci.Address],
5472
5477
  peer_resolvable_address: Optional[hci.Address],
5473
5478
  role: hci.Role,
5474
- connection_parameters: Optional[core.ConnectionParameters],
5479
+ connection_interval: int,
5480
+ peripheral_latency: int,
5481
+ supervision_timeout: int,
5475
5482
  ) -> None:
5476
5483
  # Convert all-zeros addresses into None.
5477
5484
  if self_resolvable_address == hci.Address.ANY_RANDOM:
@@ -5491,19 +5498,6 @@ class Device(utils.CompositeEventEmitter):
5491
5498
  'new connection reuses the same handle as a previous connection'
5492
5499
  )
5493
5500
 
5494
- if transport == PhysicalTransport.BR_EDR:
5495
- # Create a new connection
5496
- connection = self.pending_connections.pop(peer_address)
5497
- connection.complete(connection_handle, connection_parameters)
5498
- self.connections[connection_handle] = connection
5499
-
5500
- # Emit an event to notify listeners of the new connection
5501
- self.emit(self.EVENT_CONNECTION, connection)
5502
-
5503
- return
5504
-
5505
- assert connection_parameters is not None
5506
-
5507
5501
  if peer_resolvable_address is None:
5508
5502
  # Resolve the peer address if we can
5509
5503
  if self.address_resolver:
@@ -5553,16 +5547,16 @@ class Device(utils.CompositeEventEmitter):
5553
5547
  connection = Connection(
5554
5548
  self,
5555
5549
  connection_handle,
5556
- transport,
5550
+ PhysicalTransport.LE,
5557
5551
  self_address,
5558
5552
  self_resolvable_address,
5559
5553
  peer_address,
5560
5554
  peer_resolvable_address,
5561
5555
  role,
5562
5556
  Connection.Parameters(
5563
- connection_parameters.connection_interval * 1.25,
5564
- connection_parameters.peripheral_latency,
5565
- connection_parameters.supervision_timeout * 10.0,
5557
+ connection_interval * 1.25,
5558
+ peripheral_latency,
5559
+ supervision_timeout * 10.0,
5566
5560
  ),
5567
5561
  )
5568
5562
  self.connections[connection_handle] = connection
@@ -5594,7 +5588,12 @@ class Device(utils.CompositeEventEmitter):
5594
5588
  )
5595
5589
 
5596
5590
  @host_event_handler
5597
- def on_connection_failure(self, transport, peer_address, error_code):
5591
+ def on_connection_failure(
5592
+ self,
5593
+ transport: hci.PhysicalTransport,
5594
+ peer_address: hci.Address,
5595
+ error_code: int,
5596
+ ):
5598
5597
  logger.debug(
5599
5598
  f'*** Connection failed: {hci.HCI_Constant.error_name(error_code)}'
5600
5599
  )
@@ -5648,9 +5647,7 @@ class Device(utils.CompositeEventEmitter):
5648
5647
  # device configuration is set to accept any incoming connection
5649
5648
  elif self.classic_accept_any:
5650
5649
  # Save pending connection
5651
- self.pending_connections[bd_addr] = Connection.incomplete(
5652
- self, bd_addr, hci.Role.PERIPHERAL
5653
- )
5650
+ self.connection_roles[bd_addr] = hci.Role.PERIPHERAL
5654
5651
 
5655
5652
  self.host.send_command_sync(
5656
5653
  hci.HCI_Accept_Connection_Request_Command(
@@ -5713,7 +5710,7 @@ class Device(utils.CompositeEventEmitter):
5713
5710
 
5714
5711
  @host_event_handler
5715
5712
  @with_connection_from_handle
5716
- def on_connection_authentication(self, connection):
5713
+ def on_connection_authentication(self, connection: Connection):
5717
5714
  logger.debug(
5718
5715
  f'*** Connection Authentication: [0x{connection.handle:04X}] '
5719
5716
  f'{connection.peer_address} as {connection.role_name}'
@@ -5723,7 +5720,9 @@ class Device(utils.CompositeEventEmitter):
5723
5720
 
5724
5721
  @host_event_handler
5725
5722
  @with_connection_from_handle
5726
- def on_connection_authentication_failure(self, connection, error):
5723
+ def on_connection_authentication_failure(
5724
+ self, connection: Connection, error: core.ConnectionError
5725
+ ):
5727
5726
  logger.debug(
5728
5727
  f'*** Connection Authentication Failure: [0x{connection.handle:04X}] '
5729
5728
  f'{connection.peer_address} as {connection.role_name}, error={error}'
@@ -5765,10 +5764,13 @@ class Device(utils.CompositeEventEmitter):
5765
5764
  @host_event_handler
5766
5765
  @with_connection_from_address
5767
5766
  def on_authentication_io_capability_response(
5768
- self, connection, io_capability, authentication_requirements
5767
+ self,
5768
+ connection: Connection,
5769
+ io_capability: int,
5770
+ authentication_requirements: int,
5769
5771
  ):
5770
- connection.peer_pairing_io_capability = io_capability
5771
- connection.peer_pairing_authentication_requirements = (
5772
+ connection.pairing_peer_io_capability = io_capability
5773
+ connection.pairing_peer_authentication_requirements = (
5772
5774
  authentication_requirements
5773
5775
  )
5774
5776
 
@@ -5779,7 +5781,7 @@ class Device(utils.CompositeEventEmitter):
5779
5781
  # Ask what the pairing config should be for this connection
5780
5782
  pairing_config = self.pairing_config_factory(connection)
5781
5783
  io_capability = pairing_config.delegate.classic_io_capability
5782
- peer_io_capability = connection.peer_pairing_io_capability
5784
+ peer_io_capability = connection.pairing_peer_io_capability
5783
5785
 
5784
5786
  async def confirm() -> bool:
5785
5787
  # Ask the user to confirm the pairing, without display
@@ -5840,8 +5842,8 @@ class Device(utils.CompositeEventEmitter):
5840
5842
  )
5841
5843
  )
5842
5844
  return
5843
- except Exception as error:
5844
- logger.warning(f'exception while confirming: {error}')
5845
+ except Exception:
5846
+ logger.exception('exception while confirming')
5845
5847
 
5846
5848
  await self.host.send_command(
5847
5849
  hci.HCI_User_Confirmation_Request_Negative_Reply_Command(
@@ -5854,7 +5856,7 @@ class Device(utils.CompositeEventEmitter):
5854
5856
  # [Classic only]
5855
5857
  @host_event_handler
5856
5858
  @with_connection_from_address
5857
- def on_authentication_user_passkey_request(self, connection) -> None:
5859
+ def on_authentication_user_passkey_request(self, connection: Connection) -> None:
5858
5860
  # Ask what the pairing config should be for this connection
5859
5861
  pairing_config = self.pairing_config_factory(connection)
5860
5862
 
@@ -5870,8 +5872,8 @@ class Device(utils.CompositeEventEmitter):
5870
5872
  )
5871
5873
  )
5872
5874
  return
5873
- except Exception as error:
5874
- logger.warning(f'exception while asking for pass-key: {error}')
5875
+ except Exception:
5876
+ logger.exception('exception while asking for pass-key')
5875
5877
 
5876
5878
  await self.host.send_command(
5877
5879
  hci.HCI_User_Passkey_Request_Negative_Reply_Command(
@@ -5897,7 +5899,7 @@ class Device(utils.CompositeEventEmitter):
5897
5899
  # [Classic only]
5898
5900
  @host_event_handler
5899
5901
  @with_connection_from_address
5900
- def on_pin_code_request(self, connection):
5902
+ def on_pin_code_request(self, connection: Connection):
5901
5903
  # Classic legacy pairing
5902
5904
  # Ask what the pairing config should be for this connection
5903
5905
  pairing_config = self.pairing_config_factory(connection)
@@ -5941,7 +5943,9 @@ class Device(utils.CompositeEventEmitter):
5941
5943
  # [Classic only]
5942
5944
  @host_event_handler
5943
5945
  @with_connection_from_address
5944
- def on_authentication_user_passkey_notification(self, connection, passkey):
5946
+ def on_authentication_user_passkey_notification(
5947
+ self, connection: Connection, passkey: int
5948
+ ):
5945
5949
  # Ask what the pairing config should be for this connection
5946
5950
  pairing_config = self.pairing_config_factory(connection)
5947
5951
 
@@ -5953,14 +5957,15 @@ class Device(utils.CompositeEventEmitter):
5953
5957
  # [Classic only]
5954
5958
  @host_event_handler
5955
5959
  @try_with_connection_from_address
5956
- def on_remote_name(self, connection: Connection, address, remote_name):
5960
+ def on_remote_name(
5961
+ self, connection: Connection, address: hci.Address, remote_name: bytes
5962
+ ):
5957
5963
  # Try to decode the name
5958
5964
  try:
5959
- remote_name = remote_name.decode('utf-8')
5960
5965
  if connection:
5961
- connection.peer_name = remote_name
5966
+ connection.peer_name = remote_name.decode('utf-8')
5962
5967
  connection.emit(connection.EVENT_REMOTE_NAME)
5963
- self.emit(self.EVENT_REMOTE_NAME, address, remote_name)
5968
+ self.emit(self.EVENT_REMOTE_NAME, address, remote_name.decode('utf-8'))
5964
5969
  except UnicodeDecodeError as error:
5965
5970
  logger.warning('peer name is not valid UTF-8')
5966
5971
  if connection:
@@ -5971,7 +5976,9 @@ class Device(utils.CompositeEventEmitter):
5971
5976
  # [Classic only]
5972
5977
  @host_event_handler
5973
5978
  @try_with_connection_from_address
5974
- def on_remote_name_failure(self, connection: Connection, address, error):
5979
+ def on_remote_name_failure(
5980
+ self, connection: Connection, address: hci.Address, error: int
5981
+ ):
5975
5982
  if connection:
5976
5983
  connection.emit(connection.EVENT_REMOTE_NAME_FAILURE, error)
5977
5984
  self.emit(self.EVENT_REMOTE_NAME_FAILURE, address, error)
@@ -6172,7 +6179,7 @@ class Device(utils.CompositeEventEmitter):
6172
6179
 
6173
6180
  @host_event_handler
6174
6181
  @with_connection_from_handle
6175
- def on_connection_encryption_key_refresh(self, connection):
6182
+ def on_connection_encryption_key_refresh(self, connection: Connection):
6176
6183
  logger.debug(
6177
6184
  f'*** Connection Key Refresh: [0x{connection.handle:04X}] '
6178
6185
  f'{connection.peer_address} as {connection.role_name}'
@@ -6182,23 +6189,37 @@ class Device(utils.CompositeEventEmitter):
6182
6189
  @host_event_handler
6183
6190
  @with_connection_from_handle
6184
6191
  def on_connection_parameters_update(
6185
- self, connection: Connection, connection_parameters: core.ConnectionParameters
6192
+ self,
6193
+ connection: Connection,
6194
+ connection_interval: int,
6195
+ peripheral_latency: int,
6196
+ supervision_timeout: int,
6186
6197
  ):
6187
6198
  logger.debug(
6188
6199
  f'*** Connection Parameters Update: [0x{connection.handle:04X}] '
6189
6200
  f'{connection.peer_address} as {connection.role_name}, '
6190
- f'{connection_parameters}'
6191
- )
6192
- connection.parameters = Connection.Parameters(
6193
- connection_parameters.connection_interval * 1.25,
6194
- connection_parameters.peripheral_latency,
6195
- connection_parameters.supervision_timeout * 10.0,
6196
6201
  )
6202
+ if connection.parameters.connection_interval != connection_interval * 1.25:
6203
+ connection.parameters = Connection.Parameters(
6204
+ connection_interval * 1.25,
6205
+ peripheral_latency,
6206
+ supervision_timeout * 10.0,
6207
+ )
6208
+ else:
6209
+ connection.parameters = Connection.Parameters(
6210
+ connection_interval * 1.25,
6211
+ peripheral_latency,
6212
+ supervision_timeout * 10.0,
6213
+ connection.parameters.subrate_factor,
6214
+ connection.parameters.continuation_number,
6215
+ )
6197
6216
  connection.emit(connection.EVENT_CONNECTION_PARAMETERS_UPDATE)
6198
6217
 
6199
6218
  @host_event_handler
6200
6219
  @with_connection_from_handle
6201
- def on_connection_parameters_update_failure(self, connection, error):
6220
+ def on_connection_parameters_update_failure(
6221
+ self, connection: Connection, error: int
6222
+ ):
6202
6223
  logger.debug(
6203
6224
  f'*** Connection Parameters Update Failed: [0x{connection.handle:04X}] '
6204
6225
  f'{connection.peer_address} as {connection.role_name}, '
@@ -6208,7 +6229,7 @@ class Device(utils.CompositeEventEmitter):
6208
6229
 
6209
6230
  @host_event_handler
6210
6231
  @with_connection_from_handle
6211
- def on_connection_phy_update(self, connection, phy):
6232
+ def on_connection_phy_update(self, connection: Connection, phy: core.ConnectionPHY):
6212
6233
  logger.debug(
6213
6234
  f'*** Connection PHY Update: [0x{connection.handle:04X}] '
6214
6235
  f'{connection.peer_address} as {connection.role_name}, '
@@ -6218,7 +6239,7 @@ class Device(utils.CompositeEventEmitter):
6218
6239
 
6219
6240
  @host_event_handler
6220
6241
  @with_connection_from_handle
6221
- def on_connection_phy_update_failure(self, connection, error):
6242
+ def on_connection_phy_update_failure(self, connection: Connection, error: int):
6222
6243
  logger.debug(
6223
6244
  f'*** Connection PHY Update Failed: [0x{connection.handle:04X}] '
6224
6245
  f'{connection.peer_address} as {connection.role_name}, '
@@ -6228,7 +6249,26 @@ class Device(utils.CompositeEventEmitter):
6228
6249
 
6229
6250
  @host_event_handler
6230
6251
  @with_connection_from_handle
6231
- def on_connection_att_mtu_update(self, connection, att_mtu):
6252
+ def on_le_subrate_change(
6253
+ self,
6254
+ connection: Connection,
6255
+ subrate_factor: int,
6256
+ peripheral_latency: int,
6257
+ continuation_number: int,
6258
+ supervision_timeout: int,
6259
+ ):
6260
+ connection.parameters = Connection.Parameters(
6261
+ connection.parameters.connection_interval,
6262
+ peripheral_latency,
6263
+ supervision_timeout * 10.0,
6264
+ subrate_factor,
6265
+ continuation_number,
6266
+ )
6267
+ connection.emit(connection.EVENT_LE_SUBRATE_CHANGE)
6268
+
6269
+ @host_event_handler
6270
+ @with_connection_from_handle
6271
+ def on_connection_att_mtu_update(self, connection: Connection, att_mtu: int):
6232
6272
  logger.debug(
6233
6273
  f'*** Connection ATT MTU Update: [0x{connection.handle:04X}] '
6234
6274
  f'{connection.peer_address} as {connection.role_name}, '
@@ -6240,7 +6280,12 @@ class Device(utils.CompositeEventEmitter):
6240
6280
  @host_event_handler
6241
6281
  @with_connection_from_handle
6242
6282
  def on_connection_data_length_change(
6243
- self, connection, max_tx_octets, max_tx_time, max_rx_octets, max_rx_time
6283
+ self,
6284
+ connection: Connection,
6285
+ max_tx_octets: int,
6286
+ max_tx_time: int,
6287
+ max_rx_octets: int,
6288
+ max_rx_time: int,
6244
6289
  ):
6245
6290
  logger.debug(
6246
6291
  f'*** Connection Data Length Change: [0x{connection.handle:04X}] '
@@ -6364,15 +6409,22 @@ class Device(utils.CompositeEventEmitter):
6364
6409
 
6365
6410
  # [Classic only]
6366
6411
  @host_event_handler
6367
- @with_connection_from_address
6368
- def on_role_change(self, connection, new_role):
6369
- connection.role = new_role
6370
- connection.emit(connection.EVENT_ROLE_CHANGE, new_role)
6412
+ @try_with_connection_from_address
6413
+ def on_role_change(
6414
+ self, connection: Connection, peer_address: hci.Address, new_role: hci.Role
6415
+ ):
6416
+ if connection:
6417
+ connection.role = new_role
6418
+ connection.emit(connection.EVENT_ROLE_CHANGE, new_role)
6419
+ else:
6420
+ self.connection_roles[peer_address] = new_role
6371
6421
 
6372
6422
  # [Classic only]
6373
6423
  @host_event_handler
6374
6424
  @try_with_connection_from_address
6375
- def on_role_change_failure(self, connection, address, error):
6425
+ def on_role_change_failure(
6426
+ self, connection: Connection, address: hci.Address, error: int
6427
+ ):
6376
6428
  if connection:
6377
6429
  connection.emit(connection.EVENT_ROLE_CHANGE_FAILURE, error)
6378
6430
  self.emit(self.EVENT_ROLE_CHANGE_FAILURE, address, error)
@@ -6386,7 +6438,7 @@ class Device(utils.CompositeEventEmitter):
6386
6438
  # [Classic only]
6387
6439
  @host_event_handler
6388
6440
  @with_connection_from_address
6389
- def on_classic_pairing_failure(self, connection: Connection, status) -> None:
6441
+ def on_classic_pairing_failure(self, connection: Connection, status: int) -> None:
6390
6442
  connection.emit(connection.EVENT_CLASSIC_PAIRING_FAILURE, status)
6391
6443
 
6392
6444
  def on_pairing_start(self, connection: Connection) -> None:
@@ -6410,7 +6462,7 @@ class Device(utils.CompositeEventEmitter):
6410
6462
  connection.emit(connection.EVENT_PAIRING_FAILURE, reason)
6411
6463
 
6412
6464
  @with_connection_from_handle
6413
- def on_gatt_pdu(self, connection, pdu):
6465
+ def on_gatt_pdu(self, connection: Connection, pdu: bytes):
6414
6466
  # Parse the L2CAP payload into an ATT PDU object
6415
6467
  att_pdu = ATT_PDU.from_bytes(pdu)
6416
6468
 
@@ -6432,7 +6484,7 @@ class Device(utils.CompositeEventEmitter):
6432
6484
  connection.gatt_server.on_gatt_pdu(connection, att_pdu)
6433
6485
 
6434
6486
  @with_connection_from_handle
6435
- def on_smp_pdu(self, connection, pdu):
6487
+ def on_smp_pdu(self, connection: Connection, pdu: bytes):
6436
6488
  self.smp_manager.on_smp_pdu(connection, pdu)
6437
6489
 
6438
6490
  @host_event_handler