bumble 0.0.220__py3-none-any.whl → 0.0.221__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 +5 -5
- bumble/apps/auracast.py +746 -473
- bumble/apps/bench.py +4 -5
- bumble/apps/console.py +5 -10
- bumble/apps/controller_info.py +12 -7
- bumble/apps/controller_loopback.py +1 -2
- bumble/apps/device_info.py +2 -3
- bumble/apps/gatt_dump.py +0 -1
- bumble/apps/lea_unicast/app.py +1 -1
- bumble/apps/pair.py +49 -46
- bumble/apps/pandora_server.py +2 -2
- bumble/apps/player/player.py +10 -12
- bumble/apps/rfcomm_bridge.py +10 -11
- bumble/apps/scan.py +1 -3
- bumble/apps/speaker/speaker.py +3 -4
- bumble/at.py +4 -5
- bumble/att.py +91 -25
- bumble/audio/io.py +5 -3
- bumble/avc.py +1 -2
- bumble/avctp.py +2 -3
- bumble/avdtp.py +53 -57
- bumble/avrcp.py +25 -27
- bumble/codecs.py +15 -15
- bumble/colors.py +7 -8
- bumble/controller.py +663 -391
- bumble/core.py +41 -49
- bumble/crypto/__init__.py +2 -1
- bumble/crypto/builtin.py +2 -8
- bumble/data_types.py +2 -1
- bumble/decoder.py +2 -3
- bumble/device.py +171 -142
- bumble/drivers/__init__.py +3 -2
- bumble/drivers/intel.py +6 -8
- bumble/drivers/rtk.py +1 -1
- bumble/gatt.py +9 -9
- bumble/gatt_adapters.py +6 -6
- bumble/gatt_client.py +110 -60
- bumble/gatt_server.py +209 -139
- bumble/hci.py +87 -74
- bumble/helpers.py +5 -5
- bumble/hfp.py +27 -26
- bumble/hid.py +9 -9
- bumble/host.py +44 -50
- bumble/keys.py +17 -17
- bumble/l2cap.py +1015 -218
- bumble/link.py +26 -159
- bumble/ll.py +200 -0
- bumble/pairing.py +14 -15
- bumble/pandora/__init__.py +2 -2
- bumble/pandora/device.py +6 -4
- bumble/pandora/host.py +19 -10
- bumble/pandora/l2cap.py +8 -9
- bumble/pandora/security.py +18 -16
- bumble/pandora/utils.py +4 -4
- bumble/profiles/aics.py +6 -8
- bumble/profiles/ams.py +3 -5
- bumble/profiles/ancs.py +11 -11
- bumble/profiles/ascs.py +5 -5
- bumble/profiles/asha.py +10 -9
- bumble/profiles/bass.py +9 -3
- bumble/profiles/battery_service.py +1 -2
- bumble/profiles/csip.py +9 -10
- bumble/profiles/device_information_service.py +16 -17
- bumble/profiles/gap.py +3 -4
- bumble/profiles/gatt_service.py +0 -1
- bumble/profiles/gmap.py +12 -13
- bumble/profiles/hap.py +3 -3
- bumble/profiles/heart_rate_service.py +7 -8
- bumble/profiles/le_audio.py +1 -1
- bumble/profiles/mcp.py +28 -28
- bumble/profiles/pacs.py +13 -17
- bumble/profiles/pbp.py +16 -0
- bumble/profiles/vcs.py +2 -2
- bumble/profiles/vocs.py +6 -9
- bumble/rfcomm.py +19 -18
- bumble/sdp.py +12 -11
- bumble/smp.py +20 -30
- bumble/snoop.py +2 -1
- bumble/tools/generate_company_id_list.py +1 -1
- bumble/tools/intel_util.py +2 -2
- bumble/tools/rtk_fw_download.py +1 -1
- bumble/tools/rtk_util.py +1 -1
- bumble/transport/__init__.py +1 -2
- bumble/transport/android_emulator.py +2 -3
- bumble/transport/android_netsim.py +49 -40
- bumble/transport/common.py +9 -9
- bumble/transport/file.py +1 -2
- bumble/transport/hci_socket.py +2 -3
- bumble/transport/pty.py +3 -5
- bumble/transport/pyusb.py +8 -5
- bumble/transport/serial.py +1 -2
- bumble/transport/vhci.py +1 -2
- bumble/transport/ws_server.py +2 -3
- bumble/utils.py +22 -9
- bumble/vendor/android/hci.py +4 -2
- {bumble-0.0.220.dist-info → bumble-0.0.221.dist-info}/METADATA +3 -2
- {bumble-0.0.220.dist-info → bumble-0.0.221.dist-info}/RECORD +102 -101
- {bumble-0.0.220.dist-info → bumble-0.0.221.dist-info}/WHEEL +0 -0
- {bumble-0.0.220.dist-info → bumble-0.0.221.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.220.dist-info → bumble-0.0.221.dist-info}/licenses/LICENSE +0 -0
- {bumble-0.0.220.dist-info → bumble-0.0.221.dist-info}/top_level.txt +0 -0
bumble/device.py
CHANGED
|
@@ -25,20 +25,15 @@ import itertools
|
|
|
25
25
|
import json
|
|
26
26
|
import logging
|
|
27
27
|
import secrets
|
|
28
|
-
import
|
|
29
|
-
from collections.abc import Iterable, Sequence
|
|
28
|
+
from collections.abc import Awaitable, Callable, Iterable, Sequence
|
|
30
29
|
from contextlib import AsyncExitStack, asynccontextmanager, closing
|
|
31
30
|
from dataclasses import dataclass, field
|
|
32
31
|
from enum import Enum, IntEnum
|
|
33
32
|
from typing import (
|
|
34
33
|
TYPE_CHECKING,
|
|
35
34
|
Any,
|
|
36
|
-
Awaitable,
|
|
37
|
-
Callable,
|
|
38
35
|
ClassVar,
|
|
39
|
-
Optional,
|
|
40
36
|
TypeVar,
|
|
41
|
-
Union,
|
|
42
37
|
cast,
|
|
43
38
|
overload,
|
|
44
39
|
)
|
|
@@ -46,6 +41,7 @@ from typing import (
|
|
|
46
41
|
from typing_extensions import Self
|
|
47
42
|
|
|
48
43
|
from bumble import (
|
|
44
|
+
att,
|
|
49
45
|
core,
|
|
50
46
|
data_types,
|
|
51
47
|
gatt,
|
|
@@ -58,7 +54,6 @@ from bumble import (
|
|
|
58
54
|
smp,
|
|
59
55
|
utils,
|
|
60
56
|
)
|
|
61
|
-
from bumble.att import ATT_CID, ATT_DEFAULT_MTU, ATT_PDU
|
|
62
57
|
from bumble.colors import color
|
|
63
58
|
from bumble.core import (
|
|
64
59
|
AdvertisingData,
|
|
@@ -176,6 +171,7 @@ class Advertisement:
|
|
|
176
171
|
)
|
|
177
172
|
sid: int = 0
|
|
178
173
|
data_bytes: bytes = b''
|
|
174
|
+
data: AdvertisingData = field(init=False)
|
|
179
175
|
|
|
180
176
|
# Constants
|
|
181
177
|
TX_POWER_NOT_AVAILABLE: ClassVar[int] = (
|
|
@@ -189,7 +185,7 @@ class Advertisement:
|
|
|
189
185
|
self.data = AdvertisingData.from_bytes(self.data_bytes)
|
|
190
186
|
|
|
191
187
|
@classmethod
|
|
192
|
-
def from_advertising_report(cls, report) ->
|
|
188
|
+
def from_advertising_report(cls, report) -> Advertisement | None:
|
|
193
189
|
if isinstance(report, hci.HCI_LE_Advertising_Report_Event.Report):
|
|
194
190
|
return LegacyAdvertisement.from_advertising_report(report)
|
|
195
191
|
|
|
@@ -265,12 +261,22 @@ class ExtendedAdvertisement(Advertisement):
|
|
|
265
261
|
|
|
266
262
|
# -----------------------------------------------------------------------------
|
|
267
263
|
class AdvertisementDataAccumulator:
|
|
264
|
+
last_advertisement: Advertisement | None
|
|
265
|
+
last_data: bytes
|
|
266
|
+
passive: bool
|
|
267
|
+
|
|
268
268
|
def __init__(self, passive: bool = False):
|
|
269
269
|
self.passive = passive
|
|
270
270
|
self.last_advertisement = None
|
|
271
271
|
self.last_data = b''
|
|
272
272
|
|
|
273
|
-
def update(
|
|
273
|
+
def update(
|
|
274
|
+
self,
|
|
275
|
+
report: (
|
|
276
|
+
hci.HCI_LE_Advertising_Report_Event.Report
|
|
277
|
+
| hci.HCI_LE_Extended_Advertising_Report_Event.Report
|
|
278
|
+
),
|
|
279
|
+
) -> Advertisement | None:
|
|
274
280
|
advertisement = Advertisement.from_advertising_report(report)
|
|
275
281
|
if advertisement is None:
|
|
276
282
|
return None
|
|
@@ -283,10 +289,12 @@ class AdvertisementDataAccumulator:
|
|
|
283
289
|
and not self.last_advertisement.is_scan_response
|
|
284
290
|
):
|
|
285
291
|
# This is the response to a scannable advertisement
|
|
286
|
-
result
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
292
|
+
if result := Advertisement.from_advertising_report(report):
|
|
293
|
+
result.is_connectable = self.last_advertisement.is_connectable
|
|
294
|
+
result.is_scannable = True
|
|
295
|
+
result.data = AdvertisingData.from_bytes(
|
|
296
|
+
self.last_data + report.data
|
|
297
|
+
)
|
|
290
298
|
self.last_data = b''
|
|
291
299
|
else:
|
|
292
300
|
if (
|
|
@@ -473,6 +481,7 @@ class PeriodicAdvertisement:
|
|
|
473
481
|
rssi: int = hci.HCI_LE_Periodic_Advertising_Report_Event.RSSI_NOT_AVAILABLE
|
|
474
482
|
is_truncated: bool = False
|
|
475
483
|
data_bytes: bytes = b''
|
|
484
|
+
data: AdvertisingData | None = field(init=False)
|
|
476
485
|
|
|
477
486
|
# Constants
|
|
478
487
|
TX_POWER_NOT_AVAILABLE: ClassVar[int] = (
|
|
@@ -593,11 +602,11 @@ class AdvertisingSet(utils.EventEmitter):
|
|
|
593
602
|
device: Device
|
|
594
603
|
advertising_handle: int
|
|
595
604
|
auto_restart: bool
|
|
596
|
-
random_address:
|
|
605
|
+
random_address: hci.Address | None
|
|
597
606
|
advertising_parameters: AdvertisingParameters
|
|
598
607
|
advertising_data: bytes
|
|
599
608
|
scan_response_data: bytes
|
|
600
|
-
periodic_advertising_parameters:
|
|
609
|
+
periodic_advertising_parameters: PeriodicAdvertisingParameters | None
|
|
601
610
|
periodic_advertising_data: bytes
|
|
602
611
|
selected_tx_power: int = 0
|
|
603
612
|
enabled: bool = False
|
|
@@ -844,7 +853,7 @@ class PeriodicAdvertisingSync(utils.EventEmitter):
|
|
|
844
853
|
TERMINATED = 6
|
|
845
854
|
|
|
846
855
|
_state: State
|
|
847
|
-
sync_handle:
|
|
856
|
+
sync_handle: int | None
|
|
848
857
|
advertiser_address: hci.Address
|
|
849
858
|
sid: int
|
|
850
859
|
skip: int
|
|
@@ -857,8 +866,8 @@ class PeriodicAdvertisingSync(utils.EventEmitter):
|
|
|
857
866
|
|
|
858
867
|
EVENT_STATE_CHANGE = "state_change"
|
|
859
868
|
EVENT_ESTABLISHMENT = "establishment"
|
|
869
|
+
EVENT_ESTABLISHMENT_ERROR = "establishment_error"
|
|
860
870
|
EVENT_CANCELLATION = "cancellation"
|
|
861
|
-
EVENT_ERROR = "error"
|
|
862
871
|
EVENT_LOSS = "loss"
|
|
863
872
|
EVENT_PERIODIC_ADVERTISEMENT = "periodic_advertisement"
|
|
864
873
|
EVENT_BIGINFO_ADVERTISEMENT = "biginfo_advertisement"
|
|
@@ -991,7 +1000,7 @@ class PeriodicAdvertisingSync(utils.EventEmitter):
|
|
|
991
1000
|
return
|
|
992
1001
|
|
|
993
1002
|
self.state = self.State.ERROR
|
|
994
|
-
self.emit(self.
|
|
1003
|
+
self.emit(self.EVENT_ESTABLISHMENT_ERROR)
|
|
995
1004
|
|
|
996
1005
|
def on_loss(self):
|
|
997
1006
|
self.state = self.State.LOST
|
|
@@ -1092,6 +1101,7 @@ class Big(utils.EventEmitter):
|
|
|
1092
1101
|
max_pdu: int = 0
|
|
1093
1102
|
iso_interval: float = 0.0 # ISO interval, in milliseconds
|
|
1094
1103
|
bis_links: Sequence[BisLink] = ()
|
|
1104
|
+
device: Device = field(init=False)
|
|
1095
1105
|
|
|
1096
1106
|
def __post_init__(self) -> None:
|
|
1097
1107
|
super().__init__()
|
|
@@ -1153,6 +1163,7 @@ class BigSync(utils.EventEmitter):
|
|
|
1153
1163
|
max_pdu: int = 0
|
|
1154
1164
|
iso_interval: float = 0.0
|
|
1155
1165
|
bis_links: Sequence[BisLink] = ()
|
|
1166
|
+
device: Device = field(init=False)
|
|
1156
1167
|
|
|
1157
1168
|
def __post_init__(self) -> None:
|
|
1158
1169
|
super().__init__()
|
|
@@ -1271,7 +1282,7 @@ class Peer:
|
|
|
1271
1282
|
return mtu
|
|
1272
1283
|
|
|
1273
1284
|
async def discover_service(
|
|
1274
|
-
self, uuid:
|
|
1285
|
+
self, uuid: core.UUID | str
|
|
1275
1286
|
) -> list[gatt_client.ServiceProxy]:
|
|
1276
1287
|
return await self.gatt_client.discover_service(uuid)
|
|
1277
1288
|
|
|
@@ -1287,8 +1298,8 @@ class Peer:
|
|
|
1287
1298
|
|
|
1288
1299
|
async def discover_characteristics(
|
|
1289
1300
|
self,
|
|
1290
|
-
uuids: Iterable[
|
|
1291
|
-
service:
|
|
1301
|
+
uuids: Iterable[core.UUID | str] = (),
|
|
1302
|
+
service: gatt_client.ServiceProxy | None = None,
|
|
1292
1303
|
) -> list[gatt_client.CharacteristicProxy[bytes]]:
|
|
1293
1304
|
return await self.gatt_client.discover_characteristics(
|
|
1294
1305
|
uuids=uuids, service=service
|
|
@@ -1296,9 +1307,9 @@ class Peer:
|
|
|
1296
1307
|
|
|
1297
1308
|
async def discover_descriptors(
|
|
1298
1309
|
self,
|
|
1299
|
-
characteristic:
|
|
1300
|
-
start_handle:
|
|
1301
|
-
end_handle:
|
|
1310
|
+
characteristic: gatt_client.CharacteristicProxy | None = None,
|
|
1311
|
+
start_handle: int | None = None,
|
|
1312
|
+
end_handle: int | None = None,
|
|
1302
1313
|
):
|
|
1303
1314
|
return await self.gatt_client.discover_descriptors(
|
|
1304
1315
|
characteristic, start_handle, end_handle
|
|
@@ -1319,7 +1330,7 @@ class Peer:
|
|
|
1319
1330
|
async def subscribe(
|
|
1320
1331
|
self,
|
|
1321
1332
|
characteristic: gatt_client.CharacteristicProxy,
|
|
1322
|
-
subscriber:
|
|
1333
|
+
subscriber: Callable[[bytes], Any] | None = None,
|
|
1323
1334
|
prefer_notify: bool = True,
|
|
1324
1335
|
) -> None:
|
|
1325
1336
|
return await self.gatt_client.subscribe(
|
|
@@ -1329,25 +1340,23 @@ class Peer:
|
|
|
1329
1340
|
async def unsubscribe(
|
|
1330
1341
|
self,
|
|
1331
1342
|
characteristic: gatt_client.CharacteristicProxy,
|
|
1332
|
-
subscriber:
|
|
1343
|
+
subscriber: Callable[[bytes], Any] | None = None,
|
|
1333
1344
|
) -> None:
|
|
1334
1345
|
return await self.gatt_client.unsubscribe(characteristic, subscriber)
|
|
1335
1346
|
|
|
1336
|
-
async def read_value(
|
|
1337
|
-
self, attribute: Union[int, gatt_client.AttributeProxy]
|
|
1338
|
-
) -> bytes:
|
|
1347
|
+
async def read_value(self, attribute: int | gatt_client.AttributeProxy) -> bytes:
|
|
1339
1348
|
return await self.gatt_client.read_value(attribute)
|
|
1340
1349
|
|
|
1341
1350
|
async def write_value(
|
|
1342
1351
|
self,
|
|
1343
|
-
attribute:
|
|
1352
|
+
attribute: int | gatt_client.AttributeProxy,
|
|
1344
1353
|
value: bytes,
|
|
1345
1354
|
with_response: bool = False,
|
|
1346
1355
|
) -> None:
|
|
1347
1356
|
return await self.gatt_client.write_value(attribute, value, with_response)
|
|
1348
1357
|
|
|
1349
1358
|
async def read_characteristics_by_uuid(
|
|
1350
|
-
self, uuid: core.UUID, service:
|
|
1359
|
+
self, uuid: core.UUID, service: gatt_client.ServiceProxy | None = None
|
|
1351
1360
|
) -> list[bytes]:
|
|
1352
1361
|
return await self.gatt_client.read_characteristics_by_uuid(uuid, service)
|
|
1353
1362
|
|
|
@@ -1357,7 +1366,7 @@ class Peer:
|
|
|
1357
1366
|
def get_characteristics_by_uuid(
|
|
1358
1367
|
self,
|
|
1359
1368
|
uuid: core.UUID,
|
|
1360
|
-
service:
|
|
1369
|
+
service: gatt_client.ServiceProxy | core.UUID | None = None,
|
|
1361
1370
|
) -> list[gatt_client.CharacteristicProxy[bytes]]:
|
|
1362
1371
|
if isinstance(service, core.UUID):
|
|
1363
1372
|
return list(
|
|
@@ -1373,7 +1382,7 @@ class Peer:
|
|
|
1373
1382
|
|
|
1374
1383
|
def create_service_proxy(
|
|
1375
1384
|
self, proxy_class: type[_PROXY_CLASS]
|
|
1376
|
-
) ->
|
|
1385
|
+
) -> _PROXY_CLASS | None:
|
|
1377
1386
|
if proxy := proxy_class.from_client(self.gatt_client):
|
|
1378
1387
|
return cast(_PROXY_CLASS, proxy)
|
|
1379
1388
|
|
|
@@ -1381,7 +1390,7 @@ class Peer:
|
|
|
1381
1390
|
|
|
1382
1391
|
async def discover_service_and_create_proxy(
|
|
1383
1392
|
self, proxy_class: type[_PROXY_CLASS]
|
|
1384
|
-
) ->
|
|
1393
|
+
) -> _PROXY_CLASS | None:
|
|
1385
1394
|
# Discover the first matching service and its characteristics
|
|
1386
1395
|
services = await self.discover_service(proxy_class.SERVICE_CLASS.UUID)
|
|
1387
1396
|
if services:
|
|
@@ -1390,7 +1399,7 @@ class Peer:
|
|
|
1390
1399
|
return self.create_service_proxy(proxy_class)
|
|
1391
1400
|
return None
|
|
1392
1401
|
|
|
1393
|
-
async def sustain(self, timeout:
|
|
1402
|
+
async def sustain(self, timeout: float | None = None) -> None:
|
|
1394
1403
|
await self.connection.sustain(timeout)
|
|
1395
1404
|
|
|
1396
1405
|
# [Classic only]
|
|
@@ -1433,7 +1442,7 @@ class ScoLink(utils.CompositeEventEmitter):
|
|
|
1433
1442
|
acl_connection: Connection
|
|
1434
1443
|
handle: int
|
|
1435
1444
|
link_type: int
|
|
1436
|
-
sink:
|
|
1445
|
+
sink: Callable[[hci.HCI_SynchronousDataPacket], Any] | None = None
|
|
1437
1446
|
|
|
1438
1447
|
EVENT_DISCONNECTION: ClassVar[str] = "disconnection"
|
|
1439
1448
|
EVENT_DISCONNECTION_FAILURE: ClassVar[str] = "disconnection_failure"
|
|
@@ -1616,8 +1625,8 @@ class CisLink(utils.EventEmitter, _IsoLink):
|
|
|
1616
1625
|
cis_sync_delay: int = 0 # CIS sync delay, in microseconds
|
|
1617
1626
|
transport_latency_c_to_p: int = 0 # C->P transport latency, in microseconds
|
|
1618
1627
|
transport_latency_p_to_c: int = 0 # P->C transport latency, in microseconds
|
|
1619
|
-
phy_c_to_p:
|
|
1620
|
-
phy_p_to_c:
|
|
1628
|
+
phy_c_to_p: hci.Phy | None = None
|
|
1629
|
+
phy_p_to_c: hci.Phy | None = None
|
|
1621
1630
|
nse: int = 0
|
|
1622
1631
|
bn_c_to_p: int = 0
|
|
1623
1632
|
bn_p_to_c: int = 0
|
|
@@ -1650,6 +1659,7 @@ class BisLink(_IsoLink):
|
|
|
1650
1659
|
handle: int
|
|
1651
1660
|
big: Big | BigSync
|
|
1652
1661
|
sink: Callable[[hci.HCI_IsoDataPacket], Any] | None = None
|
|
1662
|
+
device: Device = field(init=False)
|
|
1653
1663
|
|
|
1654
1664
|
def __post_init__(self) -> None:
|
|
1655
1665
|
super().__init__()
|
|
@@ -1705,11 +1715,11 @@ class Connection(utils.CompositeEventEmitter):
|
|
|
1705
1715
|
handle: int
|
|
1706
1716
|
transport: core.PhysicalTransport
|
|
1707
1717
|
self_address: hci.Address
|
|
1708
|
-
self_resolvable_address:
|
|
1718
|
+
self_resolvable_address: hci.Address | None
|
|
1709
1719
|
peer_address: hci.Address
|
|
1710
|
-
peer_name:
|
|
1711
|
-
peer_resolvable_address:
|
|
1712
|
-
peer_le_features:
|
|
1720
|
+
peer_name: str | None
|
|
1721
|
+
peer_resolvable_address: hci.Address | None
|
|
1722
|
+
peer_le_features: hci.LeFeatureMask | None
|
|
1713
1723
|
role: hci.Role
|
|
1714
1724
|
parameters: Parameters
|
|
1715
1725
|
encryption: int
|
|
@@ -1717,8 +1727,8 @@ class Connection(utils.CompositeEventEmitter):
|
|
|
1717
1727
|
authenticated: bool
|
|
1718
1728
|
sc: bool
|
|
1719
1729
|
gatt_client: gatt_client.Client
|
|
1720
|
-
pairing_peer_io_capability:
|
|
1721
|
-
pairing_peer_authentication_requirements:
|
|
1730
|
+
pairing_peer_io_capability: int | None
|
|
1731
|
+
pairing_peer_authentication_requirements: int | None
|
|
1722
1732
|
cs_configs: dict[int, ChannelSoundingConfig] # Config ID to Configuration
|
|
1723
1733
|
cs_procedures: dict[int, ChannelSoundingProcedure] # Config ID to Procedures
|
|
1724
1734
|
classic_mode: int = hci.HCI_Mode_Change_Event.Mode.ACTIVE
|
|
@@ -1738,7 +1748,6 @@ class Connection(utils.CompositeEventEmitter):
|
|
|
1738
1748
|
EVENT_CONNECTION_PARAMETERS_UPDATE_FAILURE = "connection_parameters_update_failure"
|
|
1739
1749
|
EVENT_CONNECTION_PHY_UPDATE = "connection_phy_update"
|
|
1740
1750
|
EVENT_CONNECTION_PHY_UPDATE_FAILURE = "connection_phy_update_failure"
|
|
1741
|
-
EVENT_CONNECTION_ATT_MTU_UPDATE = "connection_att_mtu_update"
|
|
1742
1751
|
EVENT_CONNECTION_DATA_LENGTH_CHANGE = "connection_data_length_change"
|
|
1743
1752
|
EVENT_CHANNEL_SOUNDING_CAPABILITIES_FAILURE = (
|
|
1744
1753
|
"channel_sounding_capabilities_failure"
|
|
@@ -1820,9 +1829,9 @@ class Connection(utils.CompositeEventEmitter):
|
|
|
1820
1829
|
handle: int,
|
|
1821
1830
|
transport: core.PhysicalTransport,
|
|
1822
1831
|
self_address: hci.Address,
|
|
1823
|
-
self_resolvable_address:
|
|
1832
|
+
self_resolvable_address: hci.Address | None,
|
|
1824
1833
|
peer_address: hci.Address,
|
|
1825
|
-
peer_resolvable_address:
|
|
1834
|
+
peer_resolvable_address: hci.Address | None,
|
|
1826
1835
|
role: hci.Role,
|
|
1827
1836
|
parameters: Parameters,
|
|
1828
1837
|
):
|
|
@@ -1841,7 +1850,7 @@ class Connection(utils.CompositeEventEmitter):
|
|
|
1841
1850
|
self.encryption_key_size = 0
|
|
1842
1851
|
self.authenticated = False
|
|
1843
1852
|
self.sc = False
|
|
1844
|
-
self.att_mtu = ATT_DEFAULT_MTU
|
|
1853
|
+
self.att_mtu = att.ATT_DEFAULT_MTU
|
|
1845
1854
|
self.data_length = DEVICE_DEFAULT_DATA_LENGTH
|
|
1846
1855
|
self.gatt_client = gatt_client.Client(self) # Per-connection client
|
|
1847
1856
|
self.gatt_server = (
|
|
@@ -1885,8 +1894,8 @@ class Connection(utils.CompositeEventEmitter):
|
|
|
1885
1894
|
) -> l2cap.LeCreditBasedChannel: ...
|
|
1886
1895
|
|
|
1887
1896
|
async def create_l2cap_channel(
|
|
1888
|
-
self, spec:
|
|
1889
|
-
) ->
|
|
1897
|
+
self, spec: l2cap.ClassicChannelSpec | l2cap.LeCreditBasedChannelSpec
|
|
1898
|
+
) -> l2cap.ClassicChannel | l2cap.LeCreditBasedChannel:
|
|
1890
1899
|
return await self.device.create_l2cap_channel(connection=self, spec=spec)
|
|
1891
1900
|
|
|
1892
1901
|
async def disconnect(
|
|
@@ -1910,7 +1919,7 @@ class Connection(utils.CompositeEventEmitter):
|
|
|
1910
1919
|
async def switch_role(self, role: hci.Role) -> None:
|
|
1911
1920
|
return await self.device.switch_role(self, role)
|
|
1912
1921
|
|
|
1913
|
-
async def sustain(self, timeout:
|
|
1922
|
+
async def sustain(self, timeout: float | None = None) -> None:
|
|
1914
1923
|
"""Idles the current task waiting for a disconnect or timeout"""
|
|
1915
1924
|
|
|
1916
1925
|
abort = asyncio.get_running_loop().create_future()
|
|
@@ -1954,8 +1963,8 @@ class Connection(utils.CompositeEventEmitter):
|
|
|
1954
1963
|
|
|
1955
1964
|
async def set_phy(
|
|
1956
1965
|
self,
|
|
1957
|
-
tx_phys:
|
|
1958
|
-
rx_phys:
|
|
1966
|
+
tx_phys: Iterable[hci.Phy] | None = None,
|
|
1967
|
+
rx_phys: Iterable[hci.Phy] | None = None,
|
|
1959
1968
|
phy_options: int = 0,
|
|
1960
1969
|
):
|
|
1961
1970
|
return await self.device.set_connection_phy(self, tx_phys, rx_phys, phy_options)
|
|
@@ -1991,6 +2000,15 @@ class Connection(utils.CompositeEventEmitter):
|
|
|
1991
2000
|
self.peer_le_features = await self.device.get_remote_le_features(self)
|
|
1992
2001
|
return self.peer_le_features
|
|
1993
2002
|
|
|
2003
|
+
def on_att_mtu_update(self, mtu: int):
|
|
2004
|
+
logger.debug(
|
|
2005
|
+
f'*** Connection ATT MTU Update: [0x{self.handle:04X}] '
|
|
2006
|
+
f'{self.peer_address} as {self.role_name}, '
|
|
2007
|
+
f'{mtu}'
|
|
2008
|
+
)
|
|
2009
|
+
self.att_mtu = mtu
|
|
2010
|
+
self.emit(self.EVENT_CONNECTION_ATT_MTU_UPDATE)
|
|
2011
|
+
|
|
1994
2012
|
@property
|
|
1995
2013
|
def data_packet_queue(self) -> DataPacketQueue | None:
|
|
1996
2014
|
return self.device.host.get_data_packet_queue(self.handle)
|
|
@@ -2059,18 +2077,26 @@ class DeviceConfiguration:
|
|
|
2059
2077
|
AdvertisingData([data_types.CompleteLocalName(DEVICE_DEFAULT_NAME)])
|
|
2060
2078
|
)
|
|
2061
2079
|
irk: bytes = bytes(16) # This really must be changed for any level of security
|
|
2062
|
-
keystore:
|
|
2080
|
+
keystore: str | None = None
|
|
2063
2081
|
address_resolution_offload: bool = False
|
|
2064
2082
|
address_generation_offload: bool = False
|
|
2065
2083
|
cis_enabled: bool = False
|
|
2066
2084
|
channel_sounding_enabled: bool = False
|
|
2067
|
-
identity_address_type:
|
|
2085
|
+
identity_address_type: int | None = None
|
|
2068
2086
|
io_capability: int = pairing.PairingDelegate.IoCapability.NO_OUTPUT_NO_INPUT
|
|
2069
2087
|
gap_service_enabled: bool = True
|
|
2070
2088
|
gatt_service_enabled: bool = True
|
|
2089
|
+
enhanced_retransmission_supported: bool = False
|
|
2090
|
+
l2cap_extended_features: Sequence[int] = (
|
|
2091
|
+
l2cap.L2CAP_Information_Request.ExtendedFeatures.FIXED_CHANNELS,
|
|
2092
|
+
l2cap.L2CAP_Information_Request.ExtendedFeatures.FCS_OPTION,
|
|
2093
|
+
l2cap.L2CAP_Information_Request.ExtendedFeatures.ENHANCED_RETRANSMISSION_MODE,
|
|
2094
|
+
)
|
|
2095
|
+
eatt_enabled: bool = False
|
|
2096
|
+
gatt_services: list[dict[str, Any]] = field(init=False)
|
|
2071
2097
|
|
|
2072
2098
|
def __post_init__(self) -> None:
|
|
2073
|
-
self.gatt_services
|
|
2099
|
+
self.gatt_services = []
|
|
2074
2100
|
|
|
2075
2101
|
def load_from_dict(self, config: dict[str, Any]) -> None:
|
|
2076
2102
|
config = copy.deepcopy(config)
|
|
@@ -2126,7 +2152,7 @@ class DeviceConfiguration:
|
|
|
2126
2152
|
setattr(self, key, value)
|
|
2127
2153
|
|
|
2128
2154
|
def load_from_file(self, filename: str) -> None:
|
|
2129
|
-
with open(filename,
|
|
2155
|
+
with open(filename, encoding='utf-8') as file:
|
|
2130
2156
|
self.load_from_dict(json.load(file))
|
|
2131
2157
|
|
|
2132
2158
|
@classmethod
|
|
@@ -2237,12 +2263,12 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2237
2263
|
pending_connections: dict[hci.Address, Connection]
|
|
2238
2264
|
classic_pending_accepts: dict[
|
|
2239
2265
|
hci.Address,
|
|
2240
|
-
list[asyncio.Future[
|
|
2266
|
+
list[asyncio.Future[Connection | tuple[hci.Address, int, int]]],
|
|
2241
2267
|
]
|
|
2242
2268
|
advertisement_accumulators: dict[hci.Address, AdvertisementDataAccumulator]
|
|
2243
2269
|
periodic_advertising_syncs: list[PeriodicAdvertisingSync]
|
|
2244
2270
|
config: DeviceConfiguration
|
|
2245
|
-
legacy_advertiser:
|
|
2271
|
+
legacy_advertiser: LegacyAdvertiser | None
|
|
2246
2272
|
sco_links: dict[int, ScoLink]
|
|
2247
2273
|
cis_links: dict[int, CisLink]
|
|
2248
2274
|
bigs: dict[int, Big]
|
|
@@ -2250,6 +2276,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2250
2276
|
big_syncs: dict[int, BigSync]
|
|
2251
2277
|
_pending_cis: dict[int, tuple[int, int]]
|
|
2252
2278
|
gatt_service: gatt_service.GenericAttributeProfileService | None = None
|
|
2279
|
+
keystore: KeyStore | None = None
|
|
2253
2280
|
|
|
2254
2281
|
EVENT_ADVERTISEMENT = "advertisement"
|
|
2255
2282
|
EVENT_PERIODIC_ADVERTISING_SYNC_TRANSFER = "periodic_advertising_sync_transfer"
|
|
@@ -2330,13 +2357,17 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2330
2357
|
|
|
2331
2358
|
def __init__(
|
|
2332
2359
|
self,
|
|
2333
|
-
name:
|
|
2334
|
-
address:
|
|
2335
|
-
config:
|
|
2336
|
-
host:
|
|
2360
|
+
name: str | None = None,
|
|
2361
|
+
address: hci.Address | None = None,
|
|
2362
|
+
config: DeviceConfiguration | None = None,
|
|
2363
|
+
host: Host | None = None,
|
|
2337
2364
|
) -> None:
|
|
2338
2365
|
super().__init__()
|
|
2339
2366
|
|
|
2367
|
+
# Use the initial config or a default
|
|
2368
|
+
config = config or DeviceConfiguration()
|
|
2369
|
+
self.config = config
|
|
2370
|
+
|
|
2340
2371
|
self._host = None
|
|
2341
2372
|
self.powered_on = False
|
|
2342
2373
|
self.auto_restart_inquiry = True
|
|
@@ -2344,7 +2375,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2344
2375
|
self.gatt_server = gatt_server.Server(self)
|
|
2345
2376
|
self.sdp_server = sdp.Server(self)
|
|
2346
2377
|
self.l2cap_channel_manager = l2cap.ChannelManager(
|
|
2347
|
-
|
|
2378
|
+
config.l2cap_extended_features
|
|
2348
2379
|
)
|
|
2349
2380
|
self.advertisement_accumulators = {} # Accumulators, by address
|
|
2350
2381
|
self.periodic_advertising_syncs = []
|
|
@@ -2375,10 +2406,6 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2375
2406
|
# Own address type cache
|
|
2376
2407
|
self.connect_own_address_type = None
|
|
2377
2408
|
|
|
2378
|
-
# Use the initial config or a default
|
|
2379
|
-
config = config or DeviceConfiguration()
|
|
2380
|
-
self.config = config
|
|
2381
|
-
|
|
2382
2409
|
self.name = config.name
|
|
2383
2410
|
self.public_address = hci.Address.ANY
|
|
2384
2411
|
self.random_address = config.address
|
|
@@ -2390,7 +2417,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2390
2417
|
self.le_simultaneous_enabled = config.le_simultaneous_enabled
|
|
2391
2418
|
self.le_privacy_enabled = config.le_privacy_enabled
|
|
2392
2419
|
self.le_rpa_timeout = config.le_rpa_timeout
|
|
2393
|
-
self.le_rpa_periodic_update_task:
|
|
2420
|
+
self.le_rpa_periodic_update_task: asyncio.Task | None = None
|
|
2394
2421
|
self.le_subrate_enabled = config.le_subrate_enabled
|
|
2395
2422
|
self.classic_enabled = config.classic_enabled
|
|
2396
2423
|
self.cis_enabled = config.cis_enabled
|
|
@@ -2414,8 +2441,8 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2414
2441
|
# can be initialized from a config object, and for backward compatibility for
|
|
2415
2442
|
# client code that may set those values directly before calling
|
|
2416
2443
|
# start_advertising().
|
|
2417
|
-
self.legacy_advertising_set:
|
|
2418
|
-
self.legacy_advertiser:
|
|
2444
|
+
self.legacy_advertising_set: AdvertisingSet | None = None
|
|
2445
|
+
self.legacy_advertiser: LegacyAdvertiser | None = None
|
|
2419
2446
|
self.advertising_data = config.advertising_data
|
|
2420
2447
|
self.scan_response_data = config.scan_response_data
|
|
2421
2448
|
self.advertising_interval_min = config.advertising_interval_min
|
|
@@ -2486,7 +2513,10 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2486
2513
|
add_gap_service=config.gap_service_enabled,
|
|
2487
2514
|
add_gatt_service=config.gatt_service_enabled,
|
|
2488
2515
|
)
|
|
2489
|
-
self.l2cap_channel_manager.register_fixed_channel(ATT_CID, self.on_gatt_pdu)
|
|
2516
|
+
self.l2cap_channel_manager.register_fixed_channel(att.ATT_CID, self.on_gatt_pdu)
|
|
2517
|
+
|
|
2518
|
+
if self.config.eatt_enabled:
|
|
2519
|
+
self.gatt_server.register_eatt()
|
|
2490
2520
|
|
|
2491
2521
|
# Forward some events
|
|
2492
2522
|
utils.setup_event_forwarding(
|
|
@@ -2533,7 +2563,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2533
2563
|
def sdp_service_records(self, service_records):
|
|
2534
2564
|
self.sdp_server.service_records = service_records
|
|
2535
2565
|
|
|
2536
|
-
def lookup_connection(self, connection_handle: int) ->
|
|
2566
|
+
def lookup_connection(self, connection_handle: int) -> Connection | None:
|
|
2537
2567
|
if connection := self.connections.get(connection_handle):
|
|
2538
2568
|
return connection
|
|
2539
2569
|
|
|
@@ -2542,9 +2572,9 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2542
2572
|
def find_connection_by_bd_addr(
|
|
2543
2573
|
self,
|
|
2544
2574
|
bd_addr: hci.Address,
|
|
2545
|
-
transport:
|
|
2575
|
+
transport: int | None = None,
|
|
2546
2576
|
check_address_type: bool = False,
|
|
2547
|
-
) ->
|
|
2577
|
+
) -> Connection | None:
|
|
2548
2578
|
for connection in self.connections.values():
|
|
2549
2579
|
if bytes(connection.peer_address) == bytes(bd_addr):
|
|
2550
2580
|
if (
|
|
@@ -2559,7 +2589,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2559
2589
|
|
|
2560
2590
|
def lookup_periodic_advertising_sync(
|
|
2561
2591
|
self, sync_handle: int
|
|
2562
|
-
) ->
|
|
2592
|
+
) -> PeriodicAdvertisingSync | None:
|
|
2563
2593
|
return next(
|
|
2564
2594
|
(
|
|
2565
2595
|
sync
|
|
@@ -2597,8 +2627,8 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2597
2627
|
async def create_l2cap_channel(
|
|
2598
2628
|
self,
|
|
2599
2629
|
connection: Connection,
|
|
2600
|
-
spec:
|
|
2601
|
-
) ->
|
|
2630
|
+
spec: l2cap.ClassicChannelSpec | l2cap.LeCreditBasedChannelSpec,
|
|
2631
|
+
) -> l2cap.ClassicChannel | l2cap.LeCreditBasedChannel:
|
|
2602
2632
|
if isinstance(spec, l2cap.ClassicChannelSpec):
|
|
2603
2633
|
return await self.l2cap_channel_manager.create_classic_channel(
|
|
2604
2634
|
connection=connection, spec=spec
|
|
@@ -2612,25 +2642,25 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2612
2642
|
def create_l2cap_server(
|
|
2613
2643
|
self,
|
|
2614
2644
|
spec: l2cap.ClassicChannelSpec,
|
|
2615
|
-
handler:
|
|
2645
|
+
handler: Callable[[l2cap.ClassicChannel], Any] | None = None,
|
|
2616
2646
|
) -> l2cap.ClassicChannelServer: ...
|
|
2617
2647
|
|
|
2618
2648
|
@overload
|
|
2619
2649
|
def create_l2cap_server(
|
|
2620
2650
|
self,
|
|
2621
2651
|
spec: l2cap.LeCreditBasedChannelSpec,
|
|
2622
|
-
handler:
|
|
2652
|
+
handler: Callable[[l2cap.LeCreditBasedChannel], Any] | None = None,
|
|
2623
2653
|
) -> l2cap.LeCreditBasedChannelServer: ...
|
|
2624
2654
|
|
|
2625
2655
|
def create_l2cap_server(
|
|
2626
2656
|
self,
|
|
2627
|
-
spec:
|
|
2628
|
-
handler:
|
|
2629
|
-
Callable[[l2cap.ClassicChannel], Any]
|
|
2630
|
-
Callable[[l2cap.LeCreditBasedChannel], Any]
|
|
2631
|
-
None
|
|
2632
|
-
|
|
2633
|
-
) ->
|
|
2657
|
+
spec: l2cap.ClassicChannelSpec | l2cap.LeCreditBasedChannelSpec,
|
|
2658
|
+
handler: (
|
|
2659
|
+
Callable[[l2cap.ClassicChannel], Any]
|
|
2660
|
+
| Callable[[l2cap.LeCreditBasedChannel], Any]
|
|
2661
|
+
| None
|
|
2662
|
+
) = None,
|
|
2663
|
+
) -> l2cap.ClassicChannelServer | l2cap.LeCreditBasedChannelServer:
|
|
2634
2664
|
if isinstance(spec, l2cap.ClassicChannelSpec):
|
|
2635
2665
|
return self.l2cap_channel_manager.create_classic_server(
|
|
2636
2666
|
spec=spec,
|
|
@@ -2932,13 +2962,13 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2932
2962
|
async def start_advertising(
|
|
2933
2963
|
self,
|
|
2934
2964
|
advertising_type: AdvertisingType = AdvertisingType.UNDIRECTED_CONNECTABLE_SCANNABLE,
|
|
2935
|
-
target:
|
|
2965
|
+
target: hci.Address | None = None,
|
|
2936
2966
|
own_address_type: hci.OwnAddressType = hci.OwnAddressType.RANDOM,
|
|
2937
2967
|
auto_restart: bool = False,
|
|
2938
|
-
advertising_data:
|
|
2939
|
-
scan_response_data:
|
|
2940
|
-
advertising_interval_min:
|
|
2941
|
-
advertising_interval_max:
|
|
2968
|
+
advertising_data: bytes | None = None,
|
|
2969
|
+
scan_response_data: bytes | None = None,
|
|
2970
|
+
advertising_interval_min: float | None = None,
|
|
2971
|
+
advertising_interval_max: float | None = None,
|
|
2942
2972
|
) -> None:
|
|
2943
2973
|
"""Start legacy advertising.
|
|
2944
2974
|
|
|
@@ -3042,11 +3072,11 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3042
3072
|
|
|
3043
3073
|
async def create_advertising_set(
|
|
3044
3074
|
self,
|
|
3045
|
-
advertising_parameters:
|
|
3046
|
-
random_address:
|
|
3075
|
+
advertising_parameters: AdvertisingParameters | None = None,
|
|
3076
|
+
random_address: hci.Address | None = None,
|
|
3047
3077
|
advertising_data: bytes = b'',
|
|
3048
3078
|
scan_response_data: bytes = b'',
|
|
3049
|
-
periodic_advertising_parameters:
|
|
3079
|
+
periodic_advertising_parameters: PeriodicAdvertisingParameters | None = None,
|
|
3050
3080
|
periodic_advertising_data: bytes = b'',
|
|
3051
3081
|
auto_start: bool = True,
|
|
3052
3082
|
auto_restart: bool = False,
|
|
@@ -3333,7 +3363,13 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3333
3363
|
return self.scanning
|
|
3334
3364
|
|
|
3335
3365
|
@host_event_handler
|
|
3336
|
-
def on_advertising_report(
|
|
3366
|
+
def on_advertising_report(
|
|
3367
|
+
self,
|
|
3368
|
+
report: (
|
|
3369
|
+
hci.HCI_LE_Advertising_Report_Event.Report
|
|
3370
|
+
| hci.HCI_LE_Extended_Advertising_Report_Event.Report
|
|
3371
|
+
),
|
|
3372
|
+
) -> None:
|
|
3337
3373
|
if not (accumulator := self.advertisement_accumulators.get(report.address)):
|
|
3338
3374
|
accumulator = AdvertisementDataAccumulator(passive=self.scanning_is_passive)
|
|
3339
3375
|
self.advertisement_accumulators[report.address] = accumulator
|
|
@@ -3576,13 +3612,13 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3576
3612
|
|
|
3577
3613
|
async def connect(
|
|
3578
3614
|
self,
|
|
3579
|
-
peer_address:
|
|
3615
|
+
peer_address: hci.Address | str,
|
|
3580
3616
|
transport: core.PhysicalTransport = PhysicalTransport.LE,
|
|
3581
|
-
connection_parameters_preferences:
|
|
3582
|
-
dict[hci.Phy, ConnectionParametersPreferences]
|
|
3583
|
-
|
|
3617
|
+
connection_parameters_preferences: (
|
|
3618
|
+
dict[hci.Phy, ConnectionParametersPreferences] | None
|
|
3619
|
+
) = None,
|
|
3584
3620
|
own_address_type: hci.OwnAddressType = hci.OwnAddressType.RANDOM,
|
|
3585
|
-
timeout:
|
|
3621
|
+
timeout: float | None = DEVICE_DEFAULT_CONNECT_TIMEOUT,
|
|
3586
3622
|
always_resolve: bool = False,
|
|
3587
3623
|
) -> Connection:
|
|
3588
3624
|
'''
|
|
@@ -3892,9 +3928,9 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3892
3928
|
|
|
3893
3929
|
async def accept(
|
|
3894
3930
|
self,
|
|
3895
|
-
peer_address:
|
|
3931
|
+
peer_address: hci.Address | str = hci.Address.ANY,
|
|
3896
3932
|
role: hci.Role = hci.Role.PERIPHERAL,
|
|
3897
|
-
timeout:
|
|
3933
|
+
timeout: float | None = DEVICE_DEFAULT_CONNECT_TIMEOUT,
|
|
3898
3934
|
) -> Connection:
|
|
3899
3935
|
'''
|
|
3900
3936
|
Wait and accept any incoming connection or a connection from `peer_address` when
|
|
@@ -4018,7 +4054,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4018
4054
|
self.pending_connections.pop(peer_address, None)
|
|
4019
4055
|
|
|
4020
4056
|
@asynccontextmanager
|
|
4021
|
-
async def connect_as_gatt(self, peer_address:
|
|
4057
|
+
async def connect_as_gatt(self, peer_address: hci.Address | str):
|
|
4022
4058
|
async with AsyncExitStack() as stack:
|
|
4023
4059
|
connection = await stack.enter_async_context(
|
|
4024
4060
|
await self.connect(peer_address)
|
|
@@ -4065,7 +4101,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4065
4101
|
)
|
|
4066
4102
|
|
|
4067
4103
|
async def disconnect(
|
|
4068
|
-
self, connection:
|
|
4104
|
+
self, connection: Connection | ScoLink | CisLink, reason: int
|
|
4069
4105
|
) -> None:
|
|
4070
4106
|
# Create a future so that we can wait for the disconnection's result
|
|
4071
4107
|
pending_disconnection = asyncio.get_running_loop().create_future()
|
|
@@ -4204,8 +4240,8 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4204
4240
|
async def set_connection_phy(
|
|
4205
4241
|
self,
|
|
4206
4242
|
connection: Connection,
|
|
4207
|
-
tx_phys:
|
|
4208
|
-
rx_phys:
|
|
4243
|
+
tx_phys: Iterable[hci.Phy] | None = None,
|
|
4244
|
+
rx_phys: Iterable[hci.Phy] | None = None,
|
|
4209
4245
|
phy_options: int = 0,
|
|
4210
4246
|
):
|
|
4211
4247
|
if not self.host.supports_command(hci.HCI_LE_SET_PHY_COMMAND):
|
|
@@ -4229,8 +4265,8 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4229
4265
|
|
|
4230
4266
|
async def set_default_phy(
|
|
4231
4267
|
self,
|
|
4232
|
-
tx_phys:
|
|
4233
|
-
rx_phys:
|
|
4268
|
+
tx_phys: Iterable[hci.Phy] | None = None,
|
|
4269
|
+
rx_phys: Iterable[hci.Phy] | None = None,
|
|
4234
4270
|
):
|
|
4235
4271
|
all_phys_bits = (1 if tx_phys is None else 0) | (
|
|
4236
4272
|
(1 if rx_phys is None else 0) << 1
|
|
@@ -4284,7 +4320,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4284
4320
|
if local_name == name:
|
|
4285
4321
|
peer_address.set_result(address)
|
|
4286
4322
|
|
|
4287
|
-
listener:
|
|
4323
|
+
listener: Callable[..., None] | None = None
|
|
4288
4324
|
was_scanning = self.scanning
|
|
4289
4325
|
was_discovering = self.discovering
|
|
4290
4326
|
try:
|
|
@@ -4398,7 +4434,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4398
4434
|
|
|
4399
4435
|
async def get_long_term_key(
|
|
4400
4436
|
self, connection_handle: int, rand: bytes, ediv: int
|
|
4401
|
-
) ->
|
|
4437
|
+
) -> bytes | None:
|
|
4402
4438
|
if (connection := self.lookup_connection(connection_handle)) is None:
|
|
4403
4439
|
return None
|
|
4404
4440
|
|
|
@@ -4422,7 +4458,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4422
4458
|
return keys.ltk_peripheral.value
|
|
4423
4459
|
return None
|
|
4424
4460
|
|
|
4425
|
-
async def get_link_key(self, address: hci.Address) ->
|
|
4461
|
+
async def get_link_key(self, address: hci.Address) -> bytes | None:
|
|
4426
4462
|
if self.keystore is None:
|
|
4427
4463
|
return None
|
|
4428
4464
|
|
|
@@ -4498,8 +4534,8 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4498
4534
|
ediv = 0
|
|
4499
4535
|
elif keys.ltk_central is not None:
|
|
4500
4536
|
ltk = keys.ltk_central.value
|
|
4501
|
-
rand = keys.ltk_central.rand
|
|
4502
|
-
ediv = keys.ltk_central.ediv
|
|
4537
|
+
rand = keys.ltk_central.rand or b''
|
|
4538
|
+
ediv = keys.ltk_central.ediv or 0
|
|
4503
4539
|
else:
|
|
4504
4540
|
raise InvalidOperationError('no LTK found for peer')
|
|
4505
4541
|
|
|
@@ -4560,7 +4596,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4560
4596
|
await connection.cancel_on_disconnection(pending_role_change)
|
|
4561
4597
|
|
|
4562
4598
|
# [Classic only]
|
|
4563
|
-
async def request_remote_name(self, remote:
|
|
4599
|
+
async def request_remote_name(self, remote: hci.Address | Connection) -> str:
|
|
4564
4600
|
# Set up event handlers
|
|
4565
4601
|
pending_name: asyncio.Future[str] = asyncio.get_running_loop().create_future()
|
|
4566
4602
|
|
|
@@ -5123,14 +5159,18 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5123
5159
|
if add_gap_service:
|
|
5124
5160
|
self.gatt_server.add_service(GenericAccessService(self.name))
|
|
5125
5161
|
if add_gatt_service:
|
|
5126
|
-
self.gatt_service = gatt_service.GenericAttributeProfileService(
|
|
5162
|
+
self.gatt_service = gatt_service.GenericAttributeProfileService(
|
|
5163
|
+
gatt.ServerSupportedFeatures.EATT_SUPPORTED
|
|
5164
|
+
if self.config.eatt_enabled
|
|
5165
|
+
else None
|
|
5166
|
+
)
|
|
5127
5167
|
self.gatt_server.add_service(self.gatt_service)
|
|
5128
5168
|
|
|
5129
5169
|
async def notify_subscriber(
|
|
5130
5170
|
self,
|
|
5131
5171
|
connection: Connection,
|
|
5132
5172
|
attribute: Attribute,
|
|
5133
|
-
value:
|
|
5173
|
+
value: Any | None = None,
|
|
5134
5174
|
force: bool = False,
|
|
5135
5175
|
) -> None:
|
|
5136
5176
|
"""
|
|
@@ -5149,7 +5189,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5149
5189
|
await self.gatt_server.notify_subscriber(connection, attribute, value, force)
|
|
5150
5190
|
|
|
5151
5191
|
async def notify_subscribers(
|
|
5152
|
-
self, attribute: Attribute, value=None, force=False
|
|
5192
|
+
self, attribute: Attribute, value: Any | None = None, force: bool = False
|
|
5153
5193
|
) -> None:
|
|
5154
5194
|
"""
|
|
5155
5195
|
Send a notification to all the subscribers of an attribute.
|
|
@@ -5169,7 +5209,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5169
5209
|
self,
|
|
5170
5210
|
connection: Connection,
|
|
5171
5211
|
attribute: Attribute,
|
|
5172
|
-
value:
|
|
5212
|
+
value: Any | None = None,
|
|
5173
5213
|
force: bool = False,
|
|
5174
5214
|
):
|
|
5175
5215
|
"""
|
|
@@ -5190,7 +5230,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5190
5230
|
await self.gatt_server.indicate_subscriber(connection, attribute, value, force)
|
|
5191
5231
|
|
|
5192
5232
|
async def indicate_subscribers(
|
|
5193
|
-
self, attribute: Attribute, value:
|
|
5233
|
+
self, attribute: Attribute, value: Any | None = None, force: bool = False
|
|
5194
5234
|
):
|
|
5195
5235
|
"""
|
|
5196
5236
|
Send an indication to all the subscribers of an attribute.
|
|
@@ -5225,8 +5265,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5225
5265
|
|
|
5226
5266
|
if status != hci.HCI_SUCCESS:
|
|
5227
5267
|
logger.debug(
|
|
5228
|
-
f'advertising set {advertising_handle} '
|
|
5229
|
-
f'terminated with status {status}'
|
|
5268
|
+
f'advertising set {advertising_handle} terminated with status {status}'
|
|
5230
5269
|
)
|
|
5231
5270
|
return
|
|
5232
5271
|
|
|
@@ -5415,8 +5454,8 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5415
5454
|
self,
|
|
5416
5455
|
connection_handle: int,
|
|
5417
5456
|
peer_address: hci.Address,
|
|
5418
|
-
self_resolvable_address:
|
|
5419
|
-
peer_resolvable_address:
|
|
5457
|
+
self_resolvable_address: hci.Address | None,
|
|
5458
|
+
peer_resolvable_address: hci.Address | None,
|
|
5420
5459
|
role: hci.Role,
|
|
5421
5460
|
connection_interval: int,
|
|
5422
5461
|
peripheral_latency: int,
|
|
@@ -5451,7 +5490,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5451
5490
|
peer_address = resolved_address
|
|
5452
5491
|
|
|
5453
5492
|
self_address = None
|
|
5454
|
-
own_address_type:
|
|
5493
|
+
own_address_type: hci.OwnAddressType | None = None
|
|
5455
5494
|
if role == hci.Role.CENTRAL:
|
|
5456
5495
|
own_address_type = self.connect_own_address_type
|
|
5457
5496
|
assert own_address_type is not None
|
|
@@ -5605,7 +5644,8 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5605
5644
|
|
|
5606
5645
|
self.host.send_command_sync(
|
|
5607
5646
|
hci.HCI_Accept_Connection_Request_Command(
|
|
5608
|
-
bd_addr=bd_addr,
|
|
5647
|
+
bd_addr=bd_addr,
|
|
5648
|
+
role=0x01, # Remain the peripheral
|
|
5609
5649
|
)
|
|
5610
5650
|
)
|
|
5611
5651
|
|
|
@@ -5915,7 +5955,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5915
5955
|
@host_event_handler
|
|
5916
5956
|
@try_with_connection_from_address
|
|
5917
5957
|
def on_remote_name(
|
|
5918
|
-
self, connection:
|
|
5958
|
+
self, connection: Connection | None, address: hci.Address, remote_name: bytes
|
|
5919
5959
|
):
|
|
5920
5960
|
# Try to decode the name
|
|
5921
5961
|
try:
|
|
@@ -5934,7 +5974,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5934
5974
|
@host_event_handler
|
|
5935
5975
|
@try_with_connection_from_address
|
|
5936
5976
|
def on_remote_name_failure(
|
|
5937
|
-
self, connection:
|
|
5977
|
+
self, connection: Connection | None, address: hci.Address, error: int
|
|
5938
5978
|
):
|
|
5939
5979
|
if connection:
|
|
5940
5980
|
connection.emit(connection.EVENT_REMOTE_NAME_FAILURE, error)
|
|
@@ -6223,17 +6263,6 @@ class Device(utils.CompositeEventEmitter):
|
|
|
6223
6263
|
)
|
|
6224
6264
|
connection.emit(connection.EVENT_LE_SUBRATE_CHANGE)
|
|
6225
6265
|
|
|
6226
|
-
@host_event_handler
|
|
6227
|
-
@with_connection_from_handle
|
|
6228
|
-
def on_connection_att_mtu_update(self, connection: Connection, att_mtu: int):
|
|
6229
|
-
logger.debug(
|
|
6230
|
-
f'*** Connection ATT MTU Update: [0x{connection.handle:04X}] '
|
|
6231
|
-
f'{connection.peer_address} as {connection.role_name}, '
|
|
6232
|
-
f'{att_mtu}'
|
|
6233
|
-
)
|
|
6234
|
-
connection.att_mtu = att_mtu
|
|
6235
|
-
connection.emit(connection.EVENT_CONNECTION_ATT_MTU_UPDATE)
|
|
6236
|
-
|
|
6237
6266
|
@host_event_handler
|
|
6238
6267
|
@with_connection_from_handle
|
|
6239
6268
|
def on_connection_data_length_change(
|
|
@@ -6379,7 +6408,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
6379
6408
|
@host_event_handler
|
|
6380
6409
|
@try_with_connection_from_address
|
|
6381
6410
|
def on_role_change_failure(
|
|
6382
|
-
self, connection:
|
|
6411
|
+
self, connection: Connection | None, address: hci.Address, error: int
|
|
6383
6412
|
):
|
|
6384
6413
|
if connection:
|
|
6385
6414
|
connection.emit(connection.EVENT_ROLE_CHANGE_FAILURE, error)
|
|
@@ -6403,7 +6432,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
6403
6432
|
def on_pairing(
|
|
6404
6433
|
self,
|
|
6405
6434
|
connection: Connection,
|
|
6406
|
-
identity_address:
|
|
6435
|
+
identity_address: hci.Address | None,
|
|
6407
6436
|
keys: PairingKeys,
|
|
6408
6437
|
sc: bool,
|
|
6409
6438
|
) -> None:
|
|
@@ -6420,7 +6449,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
6420
6449
|
@with_connection_from_handle
|
|
6421
6450
|
def on_gatt_pdu(self, connection: Connection, pdu: bytes):
|
|
6422
6451
|
# Parse the L2CAP payload into an ATT PDU object
|
|
6423
|
-
att_pdu = ATT_PDU.from_bytes(pdu)
|
|
6452
|
+
att_pdu = att.ATT_PDU.from_bytes(pdu)
|
|
6424
6453
|
|
|
6425
6454
|
# Conveniently, even-numbered op codes are client->server and
|
|
6426
6455
|
# odd-numbered ones are server->client
|