bumble 0.0.214__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 (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 +280 -278
  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.215.dist-info}/METADATA +2 -1
  117. bumble-0.0.215.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.215.dist-info}/WHEEL +0 -0
  120. {bumble-0.0.214.dist-info → bumble-0.0.215.dist-info}/entry_points.txt +0 -0
  121. {bumble-0.0.214.dist-info → bumble-0.0.215.dist-info}/licenses/LICENSE +0 -0
  122. {bumble-0.0.214.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
@@ -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,9 +2169,7 @@ 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)
2172
+ def wrapper(self, address: hci.Address, *args, **kwargs):
2195
2173
  for connection in self.connections.values():
2196
2174
  if connection.peer_address == address:
2197
2175
  return function(self, connection, *args, **kwargs)
@@ -2205,8 +2183,6 @@ def with_connection_from_address(function):
2205
2183
  def try_with_connection_from_address(function):
2206
2184
  @functools.wraps(function)
2207
2185
  def wrapper(self, address, *args, **kwargs):
2208
- if connection := self.pending_connections.get(address, False):
2209
- return function(self, connection, address, *args, **kwargs)
2210
2186
  for connection in self.connections.values():
2211
2187
  if connection.peer_address == address:
2212
2188
  return function(self, connection, address, *args, **kwargs)
@@ -2258,7 +2234,7 @@ class Device(utils.CompositeEventEmitter):
2258
2234
  scan_response_data: bytes
2259
2235
  cs_capabilities: ChannelSoundingCapabilities | None = None
2260
2236
  connections: dict[int, Connection]
2261
- pending_connections: dict[hci.Address, Connection]
2237
+ connection_roles: dict[hci.Address, hci.Role]
2262
2238
  classic_pending_accepts: dict[
2263
2239
  hci.Address,
2264
2240
  list[asyncio.Future[Union[Connection, tuple[hci.Address, int, int]]]],
@@ -2380,7 +2356,9 @@ class Device(utils.CompositeEventEmitter):
2380
2356
  self.le_connecting = False
2381
2357
  self.disconnecting = False
2382
2358
  self.connections = {} # Connections, by connection handle
2383
- 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)
2384
2362
  self.sco_links = {} # ScoLinks, by connection handle (BR/EDR only)
2385
2363
  self.cis_links = {} # CisLinks, by connection handle (LE only)
2386
2364
  self._pending_cis = {} # (CIS_ID, CIG_ID), by CIS_handle
@@ -2608,36 +2586,6 @@ class Device(utils.CompositeEventEmitter):
2608
2586
  None,
2609
2587
  )
2610
2588
 
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
2589
  @overload
2642
2590
  async def create_l2cap_channel(
2643
2591
  self,
@@ -2705,7 +2653,7 @@ class Device(utils.CompositeEventEmitter):
2705
2653
  def send_l2cap_pdu(self, connection_handle: int, cid: int, pdu: bytes) -> None:
2706
2654
  self.host.send_l2cap_pdu(connection_handle, cid, pdu)
2707
2655
 
2708
- async def send_command(self, command, check_result=False):
2656
+ async def send_command(self, command: hci.HCI_Command, check_result: bool = False):
2709
2657
  try:
2710
2658
  return await asyncio.wait_for(
2711
2659
  self.host.send_command(command, check_result), self.command_timeout
@@ -2962,13 +2910,13 @@ class Device(utils.CompositeEventEmitter):
2962
2910
  def supports_le_features(self, feature: hci.LeFeatureMask) -> bool:
2963
2911
  return self.host.supports_le_features(feature)
2964
2912
 
2965
- def supports_le_phy(self, phy: int) -> bool:
2966
- if phy == hci.HCI_LE_1M_PHY:
2913
+ def supports_le_phy(self, phy: hci.Phy) -> bool:
2914
+ if phy == hci.Phy.LE_1M:
2967
2915
  return True
2968
2916
 
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,
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,
2972
2920
  }
2973
2921
  if phy not in feature_map:
2974
2922
  raise InvalidArgumentError('invalid PHY')
@@ -3245,8 +3193,8 @@ class Device(utils.CompositeEventEmitter):
3245
3193
  else 0
3246
3194
  )
3247
3195
  await advertising_set.start(duration=duration)
