bumble 0.0.214__py3-none-any.whl → 0.0.216__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 (122) hide show
  1. bumble/_version.py +16 -3
  2. bumble/a2dp.py +15 -16
  3. bumble/apps/auracast.py +13 -38
  4. bumble/apps/bench.py +9 -10
  5. bumble/apps/ble_rpa_tool.py +1 -0
  6. bumble/apps/console.py +22 -25
  7. bumble/apps/controller_info.py +19 -19
  8. bumble/apps/controller_loopback.py +2 -2
  9. bumble/apps/controllers.py +1 -1
  10. bumble/apps/device_info.py +3 -3
  11. bumble/apps/gatt_dump.py +1 -1
  12. bumble/apps/gg_bridge.py +5 -6
  13. bumble/apps/hci_bridge.py +3 -3
  14. bumble/apps/l2cap_bridge.py +3 -3
  15. bumble/apps/lea_unicast/app.py +15 -25
  16. bumble/apps/pair.py +30 -43
  17. bumble/apps/pandora_server.py +5 -4
  18. bumble/apps/player/player.py +19 -22
  19. bumble/apps/rfcomm_bridge.py +3 -8
  20. bumble/apps/scan.py +16 -6
  21. bumble/apps/show.py +3 -4
  22. bumble/apps/speaker/speaker.py +22 -22
  23. bumble/apps/unbond.py +2 -1
  24. bumble/apps/usb_probe.py +1 -2
  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 +1096 -588
  31. bumble/codecs.py +2 -0
  32. bumble/controller.py +52 -13
  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 +318 -279
  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 +67 -18
  48. bumble/helpers.py +19 -26
  49. bumble/hfp.py +10 -21
  50. bumble/hid.py +22 -16
  51. bumble/host.py +181 -103
  52. bumble/keys.py +5 -3
  53. bumble/l2cap.py +121 -74
  54. bumble/link.py +8 -9
  55. bumble/pairing.py +7 -6
  56. bumble/pandora/__init__.py +8 -7
  57. bumble/pandora/config.py +3 -1
  58. bumble/pandora/device.py +3 -2
  59. bumble/pandora/host.py +38 -36
  60. bumble/pandora/l2cap.py +22 -21
  61. bumble/pandora/security.py +15 -15
  62. bumble/pandora/utils.py +5 -3
  63. bumble/profiles/aics.py +11 -11
  64. bumble/profiles/ams.py +7 -8
  65. bumble/profiles/ancs.py +6 -7
  66. bumble/profiles/ascs.py +4 -9
  67. bumble/profiles/asha.py +8 -12
  68. bumble/profiles/bap.py +11 -23
  69. bumble/profiles/bass.py +2 -7
  70. bumble/profiles/battery_service.py +3 -4
  71. bumble/profiles/cap.py +1 -2
  72. bumble/profiles/csip.py +2 -6
  73. bumble/profiles/device_information_service.py +2 -2
  74. bumble/profiles/gap.py +4 -4
  75. bumble/profiles/gatt_service.py +1 -4
  76. bumble/profiles/gmap.py +5 -5
  77. bumble/profiles/hap.py +62 -59
  78. bumble/profiles/heart_rate_service.py +5 -4
  79. bumble/profiles/le_audio.py +3 -1
  80. bumble/profiles/mcp.py +3 -7
  81. bumble/profiles/pacs.py +3 -6
  82. bumble/profiles/pbp.py +2 -0
  83. bumble/profiles/tmap.py +2 -3
  84. bumble/profiles/vcs.py +2 -8
  85. bumble/profiles/vocs.py +8 -8
  86. bumble/rfcomm.py +11 -14
  87. bumble/rtp.py +1 -0
  88. bumble/sdp.py +10 -8
  89. bumble/smp.py +142 -153
  90. bumble/snoop.py +5 -5
  91. bumble/tools/generate_company_id_list.py +1 -0
  92. bumble/tools/intel_fw_download.py +3 -3
  93. bumble/tools/intel_util.py +4 -4
  94. bumble/tools/rtk_fw_download.py +6 -3
  95. bumble/tools/rtk_util.py +24 -7
  96. bumble/transport/__init__.py +19 -15
  97. bumble/transport/android_emulator.py +8 -13
  98. bumble/transport/android_netsim.py +19 -18
  99. bumble/transport/common.py +12 -15
  100. bumble/transport/file.py +1 -1
  101. bumble/transport/hci_socket.py +4 -6
  102. bumble/transport/pty.py +5 -6
  103. bumble/transport/pyusb.py +7 -10
  104. bumble/transport/serial.py +2 -1
  105. bumble/transport/tcp_client.py +2 -2
  106. bumble/transport/tcp_server.py +11 -14
  107. bumble/transport/udp.py +3 -3
  108. bumble/transport/unix.py +67 -1
  109. bumble/transport/usb.py +6 -6
  110. bumble/transport/vhci.py +0 -1
  111. bumble/transport/ws_client.py +2 -1
  112. bumble/transport/ws_server.py +3 -2
  113. bumble/utils.py +20 -5
  114. bumble/vendor/android/hci.py +1 -2
  115. bumble/vendor/zephyr/hci.py +0 -1
  116. {bumble-0.0.214.dist-info → bumble-0.0.216.dist-info}/METADATA +2 -1
  117. bumble-0.0.216.dist-info/RECORD +183 -0
  118. bumble-0.0.214.dist-info/RECORD +0 -182
  119. {bumble-0.0.214.dist-info → bumble-0.0.216.dist-info}/WHEEL +0 -0
  120. {bumble-0.0.214.dist-info → bumble-0.0.216.dist-info}/entry_points.txt +0 -0
  121. {bumble-0.0.214.dist-info → bumble-0.0.216.dist-info}/licenses/LICENSE +0 -0
  122. {bumble-0.0.214.dist-info → bumble-0.0.216.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
@@ -1786,15 +1798,22 @@ class Connection(utils.CompositeEventEmitter):
1786
1798
 
1787
1799
  @dataclass
1788
1800
  class Parameters:
1789
- connection_interval: float # Connection interval, in milliseconds. [LE only]
1790
- peripheral_latency: int # Peripheral latency, in number of intervals. [LE only]
1791
- supervision_timeout: float # Supervision timeout, in milliseconds.
1792
- subrate_factor: int = (
1793
- 1 # See Bluetooth spec Vol 6, Part B - 4.5.1 Connection events
1794
- )
1795
- continuation_number: int = (
1796
- 0 # See Bluetooth spec Vol 6, Part B - 4.5.1 Connection events
1797
- )
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
1798
1817
 
1799
1818
  def __init__(
1800
1819
  self,
@@ -1835,36 +1854,6 @@ class Connection(utils.CompositeEventEmitter):
1835
1854
  self.cs_configs = {}
1836
1855
  self.cs_procedures = {}
1837
1856
 
1838
- # [Classic only]
1839
- @classmethod
1840
- def incomplete(cls, device, peer_address, role):
1841
- """
1842
- Instantiate an incomplete connection (ie. one waiting for a HCI Connection
1843
- Complete event).
1844
- Once received it shall be completed using the `.complete` method.
1845
- """
1846
- return cls(
1847
- device,
1848
- None,
1849
- PhysicalTransport.BR_EDR,
1850
- device.public_address,
1851
- None,
1852
- peer_address,
1853
- None,
1854
- role,
1855
- None,
1856
- )
1857
-
1858
- # [Classic only]
1859
- def complete(self, handle, parameters):
1860
- """
1861
- Finish an incomplete connection upon completion.
1862
- """
1863
- assert self.handle is None
1864
- assert self.transport == PhysicalTransport.BR_EDR
1865
- self.handle = handle
1866
- self.parameters = parameters
1867
-
1868
1857
  @property
1869
1858
  def role_name(self):
1870
1859
  if self.role is None:
@@ -1876,7 +1865,7 @@ class Connection(utils.CompositeEventEmitter):
1876
1865
  return f'UNKNOWN[{self.role}]'
1877
1866
 
1878
1867
  @property
1879
- def is_encrypted(self):
1868
+ def is_encrypted(self) -> bool:
1880
1869
  return self.encryption != 0
1881
1870
 
1882
1871
  @property
@@ -1886,16 +1875,6 @@ class Connection(utils.CompositeEventEmitter):
1886
1875
  def send_l2cap_pdu(self, cid: int, pdu: bytes) -> None:
1887
1876
  self.device.send_l2cap_pdu(self.handle, cid, pdu)
1888
1877
 
1889
- @utils.deprecated("Please use create_l2cap_channel()")
1890
- async def open_l2cap_channel(
1891
- self,
1892
- psm,
1893
- max_credits=DEVICE_DEFAULT_L2CAP_COC_MAX_CREDITS,
1894
- mtu=DEVICE_DEFAULT_L2CAP_COC_MTU,
1895
- mps=DEVICE_DEFAULT_L2CAP_COC_MPS,
1896
- ):
1897
- return await self.device.open_l2cap_channel(self, psm, max_credits, mtu, mps)
1898
-
1899
1878
  @overload
1900
1879
  async def create_l2cap_channel(
1901
1880
  self, spec: l2cap.ClassicChannelSpec
@@ -1947,7 +1926,7 @@ class Connection(utils.CompositeEventEmitter):
1947
1926
  self.remove_listener(self.EVENT_DISCONNECTION, abort.set_result)
1948
1927
  self.remove_listener(self.EVENT_DISCONNECTION_FAILURE, abort.set_exception)
1949
1928
 
1950
- 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:
1951
1930
  return await self.device.set_data_length(self, tx_octets, tx_time)
1952
1931
 
1953
1932
  async def update_parameters(
@@ -1977,7 +1956,12 @@ class Connection(utils.CompositeEventEmitter):
1977
1956
  use_l2cap=use_l2cap,
1978
1957
  )
1979
1958
 
1980
- 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
+ ):
1981
1965
  return await self.device.set_connection_phy(self, tx_phys, rx_phys, phy_options)
1982
1966
 
1983
1967
  async def get_phy(self) -> ConnectionPHY:
@@ -2076,9 +2060,7 @@ class DeviceConfiguration:
2076
2060
  connectable: bool = True
2077
2061
  discoverable: bool = True
2078
2062
  advertising_data: bytes = bytes(
2079
- AdvertisingData(
2080
- [(AdvertisingData.COMPLETE_LOCAL_NAME, bytes(DEVICE_DEFAULT_NAME, 'utf-8'))]
2081
- )
2063
+ AdvertisingData([data_types.CompleteLocalName(DEVICE_DEFAULT_NAME)])
2082
2064
  )
2083
2065
  irk: bytes = bytes(16) # This really must be changed for any level of security
2084
2066
  keystore: Optional[str] = None
@@ -2122,9 +2104,7 @@ class DeviceConfiguration:
2122
2104
  self.advertising_data = bytes.fromhex(advertising_data)
2123
2105
  elif name is not None:
2124
2106
  self.advertising_data = bytes(
2125
- AdvertisingData(
2126
- [(AdvertisingData.COMPLETE_LOCAL_NAME, bytes(self.name, 'utf-8'))]
2127
- )
2107
+ AdvertisingData([data_types.CompleteLocalName(self.name)])
2128
2108
  )
2129
2109
 
2130
2110
  # Load scan response data
@@ -2176,7 +2156,7 @@ class DeviceConfiguration:
2176
2156
  # Decorator that converts the first argument from a connection handle to a connection
2177
2157
  def with_connection_from_handle(function):
2178
2158
  @functools.wraps(function)
2179
- def wrapper(self, connection_handle, *args, **kwargs):
2159
+ def wrapper(self, connection_handle: int, *args, **kwargs):
2180
2160
  if (connection := self.lookup_connection(connection_handle)) is None:
2181
2161
  raise ObjectLookupError(
2182
2162
  f'no connection for handle: 0x{connection_handle:04x}'
@@ -2189,12 +2169,12 @@ def with_connection_from_handle(function):
2189
2169
  # Decorator that converts the first argument from a bluetooth address to a connection
2190
2170
  def with_connection_from_address(function):
2191
2171
  @functools.wraps(function)
2192
- def wrapper(self, address, *args, **kwargs):
2193
- if connection := self.pending_connections.get(address, False):
2194
- return function(self, connection, *args, **kwargs)
2195
- for connection in self.connections.values():
2172
+ def wrapper(device: Device, address: hci.Address, *args, **kwargs):
2173
+ if connection := device.pending_connections.get(address):
2174
+ return function(device, connection, address, *args, **kwargs)
2175
+ for connection in device.connections.values():
2196
2176
  if connection.peer_address == address:
2197
- return function(self, connection, *args, **kwargs)
2177
+ return function(device, connection, *args, **kwargs)
2198
2178
  raise ObjectLookupError('no connection for address')
2199
2179
 
2200
2180
  return wrapper
@@ -2204,13 +2184,13 @@ def with_connection_from_address(function):
2204
2184
  # connection
2205
2185
  def try_with_connection_from_address(function):
2206
2186
  @functools.wraps(function)
2207
- def wrapper(self, address, *args, **kwargs):
2208
- if connection := self.pending_connections.get(address, False):
2209
- return function(self, connection, address, *args, **kwargs)
2210
- for connection in self.connections.values():
2187
+ def wrapper(device: Device, address: hci.Address, *args, **kwargs):
2188
+ if connection := device.pending_connections.get(address):
2189
+ return function(device, connection, address, *args, **kwargs)
2190
+ for connection in device.connections.values():
2211
2191
  if connection.peer_address == address:
2212
- return function(self, connection, address, *args, **kwargs)
2213
- return function(self, None, address, *args, **kwargs)
2192
+ return function(device, connection, address, *args, **kwargs)
2193
+ return function(device, None, address, *args, **kwargs)
2214
2194
 
2215
2195
  return wrapper
2216
2196
 
@@ -2380,7 +2360,9 @@ class Device(utils.CompositeEventEmitter):
2380
2360
  self.le_connecting = False
2381
2361
  self.disconnecting = False
2382
2362
  self.connections = {} # Connections, by connection handle
2383
- self.pending_connections = {} # Connections, by BD address (BR/EDR only)
2363
+ self.pending_connections = (
2364
+ {}
2365
+ ) # Pending connections, by BD address (BR/EDR only)
2384
2366
  self.sco_links = {} # ScoLinks, by connection handle (BR/EDR only)
2385
2367
  self.cis_links = {} # CisLinks, by connection handle (LE only)
2386
2368
  self._pending_cis = {} # (CIS_ID, CIG_ID), by CIS_handle
@@ -2608,36 +2590,6 @@ class Device(utils.CompositeEventEmitter):
2608
2590
  None,
2609
2591
  )
2610
2592
 
2611
- @utils.deprecated("Please use create_l2cap_server()")
2612
- def register_l2cap_server(self, psm, server) -> int:
2613
- return self.l2cap_channel_manager.register_server(psm, server)
2614
-
2615
- @utils.deprecated("Please use create_l2cap_server()")
2616
- def register_l2cap_channel_server(
2617
- self,
2618
- psm,
2619
- server,
2620
- max_credits=DEVICE_DEFAULT_L2CAP_COC_MAX_CREDITS,
2621
- mtu=DEVICE_DEFAULT_L2CAP_COC_MTU,
2622
- mps=DEVICE_DEFAULT_L2CAP_COC_MPS,
2623
- ):
2624
- return self.l2cap_channel_manager.register_le_coc_server(
2625
- psm, server, max_credits, mtu, mps
2626
- )
2627
-
2628
- @utils.deprecated("Please use create_l2cap_channel()")
2629
- async def open_l2cap_channel(
2630
- self,
2631
- connection,
2632
- psm,
2633
- max_credits=DEVICE_DEFAULT_L2CAP_COC_MAX_CREDITS,
2634
- mtu=DEVICE_DEFAULT_L2CAP_COC_MTU,
2635
- mps=DEVICE_DEFAULT_L2CAP_COC_MPS,
2636
- ):
2637
- return await self.l2cap_channel_manager.open_le_coc(
2638
- connection, psm, max_credits, mtu, mps
2639
- )
2640
-
2641
2593
  @overload
2642
2594
  async def create_l2cap_channel(
2643
2595
  self,
@@ -2705,7 +2657,7 @@ class Device(utils.CompositeEventEmitter):
2705
2657
  def send_l2cap_pdu(self, connection_handle: int, cid: int, pdu: bytes) -> None:
2706
2658
  self.host.send_l2cap_pdu(connection_handle, cid, pdu)
2707
2659
 
2708
- async def send_command(self, command, check_result=False):
2660
+ async def send_command(self, command: hci.HCI_Command, check_result: bool = False):
2709
2661
  try:
2710
2662
  return await asyncio.wait_for(
2711
2663
  self.host.send_command(command, check_result), self.command_timeout
@@ -2962,13 +2914,13 @@ class Device(utils.CompositeEventEmitter):
2962
2914
  def supports_le_features(self, feature: hci.LeFeatureMask) -> bool:
2963
2915
  return self.host.supports_le_features(feature)
2964
2916
 
2965
- def supports_le_phy(self, phy: int) -> bool:
2966
- if phy == hci.HCI_LE_1M_PHY:
2917
+ def supports_le_phy(self, phy: hci.Phy) -> bool:
2918
+ if phy == hci.Phy.LE_1M:
2967
2919
  return True
2968
2920
 
2969
- feature_map: dict[int, hci.LeFeatureMask] = {
2970
- hci.HCI_LE_2M_PHY: hci.LeFeatureMask.LE_2M_PHY,
2971
- hci.HCI_LE_CODED_PHY: hci.LeFeatureMask.LE_CODED_PHY,
2921
+ feature_map: dict[hci.Phy, hci.LeFeatureMask] = {
2922
+ hci.Phy.LE_2M: hci.LeFeatureMask.LE_2M_PHY,
2923
+ hci.Phy.LE_CODED: hci.LeFeatureMask.LE_CODED_PHY,
2972
2924
  }
2973
2925
  if phy not in feature_map:
2974
2926
  raise InvalidArgumentError('invalid PHY')
@@ -3245,8 +3197,8 @@ class Device(utils.CompositeEventEmitter):
3245
3197
  else 0
3246
3198
  )
3247
3199
  await advertising_set.start(duration=duration)
3248
- except Exception as error:
3249
- logger.exception(f'failed to start advertising set: {error}')
3200
+ except Exception:
3201
+ logger.exception('failed to start advertising set')
3250
3202
  await advertising_set.remove()
3251
3203
  raise
3252
3204
 
@@ -3572,7 +3524,9 @@ class Device(utils.CompositeEventEmitter):
3572
3524
  self.discovering = False
3573
3525
 
3574
3526
  @host_event_handler
3575
- def on_inquiry_result(self, address, class_of_device, data, rssi):
3527
+ def on_inquiry_result(
3528
+ self, address: hci.Address, class_of_device: int, data: bytes, rssi: int
3529
+ ):
3576
3530
  self.emit(
3577
3531
  self.EVENT_INQUIRY_RESULT,
3578
3532
  address,
@@ -3581,7 +3535,9 @@ class Device(utils.CompositeEventEmitter):
3581
3535
  rssi,
3582
3536
  )
3583
3537
 
3584
- async def set_scan_enable(self, inquiry_scan_enabled, page_scan_enabled):
3538
+ async def set_scan_enable(
3539
+ self, inquiry_scan_enabled: bool, page_scan_enabled: bool
3540
+ ):
3585
3541
  if inquiry_scan_enabled and page_scan_enabled:
3586
3542
  scan_enable = 0x03
3587
3543
  elif page_scan_enabled:
@@ -3601,14 +3557,7 @@ class Device(utils.CompositeEventEmitter):
3601
3557
  # Synthesize an inquiry response if none is set already
3602
3558
  if self.inquiry_response is None:
3603
3559
  self.inquiry_response = bytes(
3604
- AdvertisingData(
3605
- [
3606
- (
3607
- AdvertisingData.COMPLETE_LOCAL_NAME,
3608
- bytes(self.name, 'utf-8'),
3609
- )
3610
- ]
3611
- )
3560
+ AdvertisingData([data_types.CompleteLocalName(self.name)])
3612
3561
  )
3613
3562
 
3614
3563
  # Update the controller
@@ -3714,6 +3663,7 @@ class Device(utils.CompositeEventEmitter):
3714
3663
  # If the address is not parsable, assume it is a name instead
3715
3664
  always_resolve = False
3716
3665
  logger.debug('looking for peer by name')
3666
+ assert isinstance(peer_address, str)
3717
3667
  peer_address = await self.find_peer_by_name(
3718
3668
  peer_address, transport
3719
3669
  ) # TODO: timeout
@@ -3741,7 +3691,7 @@ class Device(utils.CompositeEventEmitter):
3741
3691
  ):
3742
3692
  pending_connection.set_result(connection)
3743
3693
 
3744
- def on_connection_failure(error):
3694
+ def on_connection_failure(error: core.ConnectionError):
3745
3695
  if transport == PhysicalTransport.LE or (
3746
3696
  # match BR/EDR connection failure event against peer address
3747
3697
  error.transport == transport
@@ -3881,8 +3831,16 @@ class Device(utils.CompositeEventEmitter):
3881
3831
  )
3882
3832
  else:
3883
3833
  # Save pending connection
3884
- self.pending_connections[peer_address] = Connection.incomplete(
3885
- self, peer_address, hci.Role.CENTRAL
3834
+ self.pending_connections[peer_address] = Connection(
3835
+ device=self,
3836
+ handle=0,
3837
+ transport=core.PhysicalTransport.BR_EDR,
3838
+ self_address=self.public_address,
3839
+ self_resolvable_address=None,
3840
+ peer_address=peer_address,
3841
+ peer_resolvable_address=None,
3842
+ role=hci.Role.CENTRAL,
3843
+ parameters=Connection.Parameters(0, 0, 0),
3886
3844
  )
3887
3845
 
3888
3846
  # TODO: allow passing other settings
@@ -3961,6 +3919,7 @@ class Device(utils.CompositeEventEmitter):
3961
3919
  except InvalidArgumentError:
3962
3920
  # If the address is not parsable, assume it is a name instead
3963
3921
  logger.debug('looking for peer by name')
3922
+ assert isinstance(peer_address, str)
3964
3923
  peer_address = await self.find_peer_by_name(
3965
3924
  peer_address, PhysicalTransport.BR_EDR
3966
3925
  ) # TODO: timeout
@@ -4019,7 +3978,7 @@ class Device(utils.CompositeEventEmitter):
4019
3978
  ):
4020
3979
  pending_connection.set_result(connection)
4021
3980
 
4022
- def on_connection_failure(error):
3981
+ def on_connection_failure(error: core.ConnectionError):
4023
3982
  if (
4024
3983
  error.transport == PhysicalTransport.BR_EDR
4025
3984
  and error.peer_address == peer_address
@@ -4029,12 +3988,20 @@ class Device(utils.CompositeEventEmitter):
4029
3988
  self.on(self.EVENT_CONNECTION, on_connection)
4030
3989
  self.on(self.EVENT_CONNECTION_FAILURE, on_connection_failure)
4031
3990
 
4032
- # Save pending connection, with the Peripheral hci.role.
3991
+ # Save Peripheral hci.role.
4033
3992
  # Even if we requested a role switch in the hci.HCI_Accept_Connection_Request
4034
3993
  # command, this connection is still considered Peripheral until an eventual
4035
3994
  # role change event.
4036
- self.pending_connections[peer_address] = Connection.incomplete(
4037
- self, peer_address, hci.Role.PERIPHERAL
3995
+ self.pending_connections[peer_address] = Connection(
3996
+ device=self,
3997
+ handle=0,
3998
+ transport=core.PhysicalTransport.BR_EDR,
3999
+ self_address=self.public_address,
4000
+ self_resolvable_address=None,
4001
+ peer_address=peer_address,
4002
+ peer_resolvable_address=None,
4003
+ role=hci.Role.PERIPHERAL,
4004
+ parameters=Connection.Parameters(0, 0, 0),
4038
4005
  )
4039
4006
 
4040
4007
  try:
@@ -4056,7 +4023,7 @@ class Device(utils.CompositeEventEmitter):
4056
4023
  self.pending_connections.pop(peer_address, None)
4057
4024
 
4058
4025
  @asynccontextmanager
4059
- async def connect_as_gatt(self, peer_address):
4026
+ async def connect_as_gatt(self, peer_address: Union[hci.Address, str]):
4060
4027
  async with AsyncExitStack() as stack:
4061
4028
  connection = await stack.enter_async_context(
4062
4029
  await self.connect(peer_address)
@@ -4092,6 +4059,7 @@ class Device(utils.CompositeEventEmitter):
4092
4059
  except InvalidArgumentError:
4093
4060
  # If the address is not parsable, assume it is a name instead
4094
4061
  logger.debug('looking for peer by name')
4062
+ assert isinstance(peer_address, str)
4095
4063
  peer_address = await self.find_peer_by_name(
4096
4064
  peer_address, PhysicalTransport.BR_EDR
4097
4065
  ) # TODO: timeout
@@ -4137,7 +4105,9 @@ class Device(utils.CompositeEventEmitter):
4137
4105
  )
4138
4106
  self.disconnecting = False
4139
4107
 
4140
- async def set_data_length(self, connection, tx_octets, tx_time) -> None:
4108
+ async def set_data_length(
4109
+ self, connection: Connection, tx_octets: int, tx_time: int
4110
+ ) -> None:
4141
4111
  if tx_octets < 0x001B or tx_octets > 0x00FB:
4142
4112
  raise InvalidArgumentError('tx_octets must be between 0x001B and 0x00FB')
4143
4113
 
@@ -4240,7 +4210,11 @@ class Device(utils.CompositeEventEmitter):
4240
4210
  )
4241
4211
 
4242
4212
  async def set_connection_phy(
4243
- self, connection, tx_phys=None, rx_phys=None, phy_options=None
4213
+ self,
4214
+ connection: Connection,
4215
+ tx_phys: Optional[Iterable[hci.Phy]] = None,
4216
+ rx_phys: Optional[Iterable[hci.Phy]] = None,
4217
+ phy_options: int = 0,
4244
4218
  ):
4245
4219
  if not self.host.supports_command(hci.HCI_LE_SET_PHY_COMMAND):
4246
4220
  logger.warning('ignoring request, command not supported')
@@ -4256,7 +4230,7 @@ class Device(utils.CompositeEventEmitter):
4256
4230
  all_phys=all_phys_bits,
4257
4231
  tx_phys=hci.phy_list_to_bits(tx_phys),
4258
4232
  rx_phys=hci.phy_list_to_bits(rx_phys),
4259
- phy_options=0 if phy_options is None else int(phy_options),
4233
+ phy_options=phy_options,
4260
4234
  )
4261
4235
  )
4262
4236
 
@@ -4267,7 +4241,11 @@ class Device(utils.CompositeEventEmitter):
4267
4241
  )
4268
4242
  raise hci.HCI_StatusError(result)
4269
4243
 
4270
- async def set_default_phy(self, tx_phys=None, rx_phys=None):
4244
+ async def set_default_phy(
4245
+ self,
4246
+ tx_phys: Optional[Iterable[hci.Phy]] = None,
4247
+ rx_phys: Optional[Iterable[hci.Phy]] = None,
4248
+ ):
4271
4249
  all_phys_bits = (1 if tx_phys is None else 0) | (
4272
4250
  (1 if rx_phys is None else 0) << 1
4273
4251
  )
@@ -4305,7 +4283,7 @@ class Device(utils.CompositeEventEmitter):
4305
4283
  check_result=True,
4306
4284
  )
