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