3248
- except Exception as error:
3249
- logger.exception(f'failed to start advertising set: {error}')
3196
+ except Exception:
3197
+ logger.exception('failed to start advertising set')
3250
3198
  await advertising_set.remove()
3251
3199
  raise
3252
3200
 
@@ -3572,7 +3520,9 @@ class Device(utils.CompositeEventEmitter):
3572
3520
  self.discovering = False
3573
3521
 
3574
3522
  @host_event_handler
3575
- 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
+ ):
3576
3526
  self.emit(
3577
3527
  self.EVENT_INQUIRY_RESULT,
3578
3528
  address,
@@ -3581,7 +3531,9 @@ class Device(utils.CompositeEventEmitter):
3581
3531
  rssi,
3582
3532
  )
3583
3533
 
3584
- 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
+ ):
3585
3537
  if inquiry_scan_enabled and page_scan_enabled:
3586
3538
  scan_enable = 0x03
3587
3539
  elif page_scan_enabled:
@@ -3601,14 +3553,7 @@ class Device(utils.CompositeEventEmitter):
3601
3553
  # Synthesize an inquiry response if none is set already
3602
3554
  if self.inquiry_response is None:
3603
3555
  self.inquiry_response = bytes(
3604
- AdvertisingData(
3605
- [
3606
- (
3607
- AdvertisingData.COMPLETE_LOCAL_NAME,
3608
- bytes(self.name, 'utf-8'),
3609
- )
3610
- ]
3611
- )
3556
+ AdvertisingData([data_types.CompleteLocalName(self.name)])
3612
3557
  )
3613
3558
 
3614
3559
  # Update the controller
@@ -3714,6 +3659,7 @@ class Device(utils.CompositeEventEmitter):
3714
3659
  # If the address is not parsable, assume it is a name instead
3715
3660
  always_resolve = False
3716
3661
  logger.debug('looking for peer by name')
3662
+ assert isinstance(peer_address, str)
3717
3663
  peer_address = await self.find_peer_by_name(
3718
3664
  peer_address, transport
3719
3665
  ) # TODO: timeout
@@ -3741,7 +3687,7 @@ class Device(utils.CompositeEventEmitter):
3741
3687
  ):
3742
3688
  pending_connection.set_result(connection)
3743
3689
 
3744
- def on_connection_failure(error):
3690
+ def on_connection_failure(error: core.ConnectionError):
3745
3691
  if transport == PhysicalTransport.LE or (
3746
3692
  # match BR/EDR connection failure event against peer address
3747
3693
  error.transport == transport
@@ -3881,9 +3827,7 @@ class Device(utils.CompositeEventEmitter):
3881
3827
  )
3882
3828
  else:
3883
3829
  # Save pending connection
3884
- self.pending_connections[peer_address] = Connection.incomplete(
3885
- self, peer_address, hci.Role.CENTRAL
3886
- )
3830
+ self.connection_roles[peer_address] = hci.Role.CENTRAL
3887
3831
 
3888
3832
  # TODO: allow passing other settings