4307
4285
 
4308
- async def find_peer_by_name(self, name, transport=PhysicalTransport.LE):
4286
+ async def find_peer_by_name(self, name: str, transport=PhysicalTransport.LE):
4309
4287
  """
4310
4288
  Scan for a peer with a given name and return its address.
4311
4289
  """
@@ -4320,7 +4298,7 @@ class Device(utils.CompositeEventEmitter):
4320
4298
  if local_name == name:
4321
4299
  peer_address.set_result(address)
4322
4300
 
4323
- listener = None
4301
+ listener: Optional[Callable[..., None]] = None
4324
4302
  was_scanning = self.scanning
4325
4303
  was_discovering = self.discovering
4326
4304
  try:
@@ -4426,10 +4404,10 @@ class Device(utils.CompositeEventEmitter):
4426
4404
  def smp_session_proxy(self, session_proxy: type[smp.Session]) -> None:
4427
4405
  self.smp_manager.session_proxy = session_proxy
4428
4406
 
4429
- async def pair(self, connection):
4407
+ async def pair(self, connection: Connection):
4430
4408
  return await self.smp_manager.pair(connection)
4431
4409
 
4432
- def request_pairing(self, connection):
4410
+ def request_pairing(self, connection: Connection):
4433
4411
  return self.smp_manager.request_pairing(connection)
