bumble 0.0.180__py3-none-any.whl → 0.0.181__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/apps/bench.py +110 -81
- bumble/apps/ble_rpa_tool.py +63 -0
- bumble/apps/console.py +4 -4
- bumble/apps/controller_info.py +34 -2
- bumble/apps/pair.py +6 -8
- bumble/att.py +53 -11
- bumble/controller.py +28 -1
- bumble/crypto.py +10 -0
- bumble/device.py +566 -111
- bumble/drivers/__init__.py +27 -31
- bumble/drivers/common.py +45 -0
- bumble/drivers/rtk.py +11 -4
- bumble/gatt.py +66 -51
- bumble/gatt_server.py +30 -22
- bumble/hci.py +223 -92
- bumble/helpers.py +14 -0
- bumble/hfp.py +37 -27
- bumble/hid.py +282 -61
- bumble/host.py +158 -93
- bumble/l2cap.py +3 -3
- bumble/profiles/asha_service.py +2 -2
- bumble/profiles/bap.py +1247 -0
- bumble/profiles/cap.py +52 -0
- bumble/profiles/csip.py +62 -4
- bumble/rfcomm.py +24 -17
- bumble/smp.py +1 -1
- bumble/transport/__init__.py +49 -21
- bumble/transport/android_emulator.py +1 -1
- bumble/transport/common.py +2 -1
- bumble/transport/hci_socket.py +1 -4
- bumble/transport/usb.py +1 -1
- bumble/utils.py +3 -6
- {bumble-0.0.180.dist-info → bumble-0.0.181.dist-info}/METADATA +1 -1
- {bumble-0.0.180.dist-info → bumble-0.0.181.dist-info}/RECORD +39 -35
- {bumble-0.0.180.dist-info → bumble-0.0.181.dist-info}/entry_points.txt +1 -0
- {bumble-0.0.180.dist-info → bumble-0.0.181.dist-info}/LICENSE +0 -0
- {bumble-0.0.180.dist-info → bumble-0.0.181.dist-info}/WHEEL +0 -0
- {bumble-0.0.180.dist-info → bumble-0.0.181.dist-info}/top_level.txt +0 -0
bumble/device.py
CHANGED
|
@@ -21,7 +21,7 @@ import functools
|
|
|
21
21
|
import json
|
|
22
22
|
import asyncio
|
|
23
23
|
import logging
|
|
24
|
-
from contextlib import asynccontextmanager, AsyncExitStack
|
|
24
|
+
from contextlib import asynccontextmanager, AsyncExitStack, closing
|
|
25
25
|
from dataclasses import dataclass
|
|
26
26
|
from collections.abc import Iterable
|
|
27
27
|
from typing import (
|
|
@@ -49,6 +49,7 @@ from .hci import (
|
|
|
49
49
|
HCI_AUTHENTICATED_COMBINATION_KEY_GENERATED_FROM_P_256_TYPE,
|
|
50
50
|
HCI_CENTRAL_ROLE,
|
|
51
51
|
HCI_COMMAND_STATUS_PENDING,
|
|
52
|
+
HCI_CONNECTED_ISOCHRONOUS_STREAM_LE_SUPPORTED_FEATURE,
|
|
52
53
|
HCI_CONNECTION_REJECTED_DUE_TO_LIMITED_RESOURCES_ERROR,
|
|
53
54
|
HCI_DISPLAY_YES_NO_IO_CAPABILITY,
|
|
54
55
|
HCI_DISPLAY_ONLY_IO_CAPABILITY,
|
|
@@ -85,29 +86,35 @@ from .hci import (
|
|
|
85
86
|
HCI_Constant,
|
|
86
87
|
HCI_Create_Connection_Cancel_Command,
|
|
87
88
|
HCI_Create_Connection_Command,
|
|
89
|
+
HCI_Create_Connection_Command,
|
|
88
90
|
HCI_Disconnect_Command,
|
|
89
91
|
HCI_Encryption_Change_Event,
|
|
90
92
|
HCI_Error,
|
|
91
93
|
HCI_IO_Capability_Request_Reply_Command,
|
|
92
94
|
HCI_Inquiry_Cancel_Command,
|
|
93
95
|
HCI_Inquiry_Command,
|
|
96
|
+
HCI_IsoDataPacket,
|
|
97
|
+
HCI_LE_Accept_CIS_Request_Command,
|
|
94
98
|
HCI_LE_Add_Device_To_Resolving_List_Command,
|
|
95
99
|
HCI_LE_Advertising_Report_Event,
|
|
96
100
|
HCI_LE_Clear_Resolving_List_Command,
|
|
97
101
|
HCI_LE_Connection_Update_Command,
|
|
98
102
|
HCI_LE_Create_Connection_Cancel_Command,
|
|
99
103
|
HCI_LE_Create_Connection_Command,
|
|
104
|
+
HCI_LE_Create_CIS_Command,
|
|
100
105
|
HCI_LE_Enable_Encryption_Command,
|
|
101
106
|
HCI_LE_Extended_Advertising_Report_Event,
|
|
102
107
|
HCI_LE_Extended_Create_Connection_Command,
|
|
103
108
|
HCI_LE_Rand_Command,
|
|
104
109
|
HCI_LE_Read_PHY_Command,
|
|
110
|
+
HCI_LE_Reject_CIS_Request_Command,
|
|
105
111
|
HCI_LE_Remove_Advertising_Set_Command,
|
|
106
112
|
HCI_LE_Set_Address_Resolution_Enable_Command,
|
|
107
113
|
HCI_LE_Set_Advertising_Data_Command,
|
|
108
114
|
HCI_LE_Set_Advertising_Enable_Command,
|
|
109
115
|
HCI_LE_Set_Advertising_Parameters_Command,
|
|
110
116
|
HCI_LE_Set_Advertising_Set_Random_Address_Command,
|
|
117
|
+
HCI_LE_Set_CIG_Parameters_Command,
|
|
111
118
|
HCI_LE_Set_Data_Length_Command,
|
|
112
119
|
HCI_LE_Set_Default_PHY_Command,
|
|
113
120
|
HCI_LE_Set_Extended_Scan_Enable_Command,
|
|
@@ -116,6 +123,7 @@ from .hci import (
|
|
|
116
123
|
HCI_LE_Set_Extended_Advertising_Data_Command,
|
|
117
124
|
HCI_LE_Set_Extended_Advertising_Enable_Command,
|
|
118
125
|
HCI_LE_Set_Extended_Advertising_Parameters_Command,
|
|
126
|
+
HCI_LE_Set_Host_Feature_Command,
|
|
119
127
|
HCI_LE_Set_PHY_Command,
|
|
120
128
|
HCI_LE_Set_Random_Address_Command,
|
|
121
129
|
HCI_LE_Set_Scan_Enable_Command,
|
|
@@ -130,6 +138,7 @@ from .hci import (
|
|
|
130
138
|
HCI_Switch_Role_Command,
|
|
131
139
|
HCI_Set_Connection_Encryption_Command,
|
|
132
140
|
HCI_StatusError,
|
|
141
|
+
HCI_SynchronousDataPacket,
|
|
133
142
|
HCI_User_Confirmation_Request_Negative_Reply_Command,
|
|
134
143
|
HCI_User_Confirmation_Request_Reply_Command,
|
|
135
144
|
HCI_User_Passkey_Request_Negative_Reply_Command,
|
|
@@ -161,6 +170,7 @@ from .core import (
|
|
|
161
170
|
from .utils import (
|
|
162
171
|
AsyncRunner,
|
|
163
172
|
CompositeEventEmitter,
|
|
173
|
+
EventWatcher,
|
|
164
174
|
setup_event_forwarding,
|
|
165
175
|
composite_listener,
|
|
166
176
|
deprecated,
|
|
@@ -427,6 +437,38 @@ class AdvertisingType(IntEnum):
|
|
|
427
437
|
)
|
|
428
438
|
|
|
429
439
|
|
|
440
|
+
# -----------------------------------------------------------------------------
|
|
441
|
+
@dataclass
|
|
442
|
+
class LegacyAdvertiser:
|
|
443
|
+
device: Device
|
|
444
|
+
advertising_type: AdvertisingType
|
|
445
|
+
own_address_type: OwnAddressType
|
|
446
|
+
auto_restart: bool
|
|
447
|
+
advertising_data: Optional[bytes]
|
|
448
|
+
scan_response_data: Optional[bytes]
|
|
449
|
+
|
|
450
|
+
async def stop(self) -> None:
|
|
451
|
+
await self.device.stop_legacy_advertising()
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
# -----------------------------------------------------------------------------
|
|
455
|
+
@dataclass
|
|
456
|
+
class ExtendedAdvertiser(CompositeEventEmitter):
|
|
457
|
+
device: Device
|
|
458
|
+
handle: int
|
|
459
|
+
advertising_properties: HCI_LE_Set_Extended_Advertising_Parameters_Command.AdvertisingProperties
|
|
460
|
+
own_address_type: OwnAddressType
|
|
461
|
+
auto_restart: bool
|
|
462
|
+
advertising_data: Optional[bytes]
|
|
463
|
+
scan_response_data: Optional[bytes]
|
|
464
|
+
|
|
465
|
+
def __post_init__(self) -> None:
|
|
466
|
+
super().__init__()
|
|
467
|
+
|
|
468
|
+
async def stop(self) -> None:
|
|
469
|
+
await self.device.stop_extended_advertising(self.handle)
|
|
470
|
+
|
|
471
|
+
|
|
430
472
|
# -----------------------------------------------------------------------------
|
|
431
473
|
class LePhyOptions:
|
|
432
474
|
# Coded PHY preference
|
|
@@ -592,6 +634,46 @@ class ConnectionParametersPreferences:
|
|
|
592
634
|
ConnectionParametersPreferences.default = ConnectionParametersPreferences()
|
|
593
635
|
|
|
594
636
|
|
|
637
|
+
# -----------------------------------------------------------------------------
|
|
638
|
+
@dataclass
|
|
639
|
+
class ScoLink(CompositeEventEmitter):
|
|
640
|
+
device: Device
|
|
641
|
+
acl_connection: Connection
|
|
642
|
+
handle: int
|
|
643
|
+
link_type: int
|
|
644
|
+
|
|
645
|
+
def __post_init__(self):
|
|
646
|
+
super().__init__()
|
|
647
|
+
|
|
648
|
+
async def disconnect(
|
|
649
|
+
self, reason: int = HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR
|
|
650
|
+
) -> None:
|
|
651
|
+
await self.device.disconnect(self, reason)
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
# -----------------------------------------------------------------------------
|
|
655
|
+
@dataclass
|
|
656
|
+
class CisLink(CompositeEventEmitter):
|
|
657
|
+
class State(IntEnum):
|
|
658
|
+
PENDING = 0
|
|
659
|
+
ESTABLISHED = 1
|
|
660
|
+
|
|
661
|
+
device: Device
|
|
662
|
+
acl_connection: Connection # Based ACL connection
|
|
663
|
+
handle: int # CIS handle assigned by Controller (in LE_Set_CIG_Parameters Complete or LE_CIS_Request events)
|
|
664
|
+
cis_id: int # CIS ID assigned by Central device
|
|
665
|
+
cig_id: int # CIG ID assigned by Central device
|
|
666
|
+
state: State = State.PENDING
|
|
667
|
+
|
|
668
|
+
def __post_init__(self):
|
|
669
|
+
super().__init__()
|
|
670
|
+
|
|
671
|
+
async def disconnect(
|
|
672
|
+
self, reason: int = HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR
|
|
673
|
+
) -> None:
|
|
674
|
+
await self.device.disconnect(self, reason)
|
|
675
|
+
|
|
676
|
+
|
|
595
677
|
# -----------------------------------------------------------------------------
|
|
596
678
|
class Connection(CompositeEventEmitter):
|
|
597
679
|
device: Device
|
|
@@ -608,6 +690,9 @@ class Connection(CompositeEventEmitter):
|
|
|
608
690
|
gatt_client: gatt_client.Client
|
|
609
691
|
pairing_peer_io_capability: Optional[int]
|
|
610
692
|
pairing_peer_authentication_requirements: Optional[int]
|
|
693
|
+
advertiser_after_disconnection: Union[
|
|
694
|
+
LegacyAdvertiser, ExtendedAdvertiser, None
|
|
695
|
+
] = None
|
|
611
696
|
|
|
612
697
|
@composite_listener
|
|
613
698
|
class Listener:
|
|
@@ -870,6 +955,7 @@ class DeviceConfiguration:
|
|
|
870
955
|
self.keystore = None
|
|
871
956
|
self.gatt_services: List[Dict[str, Any]] = []
|
|
872
957
|
self.address_resolution_offload = False
|
|
958
|
+
self.cis_enabled = False
|
|
873
959
|
|
|
874
960
|
def load_from_dict(self, config: Dict[str, Any]) -> None:
|
|
875
961
|
# Load simple properties
|
|
@@ -905,6 +991,7 @@ class DeviceConfiguration:
|
|
|
905
991
|
self.address_resolution_offload = config.get(
|
|
906
992
|
'address_resolution_offload', self.address_resolution_offload
|
|
907
993
|
)
|
|
994
|
+
self.cis_enabled = config.get('cis_enabled', self.cis_enabled)
|
|
908
995
|
|
|
909
996
|
# Load or synthesize an IRK
|
|
910
997
|
irk = config.get('irk')
|
|
@@ -1011,7 +1098,11 @@ class Device(CompositeEventEmitter):
|
|
|
1011
1098
|
]
|
|
1012
1099
|
advertisement_accumulators: Dict[Address, AdvertisementDataAccumulator]
|
|
1013
1100
|
config: DeviceConfiguration
|
|
1014
|
-
|
|
1101
|
+
legacy_advertiser: Optional[LegacyAdvertiser]
|
|
1102
|
+
extended_advertisers: Dict[int, ExtendedAdvertiser]
|
|
1103
|
+
sco_links: Dict[int, ScoLink]
|
|
1104
|
+
cis_links: Dict[int, CisLink]
|
|
1105
|
+
_pending_cis: Dict[int, Tuple[int, int]]
|
|
1015
1106
|
|
|
1016
1107
|
@composite_listener
|
|
1017
1108
|
class Listener:
|
|
@@ -1086,10 +1177,7 @@ class Device(CompositeEventEmitter):
|
|
|
1086
1177
|
|
|
1087
1178
|
self._host = None
|
|
1088
1179
|
self.powered_on = False
|
|
1089
|
-
self.advertising = False
|
|
1090
|
-
self.advertising_type = None
|
|
1091
1180
|
self.auto_restart_inquiry = True
|
|
1092
|
-
self.auto_restart_advertising = False
|
|
1093
1181
|
self.command_timeout = 10 # seconds
|
|
1094
1182
|
self.gatt_server = gatt_server.Server(self)
|
|
1095
1183
|
self.sdp_server = sdp.Server(self)
|
|
@@ -1104,16 +1192,19 @@ class Device(CompositeEventEmitter):
|
|
|
1104
1192
|
self.disconnecting = False
|
|
1105
1193
|
self.connections = {} # Connections, by connection handle
|
|
1106
1194
|
self.pending_connections = {} # Connections, by BD address (BR/EDR only)
|
|
1195
|
+
self.sco_links = {} # ScoLinks, by connection handle (BR/EDR only)
|
|
1196
|
+
self.cis_links = {} # CisLinks, by connection handle (LE only)
|
|
1197
|
+
self._pending_cis = {} # (CIS_ID, CIG_ID), by CIS_handle
|
|
1107
1198
|
self.classic_enabled = False
|
|
1108
1199
|
self.inquiry_response = None
|
|
1109
1200
|
self.address_resolver = None
|
|
1110
1201
|
self.classic_pending_accepts = {
|
|
1111
1202
|
Address.ANY: []
|
|
1112
1203
|
} # Futures, by BD address OR [Futures] for Address.ANY
|
|
1113
|
-
self.
|
|
1204
|
+
self.legacy_advertiser = None
|
|
1205
|
+
self.extended_advertisers = {}
|
|
1114
1206
|
|
|
1115
1207
|
# Own address type cache
|
|
1116
|
-
self.advertising_own_address_type = None
|
|
1117
1208
|
self.connect_own_address_type = None
|
|
1118
1209
|
|
|
1119
1210
|
# Use the initial config or a default
|
|
@@ -1133,6 +1224,7 @@ class Device(CompositeEventEmitter):
|
|
|
1133
1224
|
self.le_enabled = config.le_enabled
|
|
1134
1225
|
self.classic_enabled = config.classic_enabled
|
|
1135
1226
|
self.le_simultaneous_enabled = config.le_simultaneous_enabled
|
|
1227
|
+
self.cis_enabled = config.cis_enabled
|
|
1136
1228
|
self.classic_sc_enabled = config.classic_sc_enabled
|
|
1137
1229
|
self.classic_ssp_enabled = config.classic_ssp_enabled
|
|
1138
1230
|
self.classic_smp_enabled = config.classic_smp_enabled
|
|
@@ -1373,7 +1465,7 @@ class Device(CompositeEventEmitter):
|
|
|
1373
1465
|
await self.host.reset()
|
|
1374
1466
|
|
|
1375
1467
|
# Try to get the public address from the controller
|
|
1376
|
-
response = await self.send_command(HCI_Read_BD_ADDR_Command())
|
|
1468
|
+
response = await self.send_command(HCI_Read_BD_ADDR_Command())
|
|
1377
1469
|
if response.return_parameters.status == HCI_SUCCESS:
|
|
1378
1470
|
logger.debug(
|
|
1379
1471
|
color(f'BD_ADDR: {response.return_parameters.bd_addr}', 'yellow')
|
|
@@ -1396,7 +1488,7 @@ class Device(CompositeEventEmitter):
|
|
|
1396
1488
|
HCI_Write_LE_Host_Support_Command(
|
|
1397
1489
|
le_supported_host=int(self.le_enabled),
|
|
1398
1490
|
simultaneous_le_host=int(self.le_simultaneous_enabled),
|
|
1399
|
-
)
|
|
1491
|
+
)
|
|
1400
1492
|
)
|
|
1401
1493
|
|
|
1402
1494
|
if self.le_enabled:
|
|
@@ -1406,7 +1498,7 @@ class Device(CompositeEventEmitter):
|
|
|
1406
1498
|
if self.host.supports_command(HCI_LE_RAND_COMMAND):
|
|
1407
1499
|
# Get 8 random bytes
|
|
1408
1500
|
response = await self.send_command(
|
|
1409
|
-
HCI_LE_Rand_Command(), check_result=True
|
|
1501
|
+
HCI_LE_Rand_Command(), check_result=True
|
|
1410
1502
|
)
|
|
1411
1503
|
|
|
1412
1504
|
# Ensure the address bytes can be a static random address
|
|
@@ -1427,7 +1519,7 @@ class Device(CompositeEventEmitter):
|
|
|
1427
1519
|
await self.send_command(
|
|
1428
1520
|
HCI_LE_Set_Random_Address_Command(
|
|
1429
1521
|
random_address=self.random_address
|
|
1430
|
-
),
|
|
1522
|
+
),
|
|
1431
1523
|
check_result=True,
|
|
1432
1524
|
)
|
|
1433
1525
|
|
|
@@ -1440,25 +1532,35 @@ class Device(CompositeEventEmitter):
|
|
|
1440
1532
|
await self.send_command(
|
|
1441
1533
|
HCI_LE_Set_Address_Resolution_Enable_Command(
|
|
1442
1534
|
address_resolution_enable=1
|
|
1443
|
-
)
|
|
1535
|
+
)
|
|
1536
|
+
)
|
|
1537
|
+
|
|
1538
|
+
if self.cis_enabled:
|
|
1539
|
+
await self.send_command(
|
|
1540
|
+
HCI_LE_Set_Host_Feature_Command(
|
|
1541
|
+
bit_number=(
|
|
1542
|
+
HCI_CONNECTED_ISOCHRONOUS_STREAM_LE_SUPPORTED_FEATURE
|
|
1543
|
+
),
|
|
1544
|
+
bit_value=1,
|
|
1545
|
+
)
|
|
1444
1546
|
)
|
|
1445
1547
|
|
|
1446
1548
|
if self.classic_enabled:
|
|
1447
1549
|
await self.send_command(
|
|
1448
|
-
HCI_Write_Local_Name_Command(local_name=self.name.encode('utf8'))
|
|
1550
|
+
HCI_Write_Local_Name_Command(local_name=self.name.encode('utf8'))
|
|
1449
1551
|
)
|
|
1450
1552
|
await self.send_command(
|
|
1451
|
-
HCI_Write_Class_Of_Device_Command(class_of_device=self.class_of_device)
|
|
1553
|
+
HCI_Write_Class_Of_Device_Command(class_of_device=self.class_of_device)
|
|
1452
1554
|
)
|
|
1453
1555
|
await self.send_command(
|
|
1454
1556
|
HCI_Write_Simple_Pairing_Mode_Command(
|
|
1455
1557
|
simple_pairing_mode=int(self.classic_ssp_enabled)
|
|
1456
|
-
)
|
|
1558
|
+
)
|
|
1457
1559
|
)
|
|
1458
1560
|
await self.send_command(
|
|
1459
1561
|
HCI_Write_Secure_Connections_Host_Support_Command(
|
|
1460
1562
|
secure_connections_host_support=int(self.classic_sc_enabled)
|
|
1461
|
-
)
|
|
1563
|
+
)
|
|
1462
1564
|
)
|
|
1463
1565
|
await self.set_connectable(self.connectable)
|
|
1464
1566
|
await self.set_discoverable(self.discoverable)
|
|
@@ -1482,7 +1584,7 @@ class Device(CompositeEventEmitter):
|
|
|
1482
1584
|
self.address_resolver = smp.AddressResolver(resolving_keys)
|
|
1483
1585
|
|
|
1484
1586
|
if self.address_resolution_offload:
|
|
1485
|
-
await self.send_command(HCI_LE_Clear_Resolving_List_Command())
|
|
1587
|
+
await self.send_command(HCI_LE_Clear_Resolving_List_Command())
|
|
1486
1588
|
|
|
1487
1589
|
for irk, address in resolving_keys:
|
|
1488
1590
|
await self.send_command(
|
|
@@ -1491,7 +1593,7 @@ class Device(CompositeEventEmitter):
|
|
|
1491
1593
|
peer_identity_address=address,
|
|
1492
1594
|
peer_irk=irk,
|
|
1493
1595
|
local_irk=self.irk,
|
|
1494
|
-
)
|
|
1596
|
+
)
|
|
1495
1597
|
)
|
|
1496
1598
|
|
|
1497
1599
|
def supports_le_feature(self, feature):
|
|
@@ -1510,6 +1612,7 @@ class Device(CompositeEventEmitter):
|
|
|
1510
1612
|
|
|
1511
1613
|
return self.host.supports_le_feature(feature_map[phy])
|
|
1512
1614
|
|
|
1615
|
+
@deprecated("Please use start_legacy_advertising.")
|
|
1513
1616
|
async def start_advertising(
|
|
1514
1617
|
self,
|
|
1515
1618
|
advertising_type: AdvertisingType = AdvertisingType.UNDIRECTED_CONNECTABLE_SCANNABLE,
|
|
@@ -1517,16 +1620,50 @@ class Device(CompositeEventEmitter):
|
|
|
1517
1620
|
own_address_type: int = OwnAddressType.RANDOM,
|
|
1518
1621
|
auto_restart: bool = False,
|
|
1519
1622
|
) -> None:
|
|
1623
|
+
await self.start_legacy_advertising(
|
|
1624
|
+
advertising_type=advertising_type,
|
|
1625
|
+
target=target,
|
|
1626
|
+
own_address_type=OwnAddressType(own_address_type),
|
|
1627
|
+
auto_restart=auto_restart,
|
|
1628
|
+
)
|
|
1629
|
+
|
|
1630
|
+
async def start_legacy_advertising(
|
|
1631
|
+
self,
|
|
1632
|
+
advertising_type: AdvertisingType = AdvertisingType.UNDIRECTED_CONNECTABLE_SCANNABLE,
|
|
1633
|
+
target: Optional[Address] = None,
|
|
1634
|
+
own_address_type: OwnAddressType = OwnAddressType.RANDOM,
|
|
1635
|
+
auto_restart: bool = False,
|
|
1636
|
+
advertising_data: Optional[bytes] = None,
|
|
1637
|
+
scan_response_data: Optional[bytes] = None,
|
|
1638
|
+
) -> LegacyAdvertiser:
|
|
1639
|
+
"""Starts an legacy advertisement.
|
|
1640
|
+
|
|
1641
|
+
Args:
|
|
1642
|
+
advertising_type: Advertising type passed to HCI_LE_Set_Advertising_Parameters_Command.
|
|
1643
|
+
target: Directed advertising target. Directed type should be set in advertising_type arg.
|
|
1644
|
+
own_address_type: own address type to use in the advertising.
|
|
1645
|
+
auto_restart: whether the advertisement will be restarted after disconnection.
|
|
1646
|
+
scan_response_data: raw scan response.
|
|
1647
|
+
advertising_data: raw advertising data.
|
|
1648
|
+
|
|
1649
|
+
Returns:
|
|
1650
|
+
LegacyAdvertiser object containing the metadata of advertisement.
|
|
1651
|
+
"""
|
|
1652
|
+
if self.extended_advertisers:
|
|
1653
|
+
logger.warning(
|
|
1654
|
+
'Trying to start Legacy and Extended Advertising at the same time!'
|
|
1655
|
+
)
|
|
1656
|
+
|
|
1520
1657
|
# If we're advertising, stop first
|
|
1521
|
-
if self.
|
|
1658
|
+
if self.legacy_advertiser:
|
|
1522
1659
|
await self.stop_advertising()
|
|
1523
1660
|
|
|
1524
1661
|
# Set/update the advertising data if the advertising type allows it
|
|
1525
1662
|
if advertising_type.has_data:
|
|
1526
1663
|
await self.send_command(
|
|
1527
1664
|
HCI_LE_Set_Advertising_Data_Command(
|
|
1528
|
-
advertising_data=self.advertising_data
|
|
1529
|
-
),
|
|
1665
|
+
advertising_data=advertising_data or self.advertising_data or b''
|
|
1666
|
+
),
|
|
1530
1667
|
check_result=True,
|
|
1531
1668
|
)
|
|
1532
1669
|
|
|
@@ -1534,8 +1671,10 @@ class Device(CompositeEventEmitter):
|
|
|
1534
1671
|
if advertising_type.is_scannable:
|
|
1535
1672
|
await self.send_command(
|
|
1536
1673
|
HCI_LE_Set_Scan_Response_Data_Command(
|
|
1537
|
-
scan_response_data=
|
|
1538
|
-
|
|
1674
|
+
scan_response_data=scan_response_data
|
|
1675
|
+
or self.scan_response_data
|
|
1676
|
+
or b''
|
|
1677
|
+
),
|
|
1539
1678
|
check_result=True,
|
|
1540
1679
|
)
|
|
1541
1680
|
|
|
@@ -1561,55 +1700,67 @@ class Device(CompositeEventEmitter):
|
|
|
1561
1700
|
peer_address=peer_address,
|
|
1562
1701
|
advertising_channel_map=7,
|
|
1563
1702
|
advertising_filter_policy=0,
|
|
1564
|
-
),
|
|
1703
|
+
),
|
|
1565
1704
|
check_result=True,
|
|
1566
1705
|
)
|
|
1567
1706
|
|
|
1568
1707
|
# Enable advertising
|
|
1569
1708
|
await self.send_command(
|
|
1570
|
-
HCI_LE_Set_Advertising_Enable_Command(advertising_enable=1),
|
|
1709
|
+
HCI_LE_Set_Advertising_Enable_Command(advertising_enable=1),
|
|
1571
1710
|
check_result=True,
|
|
1572
1711
|
)
|
|
1573
1712
|
|
|
1574
|
-
self.
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1713
|
+
self.legacy_advertiser = LegacyAdvertiser(
|
|
1714
|
+
device=self,
|
|
1715
|
+
advertising_type=advertising_type,
|
|
1716
|
+
own_address_type=own_address_type,
|
|
1717
|
+
auto_restart=auto_restart,
|
|
1718
|
+
advertising_data=advertising_data,
|
|
1719
|
+
scan_response_data=scan_response_data,
|
|
1720
|
+
)
|
|
1721
|
+
return self.legacy_advertiser
|
|
1578
1722
|
|
|
1723
|
+
@deprecated("Please use stop_legacy_advertising.")
|
|
1579
1724
|
async def stop_advertising(self) -> None:
|
|
1725
|
+
await self.stop_legacy_advertising()
|
|
1726
|
+
|
|
1727
|
+
async def stop_legacy_advertising(self) -> None:
|
|
1580
1728
|
# Disable advertising
|
|
1581
|
-
if self.
|
|
1729
|
+
if self.legacy_advertiser:
|
|
1582
1730
|
await self.send_command(
|
|
1583
|
-
HCI_LE_Set_Advertising_Enable_Command(advertising_enable=0),
|
|
1731
|
+
HCI_LE_Set_Advertising_Enable_Command(advertising_enable=0),
|
|
1584
1732
|
check_result=True,
|
|
1585
1733
|
)
|
|
1586
1734
|
|
|
1587
|
-
self.
|
|
1588
|
-
self.advertising_own_address_type = None
|
|
1589
|
-
self.advertising = False
|
|
1590
|
-
self.auto_restart_advertising = False
|
|
1735
|
+
self.legacy_advertiser = None
|
|
1591
1736
|
|
|
1592
1737
|
@experimental('Extended Advertising is still experimental - Might be changed soon.')
|
|
1593
1738
|
async def start_extended_advertising(
|
|
1594
1739
|
self,
|
|
1595
1740
|
advertising_properties: HCI_LE_Set_Extended_Advertising_Parameters_Command.AdvertisingProperties = HCI_LE_Set_Extended_Advertising_Parameters_Command.AdvertisingProperties.CONNECTABLE_ADVERTISING,
|
|
1596
1741
|
target: Address = Address.ANY,
|
|
1597
|
-
own_address_type:
|
|
1598
|
-
|
|
1742
|
+
own_address_type: OwnAddressType = OwnAddressType.RANDOM,
|
|
1743
|
+
auto_restart: bool = True,
|
|
1599
1744
|
advertising_data: Optional[bytes] = None,
|
|
1600
|
-
|
|
1745
|
+
scan_response_data: Optional[bytes] = None,
|
|
1746
|
+
) -> ExtendedAdvertiser:
|
|
1601
1747
|
"""Starts an extended advertising set.
|
|
1602
1748
|
|
|
1603
1749
|
Args:
|
|
1604
1750
|
advertising_properties: Properties to pass in HCI_LE_Set_Extended_Advertising_Parameters_Command
|
|
1605
1751
|
target: Directed advertising target. Directed property should be set in advertising_properties arg.
|
|
1606
1752
|
own_address_type: own address type to use in the advertising.
|
|
1607
|
-
|
|
1753
|
+
auto_restart: whether the advertisement will be restarted after disconnection.
|
|
1608
1754
|
advertising_data: raw advertising data. When a non-none value is set, HCI_LE_Set_Advertising_Set_Random_Address_Command will be sent.
|
|
1755
|
+
scan_response_data: raw scan response. When a non-none value is set, HCI_LE_Set_Extended_Scan_Response_Data_Command will be sent.
|
|
1609
1756
|
|
|
1610
1757
|
Returns:
|
|
1611
|
-
|
|
1758
|
+
ExtendedAdvertiser object containing the metadata of advertisement.
|
|
1612
1759
|
"""
|
|
1760
|
+
if self.legacy_advertiser:
|
|
1761
|
+
logger.warning(
|
|
1762
|
+
'Trying to start Legacy and Extended Advertising at the same time!'
|
|
1763
|
+
)
|
|
1613
1764
|
|
|
1614
1765
|
adv_handle = -1
|
|
1615
1766
|
# Find a free handle
|
|
@@ -1617,7 +1768,7 @@ class Device(CompositeEventEmitter):
|
|
|
1617
1768
|
DEVICE_MIN_EXTENDED_ADVERTISING_SET_HANDLE,
|
|
1618
1769
|
DEVICE_MAX_EXTENDED_ADVERTISING_SET_HANDLE + 1,
|
|
1619
1770
|
):
|
|
1620
|
-
if i not in self.
|
|
1771
|
+
if i not in self.extended_advertisers:
|
|
1621
1772
|
adv_handle = i
|
|
1622
1773
|
break
|
|
1623
1774
|
|
|
@@ -1647,7 +1798,7 @@ class Device(CompositeEventEmitter):
|
|
|
1647
1798
|
secondary_advertising_phy=1, # LE 1M
|
|
1648
1799
|
advertising_sid=0,
|
|
1649
1800
|
scan_request_notification_enable=0,
|
|
1650
|
-
),
|
|
1801
|
+
),
|
|
1651
1802
|
check_result=True,
|
|
1652
1803
|
)
|
|
1653
1804
|
|
|
@@ -1659,19 +1810,19 @@ class Device(CompositeEventEmitter):
|
|
|
1659
1810
|
operation=HCI_LE_Set_Extended_Advertising_Data_Command.Operation.COMPLETE_DATA,
|
|
1660
1811
|
fragment_preference=0x01, # Should not fragment
|
|
1661
1812
|
advertising_data=advertising_data,
|
|
1662
|
-
),
|
|
1813
|
+
),
|
|
1663
1814
|
check_result=True,
|
|
1664
1815
|
)
|
|
1665
1816
|
|
|
1666
1817
|
# Set the scan response if present
|
|
1667
|
-
if
|
|
1818
|
+
if scan_response_data is not None:
|
|
1668
1819
|
await self.send_command(
|
|
1669
1820
|
HCI_LE_Set_Extended_Scan_Response_Data_Command(
|
|
1670
1821
|
advertising_handle=adv_handle,
|
|
1671
1822
|
operation=HCI_LE_Set_Extended_Advertising_Data_Command.Operation.COMPLETE_DATA,
|
|
1672
1823
|
fragment_preference=0x01, # Should not fragment
|
|
1673
|
-
scan_response_data=
|
|
1674
|
-
),
|
|
1824
|
+
scan_response_data=scan_response_data,
|
|
1825
|
+
),
|
|
1675
1826
|
check_result=True,
|
|
1676
1827
|
)
|
|
1677
1828
|
|
|
@@ -1683,7 +1834,7 @@ class Device(CompositeEventEmitter):
|
|
|
1683
1834
|
HCI_LE_Set_Advertising_Set_Random_Address_Command(
|
|
1684
1835
|
advertising_handle=adv_handle,
|
|
1685
1836
|
random_address=self.random_address,
|
|
1686
|
-
),
|
|
1837
|
+
),
|
|
1687
1838
|
check_result=True,
|
|
1688
1839
|
)
|
|
1689
1840
|
|
|
@@ -1694,19 +1845,27 @@ class Device(CompositeEventEmitter):
|
|
|
1694
1845
|
advertising_handles=[adv_handle],
|
|
1695
1846
|
durations=[0], # Forever
|
|
1696
1847
|
max_extended_advertising_events=[0], # Infinite
|
|
1697
|
-
),
|
|
1848
|
+
),
|
|
1698
1849
|
check_result=True,
|
|
1699
1850
|
)
|
|
1700
1851
|
except HCI_Error as error:
|
|
1701
1852
|
# When any step fails, cleanup the advertising handle.
|
|
1702
1853
|
await self.send_command(
|
|
1703
|
-
HCI_LE_Remove_Advertising_Set_Command(advertising_handle=adv_handle),
|
|
1854
|
+
HCI_LE_Remove_Advertising_Set_Command(advertising_handle=adv_handle),
|
|
1704
1855
|
check_result=False,
|
|
1705
1856
|
)
|
|
1706
1857
|
raise error
|
|
1707
1858
|
|
|
1708
|
-
self.
|
|
1709
|
-
|
|
1859
|
+
advertiser = self.extended_advertisers[adv_handle] = ExtendedAdvertiser(
|
|
1860
|
+
device=self,
|
|
1861
|
+
handle=adv_handle,
|
|
1862
|
+
advertising_properties=advertising_properties,
|
|
1863
|
+
own_address_type=own_address_type,
|
|
1864
|
+
auto_restart=auto_restart,
|
|
1865
|
+
advertising_data=advertising_data,
|
|
1866
|
+
scan_response_data=scan_response_data,
|
|
1867
|
+
)
|
|
1868
|
+
return advertiser
|
|
1710
1869
|
|
|
1711
1870
|
@experimental('Extended Advertising is still experimental - Might be changed soon.')
|
|
1712
1871
|
async def stop_extended_advertising(self, adv_handle: int) -> None:
|
|
@@ -1722,19 +1881,19 @@ class Device(CompositeEventEmitter):
|
|
|
1722
1881
|
advertising_handles=[adv_handle],
|
|
1723
1882
|
durations=[0],
|
|
1724
1883
|
max_extended_advertising_events=[0],
|
|
1725
|
-
),
|
|
1884
|
+
),
|
|
1726
1885
|
check_result=True,
|
|
1727
1886
|
)
|
|
1728
1887
|
# Remove advertising set
|
|
1729
1888
|
await self.send_command(
|
|
1730
|
-
HCI_LE_Remove_Advertising_Set_Command(advertising_handle=adv_handle),
|
|
1889
|
+
HCI_LE_Remove_Advertising_Set_Command(advertising_handle=adv_handle),
|
|
1731
1890
|
check_result=True,
|
|
1732
1891
|
)
|
|
1733
|
-
self.
|
|
1892
|
+
del self.extended_advertisers[adv_handle]
|
|
1734
1893
|
|
|
1735
1894
|
@property
|
|
1736
1895
|
def is_advertising(self):
|
|
1737
|
-
return self.
|
|
1896
|
+
return self.legacy_advertiser or self.extended_advertisers
|
|
1738
1897
|
|
|
1739
1898
|
async def start_scanning(
|
|
1740
1899
|
self,
|
|
@@ -1795,7 +1954,7 @@ class Device(CompositeEventEmitter):
|
|
|
1795
1954
|
scan_types=[scan_type] * scanning_phy_count,
|
|
1796
1955
|
scan_intervals=[int(scan_window / 0.625)] * scanning_phy_count,
|
|
1797
1956
|
scan_windows=[int(scan_window / 0.625)] * scanning_phy_count,
|
|
1798
|
-
),
|
|
1957
|
+
),
|
|
1799
1958
|
check_result=True,
|
|
1800
1959
|
)
|
|
1801
1960
|
|
|
@@ -1806,7 +1965,7 @@ class Device(CompositeEventEmitter):
|
|
|
1806
1965
|
filter_duplicates=1 if filter_duplicates else 0,
|
|
1807
1966
|
duration=0, # TODO allow other values
|
|
1808
1967
|
period=0, # TODO allow other values
|
|
1809
|
-
),
|
|
1968
|
+
),
|
|
1810
1969
|
check_result=True,
|
|
1811
1970
|
)
|
|
1812
1971
|
else:
|
|
@@ -1824,7 +1983,7 @@ class Device(CompositeEventEmitter):
|
|
|
1824
1983
|
le_scan_window=int(scan_window / 0.625),
|
|
1825
1984
|
own_address_type=own_address_type,
|
|
1826
1985
|
scanning_filter_policy=HCI_LE_Set_Scan_Parameters_Command.BASIC_UNFILTERED_POLICY,
|
|
1827
|
-
),
|
|
1986
|
+
),
|
|
1828
1987
|
check_result=True,
|
|
1829
1988
|
)
|
|
1830
1989
|
|
|
@@ -1832,7 +1991,7 @@ class Device(CompositeEventEmitter):
|
|
|
1832
1991
|
await self.send_command(
|
|
1833
1992
|
HCI_LE_Set_Scan_Enable_Command(
|
|
1834
1993
|
le_scan_enable=1, filter_duplicates=1 if filter_duplicates else 0
|
|
1835
|
-
),
|
|
1994
|
+
),
|
|
1836
1995
|
check_result=True,
|
|
1837
1996
|
)
|
|
1838
1997
|
|
|
@@ -1845,12 +2004,12 @@ class Device(CompositeEventEmitter):
|
|
|
1845
2004
|
await self.send_command(
|
|
1846
2005
|
HCI_LE_Set_Extended_Scan_Enable_Command(
|
|
1847
2006
|
enable=0, filter_duplicates=0, duration=0, period=0
|
|
1848
|
-
),
|
|
2007
|
+
),
|
|
1849
2008
|
check_result=True,
|
|
1850
2009
|
)
|
|
1851
2010
|
else:
|
|
1852
2011
|
await self.send_command(
|
|
1853
|
-
HCI_LE_Set_Scan_Enable_Command(le_scan_enable=0, filter_duplicates=0),
|
|
2012
|
+
HCI_LE_Set_Scan_Enable_Command(le_scan_enable=0, filter_duplicates=0),
|
|
1854
2013
|
check_result=True,
|
|
1855
2014
|
)
|
|
1856
2015
|
|
|
@@ -1870,7 +2029,7 @@ class Device(CompositeEventEmitter):
|
|
|
1870
2029
|
|
|
1871
2030
|
async def start_discovery(self, auto_restart: bool = True) -> None:
|
|
1872
2031
|
await self.send_command(
|
|
1873
|
-
HCI_Write_Inquiry_Mode_Command(inquiry_mode=HCI_EXTENDED_INQUIRY_MODE),
|
|
2032
|
+
HCI_Write_Inquiry_Mode_Command(inquiry_mode=HCI_EXTENDED_INQUIRY_MODE),
|
|
1874
2033
|
check_result=True,
|
|
1875
2034
|
)
|
|
1876
2035
|
|
|
@@ -1879,7 +2038,7 @@ class Device(CompositeEventEmitter):
|
|
|
1879
2038
|
lap=HCI_GENERAL_INQUIRY_LAP,
|
|
1880
2039
|
inquiry_length=DEVICE_DEFAULT_INQUIRY_LENGTH,
|
|
1881
2040
|
num_responses=0, # Unlimited number of responses.
|
|
1882
|
-
)
|
|
2041
|
+
)
|
|
1883
2042
|
)
|
|
1884
2043
|
if response.status != HCI_Command_Status_Event.PENDING:
|
|
1885
2044
|
self.discovering = False
|
|
@@ -1890,7 +2049,7 @@ class Device(CompositeEventEmitter):
|
|
|
1890
2049
|
|
|
1891
2050
|
async def stop_discovery(self) -> None:
|
|
1892
2051
|
if self.discovering:
|
|
1893
|
-
await self.send_command(HCI_Inquiry_Cancel_Command(), check_result=True)
|
|
2052
|
+
await self.send_command(HCI_Inquiry_Cancel_Command(), check_result=True)
|
|
1894
2053
|
self.auto_restart_inquiry = True
|
|
1895
2054
|
self.discovering = False
|
|
1896
2055
|
|
|
@@ -1938,7 +2097,7 @@ class Device(CompositeEventEmitter):
|
|
|
1938
2097
|
await self.send_command(
|
|
1939
2098
|
HCI_Write_Extended_Inquiry_Response_Command(
|
|
1940
2099
|
fec_required=0, extended_inquiry_response=self.inquiry_response
|
|
1941
|
-
),
|
|
2100
|
+
),
|
|
1942
2101
|
check_result=True,
|
|
1943
2102
|
)
|
|
1944
2103
|
await self.set_scan_enable(
|
|
@@ -2127,7 +2286,7 @@ class Device(CompositeEventEmitter):
|
|
|
2127
2286
|
supervision_timeouts=supervision_timeouts,
|
|
2128
2287
|
min_ce_lengths=min_ce_lengths,
|
|
2129
2288
|
max_ce_lengths=max_ce_lengths,
|
|
2130
|
-
)
|
|
2289
|
+
)
|
|
2131
2290
|
)
|
|
2132
2291
|
else:
|
|
2133
2292
|
if HCI_LE_1M_PHY not in connection_parameters_preferences:
|
|
@@ -2156,7 +2315,7 @@ class Device(CompositeEventEmitter):
|
|
|
2156
2315
|
supervision_timeout=int(prefs.supervision_timeout / 10),
|
|
2157
2316
|
min_ce_length=int(prefs.min_ce_length / 0.625),
|
|
2158
2317
|
max_ce_length=int(prefs.max_ce_length / 0.625),
|
|
2159
|
-
)
|
|
2318
|
+
)
|
|
2160
2319
|
)
|
|
2161
2320
|
else:
|
|
2162
2321
|
# Save pending connection
|
|
@@ -2173,7 +2332,7 @@ class Device(CompositeEventEmitter):
|
|
|
2173
2332
|
clock_offset=0x0000,
|
|
2174
2333
|
allow_role_switch=0x01,
|
|
2175
2334
|
reserved=0,
|
|
2176
|
-
)
|
|
2335
|
+
)
|
|
2177
2336
|
)
|
|
2178
2337
|
|
|
2179
2338
|
if result.status != HCI_Command_Status_Event.PENDING:
|
|
@@ -2192,10 +2351,10 @@ class Device(CompositeEventEmitter):
|
|
|
2192
2351
|
)
|
|
2193
2352
|
except asyncio.TimeoutError:
|
|
2194
2353
|
if transport == BT_LE_TRANSPORT:
|
|
2195
|
-
await self.send_command(HCI_LE_Create_Connection_Cancel_Command())
|
|
2354
|
+
await self.send_command(HCI_LE_Create_Connection_Cancel_Command())
|
|
2196
2355
|
else:
|
|
2197
2356
|
await self.send_command(
|
|
2198
|
-
HCI_Create_Connection_Cancel_Command(bd_addr=peer_address)
|
|
2357
|
+
HCI_Create_Connection_Cancel_Command(bd_addr=peer_address)
|
|
2199
2358
|
)
|
|
2200
2359
|
|
|
2201
2360
|
try:
|
|
@@ -2309,7 +2468,7 @@ class Device(CompositeEventEmitter):
|
|
|
2309
2468
|
try:
|
|
2310
2469
|
# Accept connection request
|
|
2311
2470
|
await self.send_command(
|
|
2312
|
-
HCI_Accept_Connection_Request_Command(bd_addr=peer_address, role=role)
|
|
2471
|
+
HCI_Accept_Connection_Request_Command(bd_addr=peer_address, role=role)
|
|
2313
2472
|
)
|
|
2314
2473
|
|
|
2315
2474
|
# Wait for connection complete
|
|
@@ -2366,7 +2525,9 @@ class Device(CompositeEventEmitter):
|
|
|
2366
2525
|
check_result=True,
|
|
2367
2526
|
)
|
|
2368
2527
|
|
|
2369
|
-
async def disconnect(
|
|
2528
|
+
async def disconnect(
|
|
2529
|
+
self, connection: Union[Connection, ScoLink, CisLink], reason: int
|
|
2530
|
+
) -> None:
|
|
2370
2531
|
# Create a future so that we can wait for the disconnection's result
|
|
2371
2532
|
pending_disconnection = asyncio.get_running_loop().create_future()
|
|
2372
2533
|
connection.on('disconnection', pending_disconnection.set_result)
|
|
@@ -2405,7 +2566,7 @@ class Device(CompositeEventEmitter):
|
|
|
2405
2566
|
connection_handle=connection.handle,
|
|
2406
2567
|
tx_octets=tx_octets,
|
|
2407
2568
|
tx_time=tx_time,
|
|
2408
|
-
),
|
|
2569
|
+
),
|
|
2409
2570
|
check_result=True,
|
|
2410
2571
|
)
|
|
2411
2572
|
|
|
@@ -2451,7 +2612,7 @@ class Device(CompositeEventEmitter):
|
|
|
2451
2612
|
supervision_timeout=supervision_timeout,
|
|
2452
2613
|
min_ce_length=min_ce_length,
|
|
2453
2614
|
max_ce_length=max_ce_length,
|
|
2454
|
-
)
|
|
2615
|
+
)
|
|
2455
2616
|
)
|
|
2456
2617
|
if result.status != HCI_Command_Status_Event.PENDING:
|
|
2457
2618
|
raise HCI_StatusError(result)
|
|
@@ -2779,7 +2940,7 @@ class Device(CompositeEventEmitter):
|
|
|
2779
2940
|
|
|
2780
2941
|
try:
|
|
2781
2942
|
result = await self.send_command(
|
|
2782
|
-
HCI_Switch_Role_Command(bd_addr=connection.peer_address, role=role)
|
|
2943
|
+
HCI_Switch_Role_Command(bd_addr=connection.peer_address, role=role)
|
|
2783
2944
|
)
|
|
2784
2945
|
if result.status != HCI_COMMAND_STATUS_PENDING:
|
|
2785
2946
|
logger.warning(
|
|
@@ -2821,7 +2982,7 @@ class Device(CompositeEventEmitter):
|
|
|
2821
2982
|
page_scan_repetition_mode=HCI_Remote_Name_Request_Command.R2,
|
|
2822
2983
|
reserved=0,
|
|
2823
2984
|
clock_offset=0, # TODO investigate non-0 values
|
|
2824
|
-
)
|
|
2985
|
+
)
|
|
2825
2986
|
)
|
|
2826
2987
|
|
|
2827
2988
|
if result.status != HCI_COMMAND_STATUS_PENDING:
|
|
@@ -2837,6 +2998,150 @@ class Device(CompositeEventEmitter):
|
|
|
2837
2998
|
self.remove_listener('remote_name', handler)
|
|
2838
2999
|
self.remove_listener('remote_name_failure', failure_handler)
|
|
2839
3000
|
|
|
3001
|
+
# [LE only]
|
|
3002
|
+
@experimental('Only for testing.')
|
|
3003
|
+
async def setup_cig(
|
|
3004
|
+
self,
|
|
3005
|
+
cig_id: int,
|
|
3006
|
+
cis_id: List[int],
|
|
3007
|
+
sdu_interval: Tuple[int, int],
|
|
3008
|
+
framing: int,
|
|
3009
|
+
max_sdu: Tuple[int, int],
|
|
3010
|
+
retransmission_number: int,
|
|
3011
|
+
max_transport_latency: Tuple[int, int],
|
|
3012
|
+
) -> List[int]:
|
|
3013
|
+
"""Sends HCI_LE_Set_CIG_Parameters_Command.
|
|
3014
|
+
|
|
3015
|
+
Args:
|
|
3016
|
+
cig_id: CIG_ID.
|
|
3017
|
+
cis_id: CID ID list.
|
|
3018
|
+
sdu_interval: SDU intervals of (Central->Peripheral, Peripheral->Cental).
|
|
3019
|
+
framing: Un-framing(0) or Framing(1).
|
|
3020
|
+
max_sdu: Max SDU counts of (Central->Peripheral, Peripheral->Cental).
|
|
3021
|
+
retransmission_number: retransmission_number.
|
|
3022
|
+
max_transport_latency: Max transport latencies of
|
|
3023
|
+
(Central->Peripheral, Peripheral->Cental).
|
|
3024
|
+
|
|
3025
|
+
Returns:
|
|
3026
|
+
List of created CIS handles corresponding to the same order of [cid_id].
|
|
3027
|
+
"""
|
|
3028
|
+
num_cis = len(cis_id)
|
|
3029
|
+
|
|
3030
|
+
response = await self.send_command(
|
|
3031
|
+
HCI_LE_Set_CIG_Parameters_Command(
|
|
3032
|
+
cig_id=cig_id,
|
|
3033
|
+
sdu_interval_c_to_p=sdu_interval[0],
|
|
3034
|
+
sdu_interval_p_to_c=sdu_interval[1],
|
|
3035
|
+
worst_case_sca=0x00, # 251-500 ppm
|
|
3036
|
+
packing=0x00, # Sequential
|
|
3037
|
+
framing=framing,
|
|
3038
|
+
max_transport_latency_c_to_p=max_transport_latency[0],
|
|
3039
|
+
max_transport_latency_p_to_c=max_transport_latency[1],
|
|
3040
|
+
cis_id=cis_id,
|
|
3041
|
+
max_sdu_c_to_p=[max_sdu[0]] * num_cis,
|
|
3042
|
+
max_sdu_p_to_c=[max_sdu[1]] * num_cis,
|
|
3043
|
+
phy_c_to_p=[HCI_LE_2M_PHY] * num_cis,
|
|
3044
|
+
phy_p_to_c=[HCI_LE_2M_PHY] * num_cis,
|
|
3045
|
+
rtn_c_to_p=[retransmission_number] * num_cis,
|
|
3046
|
+
rtn_p_to_c=[retransmission_number] * num_cis,
|
|
3047
|
+
),
|
|
3048
|
+
check_result=True,
|
|
3049
|
+
)
|
|
3050
|
+
|
|
3051
|
+
# Ideally, we should manage CIG lifecycle, but they are not useful for Unicast
|
|
3052
|
+
# Server, so here it only provides a basic functionality for testing.
|
|
3053
|
+
cis_handles = response.return_parameters.connection_handle[:]
|
|
3054
|
+
for id, cis_handle in zip(cis_id, cis_handles):
|
|
3055
|
+
self._pending_cis[cis_handle] = (id, cig_id)
|
|
3056
|
+
|
|
3057
|
+
return cis_handles
|
|
3058
|
+
|
|
3059
|
+
# [LE only]
|
|
3060
|
+
@experimental('Only for testing.')
|
|
3061
|
+
async def create_cis(self, cis_acl_pairs: List[Tuple[int, int]]) -> List[CisLink]:
|
|
3062
|
+
for cis_handle, acl_handle in cis_acl_pairs:
|
|
3063
|
+
acl_connection = self.lookup_connection(acl_handle)
|
|
3064
|
+
assert acl_connection
|
|
3065
|
+
cis_id, cig_id = self._pending_cis.pop(cis_handle)
|
|
3066
|
+
self.cis_links[cis_handle] = CisLink(
|
|
3067
|
+
device=self,
|
|
3068
|
+
acl_connection=acl_connection,
|
|
3069
|
+
handle=cis_handle,
|
|
3070
|
+
cis_id=cis_id,
|
|
3071
|
+
cig_id=cig_id,
|
|
3072
|
+
)
|
|
3073
|
+
|
|
3074
|
+
result = await self.send_command(
|
|
3075
|
+
HCI_LE_Create_CIS_Command(
|
|
3076
|
+
cis_connection_handle=[p[0] for p in cis_acl_pairs],
|
|
3077
|
+
acl_connection_handle=[p[1] for p in cis_acl_pairs],
|
|
3078
|
+
),
|
|
3079
|
+
)
|
|
3080
|
+
if result.status != HCI_COMMAND_STATUS_PENDING:
|
|
3081
|
+
logger.warning(
|
|
3082
|
+
'HCI_LE_Create_CIS_Command failed: '
|
|
3083
|
+
f'{HCI_Constant.error_name(result.status)}'
|
|
3084
|
+
)
|
|
3085
|
+
raise HCI_StatusError(result)
|
|
3086
|
+
|
|
3087
|
+
pending_cis_establishments: Dict[int, asyncio.Future[CisLink]] = {}
|
|
3088
|
+
for cis_handle, _ in cis_acl_pairs:
|
|
3089
|
+
pending_cis_establishments[
|
|
3090
|
+
cis_handle
|
|
3091
|
+
] = asyncio.get_running_loop().create_future()
|
|
3092
|
+
|
|
3093
|
+
with closing(EventWatcher()) as watcher:
|
|
3094
|
+
|
|
3095
|
+
@watcher.on(self, 'cis_establishment')
|
|
3096
|
+
def on_cis_establishment(cis_link: CisLink) -> None:
|
|
3097
|
+
if pending_future := pending_cis_establishments.get(
|
|
3098
|
+
cis_link.handle, None
|
|
3099
|
+
):
|
|
3100
|
+
pending_future.set_result(cis_link)
|
|
3101
|
+
|
|
3102
|
+
return await asyncio.gather(*pending_cis_establishments.values())
|
|
3103
|
+
|
|
3104
|
+
# [LE only]
|
|
3105
|
+
@experimental('Only for testing.')
|
|
3106
|
+
async def accept_cis_request(self, handle: int) -> CisLink:
|
|
3107
|
+
result = await self.send_command(
|
|
3108
|
+
HCI_LE_Accept_CIS_Request_Command(connection_handle=handle),
|
|
3109
|
+
)
|
|
3110
|
+
if result.status != HCI_COMMAND_STATUS_PENDING:
|
|
3111
|
+
logger.warning(
|
|
3112
|
+
'HCI_LE_Accept_CIS_Request_Command failed: '
|
|
3113
|
+
f'{HCI_Constant.error_name(result.status)}'
|
|
3114
|
+
)
|
|
3115
|
+
raise HCI_StatusError(result)
|
|
3116
|
+
|
|
3117
|
+
pending_cis_establishment = asyncio.get_running_loop().create_future()
|
|
3118
|
+
|
|
3119
|
+
with closing(EventWatcher()) as watcher:
|
|
3120
|
+
|
|
3121
|
+
@watcher.on(self, 'cis_establishment')
|
|
3122
|
+
def on_cis_establishment(cis_link: CisLink) -> None:
|
|
3123
|
+
if cis_link.handle == handle:
|
|
3124
|
+
pending_cis_establishment.set_result(cis_link)
|
|
3125
|
+
|
|
3126
|
+
return await pending_cis_establishment
|
|
3127
|
+
|
|
3128
|
+
# [LE only]
|
|
3129
|
+
@experimental('Only for testing.')
|
|
3130
|
+
async def reject_cis_request(
|
|
3131
|
+
self,
|
|
3132
|
+
handle: int,
|
|
3133
|
+
reason: int = HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR,
|
|
3134
|
+
) -> None:
|
|
3135
|
+
result = await self.send_command(
|
|
3136
|
+
HCI_LE_Reject_CIS_Request_Command(connection_handle=handle, reason=reason),
|
|
3137
|
+
)
|
|
3138
|
+
if result.status != HCI_COMMAND_STATUS_PENDING:
|
|
3139
|
+
logger.warning(
|
|
3140
|
+
'HCI_LE_Reject_CIS_Request_Command failed: '
|
|
3141
|
+
f'{HCI_Constant.error_name(result.status)}'
|
|
3142
|
+
)
|
|
3143
|
+
raise HCI_StatusError(result)
|
|
3144
|
+
|
|
2840
3145
|
@host_event_handler
|
|
2841
3146
|
def on_flush(self):
|
|
2842
3147
|
self.emit('flush')
|
|
@@ -2929,13 +3234,18 @@ class Device(CompositeEventEmitter):
|
|
|
2929
3234
|
# Guess which own address type is used for this connection.
|
|
2930
3235
|
# This logic is somewhat correct but may need to be improved
|
|
2931
3236
|
# when multiple advertising are run simultaneously.
|
|
3237
|
+
advertiser = None
|
|
2932
3238
|
if self.connect_own_address_type is not None:
|
|
2933
3239
|
own_address_type = self.connect_own_address_type
|
|
3240
|
+
elif self.legacy_advertiser:
|
|
3241
|
+
own_address_type = self.legacy_advertiser.own_address_type
|
|
3242
|
+
# Store advertiser for restarting - it's only required for legacy, since
|
|
3243
|
+
# extended advertisement produces HCI_Advertising_Set_Terminated.
|
|
3244
|
+
if self.legacy_advertiser.auto_restart:
|
|
3245
|
+
advertiser = self.legacy_advertiser
|
|
2934
3246
|
else:
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
# We are no longer advertising
|
|
2938
|
-
self.advertising = False
|
|
3247
|
+
# For extended advertisement, determining own address type later.
|
|
3248
|
+
own_address_type = OwnAddressType.RANDOM
|
|
2939
3249
|
|
|
2940
3250
|
if own_address_type in (
|
|
2941
3251
|
OwnAddressType.PUBLIC,
|
|
@@ -2957,6 +3267,7 @@ class Device(CompositeEventEmitter):
|
|
|
2957
3267
|
connection_parameters,
|
|
2958
3268
|
ConnectionPHY(HCI_LE_1M_PHY, HCI_LE_1M_PHY),
|
|
2959
3269
|
)
|
|
3270
|
+
connection.advertiser_after_disconnection = advertiser
|
|
2960
3271
|
self.connections[connection_handle] = connection
|
|
2961
3272
|
|
|
2962
3273
|
# If supported, read which PHY we're connected with before
|
|
@@ -2988,10 +3299,10 @@ class Device(CompositeEventEmitter):
|
|
|
2988
3299
|
# For directed advertising, this means a timeout
|
|
2989
3300
|
if (
|
|
2990
3301
|
transport == BT_LE_TRANSPORT
|
|
2991
|
-
and self.
|
|
2992
|
-
and self.advertising_type.is_directed
|
|
3302
|
+
and self.legacy_advertiser
|
|
3303
|
+
and self.legacy_advertiser.advertising_type.is_directed
|
|
2993
3304
|
):
|
|
2994
|
-
self.
|
|
3305
|
+
self.legacy_advertiser = None
|
|
2995
3306
|
|
|
2996
3307
|
# Notify listeners
|
|
2997
3308
|
error = core.ConnectionError(
|
|
@@ -3041,30 +3352,49 @@ class Device(CompositeEventEmitter):
|
|
|
3041
3352
|
)
|
|
3042
3353
|
|
|
3043
3354
|
@host_event_handler
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3355
|
+
def on_disconnection(self, connection_handle: int, reason: int) -> None:
|
|
3356
|
+
if connection := self.connections.pop(connection_handle, None):
|
|
3357
|
+
logger.debug(
|
|
3358
|
+
f'*** Disconnection: [0x{connection.handle:04X}] '
|
|
3359
|
+
f'{connection.peer_address} as {connection.role_name}, reason={reason}'
|
|
3360
|
+
)
|
|
3361
|
+
connection.emit('disconnection', reason)
|
|
3362
|
+
|
|
3363
|
+
# Cleanup subsystems that maintain per-connection state
|
|
3364
|
+
self.gatt_server.on_disconnection(connection)
|
|
3365
|
+
|
|
3366
|
+
# Restart advertising if auto-restart is enabled
|
|
3367
|
+
if advertiser := connection.advertiser_after_disconnection:
|
|
3368
|
+
logger.debug('restarting advertising')
|
|
3369
|
+
if isinstance(advertiser, LegacyAdvertiser):
|
|
3370
|
+
self.abort_on(
|
|
3371
|
+
'flush',
|
|
3372
|
+
self.start_legacy_advertising(
|
|
3373
|
+
advertising_type=advertiser.advertising_type,
|
|
3374
|
+
own_address_type=advertiser.own_address_type,
|
|
3375
|
+
advertising_data=advertiser.advertising_data,
|
|
3376
|
+
scan_response_data=advertiser.scan_response_data,
|
|
3377
|
+
auto_restart=True,
|
|
3378
|
+
),
|
|
3379
|
+
)
|
|
3380
|
+
elif isinstance(advertiser, ExtendedAdvertiser):
|
|
3381
|
+
self.abort_on(
|
|
3382
|
+
'flush',
|
|
3383
|
+
self.start_extended_advertising(
|
|
3384
|
+
advertising_properties=advertiser.advertising_properties,
|
|
3385
|
+
own_address_type=advertiser.own_address_type,
|
|
3386
|
+
advertising_data=advertiser.advertising_data,
|
|
3387
|
+
scan_response_data=advertiser.scan_response_data,
|
|
3388
|
+
auto_restart=True,
|
|
3389
|
+
),
|
|
3390
|
+
)
|
|
3391
|
+
elif sco_link := self.sco_links.pop(connection_handle, None):
|
|
3392
|
+
sco_link.emit('disconnection', reason)
|
|
3393
|
+
elif cis_link := self.cis_links.pop(connection_handle, None):
|
|
3394
|
+
cis_link.emit('disconnection', reason)
|
|
3395
|
+
else:
|
|
3396
|
+
logger.error(
|
|
3397
|
+
f'*** Unknown disconnection handle=0x{connection_handle}, reason={reason} ***'
|
|
3068
3398
|
)
|
|
3069
3399
|
|
|
3070
3400
|
@host_event_handler
|
|
@@ -3215,7 +3545,7 @@ class Device(CompositeEventEmitter):
|
|
|
3215
3545
|
try:
|
|
3216
3546
|
if await connection.abort_on('disconnection', method()):
|
|
3217
3547
|
await self.host.send_command(
|
|
3218
|
-
HCI_User_Confirmation_Request_Reply_Command(
|
|
3548
|
+
HCI_User_Confirmation_Request_Reply_Command(
|
|
3219
3549
|
bd_addr=connection.peer_address
|
|
3220
3550
|
)
|
|
3221
3551
|
)
|
|
@@ -3224,7 +3554,7 @@ class Device(CompositeEventEmitter):
|
|
|
3224
3554
|
logger.warning(f'exception while confirming: {error}')
|
|
3225
3555
|
|
|
3226
3556
|
await self.host.send_command(
|
|
3227
|
-
HCI_User_Confirmation_Request_Negative_Reply_Command(
|
|
3557
|
+
HCI_User_Confirmation_Request_Negative_Reply_Command(
|
|
3228
3558
|
bd_addr=connection.peer_address
|
|
3229
3559
|
)
|
|
3230
3560
|
)
|
|
@@ -3245,7 +3575,7 @@ class Device(CompositeEventEmitter):
|
|
|
3245
3575
|
)
|
|
3246
3576
|
if number is not None:
|
|
3247
3577
|
await self.host.send_command(
|
|
3248
|
-
HCI_User_Passkey_Request_Reply_Command(
|
|
3578
|
+
HCI_User_Passkey_Request_Reply_Command(
|
|
3249
3579
|
bd_addr=connection.peer_address, numeric_value=number
|
|
3250
3580
|
)
|
|
3251
3581
|
)
|
|
@@ -3254,7 +3584,7 @@ class Device(CompositeEventEmitter):
|
|
|
3254
3584
|
logger.warning(f'exception while asking for pass-key: {error}')
|
|
3255
3585
|
|
|
3256
3586
|
await self.host.send_command(
|
|
3257
|
-
HCI_User_Passkey_Request_Negative_Reply_Command(
|
|
3587
|
+
HCI_User_Passkey_Request_Negative_Reply_Command(
|
|
3258
3588
|
bd_addr=connection.peer_address
|
|
3259
3589
|
)
|
|
3260
3590
|
)
|
|
@@ -3343,6 +3673,131 @@ class Device(CompositeEventEmitter):
|
|
|
3343
3673
|
connection.emit('remote_name_failure', error)
|
|
3344
3674
|
self.emit('remote_name_failure', address, error)
|
|
3345
3675
|
|
|
3676
|
+
# [Classic only]
|
|
3677
|
+
@host_event_handler
|
|
3678
|
+
@with_connection_from_address
|
|
3679
|
+
@experimental('Only for testing.')
|
|
3680
|
+
def on_sco_connection(
|
|
3681
|
+
self, acl_connection: Connection, sco_handle: int, link_type: int
|
|
3682
|
+
) -> None:
|
|
3683
|
+
logger.debug(
|
|
3684
|
+
f'*** SCO connected: {acl_connection.peer_address}, '
|
|
3685
|
+
f'sco_handle=[0x{sco_handle:04X}], '
|
|
3686
|
+
f'link_type=[0x{link_type:02X}] ***'
|
|
3687
|
+
)
|
|
3688
|
+
sco_link = self.sco_links[sco_handle] = ScoLink(
|
|
3689
|
+
device=self,
|
|
3690
|
+
acl_connection=acl_connection,
|
|
3691
|
+
handle=sco_handle,
|
|
3692
|
+
link_type=link_type,
|
|
3693
|
+
)
|
|
3694
|
+
self.emit('sco_connection', sco_link)
|
|
3695
|
+
|
|
3696
|
+
# [Classic only]
|
|
3697
|
+
@host_event_handler
|
|
3698
|
+
@with_connection_from_address
|
|
3699
|
+
@experimental('Only for testing.')
|
|
3700
|
+
def on_sco_connection_failure(
|
|
3701
|
+
self, acl_connection: Connection, status: int
|
|
3702
|
+
) -> None:
|
|
3703
|
+
logger.debug(f'*** SCO connection failure: {acl_connection.peer_address}***')
|
|
3704
|
+
self.emit('sco_connection_failure')
|
|
3705
|
+
|
|
3706
|
+
# [Classic only]
|
|
3707
|
+
@host_event_handler
|
|
3708
|
+
@experimental('Only for testing')
|
|
3709
|
+
def on_sco_packet(self, sco_handle: int, packet: HCI_SynchronousDataPacket) -> None:
|
|
3710
|
+
if sco_link := self.sco_links.get(sco_handle, None):
|
|
3711
|
+
sco_link.emit('pdu', packet)
|
|
3712
|
+
|
|
3713
|
+
# [LE only]
|
|
3714
|
+
@host_event_handler
|
|
3715
|
+
@experimental('Only for testing')
|
|
3716
|
+
def on_advertising_set_termination(
|
|
3717
|
+
self,
|
|
3718
|
+
status: int,
|
|
3719
|
+
advertising_handle: int,
|
|
3720
|
+
connection_handle: int,
|
|
3721
|
+
) -> None:
|
|
3722
|
+
if status == HCI_SUCCESS:
|
|
3723
|
+
connection = self.lookup_connection(connection_handle)
|
|
3724
|
+
if advertiser := self.extended_advertisers.pop(advertising_handle, None):
|
|
3725
|
+
if connection:
|
|
3726
|
+
if advertiser.auto_restart:
|
|
3727
|
+
connection.advertiser_after_disconnection = advertiser
|
|
3728
|
+
if advertiser.own_address_type in (
|
|
3729
|
+
OwnAddressType.PUBLIC,
|
|
3730
|
+
OwnAddressType.RESOLVABLE_OR_PUBLIC,
|
|
3731
|
+
):
|
|
3732
|
+
connection.self_address = self.public_address
|
|
3733
|
+
else:
|
|
3734
|
+
connection.self_address = self.random_address
|
|
3735
|
+
advertiser.emit('termination', status)
|
|
3736
|
+
|
|
3737
|
+
# [LE only]
|
|
3738
|
+
@host_event_handler
|
|
3739
|
+
@with_connection_from_handle
|
|
3740
|
+
@experimental('Only for testing')
|
|
3741
|
+
def on_cis_request(
|
|
3742
|
+
self,
|
|
3743
|
+
acl_connection: Connection,
|
|
3744
|
+
cis_handle: int,
|
|
3745
|
+
cig_id: int,
|
|
3746
|
+
cis_id: int,
|
|
3747
|
+
) -> None:
|
|
3748
|
+
logger.debug(
|
|
3749
|
+
f'*** CIS Request '
|
|
3750
|
+
f'acl_handle=[0x{acl_connection.handle:04X}]{acl_connection.peer_address}, '
|
|
3751
|
+
f'cis_handle=[0x{cis_handle:04X}], '
|
|
3752
|
+
f'cig_id=[0x{cig_id:02X}], '
|
|
3753
|
+
f'cis_id=[0x{cis_id:02X}] ***'
|
|
3754
|
+
)
|
|
3755
|
+
# LE_CIS_Established event doesn't provide info, so we must store them here.
|
|
3756
|
+
self.cis_links[cis_handle] = CisLink(
|
|
3757
|
+
device=self,
|
|
3758
|
+
acl_connection=acl_connection,
|
|
3759
|
+
handle=cis_handle,
|
|
3760
|
+
cig_id=cig_id,
|
|
3761
|
+
cis_id=cis_id,
|
|
3762
|
+
)
|
|
3763
|
+
self.emit('cis_request', acl_connection, cis_handle, cig_id, cis_id)
|
|
3764
|
+
|
|
3765
|
+
# [LE only]
|
|
3766
|
+
@host_event_handler
|
|
3767
|
+
@experimental('Only for testing')
|
|
3768
|
+
def on_cis_establishment(self, cis_handle: int) -> None:
|
|
3769
|
+
cis_link = self.cis_links[cis_handle]
|
|
3770
|
+
cis_link.state = CisLink.State.ESTABLISHED
|
|
3771
|
+
|
|
3772
|
+
assert cis_link.acl_connection
|
|
3773
|
+
|
|
3774
|
+
logger.debug(
|
|
3775
|
+
f'*** CIS Establishment '
|
|
3776
|
+
f'{cis_link.acl_connection.peer_address}, '
|
|
3777
|
+
f'cis_handle=[0x{cis_handle:04X}], '
|
|
3778
|
+
f'cig_id=[0x{cis_link.cig_id:02X}], '
|
|
3779
|
+
f'cis_id=[0x{cis_link.cis_id:02X}] ***'
|
|
3780
|
+
)
|
|
3781
|
+
|
|
3782
|
+
cis_link.emit('establishment')
|
|
3783
|
+
self.emit('cis_establishment', cis_link)
|
|
3784
|
+
|
|
3785
|
+
# [LE only]
|
|
3786
|
+
@host_event_handler
|
|
3787
|
+
@experimental('Only for testing')
|
|
3788
|
+
def on_cis_establishment_failure(self, cis_handle: int, status: int) -> None:
|
|
3789
|
+
logger.debug(f'*** CIS Establishment Failure: cis=[0x{cis_handle:04X}] ***')
|
|
3790
|
+
if cis_link := self.cis_links.pop(cis_handle, None):
|
|
3791
|
+
cis_link.emit('establishment_failure')
|
|
3792
|
+
self.emit('cis_establishment_failure', cis_handle, status)
|
|
3793
|
+
|
|
3794
|
+
# [LE only]
|
|
3795
|
+
@host_event_handler
|
|
3796
|
+
@experimental('Only for testing')
|
|
3797
|
+
def on_iso_packet(self, handle: int, packet: HCI_IsoDataPacket) -> None:
|
|
3798
|
+
if cis_link := self.cis_links.get(handle, None):
|
|
3799
|
+
cis_link.emit('pdu', packet)
|
|
3800
|
+
|
|
3346
3801
|
@host_event_handler
|
|
3347
3802
|
@with_connection_from_handle
|
|
3348
3803
|
def on_connection_encryption_change(self, connection, encryption):
|