3889
3833
  result = await self.send_command(
@@ -3936,7 +3880,7 @@ class Device(utils.CompositeEventEmitter):
3936
3880
  self.le_connecting = False
3937
3881
  self.connect_own_address_type = None
3938
3882
  else:
3939
- self.pending_connections.pop(peer_address, None)
3883
+ self.connection_roles.pop(peer_address, None)
3940
3884
 
3941
3885
  async def accept(
3942
3886
  self,
@@ -3961,6 +3905,7 @@ class Device(utils.CompositeEventEmitter):
3961
3905
  except InvalidArgumentError:
3962
3906
  # If the address is not parsable, assume it is a name instead
3963
3907
  logger.debug('looking for peer by name')
3908
+ assert isinstance(peer_address, str)
3964
3909
  peer_address = await self.find_peer_by_name(
3965
3910
  peer_address, PhysicalTransport.BR_EDR
3966
3911
  ) # TODO: timeout
@@ -4019,7 +3964,7 @@ class Device(utils.CompositeEventEmitter):
4019
3964
  ):
4020
3965
  pending_connection.set_result(connection)
4021
3966
 
4022
- def on_connection_failure(error):
3967
+ def on_connection_failure(error: core.ConnectionError):
4023
3968
  if (
4024
3969
  error.transport == PhysicalTransport.BR_EDR
4025
3970
  and error.peer_address == peer_address
@@ -4029,13 +3974,11 @@ class Device(utils.CompositeEventEmitter):
4029
3974
  self.on(self.EVENT_CONNECTION, on_connection)
4030
3975
  self.on(self.EVENT_CONNECTION_FAILURE, on_connection_failure)
4031
3976
 
4032
- # Save pending connection, with the Peripheral hci.role.
3977
+ # Save Peripheral hci.role.
4033
3978
  # Even if we requested a role switch in the hci.HCI_Accept_Connection_Request
4034
3979
  # command, this connection is still considered Peripheral until an eventual
4035
3980
  # role change event.
4036
- self.pending_connections[peer_address] = Connection.incomplete(
4037
- self, peer_address, hci.Role.PERIPHERAL
4038
- )
3981
+ self.connection_roles[peer_address] = hci.Role.PERIPHERAL
4039
3982
 
4040
3983
  try:
4041
3984
  # Accept connection request
@@ -4053,10 +3996,10 @@ class Device(utils.CompositeEventEmitter):
4053
3996
  finally:
4054
3997
  self.remove_listener(self.EVENT_CONNECTION, on_connection)
4055
3998
  self.remove_listener(self.EVENT_CONNECTION_FAILURE, on_connection_failure)
4056
- self.pending_connections.pop(peer_address, None)
3999
+ self.connection_roles.pop(peer_address, None)
4057
4000
 
4058
4001
  @asynccontextmanager
4059
- async def connect_as_gatt(self, peer_address):
4002
+ async def connect_as_gatt(self, peer_address: Union[hci.Address, str]):
4060
4003
  async with AsyncExitStack() as stack:
4061
4004
  connection = await stack.enter_async_context(
4062
4005
  await self.connect(peer_address)
@@ -4092,6 +4035,7 @@ class Device(utils.CompositeEventEmitter):
4092
4035
  except InvalidArgumentError:
4093
4036
  # If the address is not parsable, assume it is a name instead
4094
4037
  logger.debug('looking for peer by name')
4038
+ assert isinstance(peer_address, str)
4095
4039
  peer_address = await self.find_peer_by_name(
4096
4040
  peer_address, PhysicalTransport.BR_EDR
4097
4041
  ) # TODO: timeout
@@ -4137,7 +4081,9 @@ class Device(utils.CompositeEventEmitter):
4137
4081
  )
4138
4082
  self.disconnecting = False
4139
4083
 
4140
- 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:
4141
4087
  if tx_octets < 0x001B or tx_octets > 0x00FB:
4142
4088
  raise InvalidArgumentError('tx_octets must be between 0x001B and 0x00FB')
4143
4089
 
@@ -4240,7 +4186,11 @@ class Device(utils.CompositeEventEmitter):
4240
4186
  )
4241
4187
 
4242
4188
  async def set_connection_phy(
4243
- 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,
4244
4194
  ):
4245
4195
  if not self.host.supports_command(hci.HCI_LE_SET_PHY_COMMAND):
4246
4196
  logger.warning('ignoring request, command not supported')
@@ -4256,7 +4206,7 @@ class Device(utils.CompositeEventEmitter):
4256
4206
  all_phys=all_phys_bits,
4257
4207
  tx_phys=hci.phy_list_to_bits(tx_phys),
4258
4208
  rx_phys=hci.phy_list_to_bits(rx_phys),
4259
- phy_options=0 if phy_options is None else int(phy_options),
4209
+ phy_options=phy_options,
4260
4210
  )
4261
4211
  )
4262
4212
 
@@ -4267,7 +4217,11 @@ class Device(utils.CompositeEventEmitter):
4267
4217
  )
4268
4218
  raise hci.HCI_StatusError(result)
4269
4219
 
4270
- 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
+ ):
4271
4225
  all_phys_bits = (1 if tx_phys is None else 0) | (
4272
4226
  (1 if rx_phys is None else 0) << 1
4273
4227
  )
@@ -4305,7 +4259,7 @@ class Device(utils.CompositeEventEmitter):
4305
4259
  check_result=True,
4306
4260
  )
4307
4261
 
4308
- async def find_peer_by_name(self, name, transport=PhysicalTransport.LE):
4262
+ async def find_peer_by_name(self, name: str, transport=PhysicalTransport.LE):
4309
4263
  """
4310
4264
  Scan for a peer with a given name and return its address.
4311
4265
  """
