bumble 0.0.209__py3-none-any.whl → 0.0.211__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 +13 -9
- 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 +172 -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 +48 -22
- 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.211.dist-info}/METADATA +3 -2
- {bumble-0.0.209.dist-info → bumble-0.0.211.dist-info}/RECORD +74 -74
- {bumble-0.0.209.dist-info → bumble-0.0.211.dist-info}/WHEEL +1 -1
- {bumble-0.0.209.dist-info → bumble-0.0.211.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.209.dist-info → bumble-0.0.211.dist-info/licenses}/LICENSE +0 -0
- {bumble-0.0.209.dist-info → bumble-0.0.211.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
|
|
@@ -3939,6 +3953,9 @@ class Device(CompositeEventEmitter):
|
|
|
3939
3953
|
return result.return_parameters.rssi
|
|
3940
3954
|
|
|
3941
3955
|
async def get_connection_phy(self, connection: Connection) -> ConnectionPHY:
|
|
3956
|
+
if not self.host.supports_command(hci.HCI_LE_READ_PHY_COMMAND):
|
|
3957
|
+
return ConnectionPHY(hci.Phy.LE_1M, hci.Phy.LE_1M)
|
|
3958
|
+
|
|
3942
3959
|
result = await self.send_command(
|
|
3943
3960
|
hci.HCI_LE_Read_PHY_Command(connection_handle=connection.handle),
|
|
3944
3961
|
check_result=True,
|
|
@@ -4001,7 +4018,19 @@ class Device(CompositeEventEmitter):
|
|
|
4001
4018
|
check_result=True,
|
|
4002
4019
|
)
|
|
4003
4020
|
|
|
4004
|
-
async def
|
|
4021
|
+
async def transfer_periodic_set_info(
|
|
4022
|
+
self, connection: Connection, advertising_handle: int, service_data: int = 0
|
|
4023
|
+
) -> None:
|
|
4024
|
+
return await self.send_command(
|
|
4025
|
+
hci.HCI_LE_Periodic_Advertising_Set_Info_Transfer_Command(
|
|
4026
|
+
connection_handle=connection.handle,
|
|
4027
|
+
service_data=service_data,
|
|
4028
|
+
advertising_handle=advertising_handle,
|
|
4029
|
+
),
|
|
4030
|
+
check_result=True,
|
|
4031
|
+
)
|
|
4032
|
+
|
|
4033
|
+
async def find_peer_by_name(self, name, transport=PhysicalTransport.LE):
|
|
4005
4034
|
"""
|
|
4006
4035
|
Scan for a peer with a given name and return its address.
|
|
4007
4036
|
"""
|
|
@@ -4020,7 +4049,7 @@ class Device(CompositeEventEmitter):
|
|
|
4020
4049
|
was_scanning = self.scanning
|
|
4021
4050
|
was_discovering = self.discovering
|
|
4022
4051
|
try:
|
|
4023
|
-
if transport ==
|
|
4052
|
+
if transport == PhysicalTransport.LE:
|
|
4024
4053
|
event_name = 'advertisement'
|
|
4025
4054
|
listener = self.on(
|
|
4026
4055
|
event_name,
|
|
@@ -4032,7 +4061,7 @@ class Device(CompositeEventEmitter):
|
|
|
4032
4061
|
if not self.scanning:
|
|
4033
4062
|
await self.start_scanning(filter_duplicates=True)
|
|
4034
4063
|
|
|
4035
|
-
elif transport ==
|
|
4064
|
+
elif transport == PhysicalTransport.BR_EDR:
|
|
4036
4065
|
event_name = 'inquiry_result'
|
|
4037
4066
|
listener = self.on(
|
|
4038
4067
|
event_name,
|
|
@@ -4046,14 +4075,14 @@ class Device(CompositeEventEmitter):
|
|
|
4046
4075
|
else:
|
|
4047
4076
|
return None
|
|
4048
4077
|
|
|
4049
|
-
return await
|
|
4078
|
+
return await utils.cancel_on_event(self, 'flush', peer_address)
|
|
4050
4079
|
finally:
|
|
4051
4080
|
if listener is not None:
|
|
4052
4081
|
self.remove_listener(event_name, listener)
|
|
4053
4082
|
|
|
4054
|
-
if transport ==
|
|
4083
|
+
if transport == PhysicalTransport.LE and not was_scanning:
|
|
4055
4084
|
await self.stop_scanning()
|
|
4056
|
-
elif transport ==
|
|
4085
|
+
elif transport == PhysicalTransport.BR_EDR and not was_discovering:
|
|
4057
4086
|
await self.stop_discovery()
|
|
4058
4087
|
|
|
4059
4088
|
async def find_peer_by_identity_address(
|
|
@@ -4096,7 +4125,7 @@ class Device(CompositeEventEmitter):
|
|
|
4096
4125
|
if not self.scanning:
|
|
4097
4126
|
await self.start_scanning(filter_duplicates=True)
|
|
4098
4127
|
|
|
4099
|
-
return await
|
|
4128
|
+
return await utils.cancel_on_event(self, 'flush', peer_address)
|
|
4100
4129
|
finally:
|
|
4101
4130
|
if listener is not None:
|
|
4102
4131
|
self.remove_listener(event_name, listener)
|
|
@@ -4200,7 +4229,9 @@ class Device(CompositeEventEmitter):
|
|
|
4200
4229
|
raise hci.HCI_StatusError(result)
|
|
4201
4230
|
|
|
4202
4231
|
# Wait for the authentication to complete
|
|
4203
|
-
await
|
|
4232
|
+
await utils.cancel_on_event(
|
|
4233
|
+
connection, 'disconnection', pending_authentication
|
|
4234
|
+
)
|
|
4204
4235
|
finally:
|
|
4205
4236
|
connection.remove_listener('connection_authentication', on_authentication)
|
|
4206
4237
|
connection.remove_listener(
|
|
@@ -4208,7 +4239,7 @@ class Device(CompositeEventEmitter):
|
|
|
4208
4239
|
)
|
|
4209
4240
|
|
|
4210
4241
|
async def encrypt(self, connection, enable=True):
|
|
4211
|
-
if not enable and connection.transport ==
|
|
4242
|
+
if not enable and connection.transport == PhysicalTransport.LE:
|
|
4212
4243
|
raise InvalidArgumentError('`enable` parameter is classic only.')
|
|
4213
4244
|
|
|
4214
4245
|
# Set up event handlers
|
|
@@ -4225,7 +4256,7 @@ class Device(CompositeEventEmitter):
|
|
|
4225
4256
|
|
|
4226
4257
|
# Request the encryption
|
|
4227
4258
|
try:
|
|
4228
|
-
if connection.transport ==
|
|
4259
|
+
if connection.transport == PhysicalTransport.LE:
|
|
4229
4260
|
# Look for a key in the key store
|
|
4230
4261
|
if self.keystore is None:
|
|
4231
4262
|
raise InvalidOperationError('no key store')
|
|
@@ -4246,7 +4277,7 @@ class Device(CompositeEventEmitter):
|
|
|
4246
4277
|
else:
|
|
4247
4278
|
raise InvalidOperationError('no LTK found for peer')
|
|
4248
4279
|
|
|
4249
|
-
if connection.role != hci.
|
|
4280
|
+
if connection.role != hci.Role.CENTRAL:
|
|
4250
4281
|
raise InvalidStateError('only centrals can start encryption')
|
|
4251
4282
|
|
|
4252
4283
|
result = await self.send_command(
|
|
@@ -4280,7 +4311,7 @@ class Device(CompositeEventEmitter):
|
|
|
4280
4311
|
raise hci.HCI_StatusError(result)
|
|
4281
4312
|
|
|
4282
4313
|
# Wait for the result
|
|
4283
|
-
await
|
|
4314
|
+
await utils.cancel_on_event(connection, 'disconnection', pending_encryption)
|
|
4284
4315
|
finally:
|
|
4285
4316
|
connection.remove_listener(
|
|
4286
4317
|
'connection_encryption_change', on_encryption_change
|
|
@@ -4324,7 +4355,9 @@ class Device(CompositeEventEmitter):
|
|
|
4324
4355
|
f'{hci.HCI_Constant.error_name(result.status)}'
|
|
4325
4356
|
)
|
|
4326
4357
|
raise hci.HCI_StatusError(result)
|
|
4327
|
-
await
|
|
4358
|
+
await utils.cancel_on_event(
|
|
4359
|
+
connection, 'disconnection', pending_role_change
|
|
4360
|
+
)
|
|
4328
4361
|
finally:
|
|
4329
4362
|
connection.remove_listener('role_change', on_role_change)
|
|
4330
4363
|
connection.remove_listener('role_change_failure', on_role_change_failure)
|
|
@@ -4373,13 +4406,13 @@ class Device(CompositeEventEmitter):
|
|
|
4373
4406
|
raise hci.HCI_StatusError(result)
|
|
4374
4407
|
|
|
4375
4408
|
# Wait for the result
|
|
4376
|
-
return await
|
|
4409
|
+
return await utils.cancel_on_event(self, 'flush', pending_name)
|
|
4377
4410
|
finally:
|
|
4378
4411
|
self.remove_listener('remote_name', handler)
|
|
4379
4412
|
self.remove_listener('remote_name_failure', failure_handler)
|
|
4380
4413
|
|
|
4381
4414
|
# [LE only]
|
|
4382
|
-
@experimental('Only for testing.')
|
|
4415
|
+
@utils.experimental('Only for testing.')
|
|
4383
4416
|
async def setup_cig(
|
|
4384
4417
|
self,
|
|
4385
4418
|
cig_id: int,
|
|
@@ -4437,7 +4470,7 @@ class Device(CompositeEventEmitter):
|
|
|
4437
4470
|
return cis_handles
|
|
4438
4471
|
|
|
4439
4472
|
# [LE only]
|
|
4440
|
-
@experimental('Only for testing.')
|
|
4473
|
+
@utils.experimental('Only for testing.')
|
|
4441
4474
|
async def create_cis(
|
|
4442
4475
|
self, cis_acl_pairs: Sequence[tuple[int, int]]
|
|
4443
4476
|
) -> list[CisLink]:
|
|
@@ -4453,7 +4486,7 @@ class Device(CompositeEventEmitter):
|
|
|
4453
4486
|
cig_id=cig_id,
|
|
4454
4487
|
)
|
|
4455
4488
|
|
|
4456
|
-
with closing(EventWatcher()) as watcher:
|
|
4489
|
+
with closing(utils.EventWatcher()) as watcher:
|
|
4457
4490
|
pending_cis_establishments = {
|
|
4458
4491
|
cis_handle: asyncio.get_running_loop().create_future()
|
|
4459
4492
|
for cis_handle, _ in cis_acl_pairs
|
|
@@ -4480,7 +4513,7 @@ class Device(CompositeEventEmitter):
|
|
|
4480
4513
|
return await asyncio.gather(*pending_cis_establishments.values())
|
|
4481
4514
|
|
|
4482
4515
|
# [LE only]
|
|
4483
|
-
@experimental('Only for testing.')
|
|
4516
|
+
@utils.experimental('Only for testing.')
|
|
4484
4517
|
async def accept_cis_request(self, handle: int) -> CisLink:
|
|
4485
4518
|
"""[LE Only] Accepts an incoming CIS request.
|
|
4486
4519
|
|
|
@@ -4502,7 +4535,7 @@ class Device(CompositeEventEmitter):
|
|
|
4502
4535
|
if cis_link.state == CisLink.State.ESTABLISHED:
|
|
4503
4536
|
return cis_link
|
|
4504
4537
|
|
|
4505
|
-
with closing(EventWatcher()) as watcher:
|
|
4538
|
+
with closing(utils.EventWatcher()) as watcher:
|
|
4506
4539
|
pending_establishment = asyncio.get_running_loop().create_future()
|
|
4507
4540
|
|
|
4508
4541
|
def on_establishment() -> None:
|
|
@@ -4526,7 +4559,7 @@ class Device(CompositeEventEmitter):
|
|
|
4526
4559
|
raise UnreachableError()
|
|
4527
4560
|
|
|
4528
4561
|
# [LE only]
|
|
4529
|
-
@experimental('Only for testing.')
|
|
4562
|
+
@utils.experimental('Only for testing.')
|
|
4530
4563
|
async def reject_cis_request(
|
|
4531
4564
|
self,
|
|
4532
4565
|
handle: int,
|
|
@@ -4540,14 +4573,14 @@ class Device(CompositeEventEmitter):
|
|
|
4540
4573
|
)
|
|
4541
4574
|
|
|
4542
4575
|
# [LE only]
|
|
4543
|
-
@experimental('Only for testing.')
|
|
4576
|
+
@utils.experimental('Only for testing.')
|
|
4544
4577
|
async def create_big(
|
|
4545
4578
|
self, advertising_set: AdvertisingSet, parameters: BigParameters
|
|
4546
4579
|
) -> Big:
|
|
4547
4580
|
if (big_handle := self.next_big_handle()) is None:
|
|
4548
4581
|
raise core.OutOfResourcesError("All valid BIG handles already in use")
|
|
4549
4582
|
|
|
4550
|
-
with closing(EventWatcher()) as watcher:
|
|
4583
|
+
with closing(utils.EventWatcher()) as watcher:
|
|
4551
4584
|
big = Big(
|
|
4552
4585
|
big_handle=big_handle,
|
|
4553
4586
|
parameters=parameters,
|
|
@@ -4590,7 +4623,7 @@ class Device(CompositeEventEmitter):
|
|
|
4590
4623
|
return big
|
|
4591
4624
|
|
|
4592
4625
|
# [LE only]
|
|
4593
|
-
@experimental('Only for testing.')
|
|
4626
|
+
@utils.experimental('Only for testing.')
|
|
4594
4627
|
async def create_big_sync(
|
|
4595
4628
|
self, pa_sync: PeriodicAdvertisingSync, parameters: BigSyncParameters
|
|
4596
4629
|
) -> BigSync:
|
|
@@ -4600,7 +4633,7 @@ class Device(CompositeEventEmitter):
|
|
|
4600
4633
|
if (pa_sync_handle := pa_sync.sync_handle) is None:
|
|
4601
4634
|
raise core.InvalidStateError("PA Sync is not established")
|
|
4602
4635
|
|
|
4603
|
-
with closing(EventWatcher()) as watcher:
|
|
4636
|
+
with closing(utils.EventWatcher()) as watcher:
|
|
4604
4637
|
big_sync = BigSync(
|
|
4605
4638
|
big_handle=big_handle,
|
|
4606
4639
|
parameters=parameters,
|
|
@@ -4648,7 +4681,7 @@ class Device(CompositeEventEmitter):
|
|
|
4648
4681
|
Returns:
|
|
4649
4682
|
LE features supported by the remote device.
|
|
4650
4683
|
"""
|
|
4651
|
-
with closing(EventWatcher()) as watcher:
|
|
4684
|
+
with closing(utils.EventWatcher()) as watcher:
|
|
4652
4685
|
read_feature_future: asyncio.Future[hci.LeFeatureMask] = (
|
|
4653
4686
|
asyncio.get_running_loop().create_future()
|
|
4654
4687
|
)
|
|
@@ -4671,7 +4704,7 @@ class Device(CompositeEventEmitter):
|
|
|
4671
4704
|
)
|
|
4672
4705
|
return await read_feature_future
|
|
4673
4706
|
|
|
4674
|
-
@experimental('Only for testing.')
|
|
4707
|
+
@utils.experimental('Only for testing.')
|
|
4675
4708
|
async def get_remote_cs_capabilities(
|
|
4676
4709
|
self, connection: Connection
|
|
4677
4710
|
) -> ChannelSoundingCapabilities:
|
|
@@ -4679,7 +4712,7 @@ class Device(CompositeEventEmitter):
|
|
|
4679
4712
|
asyncio.get_running_loop().create_future()
|
|
4680
4713
|
)
|
|
4681
4714
|
|
|
4682
|
-
with closing(EventWatcher()) as watcher:
|
|
4715
|
+
with closing(utils.EventWatcher()) as watcher:
|
|
4683
4716
|
watcher.once(
|
|
4684
4717
|
connection, 'channel_sounding_capabilities', complete_future.set_result
|
|
4685
4718
|
)
|
|
@@ -4696,7 +4729,7 @@ class Device(CompositeEventEmitter):
|
|
|
4696
4729
|
)
|
|
4697
4730
|
return await complete_future
|
|
4698
4731
|
|
|
4699
|
-
@experimental('Only for testing.')
|
|
4732
|
+
@utils.experimental('Only for testing.')
|
|
4700
4733
|
async def set_default_cs_settings(
|
|
4701
4734
|
self,
|
|
4702
4735
|
connection: Connection,
|
|
@@ -4716,7 +4749,7 @@ class Device(CompositeEventEmitter):
|
|
|
4716
4749
|
check_result=True,
|
|
4717
4750
|
)
|
|
4718
4751
|
|
|
4719
|
-
@experimental('Only for testing.')
|
|
4752
|
+
@utils.experimental('Only for testing.')
|
|
4720
4753
|
async def create_cs_config(
|
|
4721
4754
|
self,
|
|
4722
4755
|
connection: Connection,
|
|
@@ -4753,7 +4786,7 @@ class Device(CompositeEventEmitter):
|
|
|
4753
4786
|
if config_id is None:
|
|
4754
4787
|
raise OutOfResourcesError("No available config ID on this connection!")
|
|
4755
4788
|
|
|
4756
|
-
with closing(EventWatcher()) as watcher:
|
|
4789
|
+
with closing(utils.EventWatcher()) as watcher:
|
|
4757
4790
|
watcher.once(
|
|
4758
4791
|
connection, 'channel_sounding_config', complete_future.set_result
|
|
4759
4792
|
)
|
|
@@ -4787,12 +4820,12 @@ class Device(CompositeEventEmitter):
|
|
|
4787
4820
|
)
|
|
4788
4821
|
return await complete_future
|
|
4789
4822
|
|
|
4790
|
-
@experimental('Only for testing.')
|
|
4823
|
+
@utils.experimental('Only for testing.')
|
|
4791
4824
|
async def enable_cs_security(self, connection: Connection) -> None:
|
|
4792
4825
|
complete_future: asyncio.Future[None] = (
|
|
4793
4826
|
asyncio.get_running_loop().create_future()
|
|
4794
4827
|
)
|
|
4795
|
-
with closing(EventWatcher()) as watcher:
|
|
4828
|
+
with closing(utils.EventWatcher()) as watcher:
|
|
4796
4829
|
|
|
4797
4830
|
def on_event(event: hci.HCI_LE_CS_Security_Enable_Complete_Event) -> None:
|
|
4798
4831
|
if event.connection_handle != connection.handle:
|
|
@@ -4811,7 +4844,7 @@ class Device(CompositeEventEmitter):
|
|
|
4811
4844
|
)
|
|
4812
4845
|
return await complete_future
|
|
4813
4846
|
|
|
4814
|
-
@experimental('Only for testing.')
|
|
4847
|
+
@utils.experimental('Only for testing.')
|
|
4815
4848
|
async def set_cs_procedure_parameters(
|
|
4816
4849
|
self,
|
|
4817
4850
|
connection: Connection,
|
|
@@ -4849,7 +4882,7 @@ class Device(CompositeEventEmitter):
|
|
|
4849
4882
|
check_result=True,
|
|
4850
4883
|
)
|
|
4851
4884
|
|
|
4852
|
-
@experimental('Only for testing.')
|
|
4885
|
+
@utils.experimental('Only for testing.')
|
|
4853
4886
|
async def enable_cs_procedure(
|
|
4854
4887
|
self,
|
|
4855
4888
|
connection: Connection,
|
|
@@ -4859,7 +4892,7 @@ class Device(CompositeEventEmitter):
|
|
|
4859
4892
|
complete_future: asyncio.Future[ChannelSoundingProcedure] = (
|
|
4860
4893
|
asyncio.get_running_loop().create_future()
|
|
4861
4894
|
)
|
|
4862
|
-
with closing(EventWatcher()) as watcher:
|
|
4895
|
+
with closing(utils.EventWatcher()) as watcher:
|
|
4863
4896
|
watcher.once(
|
|
4864
4897
|
connection, 'channel_sounding_procedure', complete_future.set_result
|
|
4865
4898
|
)
|
|
@@ -4899,10 +4932,12 @@ class Device(CompositeEventEmitter):
|
|
|
4899
4932
|
value=link_key, authenticated=authenticated
|
|
4900
4933
|
)
|
|
4901
4934
|
|
|
4902
|
-
|
|
4935
|
+
utils.cancel_on_event(
|
|
4936
|
+
self, 'flush', self.update_keys(str(bd_addr), pairing_keys)
|
|
4937
|
+
)
|
|
4903
4938
|
|
|
4904
4939
|
if connection := self.find_connection_by_bd_addr(
|
|
4905
|
-
bd_addr, transport=
|
|
4940
|
+
bd_addr, transport=PhysicalTransport.BR_EDR
|
|
4906
4941
|
):
|
|
4907
4942
|
connection.link_key_type = key_type
|
|
4908
4943
|
|
|
@@ -5168,7 +5203,7 @@ class Device(CompositeEventEmitter):
|
|
|
5168
5203
|
if advertising_set.auto_restart:
|
|
5169
5204
|
connection.once(
|
|
5170
5205
|
'disconnection',
|
|
5171
|
-
lambda _:
|
|
5206
|
+
lambda _: utils.cancel_on_event(self, 'flush', advertising_set.start()),
|
|
5172
5207
|
)
|
|
5173
5208
|
|
|
5174
5209
|
self.emit('connection', connection)
|
|
@@ -5202,7 +5237,7 @@ class Device(CompositeEventEmitter):
|
|
|
5202
5237
|
'new connection reuses the same handle as a previous connection'
|
|
5203
5238
|
)
|
|
5204
5239
|
|
|
5205
|
-
if transport ==
|
|
5240
|
+
if transport == PhysicalTransport.BR_EDR:
|
|
5206
5241
|
# Create a new connection
|
|
5207
5242
|
connection = self.pending_connections.pop(peer_address)
|
|
5208
5243
|
connection.complete(connection_handle, connection_parameters)
|
|
@@ -5225,7 +5260,7 @@ class Device(CompositeEventEmitter):
|
|
|
5225
5260
|
|
|
5226
5261
|
self_address = None
|
|
5227
5262
|
own_address_type: Optional[hci.OwnAddressType] = None
|
|
5228
|
-
if role == hci.
|
|
5263
|
+
if role == hci.Role.CENTRAL:
|
|
5229
5264
|
own_address_type = self.connect_own_address_type
|
|
5230
5265
|
assert own_address_type is not None
|
|
5231
5266
|
else:
|
|
@@ -5272,22 +5307,22 @@ class Device(CompositeEventEmitter):
|
|
|
5272
5307
|
)
|
|
5273
5308
|
self.connections[connection_handle] = connection
|
|
5274
5309
|
|
|
5275
|
-
if role == hci.
|
|
5310
|
+
if role == hci.Role.PERIPHERAL and self.legacy_advertiser:
|
|
5276
5311
|
if self.legacy_advertiser.auto_restart:
|
|
5277
5312
|
advertiser = self.legacy_advertiser
|
|
5278
5313
|
connection.once(
|
|
5279
5314
|
'disconnection',
|
|
5280
|
-
lambda _:
|
|
5315
|
+
lambda _: utils.cancel_on_event(self, 'flush', advertiser.start()),
|
|
5281
5316
|
)
|
|
5282
5317
|
else:
|
|
5283
5318
|
self.legacy_advertiser = None
|
|
5284
5319
|
|
|
5285
|
-
if role == hci.
|
|
5320
|
+
if role == hci.Role.CENTRAL or not self.supports_le_extended_advertising:
|
|
5286
5321
|
# We can emit now, we have all the info we need
|
|
5287
5322
|
self.emit('connection', connection)
|
|
5288
5323
|
return
|
|
5289
5324
|
|
|
5290
|
-
if role == hci.
|
|
5325
|
+
if role == hci.Role.PERIPHERAL and self.supports_le_extended_advertising:
|
|
5291
5326
|
if advertising_set := self.connecting_extended_advertising_sets.pop(
|
|
5292
5327
|
connection_handle, None
|
|
5293
5328
|
):
|
|
@@ -5304,7 +5339,7 @@ class Device(CompositeEventEmitter):
|
|
|
5304
5339
|
|
|
5305
5340
|
# For directed advertising, this means a timeout
|
|
5306
5341
|
if (
|
|
5307
|
-
transport ==
|
|
5342
|
+
transport == PhysicalTransport.LE
|
|
5308
5343
|
and self.legacy_advertiser
|
|
5309
5344
|
and self.legacy_advertiser.advertising_type.is_directed
|
|
5310
5345
|
):
|
|
@@ -5331,7 +5366,7 @@ class Device(CompositeEventEmitter):
|
|
|
5331
5366
|
hci.HCI_Connection_Complete_Event.ESCO_LINK_TYPE,
|
|
5332
5367
|
):
|
|
5333
5368
|
if connection := self.find_connection_by_bd_addr(
|
|
5334
|
-
bd_addr, transport=
|
|
5369
|
+
bd_addr, transport=PhysicalTransport.BR_EDR
|
|
5335
5370
|
):
|
|
5336
5371
|
self.emit('sco_request', connection, link_type)
|
|
5337
5372
|
else:
|
|
@@ -5404,7 +5439,7 @@ class Device(CompositeEventEmitter):
|
|
|
5404
5439
|
connection.emit('disconnection_failure', error)
|
|
5405
5440
|
|
|
5406
5441
|
@host_event_handler
|
|
5407
|
-
@AsyncRunner.run_in_task()
|
|
5442
|
+
@utils.AsyncRunner.run_in_task()
|
|
5408
5443
|
async def on_inquiry_complete(self):
|
|
5409
5444
|
if self.auto_restart_inquiry:
|
|
5410
5445
|
# Inquire again
|
|
@@ -5536,7 +5571,7 @@ class Device(CompositeEventEmitter):
|
|
|
5536
5571
|
|
|
5537
5572
|
async def reply() -> None:
|
|
5538
5573
|
try:
|
|
5539
|
-
if await
|
|
5574
|
+
if await utils.cancel_on_event(connection, 'disconnection', method()):
|
|
5540
5575
|
await self.host.send_command(
|
|
5541
5576
|
hci.HCI_User_Confirmation_Request_Reply_Command(
|
|
5542
5577
|
bd_addr=connection.peer_address
|
|
@@ -5552,7 +5587,7 @@ class Device(CompositeEventEmitter):
|
|
|
5552
5587
|
)
|
|
5553
5588
|
)
|
|
5554
5589
|
|
|
5555
|
-
AsyncRunner.spawn(reply())
|
|
5590
|
+
utils.AsyncRunner.spawn(reply())
|
|
5556
5591
|
|
|
5557
5592
|
# [Classic only]
|
|
5558
5593
|
@host_event_handler
|
|
@@ -5563,8 +5598,8 @@ class Device(CompositeEventEmitter):
|
|
|
5563
5598
|
|
|
5564
5599
|
async def reply() -> None:
|
|
5565
5600
|
try:
|
|
5566
|
-
number = await
|
|
5567
|
-
'disconnection', pairing_config.delegate.get_number()
|
|
5601
|
+
number = await utils.cancel_on_event(
|
|
5602
|
+
connection, 'disconnection', pairing_config.delegate.get_number()
|
|
5568
5603
|
)
|
|
5569
5604
|
if number is not None:
|
|
5570
5605
|
await self.host.send_command(
|
|
@@ -5582,7 +5617,7 @@ class Device(CompositeEventEmitter):
|
|
|
5582
5617
|
)
|
|
5583
5618
|
)
|
|
5584
5619
|
|
|
5585
|
-
AsyncRunner.spawn(reply())
|
|
5620
|
+
utils.AsyncRunner.spawn(reply())
|
|
5586
5621
|
|
|
5587
5622
|
# [Classic only]
|
|
5588
5623
|
@host_event_handler
|
|
@@ -5597,8 +5632,8 @@ class Device(CompositeEventEmitter):
|
|
|
5597
5632
|
if io_capability == hci.HCI_KEYBOARD_ONLY_IO_CAPABILITY:
|
|
5598
5633
|
# Ask the user to enter a string
|
|
5599
5634
|
async def get_pin_code():
|
|
5600
|
-
pin_code = await
|
|
5601
|
-
'disconnection', pairing_config.delegate.get_string(16)
|
|
5635
|
+
pin_code = await utils.cancel_on_event(
|
|
5636
|
+
connection, 'disconnection', pairing_config.delegate.get_string(16)
|
|
5602
5637
|
)
|
|
5603
5638
|
|
|
5604
5639
|
if pin_code is not None:
|
|
@@ -5636,8 +5671,8 @@ class Device(CompositeEventEmitter):
|
|
|
5636
5671
|
pairing_config = self.pairing_config_factory(connection)
|
|
5637
5672
|
|
|
5638
5673
|
# Show the passkey to the user
|
|
5639
|
-
|
|
5640
|
-
'disconnection', pairing_config.delegate.display_number(passkey)
|
|
5674
|
+
utils.cancel_on_event(
|
|
5675
|
+
connection, 'disconnection', pairing_config.delegate.display_number(passkey)
|
|
5641
5676
|
)
|
|
5642
5677
|
|
|
5643
5678
|
# [Classic only]
|
|
@@ -5669,7 +5704,7 @@ class Device(CompositeEventEmitter):
|
|
|
5669
5704
|
# [Classic only]
|
|
5670
5705
|
@host_event_handler
|
|
5671
5706
|
@with_connection_from_address
|
|
5672
|
-
@experimental('Only for testing.')
|
|
5707
|
+
@utils.experimental('Only for testing.')
|
|
5673
5708
|
def on_sco_connection(
|
|
5674
5709
|
self, acl_connection: Connection, sco_handle: int, link_type: int
|
|
5675
5710
|
) -> None:
|
|
@@ -5689,7 +5724,7 @@ class Device(CompositeEventEmitter):
|
|
|
5689
5724
|
# [Classic only]
|
|
5690
5725
|
@host_event_handler
|
|
5691
5726
|
@with_connection_from_address
|
|
5692
|
-
@experimental('Only for testing.')
|
|
5727
|
+
@utils.experimental('Only for testing.')
|
|
5693
5728
|
def on_sco_connection_failure(
|
|
5694
5729
|
self, acl_connection: Connection, status: int
|
|
5695
5730
|
) -> None:
|
|
@@ -5698,7 +5733,7 @@ class Device(CompositeEventEmitter):
|
|
|
5698
5733
|
|
|
5699
5734
|
# [Classic only]
|
|
5700
5735
|
@host_event_handler
|
|
5701
|
-
@experimental('Only for testing')
|
|
5736
|
+
@utils.experimental('Only for testing')
|
|
5702
5737
|
def on_sco_packet(
|
|
5703
5738
|
self, sco_handle: int, packet: hci.HCI_SynchronousDataPacket
|
|
5704
5739
|
) -> None:
|
|
@@ -5708,7 +5743,7 @@ class Device(CompositeEventEmitter):
|
|
|
5708
5743
|
# [LE only]
|
|
5709
5744
|
@host_event_handler
|
|
5710
5745
|
@with_connection_from_handle
|
|
5711
|
-
@experimental('Only for testing')
|
|
5746
|
+
@utils.experimental('Only for testing')
|
|
5712
5747
|
def on_cis_request(
|
|
5713
5748
|
self,
|
|
5714
5749
|
acl_connection: Connection,
|
|
@@ -5735,7 +5770,7 @@ class Device(CompositeEventEmitter):
|
|
|
5735
5770
|
|
|
5736
5771
|
# [LE only]
|
|
5737
5772
|
@host_event_handler
|
|
5738
|
-
@experimental('Only for testing')
|
|
5773
|
+
@utils.experimental('Only for testing')
|
|
5739
5774
|
def on_cis_establishment(self, cis_handle: int) -> None:
|
|
5740
5775
|
cis_link = self.cis_links[cis_handle]
|
|
5741
5776
|
cis_link.state = CisLink.State.ESTABLISHED
|
|
@@ -5755,7 +5790,7 @@ class Device(CompositeEventEmitter):
|
|
|
5755
5790
|
|
|
5756
5791
|
# [LE only]
|
|
5757
5792
|
@host_event_handler
|
|
5758
|
-
@experimental('Only for testing')
|
|
5793
|
+
@utils.experimental('Only for testing')
|
|
5759
5794
|
def on_cis_establishment_failure(self, cis_handle: int, status: int) -> None:
|
|
5760
5795
|
logger.debug(f'*** CIS Establishment Failure: cis=[0x{cis_handle:04X}] ***')
|
|
5761
5796
|
if cis_link := self.cis_links.pop(cis_handle):
|
|
@@ -5764,7 +5799,7 @@ class Device(CompositeEventEmitter):
|
|
|
5764
5799
|
|
|
5765
5800
|
# [LE only]
|
|
5766
5801
|
@host_event_handler
|
|
5767
|
-
@experimental('Only for testing')
|
|
5802
|
+
@utils.experimental('Only for testing')
|
|
5768
5803
|
def on_iso_packet(self, handle: int, packet: hci.HCI_IsoDataPacket) -> None:
|
|
5769
5804
|
if (cis_link := self.cis_links.get(handle)) and cis_link.sink:
|
|
5770
5805
|
cis_link.sink(packet)
|
|
@@ -5782,14 +5817,14 @@ class Device(CompositeEventEmitter):
|
|
|
5782
5817
|
connection.encryption = encryption
|
|
5783
5818
|
if (
|
|
5784
5819
|
not connection.authenticated
|
|
5785
|
-
and connection.transport ==
|
|
5820
|
+
and connection.transport == PhysicalTransport.BR_EDR
|
|
5786
5821
|
and encryption == hci.HCI_Encryption_Change_Event.AES_CCM
|
|
5787
5822
|
):
|
|
5788
5823
|
connection.authenticated = True
|
|
5789
5824
|
connection.sc = True
|
|
5790
5825
|
if (
|
|
5791
5826
|
not connection.authenticated
|
|
5792
|
-
and connection.transport ==
|
|
5827
|
+
and connection.transport == PhysicalTransport.LE
|
|
5793
5828
|
and encryption == hci.HCI_Encryption_Change_Event.E0_OR_AES_CCM
|
|
5794
5829
|
):
|
|
5795
5830
|
connection.authenticated = True
|