4434
4412
 
4435
4413
  async def get_long_term_key(
@@ -4517,7 +4495,7 @@ class Device(utils.CompositeEventEmitter):
4517
4495
  on_authentication_failure,
4518
4496
  )
4519
4497
 
4520
- async def encrypt(self, connection, enable=True):
4498
+ async def encrypt(self, connection: Connection, enable: bool = True):
4521
4499
  if not enable and connection.transport == PhysicalTransport.LE:
4522
4500
  raise InvalidArgumentError('`enable` parameter is classic only.')
4523
4501
 
@@ -4527,7 +4505,7 @@ class Device(utils.CompositeEventEmitter):
4527
4505
  def on_encryption_change():
4528
4506
  pending_encryption.set_result(None)
4529
4507
 
4530
- def on_encryption_failure(error_code):
4508
+ def on_encryption_failure(error_code: int):
4531
4509
  pending_encryption.set_exception(hci.HCI_Error(error_code))
4532
4510
 
4533
4511
  connection.on(
@@ -4610,8 +4588,8 @@ class Device(utils.CompositeEventEmitter):
4610
4588
  try:
4611
4589
  await self.keystore.update(address, keys)
4612
4590
  await self.refresh_resolving_list()
4613
- except Exception as error:
4614
- logger.warning(f'!!! error while storing keys: {error}')
4591
+ except Exception:
4592
+ logger.exception('!!! error while storing keys')
4615
4593
  else:
4616
4594
  self.emit(self.EVENT_KEY_STORE_UPDATE)
4617
4595
 
@@ -4619,10 +4597,10 @@ class Device(utils.CompositeEventEmitter):
4619
4597
  async def switch_role(self, connection: Connection, role: hci.Role):
4620
4598
  pending_role_change = asyncio.get_running_loop().create_future()
4621
4599
 
4622
- def on_role_change(new_role):
4600
+ def on_role_change(new_role: hci.Role):
4623
4601
  pending_role_change.set_result(new_role)
4624
4602
 
4625
- def on_role_change_failure(error_code):
4603
+ def on_role_change_failure(error_code: int):
4626
4604
  pending_role_change.set_exception(hci.HCI_Error(error_code))
4627
4605
 
4628
4606
  connection.on(connection.EVENT_ROLE_CHANGE, on_role_change)
@@ -5212,10 +5190,10 @@ class Device(utils.CompositeEventEmitter):
5212
5190
  ):