@@ -4320,7 +4274,7 @@ class Device(utils.CompositeEventEmitter):
4320
4274
  if local_name == name:
4321
4275
  peer_address.set_result(address)
4322
4276
 
4323
- listener = None
4277
+ listener: Optional[Callable[..., None]] = None
4324
4278
  was_scanning = self.scanning
4325
4279
  was_discovering = self.discovering
4326
4280
  try:
@@ -4426,10 +4380,10 @@ class Device(utils.CompositeEventEmitter):
4426
4380
  def smp_session_proxy(self, session_proxy: type[smp.Session]) -> None:
4427
4381
  self.smp_manager.session_proxy = session_proxy
4428
4382
 
4429
- async def pair(self, connection):
4383
+ async def pair(self, connection: Connection):
4430
4384
  return await self.smp_manager.pair(connection)
4431
4385
 
4432
- def request_pairing(self, connection):
4386
+ def request_pairing(self, connection: Connection):
4433
4387
  return self.smp_manager.request_pairing(connection)
4434
4388
 
4435
4389
  async def get_long_term_key(
@@ -4517,7 +4471,7 @@ class Device(utils.CompositeEventEmitter):
4517
4471
  on_authentication_failure,
4518
4472
  )
4519
4473
 
4520
- async def encrypt(self, connection, enable=True):
4474
+ async def encrypt(self, connection: Connection, enable: bool = True):
4521
4475
  if not enable and connection.transport == PhysicalTransport.LE:
4522
4476
  raise InvalidArgumentError('`enable` parameter is classic only.')
4523
4477
 
@@ -4527,7 +4481,7 @@ class Device(utils.CompositeEventEmitter):
4527
4481
  def on_encryption_change():
4528
4482
  pending_encryption.set_result(None)
4529
4483
 
4530
- def on_encryption_failure(error_code):
4484
+ def on_encryption_failure(error_code: int):
4531
4485
  pending_encryption.set_exception(hci.HCI_Error(error_code))
4532
4486
 
4533
4487
  connection.on(
@@ -4610,8 +4564,8 @@ class Device(utils.CompositeEventEmitter):
4610
4564
  try:
4611
4565
  await self.keystore.update(address, keys)
4612
4566
  await self.refresh_resolving_list()
4613
- except Exception as error:
4614
- logger.warning(f'!!! error while storing keys: {error}')
4567
+ except Exception:
4568
+ logger.exception('!!! error while storing keys')
4615
4569
  else:
4616
4570
  self.emit(self.EVENT_KEY_STORE_UPDATE)
4617
4571
 
@@ -4619,10 +4573,10 @@ class Device(utils.CompositeEventEmitter):
4619
4573
  async def switch_role(self, connection: Connection, role: hci.Role):
4620
4574
  pending_role_change = asyncio.get_running_loop().create_future()
4621
4575
 
4622
- def on_role_change(new_role):
4576
+ def on_role_change(new_role: hci.Role):
4623
4577
  pending_role_change.set_result(new_role)
4624
4578
 
4625
- def on_role_change_failure(error_code):
4579
+ def on_role_change_failure(error_code: int):
4626
4580
  pending_role_change.set_exception(hci.HCI_Error(error_code))
4627
4581
 
4628
4582
  connection.on(connection.EVENT_ROLE_CHANGE, on_role_change)
@@ -5212,10 +5166,10 @@ class Device(utils.CompositeEventEmitter):
5212
5166
  ):
5213
5167
  connection.emit(connection.EVENT_LINK_KEY)
5214
5168
 
5215
- def add_service(self, service):
5169
+ def add_service(self, service: gatt.Service):
5216
5170
  self.gatt_server.add_service(service)
5217
5171
 
5218
- def add_services(self, services):
5172
+ def add_services(self, services: Iterable[gatt.Service]):
5219
5173
  self.gatt_server.add_services(services)
5220
5174
 
