bumble 0.0.209__py3-none-any.whl → 0.0.210__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bumble/_version.py +2 -2
- bumble/a2dp.py +7 -7
- bumble/apps/auracast.py +37 -29
- bumble/apps/bench.py +9 -7
- bumble/apps/console.py +1 -1
- bumble/apps/lea_unicast/app.py +6 -2
- bumble/apps/pair.py +4 -3
- bumble/apps/player/player.py +3 -3
- bumble/apps/rfcomm_bridge.py +1 -1
- bumble/apps/speaker/speaker.py +4 -2
- bumble/att.py +2 -3
- bumble/avc.py +5 -5
- bumble/avdtp.py +9 -10
- bumble/avrcp.py +18 -19
- bumble/bridge.py +2 -2
- bumble/controller.py +6 -7
- bumble/core.py +56 -56
- bumble/device.py +169 -137
- bumble/drivers/__init__.py +2 -2
- bumble/gap.py +1 -1
- bumble/gatt_adapters.py +3 -3
- bumble/gatt_client.py +27 -21
- bumble/gatt_server.py +9 -10
- bumble/hci.py +34 -20
- bumble/hfp.py +3 -3
- bumble/hid.py +4 -3
- bumble/host.py +22 -16
- bumble/keys.py +3 -3
- bumble/l2cap.py +19 -17
- bumble/link.py +3 -4
- bumble/pairing.py +3 -3
- bumble/pandora/__init__.py +5 -5
- bumble/pandora/host.py +18 -12
- bumble/pandora/l2cap.py +2 -2
- bumble/pandora/security.py +15 -16
- bumble/profiles/aics.py +6 -6
- bumble/profiles/ancs.py +9 -10
- bumble/profiles/ascs.py +17 -10
- bumble/profiles/asha.py +5 -5
- bumble/profiles/bass.py +1 -1
- bumble/profiles/csip.py +10 -10
- bumble/profiles/gatt_service.py +12 -12
- bumble/profiles/hap.py +16 -16
- bumble/profiles/mcp.py +26 -24
- bumble/profiles/pacs.py +6 -6
- bumble/profiles/pbp.py +1 -1
- bumble/profiles/vcs.py +6 -4
- bumble/profiles/vocs.py +3 -3
- bumble/rfcomm.py +8 -8
- bumble/sdp.py +1 -1
- bumble/smp.py +36 -30
- bumble/transport/__init__.py +24 -19
- bumble/transport/android_emulator.py +8 -4
- bumble/transport/android_netsim.py +8 -5
- bumble/transport/common.py +5 -1
- bumble/transport/file.py +1 -1
- bumble/transport/hci_socket.py +1 -1
- bumble/transport/pty.py +1 -1
- bumble/transport/pyusb.py +3 -3
- bumble/transport/serial.py +1 -1
- bumble/transport/tcp_client.py +1 -1
- bumble/transport/tcp_server.py +1 -1
- bumble/transport/udp.py +1 -1
- bumble/transport/unix.py +1 -1
- bumble/transport/vhci.py +2 -2
- bumble/transport/ws_client.py +6 -1
- bumble/transport/ws_server.py +1 -1
- bumble/utils.py +89 -76
- {bumble-0.0.209.dist-info → bumble-0.0.210.dist-info}/METADATA +3 -2
- {bumble-0.0.209.dist-info → bumble-0.0.210.dist-info}/RECORD +74 -74
- {bumble-0.0.209.dist-info → bumble-0.0.210.dist-info}/WHEEL +1 -1
- {bumble-0.0.209.dist-info → bumble-0.0.210.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.209.dist-info → bumble-0.0.210.dist-info/licenses}/LICENSE +0 -0
- {bumble-0.0.209.dist-info → bumble-0.0.210.dist-info}/top_level.txt +0 -0
bumble/device.py
CHANGED
|
@@ -49,16 +49,14 @@ from typing import (
|
|
|
49
49
|
)
|
|
50
50
|
from typing_extensions import Self
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
from .
|
|
55
|
-
from .
|
|
56
|
-
from .
|
|
57
|
-
from .
|
|
58
|
-
from .
|
|
59
|
-
|
|
60
|
-
BT_BR_EDR_TRANSPORT,
|
|
61
|
-
BT_LE_TRANSPORT,
|
|
52
|
+
|
|
53
|
+
from bumble.colors import color
|
|
54
|
+
from bumble.att import ATT_CID, ATT_DEFAULT_MTU, ATT_PDU
|
|
55
|
+
from bumble.gatt import Attribute, Characteristic, Descriptor, Service
|
|
56
|
+
from bumble.host import DataPacketQueue, Host
|
|
57
|
+
from bumble.profiles.gap import GenericAccessService
|
|
58
|
+
from bumble.core import (
|
|
59
|
+
PhysicalTransport,
|
|
62
60
|
AdvertisingData,
|
|
63
61
|
BaseBumbleError,
|
|
64
62
|
ConnectionParameterUpdateError,
|
|
@@ -72,16 +70,8 @@ from .core import (
|
|
|
72
70
|
OutOfResourcesError,
|
|
73
71
|
UnreachableError,
|
|
74
72
|
)
|
|
75
|
-
from
|
|
76
|
-
|
|
77
|
-
CompositeEventEmitter,
|
|
78
|
-
EventWatcher,
|
|
79
|
-
setup_event_forwarding,
|
|
80
|
-
composite_listener,
|
|
81
|
-
deprecated,
|
|
82
|
-
experimental,
|
|
83
|
-
)
|
|
84
|
-
from .keys import (
|
|
73
|
+
from bumble import utils
|
|
74
|
+
from bumble.keys import (
|
|
85
75
|
KeyStore,
|
|
86
76
|
PairingKeys,
|
|
87
77
|
)
|
|
@@ -96,7 +86,7 @@ from bumble import core
|
|
|
96
86
|
from bumble.profiles import gatt_service
|
|
97
87
|
|
|
98
88
|
if TYPE_CHECKING:
|
|
99
|
-
from .transport.common import TransportSource, TransportSink
|
|
89
|
+
from bumble.transport.common import TransportSource, TransportSink
|
|
100
90
|
|
|
101
91
|
|
|
102
92
|
# -----------------------------------------------------------------------------
|
|
@@ -577,7 +567,7 @@ class PeriodicAdvertisingParameters:
|
|
|
577
567
|
|
|
578
568
|
# -----------------------------------------------------------------------------
|
|
579
569
|
@dataclass
|
|
580
|
-
class AdvertisingSet(EventEmitter):
|
|
570
|
+
class AdvertisingSet(utils.EventEmitter):
|
|
581
571
|
device: Device
|
|
582
572
|
advertising_handle: int
|
|
583
573
|
auto_restart: bool
|
|
@@ -794,13 +784,24 @@ class AdvertisingSet(EventEmitter):
|
|
|
794
784
|
)
|
|
795
785
|
del self.device.extended_advertising_sets[self.advertising_handle]
|
|
796
786
|
|
|
787
|
+
async def transfer_periodic_info(
|
|
788
|
+
self, connection: Connection, service_data: int = 0
|
|
789
|
+
) -> None:
|
|
790
|
+
if not self.periodic_enabled:
|
|
791
|
+
raise core.InvalidStateError(
|
|
792
|
+
f"Periodic Advertising is not enabled on Advertising Set 0x{self.advertising_handle:02X}"
|
|
793
|
+
)
|
|
794
|
+
await connection.transfer_periodic_set_info(
|
|
795
|
+
self.advertising_handle, service_data
|
|
796
|
+
)
|
|
797
|
+
|
|
797
798
|
def on_termination(self, status: int) -> None:
|
|
798
799
|
self.enabled = False
|
|
799
800
|
self.emit('termination', status)
|
|
800
801
|
|
|
801
802
|
|
|
802
803
|
# -----------------------------------------------------------------------------
|
|
803
|
-
class PeriodicAdvertisingSync(EventEmitter):
|
|
804
|
+
class PeriodicAdvertisingSync(utils.EventEmitter):
|
|
804
805
|
class State(Enum):
|
|
805
806
|
INIT = 0
|
|
806
807
|
PENDING = 1
|
|
@@ -929,7 +930,7 @@ class PeriodicAdvertisingSync(EventEmitter):
|
|
|
929
930
|
"received established event for cancelled sync, will terminate"
|
|
930
931
|
)
|
|
931
932
|
self.state = self.State.ESTABLISHED
|
|
932
|
-
AsyncRunner.spawn(self.terminate())
|
|
933
|
+
utils.AsyncRunner.spawn(self.terminate())
|
|
933
934
|
return
|
|
934
935
|
|
|
935
936
|
if status == hci.HCI_SUCCESS:
|
|
@@ -1015,7 +1016,7 @@ class BigParameters:
|
|
|
1015
1016
|
|
|
1016
1017
|
# -----------------------------------------------------------------------------
|
|
1017
1018
|
@dataclass
|
|
1018
|
-
class Big(EventEmitter):
|
|
1019
|
+
class Big(utils.EventEmitter):
|
|
1019
1020
|
class State(IntEnum):
|
|
1020
1021
|
PENDING = 0
|
|
1021
1022
|
ACTIVE = 1
|
|
@@ -1055,7 +1056,7 @@ class Big(EventEmitter):
|
|
|
1055
1056
|
logger.error('BIG %d is not active.', self.big_handle)
|
|
1056
1057
|
return
|
|
1057
1058
|
|
|
1058
|
-
with closing(EventWatcher()) as watcher:
|
|
1059
|
+
with closing(utils.EventWatcher()) as watcher:
|
|
1059
1060
|
terminated = asyncio.Event()
|
|
1060
1061
|
watcher.once(self, Big.Event.TERMINATION, lambda _: terminated.set())
|
|
1061
1062
|
await self.device.send_command(
|
|
@@ -1078,7 +1079,7 @@ class BigSyncParameters:
|
|
|
1078
1079
|
|
|
1079
1080
|
# -----------------------------------------------------------------------------
|
|
1080
1081
|
@dataclass
|
|
1081
|
-
class BigSync(EventEmitter):
|
|
1082
|
+
class BigSync(utils.EventEmitter):
|
|
1082
1083
|
class State(IntEnum):
|
|
1083
1084
|
PENDING = 0
|
|
1084
1085
|
ACTIVE = 1
|
|
@@ -1113,7 +1114,7 @@ class BigSync(EventEmitter):
|
|
|
1113
1114
|
logger.error('BIG Sync %d is not active.', self.big_handle)
|
|
1114
1115
|
return
|
|
1115
1116
|
|
|
1116
|
-
with closing(EventWatcher()) as watcher:
|
|
1117
|
+
with closing(utils.EventWatcher()) as watcher:
|
|
1117
1118
|
terminated = asyncio.Event()
|
|
1118
1119
|
watcher.once(self, BigSync.Event.TERMINATION, lambda _: terminated.set())
|
|
1119
1120
|
await self.device.send_command(
|
|
@@ -1243,7 +1244,7 @@ class Peer:
|
|
|
1243
1244
|
self,
|
|
1244
1245
|
uuids: Iterable[Union[core.UUID, str]] = (),
|
|
1245
1246
|
service: Optional[gatt_client.ServiceProxy] = None,
|
|
1246
|
-
) -> list[gatt_client.CharacteristicProxy]:
|
|
1247
|
+
) -> list[gatt_client.CharacteristicProxy[bytes]]:
|
|
1247
1248
|
return await self.gatt_client.discover_characteristics(
|
|
1248
1249
|
uuids=uuids, service=service
|
|
1249
1250
|
)
|
|
@@ -1258,7 +1259,7 @@ class Peer:
|
|
|
1258
1259
|
characteristic, start_handle, end_handle
|
|
1259
1260
|
)
|
|
1260
1261
|
|
|
1261
|
-
async def discover_attributes(self) -> list[gatt_client.AttributeProxy]:
|
|
1262
|
+
async def discover_attributes(self) -> list[gatt_client.AttributeProxy[bytes]]:
|
|
1262
1263
|
return await self.gatt_client.discover_attributes()
|
|
1263
1264
|
|
|
1264
1265
|
async def discover_all(self):
|
|
@@ -1312,7 +1313,7 @@ class Peer:
|
|
|
1312
1313
|
self,
|
|
1313
1314
|
uuid: core.UUID,
|
|
1314
1315
|
service: Optional[Union[gatt_client.ServiceProxy, core.UUID]] = None,
|
|
1315
|
-
) -> list[gatt_client.CharacteristicProxy]:
|
|
1316
|
+
) -> list[gatt_client.CharacteristicProxy[bytes]]:
|
|
1316
1317
|
if isinstance(service, core.UUID):
|
|
1317
1318
|
return list(
|
|
1318
1319
|
itertools.chain(
|
|
@@ -1382,7 +1383,7 @@ ConnectionParametersPreferences.default = ConnectionParametersPreferences()
|
|
|
1382
1383
|
|
|
1383
1384
|
# -----------------------------------------------------------------------------
|
|
1384
1385
|
@dataclass
|
|
1385
|
-
class ScoLink(CompositeEventEmitter):
|
|
1386
|
+
class ScoLink(utils.CompositeEventEmitter):
|
|
1386
1387
|
device: Device
|
|
1387
1388
|
acl_connection: Connection
|
|
1388
1389
|
handle: int
|
|
@@ -1473,7 +1474,7 @@ class _IsoLink:
|
|
|
1473
1474
|
|
|
1474
1475
|
# -----------------------------------------------------------------------------
|
|
1475
1476
|
@dataclass
|
|
1476
|
-
class CisLink(
|
|
1477
|
+
class CisLink(utils.EventEmitter, _IsoLink):
|
|
1477
1478
|
class State(IntEnum):
|
|
1478
1479
|
PENDING = 0
|
|
1479
1480
|
ESTABLISHED = 1
|
|
@@ -1550,7 +1551,7 @@ class IsoPacketStream:
|
|
|
1550
1551
|
|
|
1551
1552
|
|
|
1552
1553
|
# -----------------------------------------------------------------------------
|
|
1553
|
-
class Connection(CompositeEventEmitter):
|
|
1554
|
+
class Connection(utils.CompositeEventEmitter):
|
|
1554
1555
|
device: Device
|
|
1555
1556
|
handle: int
|
|
1556
1557
|
transport: core.PhysicalTransport
|
|
@@ -1570,7 +1571,7 @@ class Connection(CompositeEventEmitter):
|
|
|
1570
1571
|
cs_configs: dict[int, ChannelSoundingConfig] # Config ID to Configuration
|
|
1571
1572
|
cs_procedures: dict[int, ChannelSoundingProcedure] # Config ID to Procedures
|
|
1572
1573
|
|
|
1573
|
-
@composite_listener
|
|
1574
|
+
@utils.composite_listener
|
|
1574
1575
|
class Listener:
|
|
1575
1576
|
def on_disconnection(self, reason):
|
|
1576
1577
|
pass
|
|
@@ -1649,7 +1650,7 @@ class Connection(CompositeEventEmitter):
|
|
|
1649
1650
|
return cls(
|
|
1650
1651
|
device,
|
|
1651
1652
|
None,
|
|
1652
|
-
|
|
1653
|
+
PhysicalTransport.BR_EDR,
|
|
1653
1654
|
device.public_address,
|
|
1654
1655
|
None,
|
|
1655
1656
|
peer_address,
|
|
@@ -1664,7 +1665,7 @@ class Connection(CompositeEventEmitter):
|
|
|
1664
1665
|
Finish an incomplete connection upon completion.
|
|
1665
1666
|
"""
|
|
1666
1667
|
assert self.handle is None
|
|
1667
|
-
assert self.transport ==
|
|
1668
|
+
assert self.transport == PhysicalTransport.BR_EDR
|
|
1668
1669
|
self.handle = handle
|
|
1669
1670
|
self.parameters = parameters
|
|
1670
1671
|
|
|
@@ -1689,7 +1690,7 @@ class Connection(CompositeEventEmitter):
|
|
|
1689
1690
|
def send_l2cap_pdu(self, cid: int, pdu: bytes) -> None:
|
|
1690
1691
|
self.device.send_l2cap_pdu(self.handle, cid, pdu)
|
|
1691
1692
|
|
|
1692
|
-
@deprecated("Please use create_l2cap_channel()")
|
|
1693
|
+
@utils.deprecated("Please use create_l2cap_channel()")
|
|
1693
1694
|
async def open_l2cap_channel(
|
|
1694
1695
|
self,
|
|
1695
1696
|
psm,
|
|
@@ -1743,7 +1744,9 @@ class Connection(CompositeEventEmitter):
|
|
|
1743
1744
|
self.on('disconnection_failure', abort.set_exception)
|
|
1744
1745
|
|
|
1745
1746
|
try:
|
|
1746
|
-
await asyncio.wait_for(
|
|
1747
|
+
await asyncio.wait_for(
|
|
1748
|
+
utils.cancel_on_event(self.device, 'flush', abort), timeout
|
|
1749
|
+
)
|
|
1747
1750
|
finally:
|
|
1748
1751
|
self.remove_listener('disconnection', abort.set_result)
|
|
1749
1752
|
self.remove_listener('disconnection_failure', abort.set_exception)
|
|
@@ -1782,6 +1785,13 @@ class Connection(CompositeEventEmitter):
|
|
|
1782
1785
|
) -> None:
|
|
1783
1786
|
await self.device.transfer_periodic_sync(self, sync_handle, service_data)
|
|
1784
1787
|
|
|
1788
|
+
async def transfer_periodic_set_info(
|
|
1789
|
+
self, advertising_handle: int, service_data: int = 0
|
|
1790
|
+
) -> None:
|
|
1791
|
+
await self.device.transfer_periodic_set_info(
|
|
1792
|
+
self, advertising_handle, service_data
|
|
1793
|
+
)
|
|
1794
|
+
|
|
1785
1795
|
# [Classic only]
|
|
1786
1796
|
async def request_remote_name(self):
|
|
1787
1797
|
return await self.device.request_remote_name(self)
|
|
@@ -1812,7 +1822,7 @@ class Connection(CompositeEventEmitter):
|
|
|
1812
1822
|
raise
|
|
1813
1823
|
|
|
1814
1824
|
def __str__(self):
|
|
1815
|
-
if self.transport ==
|
|
1825
|
+
if self.transport == PhysicalTransport.LE:
|
|
1816
1826
|
return (
|
|
1817
1827
|
f'Connection(transport=LE, handle=0x{self.handle:04X}, '
|
|
1818
1828
|
f'role={self.role_name}, '
|
|
@@ -2020,7 +2030,7 @@ device_host_event_handlers: list[str] = []
|
|
|
2020
2030
|
|
|
2021
2031
|
|
|
2022
2032
|
# -----------------------------------------------------------------------------
|
|
2023
|
-
class Device(CompositeEventEmitter):
|
|
2033
|
+
class Device(utils.CompositeEventEmitter):
|
|
2024
2034
|
# Incomplete list of fields.
|
|
2025
2035
|
random_address: hci.Address # Random private address that may change periodically
|
|
2026
2036
|
public_address: (
|
|
@@ -2052,7 +2062,7 @@ class Device(CompositeEventEmitter):
|
|
|
2052
2062
|
_pending_cis: Dict[int, tuple[int, int]]
|
|
2053
2063
|
gatt_service: gatt_service.GenericAttributeProfileService | None = None
|
|
2054
2064
|
|
|
2055
|
-
@composite_listener
|
|
2065
|
+
@utils.composite_listener
|
|
2056
2066
|
class Listener:
|
|
2057
2067
|
def on_advertisement(self, advertisement):
|
|
2058
2068
|
pass
|
|
@@ -2273,7 +2283,9 @@ class Device(CompositeEventEmitter):
|
|
|
2273
2283
|
self.l2cap_channel_manager.register_fixed_channel(ATT_CID, self.on_gatt_pdu)
|
|
2274
2284
|
|
|
2275
2285
|
# Forward some events
|
|
2276
|
-
setup_event_forwarding(
|
|
2286
|
+
utils.setup_event_forwarding(
|
|
2287
|
+
self.gatt_server, self, 'characteristic_subscription'
|
|
2288
|
+
)
|
|
2277
2289
|
|
|
2278
2290
|
# Set the initial host
|
|
2279
2291
|
if host:
|
|
@@ -2362,11 +2374,11 @@ class Device(CompositeEventEmitter):
|
|
|
2362
2374
|
None,
|
|
2363
2375
|
)
|
|
2364
2376
|
|
|
2365
|
-
@deprecated("Please use create_l2cap_server()")
|
|
2377
|
+
@utils.deprecated("Please use create_l2cap_server()")
|
|
2366
2378
|
def register_l2cap_server(self, psm, server) -> int:
|
|
2367
2379
|
return self.l2cap_channel_manager.register_server(psm, server)
|
|
2368
2380
|
|
|
2369
|
-
@deprecated("Please use create_l2cap_server()")
|
|
2381
|
+
@utils.deprecated("Please use create_l2cap_server()")
|
|
2370
2382
|
def register_l2cap_channel_server(
|
|
2371
2383
|
self,
|
|
2372
2384
|
psm,
|
|
@@ -2379,7 +2391,7 @@ class Device(CompositeEventEmitter):
|
|
|
2379
2391
|
psm, server, max_credits, mtu, mps
|
|
2380
2392
|
)
|
|
2381
2393
|
|
|
2382
|
-
@deprecated("Please use create_l2cap_channel()")
|
|
2394
|
+
@utils.deprecated("Please use create_l2cap_channel()")
|
|
2383
2395
|
async def open_l2cap_channel(
|
|
2384
2396
|
self,
|
|
2385
2397
|
connection,
|
|
@@ -3220,7 +3232,7 @@ class Device(CompositeEventEmitter):
|
|
|
3220
3232
|
advertiser_clock_accuracy,
|
|
3221
3233
|
)
|
|
3222
3234
|
|
|
3223
|
-
AsyncRunner.spawn(self._update_periodic_advertising_syncs())
|
|
3235
|
+
utils.AsyncRunner.spawn(self._update_periodic_advertising_syncs())
|
|
3224
3236
|
|
|
3225
3237
|
return
|
|
3226
3238
|
|
|
@@ -3379,7 +3391,7 @@ class Device(CompositeEventEmitter):
|
|
|
3379
3391
|
async def connect(
|
|
3380
3392
|
self,
|
|
3381
3393
|
peer_address: Union[hci.Address, str],
|
|
3382
|
-
transport: core.PhysicalTransport =
|
|
3394
|
+
transport: core.PhysicalTransport = PhysicalTransport.LE,
|
|
3383
3395
|
connection_parameters_preferences: Optional[
|
|
3384
3396
|
dict[hci.Phy, ConnectionParametersPreferences]
|
|
3385
3397
|
] = None,
|
|
@@ -3429,23 +3441,23 @@ class Device(CompositeEventEmitter):
|
|
|
3429
3441
|
'''
|
|
3430
3442
|
|
|
3431
3443
|
# Check parameters
|
|
3432
|
-
if transport not in (
|
|
3444
|
+
if transport not in (PhysicalTransport.LE, PhysicalTransport.BR_EDR):
|
|
3433
3445
|
raise InvalidArgumentError('invalid transport')
|
|
3434
3446
|
transport = core.PhysicalTransport(transport)
|
|
3435
3447
|
|
|
3436
3448
|
# Adjust the transport automatically if we need to
|
|
3437
|
-
if transport ==
|
|
3438
|
-
transport =
|
|
3439
|
-
elif transport ==
|
|
3440
|
-
transport =
|
|
3449
|
+
if transport == PhysicalTransport.LE and not self.le_enabled:
|
|
3450
|
+
transport = PhysicalTransport.BR_EDR
|
|
3451
|
+
elif transport == PhysicalTransport.BR_EDR and not self.classic_enabled:
|
|
3452
|
+
transport = PhysicalTransport.LE
|
|
3441
3453
|
|
|
3442
3454
|
# Check that there isn't already a pending connection
|
|
3443
|
-
if transport ==
|
|
3455
|
+
if transport == PhysicalTransport.LE and self.is_le_connecting:
|
|
3444
3456
|
raise InvalidStateError('connection already pending')
|
|
3445
3457
|
|
|
3446
3458
|
if isinstance(peer_address, str):
|
|
3447
3459
|
try:
|
|
3448
|
-
if transport ==
|
|
3460
|
+
if transport == PhysicalTransport.LE and peer_address.endswith('@'):
|
|
3449
3461
|
peer_address = hci.Address.from_string_for_transport(
|
|
3450
3462
|
peer_address[:-1], transport
|
|
3451
3463
|
)
|
|
@@ -3465,21 +3477,21 @@ class Device(CompositeEventEmitter):
|
|
|
3465
3477
|
else:
|
|
3466
3478
|
# All BR/EDR addresses should be public addresses
|
|
3467
3479
|
if (
|
|
3468
|
-
transport ==
|
|
3480
|
+
transport == PhysicalTransport.BR_EDR
|
|
3469
3481
|
and peer_address.address_type != hci.Address.PUBLIC_DEVICE_ADDRESS
|
|
3470
3482
|
):
|
|
3471
3483
|
raise InvalidArgumentError('BR/EDR addresses must be PUBLIC')
|
|
3472
3484
|
|
|
3473
3485
|
assert isinstance(peer_address, hci.Address)
|
|
3474
3486
|
|
|
3475
|
-
if transport ==
|
|
3487
|
+
if transport == PhysicalTransport.LE and always_resolve:
|
|
3476
3488
|
logger.debug('resolving address')
|
|
3477
3489
|
peer_address = await self.find_peer_by_identity_address(
|
|
3478
3490
|
peer_address
|
|
3479
3491
|
) # TODO: timeout
|
|
3480
3492
|
|
|
3481
3493
|
def on_connection(connection):
|
|
3482
|
-
if transport ==
|
|
3494
|
+
if transport == PhysicalTransport.LE or (
|
|
3483
3495
|
# match BR/EDR connection event against peer address
|
|
3484
3496
|
connection.transport == transport
|
|
3485
3497
|
and connection.peer_address == peer_address
|
|
@@ -3487,7 +3499,7 @@ class Device(CompositeEventEmitter):
|
|
|
3487
3499
|
pending_connection.set_result(connection)
|
|
3488
3500
|
|
|
3489
3501
|
def on_connection_failure(error):
|
|
3490
|
-
if transport ==
|
|
3502
|
+
if transport == PhysicalTransport.LE or (
|
|
3491
3503
|
# match BR/EDR connection failure event against peer address
|
|
3492
3504
|
error.transport == transport
|
|
3493
3505
|
and error.peer_address == peer_address
|
|
@@ -3501,7 +3513,7 @@ class Device(CompositeEventEmitter):
|
|
|
3501
3513
|
|
|
3502
3514
|
try:
|
|
3503
3515
|
# Tell the controller to connect
|
|
3504
|
-
if transport ==
|
|
3516
|
+
if transport == PhysicalTransport.LE:
|
|
3505
3517
|
if connection_parameters_preferences is None:
|
|
3506
3518
|
if connection_parameters_preferences is None:
|
|
3507
3519
|
connection_parameters_preferences = {
|
|
@@ -3646,18 +3658,18 @@ class Device(CompositeEventEmitter):
|
|
|
3646
3658
|
raise hci.HCI_StatusError(result)
|
|
3647
3659
|
|
|
3648
3660
|
# Wait for the connection process to complete
|
|
3649
|
-
if transport ==
|
|
3661
|
+
if transport == PhysicalTransport.LE:
|
|
3650
3662
|
self.le_connecting = True
|
|
3651
3663
|
|
|
3652
3664
|
if timeout is None:
|
|
3653
|
-
return await
|
|
3665
|
+
return await utils.cancel_on_event(self, 'flush', pending_connection)
|
|
3654
3666
|
|
|
3655
3667
|
try:
|
|
3656
3668
|
return await asyncio.wait_for(
|
|
3657
3669
|
asyncio.shield(pending_connection), timeout
|
|
3658
3670
|
)
|
|
3659
3671
|
except asyncio.TimeoutError:
|
|
3660
|
-
if transport ==
|
|
3672
|
+
if transport == PhysicalTransport.LE:
|
|
3661
3673
|
await self.send_command(
|
|
3662
3674
|
hci.HCI_LE_Create_Connection_Cancel_Command()
|
|
3663
3675
|
)
|
|
@@ -3667,13 +3679,15 @@ class Device(CompositeEventEmitter):
|
|
|
3667
3679
|
)
|
|
3668
3680
|
|
|
3669
3681
|
try:
|
|
3670
|
-
return await
|
|
3682
|
+
return await utils.cancel_on_event(
|
|
3683
|
+
self, 'flush', pending_connection
|
|
3684
|
+
)
|
|
3671
3685
|
except core.ConnectionError as error:
|
|
3672
3686
|
raise core.TimeoutError() from error
|
|
3673
3687
|
finally:
|
|
3674
3688
|
self.remove_listener('connection', on_connection)
|
|
3675
3689
|
self.remove_listener('connection_failure', on_connection_failure)
|
|
3676
|
-
if transport ==
|
|
3690
|
+
if transport == PhysicalTransport.LE:
|
|
3677
3691
|
self.le_connecting = False
|
|
3678
3692
|
self.connect_own_address_type = None
|
|
3679
3693
|
else:
|
|
@@ -3703,7 +3717,7 @@ class Device(CompositeEventEmitter):
|
|
|
3703
3717
|
# If the address is not parsable, assume it is a name instead
|
|
3704
3718
|
logger.debug('looking for peer by name')
|
|
3705
3719
|
peer_address = await self.find_peer_by_name(
|
|
3706
|
-
peer_address,
|
|
3720
|
+
peer_address, PhysicalTransport.BR_EDR
|
|
3707
3721
|
) # TODO: timeout
|
|
3708
3722
|
|
|
3709
3723
|
assert isinstance(peer_address, hci.Address)
|
|
@@ -3723,7 +3737,7 @@ class Device(CompositeEventEmitter):
|
|
|
3723
3737
|
|
|
3724
3738
|
try:
|
|
3725
3739
|
# Wait for a request or a completed connection
|
|
3726
|
-
pending_request =
|
|
3740
|
+
pending_request = utils.cancel_on_event(self, 'flush', pending_request_fut)
|
|
3727
3741
|
result = await (
|
|
3728
3742
|
asyncio.wait_for(pending_request, timeout)
|
|
3729
3743
|
if timeout
|
|
@@ -3753,14 +3767,14 @@ class Device(CompositeEventEmitter):
|
|
|
3753
3767
|
|
|
3754
3768
|
def on_connection(connection):
|
|
3755
3769
|
if (
|
|
3756
|
-
connection.transport ==
|
|
3770
|
+
connection.transport == PhysicalTransport.BR_EDR
|
|
3757
3771
|
and connection.peer_address == peer_address
|
|
3758
3772
|
):
|
|
3759
3773
|
pending_connection.set_result(connection)
|
|
3760
3774
|
|
|
3761
3775
|
def on_connection_failure(error):
|
|
3762
3776
|
if (
|
|
3763
|
-
error.transport ==
|
|
3777
|
+
error.transport == PhysicalTransport.BR_EDR
|
|
3764
3778
|
and error.peer_address == peer_address
|
|
3765
3779
|
):
|
|
3766
3780
|
pending_connection.set_exception(error)
|
|
@@ -3785,7 +3799,7 @@ class Device(CompositeEventEmitter):
|
|
|
3785
3799
|
)
|
|
3786
3800
|
|
|
3787
3801
|
# Wait for connection complete
|
|
3788
|
-
return await
|
|
3802
|
+
return await utils.cancel_on_event(self, 'flush', pending_connection)
|
|
3789
3803
|
|
|
3790
3804
|
finally:
|
|
3791
3805
|
self.remove_listener('connection', on_connection)
|
|
@@ -3830,7 +3844,7 @@ class Device(CompositeEventEmitter):
|
|
|
3830
3844
|
# If the address is not parsable, assume it is a name instead
|
|
3831
3845
|
logger.debug('looking for peer by name')
|
|
3832
3846
|
peer_address = await self.find_peer_by_name(
|
|
3833
|
-
peer_address,
|
|
3847
|
+
peer_address, PhysicalTransport.BR_EDR
|
|
3834
3848
|
) # TODO: timeout
|
|
3835
3849
|
|
|
3836
3850
|
await self.send_command(
|
|
@@ -3859,7 +3873,7 @@ class Device(CompositeEventEmitter):
|
|
|
3859
3873
|
|
|
3860
3874
|
# Wait for the disconnection process to complete
|
|
3861
3875
|
self.disconnecting = True
|
|
3862
|
-
return await
|
|
3876
|
+
return await utils.cancel_on_event(self, 'flush', pending_disconnection)
|
|
3863
3877
|
finally:
|
|
3864
3878
|
connection.remove_listener(
|
|
3865
3879
|
'disconnection', pending_disconnection.set_result
|
|
@@ -4001,7 +4015,19 @@ class Device(CompositeEventEmitter):
|
|
|
4001
4015
|
check_result=True,
|
|
4002
4016
|
)
|
|
4003
4017
|
|
|
4004
|
-
async def
|
|
4018
|
+
async def transfer_periodic_set_info(
|
|
4019
|
+
self, connection: Connection, advertising_handle: int, service_data: int = 0
|
|
4020
|
+
) -> None:
|
|
4021
|
+
return await self.send_command(
|
|
4022
|
+
hci.HCI_LE_Periodic_Advertising_Set_Info_Transfer_Command(
|
|
4023
|
+
connection_handle=connection.handle,
|
|
4024
|
+
service_data=service_data,
|
|
4025
|
+
advertising_handle=advertising_handle,
|
|
4026
|
+
),
|
|
4027
|
+
check_result=True,
|
|
4028
|
+
)
|
|
4029
|
+
|
|
4030
|
+
async def find_peer_by_name(self, name, transport=PhysicalTransport.LE):
|
|
4005
4031
|
"""
|
|
4006
4032
|
Scan for a peer with a given name and return its address.
|
|
4007
4033
|
"""
|
|
@@ -4020,7 +4046,7 @@ class Device(CompositeEventEmitter):
|
|
|
4020
4046
|
was_scanning = self.scanning
|
|
4021
4047
|
was_discovering = self.discovering
|
|
4022
4048
|
try:
|
|
4023
|
-
if transport ==
|
|
4049
|
+
if transport == PhysicalTransport.LE:
|
|
4024
4050
|
event_name = 'advertisement'
|
|
4025
4051
|
listener = self.on(
|
|
4026
4052
|
event_name,
|
|
@@ -4032,7 +4058,7 @@ class Device(CompositeEventEmitter):
|
|
|
4032
4058
|
if not self.scanning:
|
|
4033
4059
|
await self.start_scanning(filter_duplicates=True)
|
|
4034
4060
|
|
|
4035
|
-
elif transport ==
|
|
4061
|
+
elif transport == PhysicalTransport.BR_EDR:
|
|
4036
4062
|
event_name = 'inquiry_result'
|
|
4037
4063
|
listener = self.on(
|
|
4038
4064
|
event_name,
|
|
@@ -4046,14 +4072,14 @@ class Device(CompositeEventEmitter):
|
|
|
4046
4072
|
else:
|
|
4047
4073
|
return None
|
|
4048
4074
|
|
|
4049
|
-
return await
|
|
4075
|
+
return await utils.cancel_on_event(self, 'flush', peer_address)
|
|
4050
4076
|
finally:
|
|
4051
4077
|
if listener is not None:
|
|
4052
4078
|
self.remove_listener(event_name, listener)
|
|
4053
4079
|
|
|
4054
|
-
if transport ==
|
|
4080
|
+
if transport == PhysicalTransport.LE and not was_scanning:
|
|
4055
4081
|
await self.stop_scanning()
|
|
4056
|
-
elif transport ==
|
|
4082
|
+
elif transport == PhysicalTransport.BR_EDR and not was_discovering:
|
|
4057
4083
|
await self.stop_discovery()
|
|
4058
4084
|
|
|
4059
4085
|
async def find_peer_by_identity_address(
|
|
@@ -4096,7 +4122,7 @@ class Device(CompositeEventEmitter):
|
|
|
4096
4122
|
if not self.scanning:
|
|
4097
4123
|
await self.start_scanning(filter_duplicates=True)
|
|
4098
4124
|
|
|
4099
|
-
return await
|
|
4125
|
+
return await utils.cancel_on_event(self, 'flush', peer_address)
|
|
4100
4126
|
finally:
|
|
4101
4127
|
if listener is not None:
|
|
4102
4128
|
self.remove_listener(event_name, listener)
|
|
@@ -4200,7 +4226,9 @@ class Device(CompositeEventEmitter):
|
|
|
4200
4226
|
raise hci.HCI_StatusError(result)
|
|
4201
4227
|
|
|
4202
4228
|
# Wait for the authentication to complete
|
|
4203
|
-
await
|
|
4229
|
+
await utils.cancel_on_event(
|
|
4230
|
+
connection, 'disconnection', pending_authentication
|
|
4231
|
+
)
|
|
4204
4232
|
finally:
|
|
4205
4233
|
connection.remove_listener('connection_authentication', on_authentication)
|
|
4206
4234
|
connection.remove_listener(
|
|
@@ -4208,7 +4236,7 @@ class Device(CompositeEventEmitter):
|
|
|
4208
4236
|
)
|
|
4209
4237
|
|
|
4210
4238
|
async def encrypt(self, connection, enable=True):
|
|
4211
|
-
if not enable and connection.transport ==
|
|
4239
|
+
if not enable and connection.transport == PhysicalTransport.LE:
|
|
4212
4240
|
raise InvalidArgumentError('`enable` parameter is classic only.')
|
|
4213
4241
|
|
|
4214
4242
|
# Set up event handlers
|
|
@@ -4225,7 +4253,7 @@ class Device(CompositeEventEmitter):
|
|
|
4225
4253
|
|
|
4226
4254
|
# Request the encryption
|
|
4227
4255
|
try:
|
|
4228
|
-
if connection.transport ==
|
|
4256
|
+
if connection.transport == PhysicalTransport.LE:
|
|
4229
4257
|
# Look for a key in the key store
|
|
4230
4258
|
if self.keystore is None:
|
|
4231
4259
|
raise InvalidOperationError('no key store')
|
|
@@ -4246,7 +4274,7 @@ class Device(CompositeEventEmitter):
|
|
|
4246
4274
|
else:
|
|
4247
4275
|
raise InvalidOperationError('no LTK found for peer')
|
|
4248
4276
|
|
|
4249
|
-
if connection.role != hci.
|
|
4277
|
+
if connection.role != hci.Role.CENTRAL:
|
|
4250
4278
|
raise InvalidStateError('only centrals can start encryption')
|
|
4251
4279
|
|
|
4252
4280
|
result = await self.send_command(
|
|
@@ -4280,7 +4308,7 @@ class Device(CompositeEventEmitter):
|
|
|
4280
4308
|
raise hci.HCI_StatusError(result)
|
|
4281
4309
|
|
|
4282
4310
|
# Wait for the result
|
|
4283
|
-
await
|
|
4311
|
+
await utils.cancel_on_event(connection, 'disconnection', pending_encryption)
|
|
4284
4312
|
finally:
|
|
4285
4313
|
connection.remove_listener(
|
|
4286
4314
|
'connection_encryption_change', on_encryption_change
|
|
@@ -4324,7 +4352,9 @@ class Device(CompositeEventEmitter):
|
|
|
4324
4352
|
f'{hci.HCI_Constant.error_name(result.status)}'
|
|
4325
4353
|
)
|
|
4326
4354
|
raise hci.HCI_StatusError(result)
|
|
4327
|
-
await
|
|
4355
|
+
await utils.cancel_on_event(
|
|
4356
|
+
connection, 'disconnection', pending_role_change
|
|
4357
|
+
)
|
|
4328
4358
|
finally:
|
|
4329
4359
|
connection.remove_listener('role_change', on_role_change)
|
|
4330
4360
|
connection.remove_listener('role_change_failure', on_role_change_failure)
|
|
@@ -4373,13 +4403,13 @@ class Device(CompositeEventEmitter):
|
|
|
4373
4403
|
raise hci.HCI_StatusError(result)
|
|
4374
4404
|
|
|
4375
4405
|
# Wait for the result
|
|
4376
|
-
return await
|
|
4406
|
+
return await utils.cancel_on_event(self, 'flush', pending_name)
|
|
4377
4407
|
finally:
|
|
4378
4408
|
self.remove_listener('remote_name', handler)
|
|
4379
4409
|
self.remove_listener('remote_name_failure', failure_handler)
|
|
4380
4410
|
|
|
4381
4411
|
# [LE only]
|
|
4382
|
-
@experimental('Only for testing.')
|
|
4412
|
+
@utils.experimental('Only for testing.')
|
|
4383
4413
|
async def setup_cig(
|
|
4384
4414
|
self,
|
|
4385
4415
|
cig_id: int,
|
|
@@ -4437,7 +4467,7 @@ class Device(CompositeEventEmitter):
|
|
|
4437
4467
|
return cis_handles
|
|
4438
4468
|
|
|
4439
4469
|
# [LE only]
|
|
4440
|
-
@experimental('Only for testing.')
|
|
4470
|
+
@utils.experimental('Only for testing.')
|
|
4441
4471
|
async def create_cis(
|
|
4442
4472
|
self, cis_acl_pairs: Sequence[tuple[int, int]]
|
|
4443
4473
|
) -> list[CisLink]:
|
|
@@ -4453,7 +4483,7 @@ class Device(CompositeEventEmitter):
|
|
|
4453
4483
|
cig_id=cig_id,
|
|
4454
4484
|
)
|
|
4455
4485
|
|
|
4456
|
-
with closing(EventWatcher()) as watcher:
|
|
4486
|
+
with closing(utils.EventWatcher()) as watcher:
|
|
4457
4487
|
pending_cis_establishments = {
|
|
4458
4488
|
cis_handle: asyncio.get_running_loop().create_future()
|
|
4459
4489
|
for cis_handle, _ in cis_acl_pairs
|
|
@@ -4480,7 +4510,7 @@ class Device(CompositeEventEmitter):
|
|
|
4480
4510
|
return await asyncio.gather(*pending_cis_establishments.values())
|
|
4481
4511
|
|
|
4482
4512
|
# [LE only]
|
|
4483
|
-
@experimental('Only for testing.')
|
|
4513
|
+
@utils.experimental('Only for testing.')
|
|
4484
4514
|
async def accept_cis_request(self, handle: int) -> CisLink:
|
|
4485
4515
|
"""[LE Only] Accepts an incoming CIS request.
|
|
4486
4516
|
|
|
@@ -4502,7 +4532,7 @@ class Device(CompositeEventEmitter):
|
|
|
4502
4532
|
if cis_link.state == CisLink.State.ESTABLISHED:
|
|
4503
4533
|
return cis_link
|
|
4504
4534
|
|
|
4505
|
-
with closing(EventWatcher()) as watcher:
|
|
4535
|
+
with closing(utils.EventWatcher()) as watcher:
|
|
4506
4536
|
pending_establishment = asyncio.get_running_loop().create_future()
|
|
4507
4537
|
|
|
4508
4538
|
def on_establishment() -> None:
|
|
@@ -4526,7 +4556,7 @@ class Device(CompositeEventEmitter):
|
|
|
4526
4556
|
raise UnreachableError()
|
|
4527
4557
|
|
|
4528
4558
|
# [LE only]
|
|
4529
|
-
@experimental('Only for testing.')
|
|
4559
|
+
@utils.experimental('Only for testing.')
|
|
4530
4560
|
async def reject_cis_request(
|
|
4531
4561
|
self,
|
|
4532
4562
|
handle: int,
|
|
@@ -4540,14 +4570,14 @@ class Device(CompositeEventEmitter):
|
|
|
4540
4570
|
)
|
|
4541
4571
|
|
|
4542
4572
|
# [LE only]
|
|
4543
|
-
@experimental('Only for testing.')
|
|
4573
|
+
@utils.experimental('Only for testing.')
|
|
4544
4574
|
async def create_big(
|
|
4545
4575
|
self, advertising_set: AdvertisingSet, parameters: BigParameters
|
|
4546
4576
|
) -> Big:
|
|
4547
4577
|
if (big_handle := self.next_big_handle()) is None:
|
|
4548
4578
|
raise core.OutOfResourcesError("All valid BIG handles already in use")
|
|
4549
4579
|
|
|
4550
|
-
with closing(EventWatcher()) as watcher:
|
|
4580
|
+
with closing(utils.EventWatcher()) as watcher:
|
|
4551
4581
|
big = Big(
|
|
4552
4582
|
big_handle=big_handle,
|
|
4553
4583
|
parameters=parameters,
|
|
@@ -4590,7 +4620,7 @@ class Device(CompositeEventEmitter):
|
|
|
4590
4620
|
return big
|
|
4591
4621
|
|
|
4592
4622
|
# [LE only]
|
|
4593
|
-
@experimental('Only for testing.')
|
|
4623
|
+
@utils.experimental('Only for testing.')
|
|
4594
4624
|
async def create_big_sync(
|
|
4595
4625
|
self, pa_sync: PeriodicAdvertisingSync, parameters: BigSyncParameters
|
|
4596
4626
|
) -> BigSync:
|
|
@@ -4600,7 +4630,7 @@ class Device(CompositeEventEmitter):
|
|
|
4600
4630
|
if (pa_sync_handle := pa_sync.sync_handle) is None:
|
|
4601
4631
|
raise core.InvalidStateError("PA Sync is not established")
|
|
4602
4632
|
|
|
4603
|
-
with closing(EventWatcher()) as watcher:
|
|
4633
|
+
with closing(utils.EventWatcher()) as watcher:
|
|
4604
4634
|
big_sync = BigSync(
|
|
4605
4635
|
big_handle=big_handle,
|
|
4606
4636
|
parameters=parameters,
|
|
@@ -4648,7 +4678,7 @@ class Device(CompositeEventEmitter):
|
|
|
4648
4678
|
Returns:
|
|
4649
4679
|
LE features supported by the remote device.
|
|
4650
4680
|
"""
|
|
4651
|
-
with closing(EventWatcher()) as watcher:
|
|
4681
|
+
with closing(utils.EventWatcher()) as watcher:
|
|
4652
4682
|
read_feature_future: asyncio.Future[hci.LeFeatureMask] = (
|
|
4653
4683
|
asyncio.get_running_loop().create_future()
|
|
4654
4684
|
)
|
|
@@ -4671,7 +4701,7 @@ class Device(CompositeEventEmitter):
|
|
|
4671
4701
|
)
|
|
4672
4702
|
return await read_feature_future
|
|
4673
4703
|
|
|
4674
|
-
@experimental('Only for testing.')
|
|
4704
|
+
@utils.experimental('Only for testing.')
|
|
4675
4705
|
async def get_remote_cs_capabilities(
|
|
4676
4706
|
self, connection: Connection
|
|
4677
4707
|
) -> ChannelSoundingCapabilities:
|
|
@@ -4679,7 +4709,7 @@ class Device(CompositeEventEmitter):
|
|
|
4679
4709
|
asyncio.get_running_loop().create_future()
|
|
4680
4710
|
)
|
|
4681
4711
|
|
|
4682
|
-
with closing(EventWatcher()) as watcher:
|
|
4712
|
+
with closing(utils.EventWatcher()) as watcher:
|
|
4683
4713
|
watcher.once(
|
|
4684
4714
|
connection, 'channel_sounding_capabilities', complete_future.set_result
|
|
4685
4715
|
)
|
|
@@ -4696,7 +4726,7 @@ class Device(CompositeEventEmitter):
|
|
|
4696
4726
|
)
|
|
4697
4727
|
return await complete_future
|
|
4698
4728
|
|
|
4699
|
-
@experimental('Only for testing.')
|
|
4729
|
+
@utils.experimental('Only for testing.')
|
|
4700
4730
|
async def set_default_cs_settings(
|
|
4701
4731
|
self,
|
|
4702
4732
|
connection: Connection,
|
|
@@ -4716,7 +4746,7 @@ class Device(CompositeEventEmitter):
|
|
|
4716
4746
|
check_result=True,
|
|
4717
4747
|
)
|
|
4718
4748
|
|
|
4719
|
-
@experimental('Only for testing.')
|
|
4749
|
+
@utils.experimental('Only for testing.')
|
|
4720
4750
|
async def create_cs_config(
|
|
4721
4751
|
self,
|
|
4722
4752
|
connection: Connection,
|
|
@@ -4753,7 +4783,7 @@ class Device(CompositeEventEmitter):
|
|
|
4753
4783
|
if config_id is None:
|
|
4754
4784
|
raise OutOfResourcesError("No available config ID on this connection!")
|
|
4755
4785
|
|
|
4756
|
-
with closing(EventWatcher()) as watcher:
|
|
4786
|
+
with closing(utils.EventWatcher()) as watcher:
|
|
4757
4787
|
watcher.once(
|
|
4758
4788
|
connection, 'channel_sounding_config', complete_future.set_result
|
|
4759
4789
|
)
|
|
@@ -4787,12 +4817,12 @@ class Device(CompositeEventEmitter):
|
|
|
4787
4817
|
)
|
|
4788
4818
|
return await complete_future
|
|
4789
4819
|
|
|
4790
|
-
@experimental('Only for testing.')
|
|
4820
|
+
@utils.experimental('Only for testing.')
|
|
4791
4821
|
async def enable_cs_security(self, connection: Connection) -> None:
|
|
4792
4822
|
complete_future: asyncio.Future[None] = (
|
|
4793
4823
|
asyncio.get_running_loop().create_future()
|
|
4794
4824
|
)
|
|
4795
|
-
with closing(EventWatcher()) as watcher:
|
|
4825
|
+
with closing(utils.EventWatcher()) as watcher:
|
|
4796
4826
|
|
|
4797
4827
|
def on_event(event: hci.HCI_LE_CS_Security_Enable_Complete_Event) -> None:
|
|
4798
4828
|
if event.connection_handle != connection.handle:
|
|
@@ -4811,7 +4841,7 @@ class Device(CompositeEventEmitter):
|
|
|
4811
4841
|
)
|
|
4812
4842
|
return await complete_future
|
|
4813
4843
|
|
|
4814
|
-
@experimental('Only for testing.')
|
|
4844
|
+
@utils.experimental('Only for testing.')
|
|
4815
4845
|
async def set_cs_procedure_parameters(
|
|
4816
4846
|
self,
|
|
4817
4847
|
connection: Connection,
|
|
@@ -4849,7 +4879,7 @@ class Device(CompositeEventEmitter):
|
|
|
4849
4879
|
check_result=True,
|
|
4850
4880
|
)
|
|
4851
4881
|
|
|
4852
|
-
@experimental('Only for testing.')
|
|
4882
|
+
@utils.experimental('Only for testing.')
|
|
4853
4883
|
async def enable_cs_procedure(
|
|
4854
4884
|
self,
|
|
4855
4885
|
connection: Connection,
|
|
@@ -4859,7 +4889,7 @@ class Device(CompositeEventEmitter):
|
|
|
4859
4889
|
complete_future: asyncio.Future[ChannelSoundingProcedure] = (
|
|
4860
4890
|
asyncio.get_running_loop().create_future()
|
|
4861
4891
|
)
|
|
4862
|
-
with closing(EventWatcher()) as watcher:
|
|
4892
|
+
with closing(utils.EventWatcher()) as watcher:
|
|
4863
4893
|
watcher.once(
|
|
4864
4894
|
connection, 'channel_sounding_procedure', complete_future.set_result
|
|
4865
4895
|
)
|
|
@@ -4899,10 +4929,12 @@ class Device(CompositeEventEmitter):
|
|
|
4899
4929
|
value=link_key, authenticated=authenticated
|
|
4900
4930
|
)
|
|
4901
4931
|
|
|
4902
|
-
|
|
4932
|
+
utils.cancel_on_event(
|
|
4933
|
+
self, 'flush', self.update_keys(str(bd_addr), pairing_keys)
|
|
4934
|
+
)
|
|
4903
4935
|
|
|
4904
4936
|
if connection := self.find_connection_by_bd_addr(
|
|
4905
|
-
bd_addr, transport=
|
|
4937
|
+
bd_addr, transport=PhysicalTransport.BR_EDR
|
|
4906
4938
|
):
|
|
4907
4939
|
connection.link_key_type = key_type
|
|
4908
4940
|
|
|
@@ -5168,7 +5200,7 @@ class Device(CompositeEventEmitter):
|
|
|
5168
5200
|
if advertising_set.auto_restart:
|
|
5169
5201
|
connection.once(
|
|
5170
5202
|
'disconnection',
|
|
5171
|
-
lambda _:
|
|
5203
|
+
lambda _: utils.cancel_on_event(self, 'flush', advertising_set.start()),
|
|
5172
5204
|
)
|
|
5173
5205
|
|
|
5174
5206
|
self.emit('connection', connection)
|
|
@@ -5202,7 +5234,7 @@ class Device(CompositeEventEmitter):
|
|
|
5202
5234
|
'new connection reuses the same handle as a previous connection'
|
|
5203
5235
|
)
|
|
5204
5236
|
|
|
5205
|
-
if transport ==
|
|
5237
|
+
if transport == PhysicalTransport.BR_EDR:
|
|
5206
5238
|
# Create a new connection
|
|
5207
5239
|
connection = self.pending_connections.pop(peer_address)
|
|
5208
5240
|
connection.complete(connection_handle, connection_parameters)
|
|
@@ -5225,7 +5257,7 @@ class Device(CompositeEventEmitter):
|
|
|
5225
5257
|
|
|
5226
5258
|
self_address = None
|
|
5227
5259
|
own_address_type: Optional[hci.OwnAddressType] = None
|
|
5228
|
-
if role == hci.
|
|
5260
|
+
if role == hci.Role.CENTRAL:
|
|
5229
5261
|
own_address_type = self.connect_own_address_type
|
|
5230
5262
|
assert own_address_type is not None
|
|
5231
5263
|
else:
|
|
@@ -5272,22 +5304,22 @@ class Device(CompositeEventEmitter):
|
|
|
5272
5304
|
)
|
|
5273
5305
|
self.connections[connection_handle] = connection
|
|
5274
5306
|
|
|
5275
|
-
if role == hci.
|
|
5307
|
+
if role == hci.Role.PERIPHERAL and self.legacy_advertiser:
|
|
5276
5308
|
if self.legacy_advertiser.auto_restart:
|
|
5277
5309
|
advertiser = self.legacy_advertiser
|
|
5278
5310
|
connection.once(
|
|
5279
5311
|
'disconnection',
|
|
5280
|
-
lambda _:
|
|
5312
|
+
lambda _: utils.cancel_on_event(self, 'flush', advertiser.start()),
|
|
5281
5313
|
)
|
|
5282
5314
|
else:
|
|
5283
5315
|
self.legacy_advertiser = None
|
|
5284
5316
|
|
|
5285
|
-
if role == hci.
|
|
5317
|
+
if role == hci.Role.CENTRAL or not self.supports_le_extended_advertising:
|
|
5286
5318
|
# We can emit now, we have all the info we need
|
|
5287
5319
|
self.emit('connection', connection)
|
|
5288
5320
|
return
|
|
5289
5321
|
|
|
5290
|
-
if role == hci.
|
|
5322
|
+
if role == hci.Role.PERIPHERAL and self.supports_le_extended_advertising:
|
|
5291
5323
|
if advertising_set := self.connecting_extended_advertising_sets.pop(
|
|
5292
5324
|
connection_handle, None
|
|
5293
5325
|
):
|
|
@@ -5304,7 +5336,7 @@ class Device(CompositeEventEmitter):
|
|
|
5304
5336
|
|
|
5305
5337
|
# For directed advertising, this means a timeout
|
|
5306
5338
|
if (
|
|
5307
|
-
transport ==
|
|
5339
|
+
transport == PhysicalTransport.LE
|
|
5308
5340
|
and self.legacy_advertiser
|
|
5309
5341
|
and self.legacy_advertiser.advertising_type.is_directed
|
|
5310
5342
|
):
|
|
@@ -5331,7 +5363,7 @@ class Device(CompositeEventEmitter):
|
|
|
5331
5363
|
hci.HCI_Connection_Complete_Event.ESCO_LINK_TYPE,
|
|
5332
5364
|
):
|
|
5333
5365
|
if connection := self.find_connection_by_bd_addr(
|
|
5334
|
-
bd_addr, transport=
|
|
5366
|
+
bd_addr, transport=PhysicalTransport.BR_EDR
|
|
5335
5367
|
):
|
|
5336
5368
|
self.emit('sco_request', connection, link_type)
|
|
5337
5369
|
else:
|
|
@@ -5404,7 +5436,7 @@ class Device(CompositeEventEmitter):
|
|
|
5404
5436
|
connection.emit('disconnection_failure', error)
|
|
5405
5437
|
|
|
5406
5438
|
@host_event_handler
|
|
5407
|
-
@AsyncRunner.run_in_task()
|
|
5439
|
+
@utils.AsyncRunner.run_in_task()
|
|
5408
5440
|
async def on_inquiry_complete(self):
|
|
5409
5441
|
if self.auto_restart_inquiry:
|
|
5410
5442
|
# Inquire again
|
|
@@ -5536,7 +5568,7 @@ class Device(CompositeEventEmitter):
|
|
|
5536
5568
|
|
|
5537
5569
|
async def reply() -> None:
|
|
5538
5570
|
try:
|
|
5539
|
-
if await
|
|
5571
|
+
if await utils.cancel_on_event(connection, 'disconnection', method()):
|
|
5540
5572
|
await self.host.send_command(
|
|
5541
5573
|
hci.HCI_User_Confirmation_Request_Reply_Command(
|
|
5542
5574
|
bd_addr=connection.peer_address
|
|
@@ -5552,7 +5584,7 @@ class Device(CompositeEventEmitter):
|
|
|
5552
5584
|
)
|
|
5553
5585
|
)
|
|
5554
5586
|
|
|
5555
|
-
AsyncRunner.spawn(reply())
|
|
5587
|
+
utils.AsyncRunner.spawn(reply())
|
|
5556
5588
|
|
|
5557
5589
|
# [Classic only]
|
|
5558
5590
|
@host_event_handler
|
|
@@ -5563,8 +5595,8 @@ class Device(CompositeEventEmitter):
|
|
|
5563
5595
|
|
|
5564
5596
|
async def reply() -> None:
|
|
5565
5597
|
try:
|
|
5566
|
-
number = await
|
|
5567
|
-
'disconnection', pairing_config.delegate.get_number()
|
|
5598
|
+
number = await utils.cancel_on_event(
|
|
5599
|
+
connection, 'disconnection', pairing_config.delegate.get_number()
|
|
5568
5600
|
)
|
|
5569
5601
|
if number is not None:
|
|
5570
5602
|
await self.host.send_command(
|
|
@@ -5582,7 +5614,7 @@ class Device(CompositeEventEmitter):
|
|
|
5582
5614
|
)
|
|
5583
5615
|
)
|
|
5584
5616
|
|
|
5585
|
-
AsyncRunner.spawn(reply())
|
|
5617
|
+
utils.AsyncRunner.spawn(reply())
|
|
5586
5618
|
|
|
5587
5619
|
# [Classic only]
|
|
5588
5620
|
@host_event_handler
|
|
@@ -5597,8 +5629,8 @@ class Device(CompositeEventEmitter):
|
|
|
5597
5629
|
if io_capability == hci.HCI_KEYBOARD_ONLY_IO_CAPABILITY:
|
|
5598
5630
|
# Ask the user to enter a string
|
|
5599
5631
|
async def get_pin_code():
|
|
5600
|
-
pin_code = await
|
|
5601
|
-
'disconnection', pairing_config.delegate.get_string(16)
|
|
5632
|
+
pin_code = await utils.cancel_on_event(
|
|
5633
|
+
connection, 'disconnection', pairing_config.delegate.get_string(16)
|
|
5602
5634
|
)
|
|
5603
5635
|
|
|
5604
5636
|
if pin_code is not None:
|
|
@@ -5636,8 +5668,8 @@ class Device(CompositeEventEmitter):
|
|
|
5636
5668
|
pairing_config = self.pairing_config_factory(connection)
|
|
5637
5669
|
|
|
5638
5670
|
# Show the passkey to the user
|
|
5639
|
-
|
|
5640
|
-
'disconnection', pairing_config.delegate.display_number(passkey)
|
|
5671
|
+
utils.cancel_on_event(
|
|
5672
|
+
connection, 'disconnection', pairing_config.delegate.display_number(passkey)
|
|
5641
5673
|
)
|
|
5642
5674
|
|
|
5643
5675
|
# [Classic only]
|
|
@@ -5669,7 +5701,7 @@ class Device(CompositeEventEmitter):
|
|
|
5669
5701
|
# [Classic only]
|
|
5670
5702
|
@host_event_handler
|
|
5671
5703
|
@with_connection_from_address
|
|
5672
|
-
@experimental('Only for testing.')
|
|
5704
|
+
@utils.experimental('Only for testing.')
|
|
5673
5705
|
def on_sco_connection(
|
|
5674
5706
|
self, acl_connection: Connection, sco_handle: int, link_type: int
|
|
5675
5707
|
) -> None:
|
|
@@ -5689,7 +5721,7 @@ class Device(CompositeEventEmitter):
|
|
|
5689
5721
|
# [Classic only]
|
|
5690
5722
|
@host_event_handler
|
|
5691
5723
|
@with_connection_from_address
|
|
5692
|
-
@experimental('Only for testing.')
|
|
5724
|
+
@utils.experimental('Only for testing.')
|
|
5693
5725
|
def on_sco_connection_failure(
|
|
5694
5726
|
self, acl_connection: Connection, status: int
|
|
5695
5727
|
) -> None:
|
|
@@ -5698,7 +5730,7 @@ class Device(CompositeEventEmitter):
|
|
|
5698
5730
|
|
|
5699
5731
|
# [Classic only]
|
|
5700
5732
|
@host_event_handler
|
|
5701
|
-
@experimental('Only for testing')
|
|
5733
|
+
@utils.experimental('Only for testing')
|
|
5702
5734
|
def on_sco_packet(
|
|
5703
5735
|
self, sco_handle: int, packet: hci.HCI_SynchronousDataPacket
|
|
5704
5736
|
) -> None:
|
|
@@ -5708,7 +5740,7 @@ class Device(CompositeEventEmitter):
|
|
|
5708
5740
|
# [LE only]
|
|
5709
5741
|
@host_event_handler
|
|
5710
5742
|
@with_connection_from_handle
|
|
5711
|
-
@experimental('Only for testing')
|
|
5743
|
+
@utils.experimental('Only for testing')
|
|
5712
5744
|
def on_cis_request(
|
|
5713
5745
|
self,
|
|
5714
5746
|
acl_connection: Connection,
|
|
@@ -5735,7 +5767,7 @@ class Device(CompositeEventEmitter):
|
|
|
5735
5767
|
|
|
5736
5768
|
# [LE only]
|
|
5737
5769
|
@host_event_handler
|
|
5738
|
-
@experimental('Only for testing')
|
|
5770
|
+
@utils.experimental('Only for testing')
|
|
5739
5771
|
def on_cis_establishment(self, cis_handle: int) -> None:
|
|
5740
5772
|
cis_link = self.cis_links[cis_handle]
|
|
5741
5773
|
cis_link.state = CisLink.State.ESTABLISHED
|
|
@@ -5755,7 +5787,7 @@ class Device(CompositeEventEmitter):
|
|
|
5755
5787
|
|
|
5756
5788
|
# [LE only]
|
|
5757
5789
|
@host_event_handler
|
|
5758
|
-
@experimental('Only for testing')
|
|
5790
|
+
@utils.experimental('Only for testing')
|
|
5759
5791
|
def on_cis_establishment_failure(self, cis_handle: int, status: int) -> None:
|
|
5760
5792
|
logger.debug(f'*** CIS Establishment Failure: cis=[0x{cis_handle:04X}] ***')
|
|
5761
5793
|
if cis_link := self.cis_links.pop(cis_handle):
|
|
@@ -5764,7 +5796,7 @@ class Device(CompositeEventEmitter):
|
|
|
5764
5796
|
|
|
5765
5797
|
# [LE only]
|
|
5766
5798
|
@host_event_handler
|
|
5767
|
-
@experimental('Only for testing')
|
|
5799
|
+
@utils.experimental('Only for testing')
|
|
5768
5800
|
def on_iso_packet(self, handle: int, packet: hci.HCI_IsoDataPacket) -> None:
|
|
5769
5801
|
if (cis_link := self.cis_links.get(handle)) and cis_link.sink:
|
|
5770
5802
|
cis_link.sink(packet)
|
|
@@ -5782,14 +5814,14 @@ class Device(CompositeEventEmitter):
|
|
|
5782
5814
|
connection.encryption = encryption
|
|
5783
5815
|
if (
|
|
5784
5816
|
not connection.authenticated
|
|
5785
|
-
and connection.transport ==
|
|
5817
|
+
and connection.transport == PhysicalTransport.BR_EDR
|
|
5786
5818
|
and encryption == hci.HCI_Encryption_Change_Event.AES_CCM
|
|
5787
5819
|
):
|
|
5788
5820
|
connection.authenticated = True
|
|
5789
5821
|
connection.sc = True
|
|
5790
5822
|
if (
|
|
5791
5823
|
not connection.authenticated
|
|
5792
|
-
and connection.transport ==
|
|
5824
|
+
and connection.transport == PhysicalTransport.LE
|
|
5793
5825
|
and encryption == hci.HCI_Encryption_Change_Event.E0_OR_AES_CCM
|
|
5794
5826
|
):
|
|
5795
5827
|
connection.authenticated = True
|