5213
5191
  connection.emit(connection.EVENT_LINK_KEY)
5214
5192
 
5215
- def add_service(self, service):
5193
+ def add_service(self, service: gatt.Service):
5216
5194
  self.gatt_server.add_service(service)
5217
5195
 
5218
- def add_services(self, services):
5196
+ def add_services(self, services: Iterable[gatt.Service]):
5219
5197
  self.gatt_server.add_services(services)
5220
5198
 
5221
5199
  def add_default_services(
@@ -5311,10 +5289,10 @@ class Device(utils.CompositeEventEmitter):
5311
5289
  @host_event_handler
5312
5290
  def on_advertising_set_termination(
5313
5291
  self,
5314
- status,
5315
- advertising_handle,
5316
- connection_handle,
5317
- number_of_completed_extended_advertising_events,
5292
+ status: int,
5293
+ advertising_handle: int,
5294
+ connection_handle: int,
5295
+ number_of_completed_extended_advertising_events: int,
5318
5296
  ):
5319
5297
  # Legacy advertising set is also one of extended advertising sets.
5320
5298
  if not (
@@ -5482,15 +5460,47 @@ class Device(utils.CompositeEventEmitter):
5482
5460
  self.emit(self.EVENT_CONNECTION, connection)
5483
5461
 
5484
5462
  @host_event_handler
5485
- def on_connection(
5463
+ def on_classic_connection(
5464
+ self,
5465
+ connection_handle: int,
5466
+ peer_address: hci.Address,
5467
+ ) -> None:
5468
+ if connection := self.pending_connections.pop(peer_address, None):
5469
+ connection.handle = connection_handle
5470
+ else:
5471
+ # Create a new connection
5472
+ connection = Connection(
5473
+ device=self,
5474
+ handle=connection_handle,
5475
+ transport=PhysicalTransport.BR_EDR,
5476
+ self_address=self.public_address,
5477
+ self_resolvable_address=None,
5478
+ peer_address=peer_address,
5479
+ peer_resolvable_address=None,
5480
+ role=hci.Role.PERIPHERAL,
5481
+ parameters=Connection.Parameters(0.0, 0, 0.0),
5482
+ )
5483
+
5484
+ logger.debug('*** %s', connection)
5485
+ if connection_handle in self.connections:
5486
+ logger.warning(
5487
+ 'new connection reuses the same handle as a previous connection'
5488
+ )
5489
+ self.connections[connection_handle] = connection
5490
+
5491
+ self.emit(self.EVENT_CONNECTION, connection)
5492
+
5493
+ @host_event_handler
5494
+ def on_le_connection(
5486
5495
  self,
5487
5496
  connection_handle: int,
5488
- transport: core.PhysicalTransport,
5489
5497
  peer_address: hci.Address,
5490
5498
  self_resolvable_address: Optional[hci.Address],
5491
5499
  peer_resolvable_address: Optional[hci.Address],
5492
5500
  role: hci.Role,
5493
- connection_parameters: Optional[core.ConnectionParameters],
5501
+ connection_interval: int,
5502
+ peripheral_latency: int,
5503
+ supervision_timeout: int,
5494
5504
  ) -> None:
5495
5505
  # Convert all-zeros addresses into None.
5496
5506
  if self_resolvable_address == hci.Address.ANY_RANDOM:
@@ -5510,19 +5520,6 @@ class Device(utils.CompositeEventEmitter):
5510
5520
  'new connection reuses the same handle as a previous connection'
5511
5521
  )
5512
5522
 
5513
- if transport == PhysicalTransport.BR_EDR:
5514
- # Create a new connection
5515
- connection = self.pending_connections.pop(peer_address)
5516
- connection.complete(connection_handle, connection_parameters)
5517
- self.connections[connection_handle] = connection
5518
-
5519
- # Emit an event to notify listeners of the new connection
5520
- self.emit(self.EVENT_CONNECTION, connection)
5521
-
5522
- return
5523
-
5524
- assert connection_parameters is not None
5525
-
5526
5523
  if peer_resolvable_address is None:
5527
5524
  # Resolve the peer address if we can
5528
5525
  if self.address_resolver:
@@ -5572,16 +5569,16 @@ class Device(utils.CompositeEventEmitter):
5572
5569
  connection = Connection(
5573
5570
  self,
5574
5571
  connection_handle,
5575
- transport,
5572
+ PhysicalTransport.LE,
5576
5573
  self_address,
5577
5574
  self_resolvable_address,
5578
5575
  peer_address,
5579
5576
  peer_resolvable_address,
5580
5577
  role,
5581
5578
  Connection.Parameters(
5582
- connection_parameters.connection_interval * 1.25,
5583
- connection_parameters.peripheral_latency,
5584
- connection_parameters.supervision_timeout * 10.0,
5579
+ connection_interval * 1.25,
5580
+ peripheral_latency,
5581
+ supervision_timeout * 10.0,
5585
5582
  ),
5586
5583
  )
5587
5584
  self.connections[connection_handle] = connection
@@ -5613,7 +5610,12 @@ class Device(utils.CompositeEventEmitter):
5613
5610
  )
5614
5611
 
5615
5612
  @host_event_handler
5616
- def on_connection_failure(self, transport, peer_address, error_code):
5613
+ def on_connection_failure(
5614
+ self,
5615
+ transport: hci.PhysicalTransport,
5616
+ peer_address: hci.Address,
5617
+ error_code: int,
5618
+ ):
5617
5619
  logger.debug(
5618
5620
  f'*** Connection failed: {hci.HCI_Constant.error_name(error_code)}'
5619
5621
  )
@@ -5638,7 +5640,9 @@ class Device(utils.CompositeEventEmitter):
5638
5640
 
5639
5641
  # FIXME: Explore a delegate-model for BR/EDR wait connection #56.
5640
5642
  @host_event_handler
5641
- def on_connection_request(self, bd_addr, class_of_device, link_type):
5643
+ def on_connection_request(
5644
+ self, bd_addr: hci.Address, class_of_device: int, link_type: int
5645
+ ):
5642
5646
  logger.debug(f'*** Connection request: {bd_addr}')
5643
5647
 
5644
5648
  # Handle SCO request.
@@ -5667,8 +5671,16 @@ class Device(utils.CompositeEventEmitter):
5667
5671
  # device configuration is set to accept any incoming connection
5668
5672
  elif self.classic_accept_any:
5669
5673
  # Save pending connection
5670
- self.pending_connections[bd_addr] = Connection.incomplete(
5671
- self, bd_addr, hci.Role.PERIPHERAL
5674
+ self.pending_connections[bd_addr] = Connection(
5675
+ device=self,
5676
+ handle=0,
5677
+ transport=core.PhysicalTransport.BR_EDR,
5678
+ self_address=self.public_address,
5679
+ self_resolvable_address=None,
5680
+ peer_address=bd_addr,
5681
+ peer_resolvable_address=None,
5682
+ role=hci.Role.PERIPHERAL,
5683
+ parameters=Connection.Parameters(0, 0, 0),
5672
5684
  )
5673
5685
 
5674
5686
  self.host.send_command_sync(
@@ -5732,7 +5744,7 @@ class Device(utils.CompositeEventEmitter):
5732
5744
 
5733
5745
  @host_event_handler
5734
5746
  @with_connection_from_handle
5735
- def on_connection_authentication(self, connection):
5747
+ def on_connection_authentication(self, connection: Connection):
5736
5748
  logger.debug(
5737
5749
  f'*** Connection Authentication: [0x{connection.handle:04X}] '
5738
5750
  f'{connection.peer_address} as {connection.role_name}'
@@ -5742,7 +5754,9 @@ class Device(utils.CompositeEventEmitter):
5742
5754
 
5743
5755
  @host_event_handler
5744
5756
  @with_connection_from_handle
5745
- def on_connection_authentication_failure(self, connection, error):
5757
+ def on_connection_authentication_failure(
5758
+ self, connection: Connection, error: core.ConnectionError
5759
+ ):
5746
5760
  logger.debug(
5747
5761
  f'*** Connection Authentication Failure: [0x{connection.handle:04X}] '
5748
5762
  f'{connection.peer_address} as {connection.role_name}, error={error}'
@@ -5784,10 +5798,13 @@ class Device(utils.CompositeEventEmitter):
5784
5798
  @host_event_handler
5785
5799
  @with_connection_from_address
5786
5800
  def on_authentication_io_capability_response(
5787
- self, connection, io_capability, authentication_requirements
5801
+ self,
5802
+ connection: Connection,
5803
+ io_capability: int,
5804
+ authentication_requirements: int,
5788
5805
  ):
5789
- connection.peer_pairing_io_capability = io_capability
5790
- connection.peer_pairing_authentication_requirements = (
5806
+ connection.pairing_peer_io_capability = io_capability
5807
+ connection.pairing_peer_authentication_requirements = (
5791
5808
  authentication_requirements
5792
5809
  )
5793
5810
 
@@ -5798,7 +5815,7 @@ class Device(utils.CompositeEventEmitter):
5798
5815
  # Ask what the pairing config should be for this connection
5799
5816
  pairing_config = self.pairing_config_factory(connection)
5800
5817
  io_capability = pairing_config.delegate.classic_io_capability
5801
- peer_io_capability = connection.peer_pairing_io_capability
5818
+ peer_io_capability = connection.pairing_peer_io_capability
5802
5819
 
5803
5820
  async def confirm() -> bool:
5804
5821
  # Ask the user to confirm the pairing, without display
@@ -5859,8 +5876,8 @@ class Device(utils.CompositeEventEmitter):
5859
5876
  )
5860
5877
  )
5861
5878
  return
5862
- except Exception as error:
5863
- logger.warning(f'exception while confirming: {error}')
5879
+ except Exception:
5880
+ logger.exception('exception while confirming')
5864
5881
 
5865
5882
  await self.host.send_command(
5866
5883
  hci.HCI_User_Confirmation_Request_Negative_Reply_Command(
@@ -5873,7 +5890,7 @@ class Device(utils.CompositeEventEmitter):
5873
5890
  # [Classic only]
5874
5891
  @host_event_handler
5875
5892
  @with_connection_from_address
5876
- def on_authentication_user_passkey_request(self, connection) -> None:
5893
+ def on_authentication_user_passkey_request(self, connection: Connection) -> None:
5877
5894
  # Ask what the pairing config should be for this connection
5878
5895
  pairing_config = self.pairing_config_factory(connection)
5879
5896
 
@@ -5889,8 +5906,8 @@ class Device(utils.CompositeEventEmitter):
5889
5906
  )
5890
5907
  )
5891
5908
  return
5892
- except Exception as error:
5893
- logger.warning(f'exception while asking for pass-key: {error}')
5909
+ except Exception:
5910
+ logger.exception('exception while asking for pass-key')
5894
5911
 
5895
5912
  await self.host.send_command(
5896
5913
  hci.HCI_User_Passkey_Request_Negative_Reply_Command(
@@ -5916,7 +5933,7 @@ class Device(utils.CompositeEventEmitter):
5916
5933
  # [Classic only]
5917
5934
  @host_event_handler
5918
5935
  @with_connection_from_address
5919
- def on_pin_code_request(self, connection):
5936
+ def on_pin_code_request(self, connection: Connection):
5920
5937
  # Classic legacy pairing
5921
5938
  # Ask what the pairing config should be for this connection
5922
5939
  pairing_config = self.pairing_config_factory(connection)
@@ -5960,7 +5977,9 @@ class Device(utils.CompositeEventEmitter):
5960
5977
  # [Classic only]
5961
5978
  @host_event_handler
5962
5979
  @with_connection_from_address
5963
- def on_authentication_user_passkey_notification(self, connection, passkey):
5980
+ def on_authentication_user_passkey_notification(
5981
+ self, connection: Connection, passkey: int
5982
+ ):
5964
5983
  # Ask what the pairing config should be for this connection
5965
5984
  pairing_config = self.pairing_config_factory(connection)
5966
5985
 
@@ -5972,14 +5991,15 @@ class Device(utils.CompositeEventEmitter):
5972
5991
  # [Classic only]
5973
5992
  @host_event_handler
5974
5993
  @try_with_connection_from_address
5975
- def on_remote_name(self, connection: Connection, address, remote_name):
5994
+ def on_remote_name(
5995
+ self, connection: Optional[Connection], address: hci.Address, remote_name: bytes
5996
+ ):
5976
5997
  # Try to decode the name
5977
5998
  try:
5978
- remote_name = remote_name.decode('utf-8')
5979
5999
  if connection:
5980
- connection.peer_name = remote_name
6000
+ connection.peer_name = remote_name.decode('utf-8')
5981
6001
  connection.emit(connection.EVENT_REMOTE_NAME)
5982
- self.emit(self.EVENT_REMOTE_NAME, address, remote_name)
6002
+ self.emit(self.EVENT_REMOTE_NAME, address, remote_name.decode('utf-8'))
5983
6003
  except UnicodeDecodeError as error:
5984
6004
  logger.warning('peer name is not valid UTF-8')
5985
6005
  if connection:
@@ -5990,7 +6010,9 @@ class Device(utils.CompositeEventEmitter):
5990
6010
  # [Classic only]
5991
6011
  @host_event_handler
5992
6012
  @try_with_connection_from_address
5993
- def on_remote_name_failure(self, connection: Connection, address, error):
6013
+ def on_remote_name_failure(
6014
+ self, connection: Optional[Connection], address: hci.Address, error: int
6015
+ ):
5994
6016
  if connection:
5995
6017
  connection.emit(connection.EVENT_REMOTE_NAME_FAILURE, error)
5996
6018
  self.emit(self.EVENT_REMOTE_NAME_FAILURE, address, error)
@@ -6191,7 +6213,7 @@ class Device(utils.CompositeEventEmitter):
6191
6213
 
6192
6214
  @host_event_handler
6193
6215
  @with_connection_from_handle
6194
- def on_connection_encryption_key_refresh(self, connection):
6216
+ def on_connection_encryption_key_refresh(self, connection: Connection):
6195
6217
  logger.debug(
6196
6218
  f'*** Connection Key Refresh: [0x{connection.handle:04X}] '
6197
6219
  f'{connection.peer_address} as {connection.role_name}'
@@ -6201,27 +6223,27 @@ class Device(utils.CompositeEventEmitter):
6201
6223
  @host_event_handler
6202
6224
  @with_connection_from_handle
6203
6225
  def on_connection_parameters_update(
6204
- self, connection: Connection, connection_parameters: core.ConnectionParameters
6226
+ self,
6227
+ connection: Connection,
6228
+ connection_interval: int,
6229
+ peripheral_latency: int,
6230
+ supervision_timeout: int,
6205
6231
  ):
6206
6232
  logger.debug(
6207
6233
  f'*** Connection Parameters Update: [0x{connection.handle:04X}] '
6208
6234
  f'{connection.peer_address} as {connection.role_name}, '
6209
- f'{connection_parameters}'
6210
6235
  )
6211
- if (
6212
- connection.parameters.connection_interval
6213
- != connection_parameters.connection_interval * 1.25
6214
- ):
6236
+ if connection.parameters.connection_interval != connection_interval * 1.25:
6215
6237
  connection.parameters = Connection.Parameters(
6216
- connection_parameters.connection_interval * 1.25,
6217
- connection_parameters.peripheral_latency,
6218
- connection_parameters.supervision_timeout * 10.0,
6238
+ connection_interval * 1.25,
6239
+ peripheral_latency,
6240
+ supervision_timeout * 10.0,
6219
6241
  )
6220
6242
  else:
6221
6243
  connection.parameters = Connection.Parameters(
6222
- connection_parameters.connection_interval * 1.25,
6223
- connection_parameters.peripheral_latency,
6224
- connection_parameters.supervision_timeout * 10.0,
6244
+ connection_interval * 1.25,
6245
+ peripheral_latency,
6246
+ supervision_timeout * 10.0,
6225
6247
  connection.parameters.subrate_factor,
6226
6248
  connection.parameters.continuation_number,
6227
6249
  )
@@ -6229,7 +6251,9 @@ class Device(utils.CompositeEventEmitter):
6229
6251
 
6230
6252
  @host_event_handler
6231
6253
  @with_connection_from_handle
6232
- def on_connection_parameters_update_failure(self, connection, error):
6254
+ def on_connection_parameters_update_failure(
6255
+ self, connection: Connection, error: int
6256
+ ):
6233
6257
  logger.debug(
6234
6258
  f'*** Connection Parameters Update Failed: [0x{connection.handle:04X}] '
6235
6259
  f'{connection.peer_address} as {connection.role_name}, '
@@ -6239,7 +6263,7 @@ class Device(utils.CompositeEventEmitter):
6239
6263
 
6240
6264
  @host_event_handler
6241
6265
  @with_connection_from_handle
6242
- def on_connection_phy_update(self, connection, phy):
6266
+ def on_connection_phy_update(self, connection: Connection, phy: core.ConnectionPHY):
6243
6267
  logger.debug(
6244
6268
  f'*** Connection PHY Update: [0x{connection.handle:04X}] '
6245
6269
  f'{connection.peer_address} as {connection.role_name}, '
@@ -6249,7 +6273,7 @@ class Device(utils.CompositeEventEmitter):
6249
6273
 
6250
6274
  @host_event_handler
6251
6275
  @with_connection_from_handle
6252
- def on_connection_phy_update_failure(self, connection, error):
6276
+ def on_connection_phy_update_failure(self, connection: Connection, error: int):
6253
6277
  logger.debug(
6254
6278
  f'*** Connection PHY Update Failed: [0x{connection.handle:04X}] '
6255
6279
  f'{connection.peer_address} as {connection.role_name}, '
@@ -6278,7 +6302,7 @@ class Device(utils.CompositeEventEmitter):
6278
6302
 
6279
6303
  @host_event_handler
6280
6304
  @with_connection_from_handle
6281
- def on_connection_att_mtu_update(self, connection, att_mtu):
6305
+ def on_connection_att_mtu_update(self, connection: Connection, att_mtu: int):
6282
6306
  logger.debug(
6283
6307
  f'*** Connection ATT MTU Update: [0x{connection.handle:04X}] '
6284
6308
  f'{connection.peer_address} as {connection.role_name}, '
@@ -6290,7 +6314,12 @@ class Device(utils.CompositeEventEmitter):
6290
6314
  @host_event_handler
6291
6315
  @with_connection_from_handle
6292
6316
  def on_connection_data_length_change(
6293
- self, connection, max_tx_octets, max_tx_time, max_rx_octets, max_rx_time
6317
+ self,
6318
+ connection: Connection,
6319
+ max_tx_octets: int,
6320
+ max_tx_time: int,
6321
+ max_rx_octets: int,
6322
+ max_rx_time: int,
6294
6323
  ):
6295
6324
  logger.debug(
6296
6325
  f'*** Connection Data Length Change: [0x{connection.handle:04X}] '
@@ -6414,15 +6443,25 @@ class Device(utils.CompositeEventEmitter):
6414
6443
 
6415
6444
  # [Classic only]
6416
6445
  @host_event_handler
6417
- @with_connection_from_address
6418
- def on_role_change(self, connection, new_role):
6419
- connection.role = new_role
6420
- connection.emit(connection.EVENT_ROLE_CHANGE, new_role)
6446
+ @try_with_connection_from_address
6447
+ def on_role_change(
6448
+ self,
6449
+ connection: Optional[Connection],
6450
+ peer_address: hci.Address,
6451
+ new_role: hci.Role,
6452
+ ):
6453
+ if connection:
6454
+ connection.role = new_role
6455
+ connection.emit(connection.EVENT_ROLE_CHANGE, new_role)
6456
+ else:
6457
+ logger.warning("Role change to unknown connection %s", peer_address)
6421
6458
 
6422
6459
  # [Classic only]
6423
6460
  @host_event_handler
6424
6461
  @try_with_connection_from_address
6425
- def on_role_change_failure(self, connection, address, error):
6462
+ def on_role_change_failure(
6463
+ self, connection: Optional[Connection], address: hci.Address, error: int
6464
+ ):
6426
6465
  if connection:
6427
6466
  connection.emit(connection.EVENT_ROLE_CHANGE_FAILURE, error)
6428
6467
  self.emit(self.EVENT_ROLE_CHANGE_FAILURE, address, error)
@@ -6436,7 +6475,7 @@ class Device(utils.CompositeEventEmitter):
6436
6475
  # [Classic only]
6437
6476
  @host_event_handler
6438
6477
  @with_connection_from_address
6439
- def on_classic_pairing_failure(self, connection: Connection, status) -> None:
6478
+ def on_classic_pairing_failure(self, connection: Connection, status: int) -> None:
6440
6479
  connection.emit(connection.EVENT_CLASSIC_PAIRING_FAILURE, status)
6441
6480
 
6442
6481
  def on_pairing_start(self, connection: Connection) -> None:
@@ -6460,7 +6499,7 @@ class Device(utils.CompositeEventEmitter):
6460
6499
  connection.emit(connection.EVENT_PAIRING_FAILURE, reason)
6461
6500
 
6462
6501
  @with_connection_from_handle
6463
- def on_gatt_pdu(self, connection, pdu):
6502
+ def on_gatt_pdu(self, connection: Connection, pdu: bytes):
6464
6503
  # Parse the L2CAP payload into an ATT PDU object
6465
6504
  att_pdu = ATT_PDU.from_bytes(pdu)
6466
6505
 
@@ -6482,7 +6521,7 @@ class Device(utils.CompositeEventEmitter):
6482
6521
  connection.gatt_server.on_gatt_pdu(connection, att_pdu)
6483
6522
 
6484
6523
  @with_connection_from_handle
6485
- def on_smp_pdu(self, connection, pdu):
6524
+ def on_smp_pdu(self, connection: Connection, pdu: bytes):
6486
6525
  self.smp_manager.on_smp_pdu(connection, pdu)
6487
6526
 
6488
6527
  @host_event_handler