5221
5175
  def add_default_services(
@@ -5311,10 +5265,10 @@ class Device(utils.CompositeEventEmitter):
5311
5265
  @host_event_handler
5312
5266
  def on_advertising_set_termination(
5313
5267
  self,
5314
- status,
5315
- advertising_handle,
5316
- connection_handle,
5317
- 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,
5318
5272
  ):
5319
5273
  # Legacy advertising set is also one of extended advertising sets.
5320
5274
  if not (
@@ -5482,15 +5436,49 @@ class Device(utils.CompositeEventEmitter):
5482
5436
  self.emit(self.EVENT_CONNECTION, connection)
5483
5437
 
5484
5438
  @host_event_handler
5485
- 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(
5486
5473
  self,
5487
5474
  connection_handle: int,
5488
- transport: core.PhysicalTransport,
5489
5475
  peer_address: hci.Address,
5490
5476
  self_resolvable_address: Optional[hci.Address],
5491
5477
  peer_resolvable_address: Optional[hci.Address],
5492
5478
  role: hci.Role,
5493
- connection_parameters: Optional[core.ConnectionParameters],
5479
+ connection_interval: int,
5480
+ peripheral_latency: int,
5481
+ supervision_timeout: int,
5494
5482
  ) -> None:
5495
5483
  # Convert all-zeros addresses into None.
5496
5484
  if self_resolvable_address == hci.Address.ANY_RANDOM:
@@ -5510,19 +5498,6 @@ class Device(utils.CompositeEventEmitter):
5510
5498
  'new connection reuses the same handle as a previous connection'
5511
5499
  )
5512
5500
 
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
5501
  if peer_resolvable_address is None:
5527
5502
  # Resolve the peer address if we can
5528
5503
  if self.address_resolver:
@@ -5572,16 +5547,16 @@ class Device(utils.CompositeEventEmitter):
5572
5547
  connection = Connection(
5573
5548
  self,
5574
5549
  connection_handle,
5575
- transport,
5550
+ PhysicalTransport.LE,
5576
5551
  self_address,
5577
5552
  self_resolvable_address,
5578
5553
  peer_address,
5579
5554
  peer_resolvable_address,
5580
5555
  role,
5581
5556
  Connection.Parameters(
5582
- connection_parameters.connection_interval * 1.25,
5583
- connection_parameters.peripheral_latency,
5584
- connection_parameters.supervision_timeout * 10.0,
5557
+ connection_interval * 1.25,
5558
+ peripheral_latency,
5559
+ supervision_timeout * 10.0,
5585
5560
  ),
5586
5561
  )
5587
5562
  self.connections[connection_handle] = connection
@@ -5613,7 +5588,12 @@ class Device(utils.CompositeEventEmitter):
5613
5588
  )
5614
5589
 
5615
5590
  @host_event_handler
5616
- 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
+ ):
5617
5597
  logger.debug(
5618
5598
  f'*** Connection failed: {hci.HCI_Constant.error_name(error_code)}'
5619
5599
  )
@@ -5667,9 +5647,7 @@ class Device(utils.CompositeEventEmitter):
5667
5647
  # device configuration is set to accept any incoming connection
5668
5648
  elif self.classic_accept_any:
5669
5649
  # Save pending connection
5670
- self.pending_connections[bd_addr] = Connection.incomplete(
5671
- self, bd_addr, hci.Role.PERIPHERAL
5672
- )
5650
+ self.connection_roles[bd_addr] = hci.Role.PERIPHERAL
5673
5651
 
