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