5674
5652
  self.host.send_command_sync(
5675
5653
  hci.HCI_Accept_Connection_Request_Command(
@@ -5732,7 +5710,7 @@ class Device(utils.CompositeEventEmitter):
5732
5710
 
5733
5711
  @host_event_handler
5734
5712
  @with_connection_from_handle
5735
- def on_connection_authentication(self, connection):
5713
+ def on_connection_authentication(self, connection: Connection):
5736
5714
  logger.debug(
5737
5715
  f'*** Connection Authentication: [0x{connection.handle:04X}] '
5738
5716
  f'{connection.peer_address} as {connection.role_name}'
@@ -5742,7 +5720,9 @@ class Device(utils.CompositeEventEmitter):
5742
5720
 
5743
5721
  @host_event_handler
5744
5722
  @with_connection_from_handle
5745
- def on_connection_authentication_failure(self, connection, error):
5723
+ def on_connection_authentication_failure(
5724
+ self, connection: Connection, error: core.ConnectionError
5725
+ ):
5746
5726
  logger.debug(
5747
5727
  f'*** Connection Authentication Failure: [0x{connection.handle:04X}] '
5748
5728
  f'{connection.peer_address} as {connection.role_name}, error={error}'
@@ -5784,10 +5764,13 @@ class Device(utils.CompositeEventEmitter):
5784
5764
  @host_event_handler
5785
5765
  @with_connection_from_address
5786
5766
  def on_authentication_io_capability_response(
5787
- self, connection, io_capability, authentication_requirements
5767
+ self,
5768
+ connection: Connection,
5769
+ io_capability: int,
5770
+ authentication_requirements: int,
5788
5771
  ):
5789
- connection.peer_pairing_io_capability = io_capability
5790
- connection.peer_pairing_authentication_requirements = (
5772
+ connection.pairing_peer_io_capability = io_capability
5773
+ connection.pairing_peer_authentication_requirements = (
5791
5774
  authentication_requirements
5792
5775
  )
5793
5776
 
@@ -5798,7 +5781,7 @@ class Device(utils.CompositeEventEmitter):
5798
5781
  # Ask what the pairing config should be for this connection
5799
5782
  pairing_config = self.pairing_config_factory(connection)
5800
5783
  io_capability = pairing_config.delegate.classic_io_capability
5801
- peer_io_capability = connection.peer_pairing_io_capability
5784
+ peer_io_capability = connection.pairing_peer_io_capability
5802
5785
 
5803
5786
  async def confirm() -> bool:
5804
5787
  # Ask the user to confirm the pairing, without display
@@ -5859,8 +5842,8 @@ class Device(utils.CompositeEventEmitter):
5859
5842
  )
5860
5843
  )
5861
5844
  return
5862
- except Exception as error:
5863
- logger.warning(f'exception while confirming: {error}')
5845
+ except Exception:
5846
+ logger.exception('exception while confirming')
5864
5847
 
5865
5848
  await self.host.send_command(
5866
5849
  hci.HCI_User_Confirmation_Request_Negative_Reply_Command(
@@ -5873,7 +5856,7 @@ class Device(utils.CompositeEventEmitter):
5873
5856
  # [Classic only]
5874
5857
  @host_event_handler
5875
5858
  @with_connection_from_address
5876
- def on_authentication_user_passkey_request(self, connection) -> None:
5859
+ def on_authentication_user_passkey_request(self, connection: Connection) -> None:
5877
5860
  # Ask what the pairing config should be for this connection
5878
5861
  pairing_config = self.pairing_config_factory(connection)
5879
5862
 
@@ -5889,8 +5872,8 @@ class Device(utils.CompositeEventEmitter):
5889
5872
  )
5890
5873
  )
5891
5874
  return
5892
- except Exception as error:
5893
- logger.warning(f'exception while asking for pass-key: {error}')
5875
+ except Exception:
5876
+ logger.exception('exception while asking for pass-key')
5894
5877
 
5895
5878
  await self.host.send_command(
5896
5879
  hci.HCI_User_Passkey_Request_Negative_Reply_Command(
@@ -5916,7 +5899,7 @@ class Device(utils.CompositeEventEmitter):
5916
5899
  # [Classic only]
5917
5900
  @host_event_handler
5918
5901
  @with_connection_from_address
5919
- def on_pin_code_request(self, connection):
5902
+ def on_pin_code_request(self, connection: Connection):
5920
5903
  # Classic legacy pairing
5921
5904
  # Ask what the pairing config should be for this connection
5922
5905
  pairing_config = self.pairing_config_factory(connection)
@@ -5960,7 +5943,9 @@ class Device(utils.CompositeEventEmitter):
5960
5943
  # [Classic only]
5961
5944
  @host_event_handler
5962
5945
  @with_connection_from_address
5963
- def on_authentication_user_passkey_notification(self, connection, passkey):
5946
+ def on_authentication_user_passkey_notification(
5947
+ self, connection: Connection, passkey: int
5948
+ ):
5964
5949
  # Ask what the pairing config should be for this connection
5965
5950
  pairing_config = self.pairing_config_factory(connection)
5966
5951
 
@@ -5972,14 +5957,15 @@ class Device(utils.CompositeEventEmitter):
5972
5957
  # [Classic only]
5973
5958
  @host_event_handler
5974
5959
  @try_with_connection_from_address
5975
- 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
+ ):
5976
5963
  # Try to decode the name
5977
5964
  try:
5978
- remote_name = remote_name.decode('utf-8')
5979
5965
  if connection:
5980
- connection.peer_name = remote_name
5966
+ connection.peer_name = remote_name.decode('utf-8')
5981
5967
  connection.emit(connection.EVENT_REMOTE_NAME)
5982
- self.emit(self.EVENT_REMOTE_NAME, address, remote_name)
5968
+ self.emit(self.EVENT_REMOTE_NAME, address, remote_name.decode('utf-8'))
5983
5969
  except UnicodeDecodeError as error:
5984
5970
  logger.warning('peer name is not valid UTF-8')
5985
5971
  if connection:
@@ -5990,7 +5976,9 @@ class Device(utils.CompositeEventEmitter):
5990
5976
  # [Classic only]
5991
5977
  @host_event_handler
5992
5978
  @try_with_connection_from_address
5993
- 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
+ ):
5994
5982
  if connection:
5995
5983
  connection.emit(connection.EVENT_REMOTE_NAME_FAILURE, error)
5996
5984
  self.emit(self.EVENT_REMOTE_NAME_FAILURE, address, error)
@@ -6191,7 +6179,7 @@ class Device(utils.CompositeEventEmitter):
6191
6179
 
6192
6180
  @host_event_handler
6193
6181
  @with_connection_from_handle
6194
- def on_connection_encryption_key_refresh(self, connection):
6182
+ def on_connection_encryption_key_refresh(self, connection: Connection):
6195
6183
  logger.debug(
6196
6184
  f'*** Connection Key Refresh: [0x{connection.handle:04X}] '
6197
6185
  f'{connection.peer_address} as {connection.role_name}'
@@ -6201,27 +6189,27 @@ class Device(utils.CompositeEventEmitter):
6201
6189
  @host_event_handler
6202
6190
  @with_connection_from_handle
6203
6191
  def on_connection_parameters_update(
6204
- self, connection: Connection, connection_parameters: core.ConnectionParameters
6192
+ self,
6193
+ connection: Connection,
6194
+ connection_interval: int,
6195
+ peripheral_latency: int,
6196
+ supervision_timeout: int,
6205
6197
  ):
6206
6198
  logger.debug(
6207
6199
  f'*** Connection Parameters Update: [0x{connection.handle:04X}] '
6208
6200
  f'{connection.peer_address} as {connection.role_name}, '
6209
- f'{connection_parameters}'
6210
6201
  )
6211
- if (
6212
- connection.parameters.connection_interval
6213
- != connection_parameters.connection_interval * 1.25
6214
- ):
6202
+ if connection.parameters.connection_interval != connection_interval * 1.25:
6215
6203
  connection.parameters = Connection.Parameters(
6216
- connection_parameters.connection_interval * 1.25,
6217
- connection_parameters.peripheral_latency,
6218
- connection_parameters.supervision_timeout * 10.0,
6204
+ connection_interval * 1.25,
6205
+ peripheral_latency,
6206
+ supervision_timeout * 10.0,
6219
6207
  )
6220
6208
  else:
6221
6209
  connection.parameters = Connection.Parameters(
6222
- connection_parameters.connection_interval * 1.25,
6223
- connection_parameters.peripheral_latency,
6224
- connection_parameters.supervision_timeout * 10.0,
6210
+ connection_interval * 1.25,
6211
+ peripheral_latency,
6212
+ supervision_timeout * 10.0,
6225
6213
  connection.parameters.subrate_factor,
6226
6214
  connection.parameters.continuation_number,
6227
6215
  )
@@ -6229,7 +6217,9 @@ class Device(utils.CompositeEventEmitter):
6229
6217
 
6230
6218
  @host_event_handler
6231
6219
  @with_connection_from_handle
6232
- def on_connection_parameters_update_failure(self, connection, error):
6220
+ def on_connection_parameters_update_failure(
6221
+ self, connection: Connection, error: int
6222
+ ):
6233
6223
  logger.debug(
6234
6224
  f'*** Connection Parameters Update Failed: [0x{connection.handle:04X}] '
6235
6225
  f'{connection.peer_address} as {connection.role_name}, '
@@ -6239,7 +6229,7 @@ class Device(utils.CompositeEventEmitter):
6239
6229
 
6240
6230
  @host_event_handler
6241
6231
  @with_connection_from_handle
6242
- def on_connection_phy_update(self, connection, phy):
6232
+ def on_connection_phy_update(self, connection: Connection, phy: core.ConnectionPHY):
6243
6233
  logger.debug(
6244
6234
  f'*** Connection PHY Update: [0x{connection.handle:04X}] '
6245
6235
  f'{connection.peer_address} as {connection.role_name}, '
@@ -6249,7 +6239,7 @@ class Device(utils.CompositeEventEmitter):
6249
6239
 
6250
6240
  @host_event_handler
6251
6241
  @with_connection_from_handle
6252
- def on_connection_phy_update_failure(self, connection, error):
6242
+ def on_connection_phy_update_failure(self, connection: Connection, error: int):
6253
6243
  logger.debug(
6254
6244
  f'*** Connection PHY Update Failed: [0x{connection.handle:04X}] '
6255
6245
  f'{connection.peer_address} as {connection.role_name}, '
@@ -6278,7 +6268,7 @@ class Device(utils.CompositeEventEmitter):
6278
6268
 
6279
6269
  @host_event_handler
6280
6270
  @with_connection_from_handle
6281
- def on_connection_att_mtu_update(self, connection, att_mtu):
6271
+ def on_connection_att_mtu_update(self, connection: Connection, att_mtu: int):
6282
6272
  logger.debug(
6283
6273
  f'*** Connection ATT MTU Update: [0x{connection.handle:04X}] '
6284
6274
  f'{connection.peer_address} as {connection.role_name}, '
@@ -6290,7 +6280,12 @@ class Device(utils.CompositeEventEmitter):
6290
6280
  @host_event_handler
6291
6281
  @with_connection_from_handle
6292
6282
  def on_connection_data_length_change(
6293
- 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,
6294
6289
  ):
6295
6290
  logger.debug(
6296
6291
  f'*** Connection Data Length Change: [0x{connection.handle:04X}] '
@@ -6414,15 +6409,22 @@ class Device(utils.CompositeEventEmitter):
6414
6409
 
6415
6410
  # [Classic only]
6416
6411
  @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)
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
6421
6421
 
6422
6422
  # [Classic only]
6423
6423
  @host_event_handler
6424
6424
  @try_with_connection_from_address
6425
- 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
+ ):
6426
6428
  if connection:
6427
6429
  connection.emit(connection.EVENT_ROLE_CHANGE_FAILURE, error)
6428
6430
  self.emit(self.EVENT_ROLE_CHANGE_FAILURE, address, error)
@@ -6436,7 +6438,7 @@ class Device(utils.CompositeEventEmitter):
6436
6438
  # [Classic only]
6437
6439
  @host_event_handler
6438
6440
  @with_connection_from_address
6439
- def on_classic_pairing_failure(self, connection: Connection, status) -> None:
6441
+ def on_classic_pairing_failure(self, connection: Connection, status: int) -> None:
6440
6442
  connection.emit(connection.EVENT_CLASSIC_PAIRING_FAILURE, status)
6441
6443
 
6442
6444
  def on_pairing_start(self, connection: Connection) -> None:
@@ -6460,7 +6462,7 @@ class Device(utils.CompositeEventEmitter):
6460
6462
  connection.emit(connection.EVENT_PAIRING_FAILURE, reason)
6461
6463
 
6462
6464
  @with_connection_from_handle
6463
- def on_gatt_pdu(self, connection, pdu):
6465
+ def on_gatt_pdu(self, connection: Connection, pdu: bytes):
6464
6466
  # Parse the L2CAP payload into an ATT PDU object
6465
6467
  att_pdu = ATT_PDU.from_bytes(pdu)
6466
6468
 
@@ -6482,7 +6484,7 @@ class Device(utils.CompositeEventEmitter):
6482
6484
  connection.gatt_server.on_gatt_pdu(connection, att_pdu)
6483
6485
 
6484
6486
  @with_connection_from_handle
6485
- def on_smp_pdu(self, connection, pdu):
6487
+ def on_smp_pdu(self, connection: Connection, pdu: bytes):
6486
6488
  self.smp_manager.on_smp_pdu(connection, pdu)
6487
6489
 
6488
6490
  @host_event_handler