bumble 0.0.223__py3-none-any.whl → 0.0.224__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/controller_info.py +90 -114
- bumble/apps/controller_loopback.py +11 -9
- bumble/apps/gg_bridge.py +1 -1
- bumble/apps/hci_bridge.py +3 -1
- bumble/apps/l2cap_bridge.py +1 -1
- bumble/apps/rfcomm_bridge.py +1 -1
- bumble/apps/scan.py +10 -4
- bumble/apps/speaker/speaker.py +1 -1
- bumble/avrcp.py +366 -190
- bumble/bridge.py +10 -2
- bumble/controller.py +14 -1
- bumble/core.py +1 -1
- bumble/device.py +998 -573
- bumble/drivers/intel.py +45 -39
- bumble/drivers/rtk.py +76 -40
- bumble/hci.py +1318 -796
- bumble/host.py +329 -157
- bumble/l2cap.py +10 -5
- bumble/smp.py +8 -3
- bumble/snoop.py +111 -1
- bumble/transport/android_netsim.py +1 -1
- bumble/vendor/android/hci.py +108 -86
- bumble/vendor/zephyr/hci.py +24 -18
- {bumble-0.0.223.dist-info → bumble-0.0.224.dist-info}/METADATA +4 -3
- {bumble-0.0.223.dist-info → bumble-0.0.224.dist-info}/RECORD +30 -30
- {bumble-0.0.223.dist-info → bumble-0.0.224.dist-info}/WHEEL +1 -1
- {bumble-0.0.223.dist-info → bumble-0.0.224.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.223.dist-info → bumble-0.0.224.dist-info}/licenses/LICENSE +0 -0
- {bumble-0.0.223.dist-info → bumble-0.0.224.dist-info}/top_level.txt +0 -0
bumble/device.py
CHANGED
|
@@ -79,6 +79,7 @@ if TYPE_CHECKING:
|
|
|
79
79
|
from bumble.transport.common import TransportSink, TransportSource
|
|
80
80
|
|
|
81
81
|
_T = TypeVar('_T')
|
|
82
|
+
_RP = TypeVar('_RP', bound=hci.HCI_ReturnParameters)
|
|
82
83
|
|
|
83
84
|
# -----------------------------------------------------------------------------
|
|
84
85
|
# Logging
|
|
@@ -373,24 +374,22 @@ class LegacyAdvertiser:
|
|
|
373
374
|
async def start(self) -> None:
|
|
374
375
|
# Set/update the advertising data if the advertising type allows it
|
|
375
376
|
if self.advertising_type.has_data:
|
|
376
|
-
await self.device.
|
|
377
|
+
await self.device.send_sync_command(
|
|
377
378
|
hci.HCI_LE_Set_Advertising_Data_Command(
|
|
378
379
|
advertising_data=self.device.advertising_data
|
|
379
|
-
)
|
|
380
|
-
check_result=True,
|
|
380
|
+
)
|
|
381
381
|
)
|
|
382
382
|
|
|
383
383
|
# Set/update the scan response data if the advertising is scannable
|
|
384
384
|
if self.advertising_type.is_scannable:
|
|
385
|
-
await self.device.
|
|
385
|
+
await self.device.send_sync_command(
|
|
386
386
|
hci.HCI_LE_Set_Scan_Response_Data_Command(
|
|
387
387
|
scan_response_data=self.device.scan_response_data
|
|
388
|
-
)
|
|
389
|
-
check_result=True,
|
|
388
|
+
)
|
|
390
389
|
)
|
|
391
390
|
|
|
392
391
|
# Set the advertising parameters
|
|
393
|
-
await self.device.
|
|
392
|
+
await self.device.send_sync_command(
|
|
394
393
|
hci.HCI_LE_Set_Advertising_Parameters_Command(
|
|
395
394
|
advertising_interval_min=int(
|
|
396
395
|
self.device.advertising_interval_min / 0.625
|
|
@@ -404,21 +403,18 @@ class LegacyAdvertiser:
|
|
|
404
403
|
peer_address=self.peer_address,
|
|
405
404
|
advertising_channel_map=7,
|
|
406
405
|
advertising_filter_policy=0,
|
|
407
|
-
)
|
|
408
|
-
check_result=True,
|
|
406
|
+
)
|
|
409
407
|
)
|
|
410
408
|
|
|
411
409
|
# Enable advertising
|
|
412
|
-
await self.device.
|
|
413
|
-
hci.HCI_LE_Set_Advertising_Enable_Command(advertising_enable=1)
|
|
414
|
-
check_result=True,
|
|
410
|
+
await self.device.send_sync_command(
|
|
411
|
+
hci.HCI_LE_Set_Advertising_Enable_Command(advertising_enable=1)
|
|
415
412
|
)
|
|
416
413
|
|
|
417
414
|
async def stop(self) -> None:
|
|
418
415
|
# Disable advertising
|
|
419
|
-
await self.device.
|
|
420
|
-
hci.HCI_LE_Set_Advertising_Enable_Command(advertising_enable=0)
|
|
421
|
-
check_result=True,
|
|
416
|
+
await self.device.send_sync_command(
|
|
417
|
+
hci.HCI_LE_Set_Advertising_Enable_Command(advertising_enable=0)
|
|
422
418
|
)
|
|
423
419
|
|
|
424
420
|
|
|
@@ -635,7 +631,7 @@ class AdvertisingSet(utils.EventEmitter):
|
|
|
635
631
|
"connectable and scannable"
|
|
636
632
|
)
|
|
637
633
|
|
|
638
|
-
response = await self.device.
|
|
634
|
+
response = await self.device.send_sync_command(
|
|
639
635
|
hci.HCI_LE_Set_Extended_Advertising_Parameters_Command(
|
|
640
636
|
advertising_handle=self.advertising_handle,
|
|
641
637
|
advertising_event_properties=int(
|
|
@@ -668,22 +664,20 @@ class AdvertisingSet(utils.EventEmitter):
|
|
|
668
664
|
scan_request_notification_enable=(
|
|
669
665
|
1 if advertising_parameters.enable_scan_request_notifications else 0
|
|
670
666
|
),
|
|
671
|
-
)
|
|
672
|
-
check_result=True,
|
|
667
|
+
)
|
|
673
668
|
)
|
|
674
|
-
self.selected_tx_power = response.
|
|
669
|
+
self.selected_tx_power = response.selected_tx_power
|
|
675
670
|
self.advertising_parameters = advertising_parameters
|
|
676
671
|
|
|
677
672
|
async def set_advertising_data(self, advertising_data: bytes) -> None:
|
|
678
673
|
# pylint: disable=line-too-long
|
|
679
|
-
await self.device.
|
|
674
|
+
await self.device.send_sync_command(
|
|
680
675
|
hci.HCI_LE_Set_Extended_Advertising_Data_Command(
|
|
681
676
|
advertising_handle=self.advertising_handle,
|
|
682
677
|
operation=hci.HCI_LE_Set_Extended_Advertising_Data_Command.Operation.COMPLETE_DATA,
|
|
683
678
|
fragment_preference=hci.HCI_LE_Set_Extended_Advertising_Parameters_Command.SHOULD_NOT_FRAGMENT,
|
|
684
679
|
advertising_data=advertising_data,
|
|
685
|
-
)
|
|
686
|
-
check_result=True,
|
|
680
|
+
)
|
|
687
681
|
)
|
|
688
682
|
self.advertising_data = advertising_data
|
|
689
683
|
|
|
@@ -699,21 +693,20 @@ class AdvertisingSet(utils.EventEmitter):
|
|
|
699
693
|
)
|
|
700
694
|
return
|
|
701
695
|
|
|
702
|
-
await self.device.
|
|
696
|
+
await self.device.send_sync_command(
|
|
703
697
|
hci.HCI_LE_Set_Extended_Scan_Response_Data_Command(
|
|
704
698
|
advertising_handle=self.advertising_handle,
|
|
705
699
|
operation=hci.HCI_LE_Set_Extended_Advertising_Data_Command.Operation.COMPLETE_DATA,
|
|
706
700
|
fragment_preference=hci.HCI_LE_Set_Extended_Advertising_Parameters_Command.SHOULD_NOT_FRAGMENT,
|
|
707
701
|
scan_response_data=scan_response_data,
|
|
708
|
-
)
|
|
709
|
-
check_result=True,
|
|
702
|
+
)
|
|
710
703
|
)
|
|
711
704
|
self.scan_response_data = scan_response_data
|
|
712
705
|
|
|
713
706
|
async def set_periodic_advertising_parameters(
|
|
714
707
|
self, advertising_parameters: PeriodicAdvertisingParameters
|
|
715
708
|
) -> None:
|
|
716
|
-
await self.device.
|
|
709
|
+
await self.device.send_sync_command(
|
|
717
710
|
hci.HCI_LE_Set_Periodic_Advertising_Parameters_Command(
|
|
718
711
|
advertising_handle=self.advertising_handle,
|
|
719
712
|
periodic_advertising_interval_min=int(
|
|
@@ -723,29 +716,26 @@ class AdvertisingSet(utils.EventEmitter):
|
|
|
723
716
|
advertising_parameters.periodic_advertising_interval_max / 1.25
|
|
724
717
|
),
|
|
725
718
|
periodic_advertising_properties=advertising_parameters.periodic_advertising_properties,
|
|
726
|
-
)
|
|
727
|
-
check_result=True,
|
|
719
|
+
)
|
|
728
720
|
)
|
|
729
721
|
self.periodic_advertising_parameters = advertising_parameters
|
|
730
722
|
|
|
731
723
|
async def set_periodic_advertising_data(self, advertising_data: bytes) -> None:
|
|
732
|
-
await self.device.
|
|
724
|
+
await self.device.send_sync_command(
|
|
733
725
|
hci.HCI_LE_Set_Periodic_Advertising_Data_Command(
|
|
734
726
|
advertising_handle=self.advertising_handle,
|
|
735
727
|
operation=hci.HCI_LE_Set_Extended_Advertising_Data_Command.Operation.COMPLETE_DATA,
|
|
736
728
|
advertising_data=advertising_data,
|
|
737
|
-
)
|
|
738
|
-
check_result=True,
|
|
729
|
+
)
|
|
739
730
|
)
|
|
740
731
|
self.periodic_advertising_data = advertising_data
|
|
741
732
|
|
|
742
733
|
async def set_random_address(self, random_address: hci.Address) -> None:
|
|
743
|
-
await self.device.
|
|
734
|
+
await self.device.send_sync_command(
|
|
744
735
|
hci.HCI_LE_Set_Advertising_Set_Random_Address_Command(
|
|
745
736
|
advertising_handle=self.advertising_handle,
|
|
746
737
|
random_address=(random_address or self.device.random_address),
|
|
747
|
-
)
|
|
748
|
-
check_result=True,
|
|
738
|
+
)
|
|
749
739
|
)
|
|
750
740
|
|
|
751
741
|
async def start(
|
|
@@ -761,28 +751,26 @@ class AdvertisingSet(utils.EventEmitter):
|
|
|
761
751
|
max_advertising_events: Maximum number of events to advertise for. Use 0
|
|
762
752
|
(the default) for an unlimited number of advertisements.
|
|
763
753
|
"""
|
|
764
|
-
await self.device.
|
|
754
|
+
await self.device.send_sync_command(
|
|
765
755
|
hci.HCI_LE_Set_Extended_Advertising_Enable_Command(
|
|
766
756
|
enable=1,
|
|
767
757
|
advertising_handles=[self.advertising_handle],
|
|
768
758
|
durations=[round(duration * 100)],
|
|
769
759
|
max_extended_advertising_events=[max_advertising_events],
|
|
770
|
-
)
|
|
771
|
-
check_result=True,
|
|
760
|
+
)
|
|
772
761
|
)
|
|
773
762
|
self.enabled = True
|
|
774
763
|
|
|
775
764
|
self.emit(self.EVENT_START)
|
|
776
765
|
|
|
777
766
|
async def stop(self) -> None:
|
|
778
|
-
await self.device.
|
|
767
|
+
await self.device.send_sync_command(
|
|
779
768
|
hci.HCI_LE_Set_Extended_Advertising_Enable_Command(
|
|
780
769
|
enable=0,
|
|
781
770
|
advertising_handles=[self.advertising_handle],
|
|
782
771
|
durations=[0],
|
|
783
772
|
max_extended_advertising_events=[0],
|
|
784
|
-
)
|
|
785
|
-
check_result=True,
|
|
773
|
+
)
|
|
786
774
|
)
|
|
787
775
|
self.enabled = False
|
|
788
776
|
|
|
@@ -791,12 +779,11 @@ class AdvertisingSet(utils.EventEmitter):
|
|
|
791
779
|
async def start_periodic(self, include_adi: bool = False) -> None:
|
|
792
780
|
if self.periodic_enabled:
|
|
793
781
|
return
|
|
794
|
-
await self.device.
|
|
782
|
+
await self.device.send_sync_command(
|
|
795
783
|
hci.HCI_LE_Set_Periodic_Advertising_Enable_Command(
|
|
796
784
|
enable=1 | (2 if include_adi else 0),
|
|
797
785
|
advertising_handle=self.advertising_handle,
|
|
798
|
-
)
|
|
799
|
-
check_result=True,
|
|
786
|
+
)
|
|
800
787
|
)
|
|
801
788
|
self.periodic_enabled = True
|
|
802
789
|
|
|
@@ -805,23 +792,21 @@ class AdvertisingSet(utils.EventEmitter):
|
|
|
805
792
|
async def stop_periodic(self) -> None:
|
|
806
793
|
if not self.periodic_enabled:
|
|
807
794
|
return
|
|
808
|
-
await self.device.
|
|
795
|
+
await self.device.send_sync_command(
|
|
809
796
|
hci.HCI_LE_Set_Periodic_Advertising_Enable_Command(
|
|
810
797
|
enable=0,
|
|
811
798
|
advertising_handle=self.advertising_handle,
|
|
812
|
-
)
|
|
813
|
-
check_result=True,
|
|
799
|
+
)
|
|
814
800
|
)
|
|
815
801
|
self.periodic_enabled = False
|
|
816
802
|
|
|
817
803
|
self.emit(self.EVENT_STOP_PERIODIC)
|
|
818
804
|
|
|
819
805
|
async def remove(self) -> None:
|
|
820
|
-
await self.device.
|
|
806
|
+
await self.device.send_sync_command(
|
|
821
807
|
hci.HCI_LE_Remove_Advertising_Set_Command(
|
|
822
808
|
advertising_handle=self.advertising_handle
|
|
823
|
-
)
|
|
824
|
-
check_result=True,
|
|
809
|
+
)
|
|
825
810
|
)
|
|
826
811
|
del self.device.extended_advertising_sets[self.advertising_handle]
|
|
827
812
|
|
|
@@ -916,7 +901,7 @@ class PeriodicAdvertisingSync(utils.EventEmitter):
|
|
|
916
901
|
hci.HCI_LE_Periodic_Advertising_Create_Sync_Command.Options.DUPLICATE_FILTERING_INITIALLY_ENABLED
|
|
917
902
|
)
|
|
918
903
|
|
|
919
|
-
await self.device.
|
|
904
|
+
await self.device.send_async_command(
|
|
920
905
|
hci.HCI_LE_Periodic_Advertising_Create_Sync_Command(
|
|
921
906
|
options=options,
|
|
922
907
|
advertising_sid=self.sid,
|
|
@@ -925,8 +910,7 @@ class PeriodicAdvertisingSync(utils.EventEmitter):
|
|
|
925
910
|
skip=self.skip,
|
|
926
911
|
sync_timeout=int(self.sync_timeout * 100),
|
|
927
912
|
sync_cte_type=0,
|
|
928
|
-
)
|
|
929
|
-
check_result=True,
|
|
913
|
+
)
|
|
930
914
|
)
|
|
931
915
|
|
|
932
916
|
self.state = self.State.PENDING
|
|
@@ -937,18 +921,20 @@ class PeriodicAdvertisingSync(utils.EventEmitter):
|
|
|
937
921
|
|
|
938
922
|
if self.state == self.State.PENDING:
|
|
939
923
|
self.state = self.State.CANCELLED
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
924
|
+
try:
|
|
925
|
+
await self.device.send_sync_command(
|
|
926
|
+
hci.HCI_LE_Periodic_Advertising_Create_Sync_Cancel_Command()
|
|
927
|
+
)
|
|
944
928
|
if self in self.device.periodic_advertising_syncs:
|
|
945
929
|
self.device.periodic_advertising_syncs.remove(self)
|
|
930
|
+
except hci.HCI_Error:
|
|
931
|
+
pass
|
|
946
932
|
return
|
|
947
933
|
|
|
948
934
|
if self.state in (self.State.ESTABLISHED, self.State.ERROR, self.State.LOST):
|
|
949
935
|
self.state = self.State.TERMINATED
|
|
950
936
|
if self.sync_handle is not None:
|
|
951
|
-
await self.device.
|
|
937
|
+
await self.device.send_sync_command(
|
|
952
938
|
hci.HCI_LE_Periodic_Advertising_Terminate_Sync_Command(
|
|
953
939
|
sync_handle=self.sync_handle
|
|
954
940
|
)
|
|
@@ -1118,11 +1104,10 @@ class Big(utils.EventEmitter):
|
|
|
1118
1104
|
with closing(utils.EventWatcher()) as watcher:
|
|
1119
1105
|
terminated = asyncio.Event()
|
|
1120
1106
|
watcher.once(self, Big.Event.TERMINATION, lambda _: terminated.set())
|
|
1121
|
-
await self.device.
|
|
1107
|
+
await self.device.send_async_command(
|
|
1122
1108
|
hci.HCI_LE_Terminate_BIG_Command(
|
|
1123
1109
|
big_handle=self.big_handle, reason=reason
|
|
1124
|
-
)
|
|
1125
|
-
check_result=True,
|
|
1110
|
+
)
|
|
1126
1111
|
)
|
|
1127
1112
|
await terminated.wait()
|
|
1128
1113
|
|
|
@@ -1174,9 +1159,8 @@ class BigSync(utils.EventEmitter):
|
|
|
1174
1159
|
logger.error('BIG Sync %d is not active.', self.big_handle)
|
|
1175
1160
|
return
|
|
1176
1161
|
|
|
1177
|
-
await self.device.
|
|
1178
|
-
hci.HCI_LE_BIG_Terminate_Sync_Command(big_handle=self.big_handle)
|
|
1179
|
-
check_result=True,
|
|
1162
|
+
await self.device.send_sync_command(
|
|
1163
|
+
hci.HCI_LE_BIG_Terminate_Sync_Command(big_handle=self.big_handle)
|
|
1180
1164
|
)
|
|
1181
1165
|
self.state = BigSync.State.TERMINATED
|
|
1182
1166
|
|
|
@@ -1193,7 +1177,7 @@ class ChannelSoundingCapabilities:
|
|
|
1193
1177
|
rtt_capability: int
|
|
1194
1178
|
rtt_aa_only_n: int
|
|
1195
1179
|
rtt_sounding_n: int
|
|
1196
|
-
|
|
1180
|
+
rtt_random_sequence_n: int
|
|
1197
1181
|
nadm_sounding_capability: int
|
|
1198
1182
|
nadm_random_capability: int
|
|
1199
1183
|
cs_sync_phys_supported: int
|
|
@@ -1425,8 +1409,8 @@ class ConnectionParametersPreferences:
|
|
|
1425
1409
|
connection_interval_max: float = DEVICE_DEFAULT_CONNECTION_INTERVAL_MAX
|
|
1426
1410
|
max_latency: int = DEVICE_DEFAULT_CONNECTION_MAX_LATENCY
|
|
1427
1411
|
supervision_timeout: int = DEVICE_DEFAULT_CONNECTION_SUPERVISION_TIMEOUT
|
|
1428
|
-
min_ce_length:
|
|
1429
|
-
max_ce_length:
|
|
1412
|
+
min_ce_length: float = DEVICE_DEFAULT_CONNECTION_MIN_CE_LENGTH
|
|
1413
|
+
max_ce_length: float = DEVICE_DEFAULT_CONNECTION_MAX_CE_LENGTH
|
|
1430
1414
|
|
|
1431
1415
|
|
|
1432
1416
|
ConnectionParametersPreferences.default = ConnectionParametersPreferences()
|
|
@@ -1496,7 +1480,7 @@ class _IsoLink:
|
|
|
1496
1480
|
async with self._data_path_lock:
|
|
1497
1481
|
if direction in self.data_paths:
|
|
1498
1482
|
return
|
|
1499
|
-
await self.device.
|
|
1483
|
+
await self.device.send_sync_command(
|
|
1500
1484
|
hci.HCI_LE_Setup_ISO_Data_Path_Command(
|
|
1501
1485
|
connection_handle=self.handle,
|
|
1502
1486
|
data_path_direction=direction,
|
|
@@ -1504,8 +1488,7 @@ class _IsoLink:
|
|
|
1504
1488
|
codec_id=codec_id or hci.CodingFormat(hci.CodecID.TRANSPARENT),
|
|
1505
1489
|
controller_delay=controller_delay,
|
|
1506
1490
|
codec_configuration=codec_configuration,
|
|
1507
|
-
)
|
|
1508
|
-
check_result=True,
|
|
1491
|
+
)
|
|
1509
1492
|
)
|
|
1510
1493
|
self.data_paths.add(direction)
|
|
1511
1494
|
|
|
@@ -1522,14 +1505,13 @@ class _IsoLink:
|
|
|
1522
1505
|
directions_to_remove = set(directions).intersection(self.data_paths)
|
|
1523
1506
|
if not directions_to_remove:
|
|
1524
1507
|
return
|
|
1525
|
-
await self.device.
|
|
1508
|
+
await self.device.send_sync_command(
|
|
1526
1509
|
hci.HCI_LE_Remove_ISO_Data_Path_Command(
|
|
1527
1510
|
connection_handle=self.handle,
|
|
1528
1511
|
data_path_direction=sum(
|
|
1529
1512
|
1 << direction for direction in directions_to_remove
|
|
1530
1513
|
),
|
|
1531
|
-
)
|
|
1532
|
-
check_result=True,
|
|
1514
|
+
)
|
|
1533
1515
|
)
|
|
1534
1516
|
self.data_paths.difference_update(directions_to_remove)
|
|
1535
1517
|
|
|
@@ -1538,14 +1520,13 @@ class _IsoLink:
|
|
|
1538
1520
|
self.device.host.send_iso_sdu(connection_handle=self.handle, sdu=sdu)
|
|
1539
1521
|
|
|
1540
1522
|
async def get_tx_time_stamp(self) -> tuple[int, int, int]:
|
|
1541
|
-
response = await self.device.
|
|
1542
|
-
hci.HCI_LE_Read_ISO_TX_Sync_Command(connection_handle=self.handle)
|
|
1543
|
-
check_result=True,
|
|
1523
|
+
response = await self.device.send_sync_command(
|
|
1524
|
+
hci.HCI_LE_Read_ISO_TX_Sync_Command(connection_handle=self.handle)
|
|
1544
1525
|
)
|
|
1545
1526
|
return (
|
|
1546
|
-
response.
|
|
1547
|
-
response.
|
|
1548
|
-
response.
|
|
1527
|
+
response.packet_sequence_number,
|
|
1528
|
+
response.tx_time_stamp,
|
|
1529
|
+
response.time_offset,
|
|
1549
1530
|
)
|
|
1550
1531
|
|
|
1551
1532
|
@property
|
|
@@ -1716,7 +1697,7 @@ class Connection(utils.CompositeEventEmitter):
|
|
|
1716
1697
|
peer_address: hci.Address
|
|
1717
1698
|
peer_name: str | None
|
|
1718
1699
|
peer_resolvable_address: hci.Address | None
|
|
1719
|
-
peer_le_features: hci.LeFeatureMask
|
|
1700
|
+
peer_le_features: hci.LeFeatureMask
|
|
1720
1701
|
role: hci.Role
|
|
1721
1702
|
parameters: Parameters
|
|
1722
1703
|
encryption: int
|
|
@@ -1769,8 +1750,8 @@ class Connection(utils.CompositeEventEmitter):
|
|
|
1769
1750
|
EVENT_CIS_REQUEST = "cis_request"
|
|
1770
1751
|
EVENT_CIS_ESTABLISHMENT = "cis_establishment"
|
|
1771
1752
|
EVENT_CIS_ESTABLISHMENT_FAILURE = "cis_establishment_failure"
|
|
1772
|
-
|
|
1773
|
-
|
|
1753
|
+
EVENT_LE_REMOTE_FEATURES_CHANGE = "le_remote_features_change"
|
|
1754
|
+
EVENT_LE_REMOTE_FEATURES_CHANGE_FAILURE = "le_remote_features_change_failure"
|
|
1774
1755
|
|
|
1775
1756
|
@utils.composite_listener
|
|
1776
1757
|
class Listener:
|
|
@@ -1848,14 +1829,14 @@ class Connection(utils.CompositeEventEmitter):
|
|
|
1848
1829
|
self.authenticated = False
|
|
1849
1830
|
self.sc = False
|
|
1850
1831
|
self.att_mtu = att.ATT_DEFAULT_MTU
|
|
1851
|
-
self.data_length = DEVICE_DEFAULT_DATA_LENGTH
|
|
1832
|
+
self.data_length: tuple[int, int, int, int] = DEVICE_DEFAULT_DATA_LENGTH
|
|
1852
1833
|
self.gatt_client = gatt_client.Client(self) # Per-connection client
|
|
1853
1834
|
self.gatt_server = (
|
|
1854
1835
|
device.gatt_server
|
|
1855
1836
|
) # By default, use the device's shared server
|
|
1856
1837
|
self.pairing_peer_io_capability = None
|
|
1857
1838
|
self.pairing_peer_authentication_requirements = None
|
|
1858
|
-
self.peer_le_features =
|
|
1839
|
+
self.peer_le_features = hci.LeFeatureMask(0)
|
|
1859
1840
|
self.cs_configs = {}
|
|
1860
1841
|
self.cs_procedures = {}
|
|
1861
1842
|
|
|
@@ -1937,16 +1918,21 @@ class Connection(utils.CompositeEventEmitter):
|
|
|
1937
1918
|
connection_interval_max: float,
|
|
1938
1919
|
max_latency: int,
|
|
1939
1920
|
supervision_timeout: float,
|
|
1921
|
+
min_ce_length: float = 0.0,
|
|
1922
|
+
max_ce_length: float = 0.0,
|
|
1940
1923
|
use_l2cap=False,
|
|
1941
1924
|
) -> None:
|
|
1942
1925
|
"""
|
|
1943
|
-
Request
|
|
1926
|
+
Request a change of the connection parameters.
|
|
1927
|
+
|
|
1928
|
+
For short connection intervals (below 7.5ms, introduced in Bluetooth 6.2),
|
|
1929
|
+
use the `update_parameters_with_subrate` method instead.
|
|
1944
1930
|
|
|
1945
1931
|
Args:
|
|
1946
1932
|
connection_interval_min: Minimum interval, in milliseconds.
|
|
1947
1933
|
connection_interval_max: Maximum interval, in milliseconds.
|
|
1948
|
-
max_latency:
|
|
1949
|
-
supervision_timeout: Timeout, in milliseconds.
|
|
1934
|
+
max_latency: Max latency, in number of intervals.
|
|
1935
|
+
supervision_timeout: Supervision Timeout, in milliseconds.
|
|
1950
1936
|
use_l2cap: Request the update via L2CAP.
|
|
1951
1937
|
"""
|
|
1952
1938
|
return await self.device.update_connection_parameters(
|
|
@@ -1956,6 +1942,77 @@ class Connection(utils.CompositeEventEmitter):
|
|
|
1956
1942
|
max_latency,
|
|
1957
1943
|
supervision_timeout,
|
|
1958
1944
|
use_l2cap=use_l2cap,
|
|
1945
|
+
min_ce_length=min_ce_length,
|
|
1946
|
+
max_ce_length=max_ce_length,
|
|
1947
|
+
)
|
|
1948
|
+
|
|
1949
|
+
async def update_parameters_with_subrate(
|
|
1950
|
+
self,
|
|
1951
|
+
connection_interval_min: float,
|
|
1952
|
+
connection_interval_max: float,
|
|
1953
|
+
subrate_min: int,
|
|
1954
|
+
subrate_max: int,
|
|
1955
|
+
max_latency: int,
|
|
1956
|
+
continuation_number: int,
|
|
1957
|
+
supervision_timeout: float,
|
|
1958
|
+
min_ce_length: float,
|
|
1959
|
+
max_ce_length: float,
|
|
1960
|
+
) -> None:
|
|
1961
|
+
"""
|
|
1962
|
+
Request a change of the connection parameters.
|
|
1963
|
+
This is similar to `update_parameters` but also allows specifying
|
|
1964
|
+
the subrate parameters and supports shorter connection intervals (below
|
|
1965
|
+
7.5ms, as introduced in Bluetooth 6.2).
|
|
1966
|
+
|
|
1967
|
+
Args:
|
|
1968
|
+
connection_interval_min: Minimum interval, in milliseconds.
|
|
1969
|
+
connection_interval_max: Maximum interval, in milliseconds.
|
|
1970
|
+
subrate_min: Minimum subrate factor.
|
|
1971
|
+
subrate_max: Maximum subrate factor.
|
|
1972
|
+
max_latency: Max latency, in number of intervals.
|
|
1973
|
+
continuation_number: Continuation number.
|
|
1974
|
+
supervision_timeout: Supervision Timeout, in milliseconds.
|
|
1975
|
+
min_ce_length: Minimum connection event length, in milliseconds.
|
|
1976
|
+
max_ce_length: Maximumsub connection event length, in milliseconds.
|
|
1977
|
+
"""
|
|
1978
|
+
return await self.device.update_connection_parameters_with_subrate(
|
|
1979
|
+
self,
|
|
1980
|
+
connection_interval_min,
|
|
1981
|
+
connection_interval_max,
|
|
1982
|
+
subrate_min,
|
|
1983
|
+
subrate_max,
|
|
1984
|
+
max_latency,
|
|
1985
|
+
continuation_number,
|
|
1986
|
+
supervision_timeout,
|
|
1987
|
+
min_ce_length,
|
|
1988
|
+
max_ce_length,
|
|
1989
|
+
)
|
|
1990
|
+
|
|
1991
|
+
async def update_subrate(
|
|
1992
|
+
self,
|
|
1993
|
+
subrate_min: int,
|
|
1994
|
+
subrate_max: int,
|
|
1995
|
+
max_latency: int,
|
|
1996
|
+
continuation_number: int,
|
|
1997
|
+
supervision_timeout: float,
|
|
1998
|
+
) -> None:
|
|
1999
|
+
"""
|
|
2000
|
+
Request request a change to the subrating factor and/or other parameters.
|
|
2001
|
+
|
|
2002
|
+
Args:
|
|
2003
|
+
subrate_min: Minimum subrate factor.
|
|
2004
|
+
subrate_max: Maximum subrate factor.
|
|
2005
|
+
max_latency: Max latency, in number of intervals.
|
|
2006
|
+
continuation_number: Continuation number.
|
|
2007
|
+
supervision_timeout: Supervision Timeout, in milliseconds.
|
|
2008
|
+
"""
|
|
2009
|
+
return await self.device.update_connection_subrate(
|
|
2010
|
+
self,
|
|
2011
|
+
subrate_min,
|
|
2012
|
+
subrate_max,
|
|
2013
|
+
max_latency,
|
|
2014
|
+
continuation_number,
|
|
2015
|
+
supervision_timeout,
|
|
1959
2016
|
)
|
|
1960
2017
|
|
|
1961
2018
|
async def set_phy(
|
|
@@ -2062,6 +2119,7 @@ class DeviceConfiguration:
|
|
|
2062
2119
|
le_privacy_enabled: bool = False
|
|
2063
2120
|
le_rpa_timeout: int = DEVICE_DEFAULT_LE_RPA_TIMEOUT
|
|
2064
2121
|
le_subrate_enabled: bool = False
|
|
2122
|
+
le_shorter_connection_intervals_enabled: bool = False
|
|
2065
2123
|
classic_enabled: bool = False
|
|
2066
2124
|
classic_sc_enabled: bool = True
|
|
2067
2125
|
classic_ssp_enabled: bool = True
|
|
@@ -2368,7 +2426,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2368
2426
|
self._host = None
|
|
2369
2427
|
self.powered_on = False
|
|
2370
2428
|
self.auto_restart_inquiry = True
|
|
2371
|
-
self.command_timeout = 10 # seconds
|
|
2429
|
+
self.command_timeout = 10.0 # seconds
|
|
2372
2430
|
self.gatt_server = gatt_server.Server(self)
|
|
2373
2431
|
self.sdp_server = sdp.Server(self)
|
|
2374
2432
|
self.l2cap_channel_manager = l2cap.ChannelManager(
|
|
@@ -2416,6 +2474,9 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2416
2474
|
self.le_rpa_timeout = config.le_rpa_timeout
|
|
2417
2475
|
self.le_rpa_periodic_update_task: asyncio.Task | None = None
|
|
2418
2476
|
self.le_subrate_enabled = config.le_subrate_enabled
|
|
2477
|
+
self.le_shorter_connection_intervals_enabled = (
|
|
2478
|
+
config.le_shorter_connection_intervals_enabled
|
|
2479
|
+
)
|
|
2419
2480
|
self.classic_enabled = config.classic_enabled
|
|
2420
2481
|
self.cis_enabled = config.cis_enabled
|
|
2421
2482
|
self.classic_sc_enabled = config.classic_sc_enabled
|
|
@@ -2674,10 +2735,88 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2674
2735
|
def send_l2cap_pdu(self, connection_handle: int, cid: int, pdu: bytes) -> None:
|
|
2675
2736
|
self.host.send_l2cap_pdu(connection_handle, cid, pdu)
|
|
2676
2737
|
|
|
2677
|
-
|
|
2738
|
+
@overload
|
|
2739
|
+
async def send_command(
|
|
2740
|
+
self, command: hci.HCI_SyncCommand, check_result: bool = False
|
|
2741
|
+
) -> hci.HCI_Command_Complete_Event: ...
|
|
2742
|
+
|
|
2743
|
+
@overload
|
|
2744
|
+
async def send_command(
|
|
2745
|
+
self, command: hci.HCI_AsyncCommand, check_result: bool = False
|
|
2746
|
+
) -> hci.HCI_Command_Status_Event: ...
|
|
2747
|
+
|
|
2748
|
+
async def send_command(
|
|
2749
|
+
self,
|
|
2750
|
+
command: hci.HCI_SyncCommand | hci.HCI_AsyncCommand,
|
|
2751
|
+
check_result: bool = False,
|
|
2752
|
+
) -> hci.HCI_Command_Complete_Event | hci.HCI_Command_Status_Event:
|
|
2753
|
+
'''
|
|
2754
|
+
Send a synchronous or asynchronous command via the host.
|
|
2755
|
+
|
|
2756
|
+
NOTE: use `send_sync_command` instead for synchronous commands.
|
|
2757
|
+
'''
|
|
2758
|
+
try:
|
|
2759
|
+
return await self.host.send_command(
|
|
2760
|
+
command, check_result, self.command_timeout
|
|
2761
|
+
)
|
|
2762
|
+
except asyncio.TimeoutError as error:
|
|
2763
|
+
logger.warning(f'!!! Command {command.name} timed out')
|
|
2764
|
+
raise CommandTimeoutError() from error
|
|
2765
|
+
|
|
2766
|
+
async def send_sync_command(self, command: hci.HCI_SyncCommand[_RP]) -> _RP:
|
|
2767
|
+
'''
|
|
2768
|
+
Send a synchronous command via the host.
|
|
2769
|
+
|
|
2770
|
+
If the `status` field of the response's `return_parameters` is not equal to
|
|
2771
|
+
`SUCCESS` an exception is raised.
|
|
2772
|
+
|
|
2773
|
+
Params:
|
|
2774
|
+
command: the command to send.
|
|
2775
|
+
|
|
2776
|
+
Returns:
|
|
2777
|
+
An instance of the return parameters class associated with the command class.
|
|
2778
|
+
'''
|
|
2779
|
+
try:
|
|
2780
|
+
return await self.host.send_sync_command(command, self.command_timeout)
|
|
2781
|
+
except asyncio.TimeoutError as error:
|
|
2782
|
+
logger.warning(f'!!! Command {command.name} timed out')
|
|
2783
|
+
raise CommandTimeoutError() from error
|
|
2784
|
+
|
|
2785
|
+
async def send_sync_command_raw(
|
|
2786
|
+
self, command: hci.HCI_SyncCommand[_RP]
|
|
2787
|
+
) -> hci.HCI_Command_Complete_Event[_RP]:
|
|
2788
|
+
'''
|
|
2789
|
+
Send a synchronous command via the host without checking the response.
|
|
2790
|
+
|
|
2791
|
+
Params:
|
|
2792
|
+
command: the command to send.
|
|
2793
|
+
|
|
2794
|
+
Returns:
|
|
2795
|
+
An HCI_Command_Complete_Event instance.
|
|
2796
|
+
'''
|
|
2797
|
+
try:
|
|
2798
|
+
return await self.host.send_sync_command_raw(command, self.command_timeout)
|
|
2799
|
+
except asyncio.TimeoutError as error:
|
|
2800
|
+
logger.warning(f'!!! Command {command.name} timed out')
|
|
2801
|
+
raise CommandTimeoutError() from error
|
|
2802
|
+
|
|
2803
|
+
async def send_async_command(
|
|
2804
|
+
self, command: hci.HCI_AsyncCommand, check_status: bool = True
|
|
2805
|
+
) -> hci.HCI_ErrorCode:
|
|
2806
|
+
'''
|
|
2807
|
+
Send an asynchronous command via the host.
|
|
2808
|
+
|
|
2809
|
+
Params:
|
|
2810
|
+
command: the command to send.
|
|
2811
|
+
check_status: If `True`, check the `status` field of the response and
|
|
2812
|
+
raise and exception if not equal to `PENDING`.
|
|
2813
|
+
|
|
2814
|
+
Returns:
|
|
2815
|
+
A status code.
|
|
2816
|
+
'''
|
|
2678
2817
|
try:
|
|
2679
|
-
return await
|
|
2680
|
-
|
|
2818
|
+
return await self.host.send_async_command(
|
|
2819
|
+
command, check_status, self.command_timeout
|
|
2681
2820
|
)
|
|
2682
2821
|
except asyncio.TimeoutError as error:
|
|
2683
2822
|
logger.warning(f'!!! Command {command.name} timed out')
|
|
@@ -2688,12 +2827,12 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2688
2827
|
await self.host.reset()
|
|
2689
2828
|
|
|
2690
2829
|
# Try to get the public address from the controller
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
logger.debug(
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2830
|
+
try:
|
|
2831
|
+
response = await self.host.send_sync_command(hci.HCI_Read_BD_ADDR_Command())
|
|
2832
|
+
logger.debug(color(f'BD_ADDR: {response.bd_addr}', 'yellow'))
|
|
2833
|
+
self.public_address = response.bd_addr
|
|
2834
|
+
except hci.HCI_Error:
|
|
2835
|
+
logger.debug('Controller has no public address')
|
|
2697
2836
|
|
|
2698
2837
|
# Instantiate the Key Store (we do this here rather than at __init__ time
|
|
2699
2838
|
# because some Key Store implementations use the public address as a namespace)
|
|
@@ -2707,12 +2846,11 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2707
2846
|
)
|
|
2708
2847
|
|
|
2709
2848
|
if self.host.supports_command(hci.HCI_WRITE_LE_HOST_SUPPORT_COMMAND):
|
|
2710
|
-
await self.
|
|
2849
|
+
await self.send_sync_command(
|
|
2711
2850
|
hci.HCI_Write_LE_Host_Support_Command(
|
|
2712
2851
|
le_supported_host=int(self.le_enabled),
|
|
2713
2852
|
simultaneous_le_host=int(self.le_simultaneous_enabled),
|
|
2714
|
-
)
|
|
2715
|
-
check_result=True,
|
|
2853
|
+
)
|
|
2716
2854
|
)
|
|
2717
2855
|
|
|
2718
2856
|
if self.le_enabled:
|
|
@@ -2739,11 +2877,10 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2739
2877
|
'yellow',
|
|
2740
2878
|
)
|
|
2741
2879
|
)
|
|
2742
|
-
await self.
|
|
2880
|
+
await self.send_sync_command(
|
|
2743
2881
|
hci.HCI_LE_Set_Random_Address_Command(
|
|
2744
2882
|
random_address=self.random_address
|
|
2745
|
-
)
|
|
2746
|
-
check_result=True,
|
|
2883
|
+
)
|
|
2747
2884
|
)
|
|
2748
2885
|
|
|
2749
2886
|
# Load the address resolving list
|
|
@@ -2752,81 +2889,100 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2752
2889
|
|
|
2753
2890
|
# Enable address resolution
|
|
2754
2891
|
if self.address_resolution_offload:
|
|
2755
|
-
await self.
|
|
2892
|
+
await self.send_sync_command(
|
|
2756
2893
|
hci.HCI_LE_Set_Address_Resolution_Enable_Command(
|
|
2757
2894
|
address_resolution_enable=1
|
|
2758
|
-
)
|
|
2759
|
-
check_result=True,
|
|
2895
|
+
)
|
|
2760
2896
|
)
|
|
2761
2897
|
|
|
2762
|
-
if self.cis_enabled
|
|
2763
|
-
|
|
2898
|
+
if self.cis_enabled and self.host.supports_command(
|
|
2899
|
+
hci.HCI_LE_SET_HOST_FEATURE_COMMAND
|
|
2900
|
+
):
|
|
2901
|
+
await self.send_sync_command(
|
|
2764
2902
|
hci.HCI_LE_Set_Host_Feature_Command(
|
|
2765
2903
|
bit_number=hci.LeFeature.CONNECTED_ISOCHRONOUS_STREAM,
|
|
2766
2904
|
bit_value=1,
|
|
2767
|
-
)
|
|
2768
|
-
check_result=True,
|
|
2905
|
+
)
|
|
2769
2906
|
)
|
|
2770
2907
|
|
|
2771
|
-
if
|
|
2772
|
-
|
|
2908
|
+
if (
|
|
2909
|
+
self.le_subrate_enabled
|
|
2910
|
+
and self.host.supports_command(hci.HCI_LE_SET_HOST_FEATURE_COMMAND)
|
|
2911
|
+
and self.host.supports_le_features(
|
|
2912
|
+
hci.LeFeatureMask.CONNECTION_SUBRATING
|
|
2913
|
+
)
|
|
2914
|
+
):
|
|
2915
|
+
await self.send_sync_command(
|
|
2773
2916
|
hci.HCI_LE_Set_Host_Feature_Command(
|
|
2774
2917
|
bit_number=hci.LeFeature.CONNECTION_SUBRATING_HOST_SUPPORT,
|
|
2775
2918
|
bit_value=1,
|
|
2776
|
-
)
|
|
2777
|
-
check_result=True,
|
|
2919
|
+
)
|
|
2778
2920
|
)
|
|
2779
2921
|
|
|
2780
|
-
if self.config.channel_sounding_enabled
|
|
2781
|
-
|
|
2922
|
+
if self.config.channel_sounding_enabled and self.host.supports_command(
|
|
2923
|
+
hci.HCI_LE_SET_HOST_FEATURE_COMMAND
|
|
2924
|
+
):
|
|
2925
|
+
await self.send_sync_command(
|
|
2782
2926
|
hci.HCI_LE_Set_Host_Feature_Command(
|
|
2783
2927
|
bit_number=hci.LeFeature.CHANNEL_SOUNDING_HOST_SUPPORT,
|
|
2784
2928
|
bit_value=1,
|
|
2785
|
-
)
|
|
2786
|
-
check_result=True,
|
|
2929
|
+
)
|
|
2787
2930
|
)
|
|
2788
|
-
result = await self.
|
|
2789
|
-
hci.HCI_LE_CS_Read_Local_Supported_Capabilities_Command()
|
|
2790
|
-
check_result=True,
|
|
2931
|
+
result = await self.send_sync_command(
|
|
2932
|
+
hci.HCI_LE_CS_Read_Local_Supported_Capabilities_Command()
|
|
2791
2933
|
)
|
|
2792
2934
|
self.cs_capabilities = ChannelSoundingCapabilities(
|
|
2793
|
-
num_config_supported=result.
|
|
2794
|
-
max_consecutive_procedures_supported=result.
|
|
2795
|
-
num_antennas_supported=result.
|
|
2796
|
-
max_antenna_paths_supported=result.
|
|
2797
|
-
roles_supported=result.
|
|
2798
|
-
modes_supported=result.
|
|
2799
|
-
rtt_capability=result.
|
|
2800
|
-
rtt_aa_only_n=result.
|
|
2801
|
-
rtt_sounding_n=result.
|
|
2802
|
-
|
|
2803
|
-
nadm_sounding_capability=result.
|
|
2804
|
-
nadm_random_capability=result.
|
|
2805
|
-
cs_sync_phys_supported=result.
|
|
2806
|
-
subfeatures_supported=result.
|
|
2807
|
-
t_ip1_times_supported=result.
|
|
2808
|
-
t_ip2_times_supported=result.
|
|
2809
|
-
t_fcs_times_supported=result.
|
|
2810
|
-
t_pm_times_supported=result.
|
|
2811
|
-
t_sw_time_supported=result.
|
|
2812
|
-
tx_snr_capability=result.
|
|
2935
|
+
num_config_supported=result.num_config_supported,
|
|
2936
|
+
max_consecutive_procedures_supported=result.max_consecutive_procedures_supported,
|
|
2937
|
+
num_antennas_supported=result.num_antennas_supported,
|
|
2938
|
+
max_antenna_paths_supported=result.max_antenna_paths_supported,
|
|
2939
|
+
roles_supported=result.roles_supported,
|
|
2940
|
+
modes_supported=result.modes_supported,
|
|
2941
|
+
rtt_capability=result.rtt_capability,
|
|
2942
|
+
rtt_aa_only_n=result.rtt_aa_only_n,
|
|
2943
|
+
rtt_sounding_n=result.rtt_sounding_n,
|
|
2944
|
+
rtt_random_sequence_n=result.rtt_random_sequence_n,
|
|
2945
|
+
nadm_sounding_capability=result.nadm_sounding_capability,
|
|
2946
|
+
nadm_random_capability=result.nadm_random_capability,
|
|
2947
|
+
cs_sync_phys_supported=result.cs_sync_phys_supported,
|
|
2948
|
+
subfeatures_supported=result.subfeatures_supported,
|
|
2949
|
+
t_ip1_times_supported=result.t_ip1_times_supported,
|
|
2950
|
+
t_ip2_times_supported=result.t_ip2_times_supported,
|
|
2951
|
+
t_fcs_times_supported=result.t_fcs_times_supported,
|
|
2952
|
+
t_pm_times_supported=result.t_pm_times_supported,
|
|
2953
|
+
t_sw_time_supported=result.t_sw_time_supported,
|
|
2954
|
+
tx_snr_capability=result.tx_snr_capability,
|
|
2955
|
+
)
|
|
2956
|
+
|
|
2957
|
+
if (
|
|
2958
|
+
self.le_shorter_connection_intervals_enabled
|
|
2959
|
+
and self.host.supports_command(hci.HCI_LE_SET_HOST_FEATURE_COMMAND)
|
|
2960
|
+
and self.host.supports_le_features(
|
|
2961
|
+
hci.LeFeatureMask.SHORTER_CONNECTION_INTERVALS
|
|
2962
|
+
)
|
|
2963
|
+
):
|
|
2964
|
+
await self.send_sync_command(
|
|
2965
|
+
hci.HCI_LE_Set_Host_Feature_Command(
|
|
2966
|
+
bit_number=hci.LeFeature.SHORTER_CONNECTION_INTERVALS_HOST_SUPPORT,
|
|
2967
|
+
bit_value=1,
|
|
2968
|
+
)
|
|
2813
2969
|
)
|
|
2814
2970
|
|
|
2815
2971
|
if self.classic_enabled:
|
|
2816
|
-
await self.
|
|
2972
|
+
await self.send_sync_command_raw(
|
|
2817
2973
|
hci.HCI_Write_Local_Name_Command(local_name=self.name.encode('utf8'))
|
|
2818
2974
|
)
|
|
2819
|
-
await self.
|
|
2975
|
+
await self.send_sync_command_raw(
|
|
2820
2976
|
hci.HCI_Write_Class_Of_Device_Command(
|
|
2821
2977
|
class_of_device=self.class_of_device
|
|
2822
2978
|
)
|
|
2823
2979
|
)
|
|
2824
|
-
await self.
|
|
2980
|
+
await self.send_sync_command_raw(
|
|
2825
2981
|
hci.HCI_Write_Simple_Pairing_Mode_Command(
|
|
2826
2982
|
simple_pairing_mode=int(self.classic_ssp_enabled)
|
|
2827
2983
|
)
|
|
2828
2984
|
)
|
|
2829
|
-
await self.
|
|
2985
|
+
await self.send_sync_command_raw(
|
|
2830
2986
|
hci.HCI_Write_Secure_Connections_Host_Support_Command(
|
|
2831
2987
|
secure_connections_host_support=int(self.classic_sc_enabled)
|
|
2832
2988
|
)
|
|
@@ -2838,17 +2994,15 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2838
2994
|
if self.host.supports_lmp_features(
|
|
2839
2995
|
hci.LmpFeatureMask.INTERLACED_PAGE_SCAN
|
|
2840
2996
|
):
|
|
2841
|
-
await self.
|
|
2842
|
-
hci.HCI_Write_Page_Scan_Type_Command(page_scan_type=1)
|
|
2843
|
-
check_result=True,
|
|
2997
|
+
await self.send_sync_command(
|
|
2998
|
+
hci.HCI_Write_Page_Scan_Type_Command(page_scan_type=1)
|
|
2844
2999
|
)
|
|
2845
3000
|
|
|
2846
3001
|
if self.host.supports_lmp_features(
|
|
2847
3002
|
hci.LmpFeatureMask.INTERLACED_INQUIRY_SCAN
|
|
2848
3003
|
):
|
|
2849
|
-
await self.
|
|
2850
|
-
hci.HCI_Write_Inquiry_Scan_Type_Command(scan_type=1)
|
|
2851
|
-
check_result=True,
|
|
3004
|
+
await self.send_sync_command(
|
|
3005
|
+
hci.HCI_Write_Inquiry_Scan_Type_Command(scan_type=1)
|
|
2852
3006
|
)
|
|
2853
3007
|
|
|
2854
3008
|
# Done
|
|
@@ -2880,15 +3034,17 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2880
3034
|
return False
|
|
2881
3035
|
|
|
2882
3036
|
random_address = hci.Address.generate_private_address(self.irk)
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
3037
|
+
try:
|
|
3038
|
+
await self.send_sync_command(
|
|
3039
|
+
hci.HCI_LE_Set_Random_Address_Command(
|
|
3040
|
+
random_address=self.random_address
|
|
3041
|
+
)
|
|
3042
|
+
)
|
|
2887
3043
|
logger.info(f'new RPA: {random_address}')
|
|
2888
3044
|
self.random_address = random_address
|
|
2889
3045
|
return True
|
|
2890
|
-
|
|
2891
|
-
logger.warning(f'failed to set RPA: {
|
|
3046
|
+
except hci.HCI_Error as error:
|
|
3047
|
+
logger.warning(f'failed to set RPA: {error.error_code!r}')
|
|
2892
3048
|
return False
|
|
2893
3049
|
|
|
2894
3050
|
async def _run_rpa_periodic_update(self) -> None:
|
|
@@ -2906,30 +3062,26 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2906
3062
|
self.address_resolver = smp.AddressResolver(resolving_keys)
|
|
2907
3063
|
|
|
2908
3064
|
if self.address_resolution_offload or self.address_generation_offload:
|
|
2909
|
-
await self.
|
|
2910
|
-
hci.HCI_LE_Clear_Resolving_List_Command(), check_result=True
|
|
2911
|
-
)
|
|
3065
|
+
await self.send_sync_command(hci.HCI_LE_Clear_Resolving_List_Command())
|
|
2912
3066
|
|
|
2913
3067
|
# Add an empty entry for non-directed address generation.
|
|
2914
|
-
await self.
|
|
3068
|
+
await self.send_sync_command(
|
|
2915
3069
|
hci.HCI_LE_Add_Device_To_Resolving_List_Command(
|
|
2916
3070
|
peer_identity_address_type=hci.Address.ANY.address_type,
|
|
2917
3071
|
peer_identity_address=hci.Address.ANY,
|
|
2918
3072
|
peer_irk=bytes(16),
|
|
2919
3073
|
local_irk=self.irk,
|
|
2920
|
-
)
|
|
2921
|
-
check_result=True,
|
|
3074
|
+
)
|
|
2922
3075
|
)
|
|
2923
3076
|
|
|
2924
3077
|
for irk, address in resolving_keys:
|
|
2925
|
-
await self.
|
|
3078
|
+
await self.send_sync_command(
|
|
2926
3079
|
hci.HCI_LE_Add_Device_To_Resolving_List_Command(
|
|
2927
3080
|
peer_identity_address_type=address.address_type,
|
|
2928
3081
|
peer_identity_address=address,
|
|
2929
3082
|
peer_irk=irk,
|
|
2930
3083
|
local_irk=self.irk,
|
|
2931
|
-
)
|
|
2932
|
-
check_result=True,
|
|
3084
|
+
)
|
|
2933
3085
|
)
|
|
2934
3086
|
|
|
2935
3087
|
def supports_le_features(self, feature: hci.LeFeatureMask) -> bool:
|
|
@@ -3197,11 +3349,10 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3197
3349
|
except hci.HCI_Error as error:
|
|
3198
3350
|
# Remove the advertising set so that it doesn't stay dangling in the
|
|
3199
3351
|
# controller.
|
|
3200
|
-
await self.
|
|
3352
|
+
await self.send_sync_command(
|
|
3201
3353
|
hci.HCI_LE_Remove_Advertising_Set_Command(
|
|
3202
3354
|
advertising_handle=advertising_handle
|
|
3203
|
-
)
|
|
3204
|
-
check_result=False,
|
|
3355
|
+
)
|
|
3205
3356
|
)
|
|
3206
3357
|
raise error
|
|
3207
3358
|
|
|
@@ -3284,7 +3435,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3284
3435
|
if scanning_phy_count == 0:
|
|
3285
3436
|
raise InvalidArgumentError('at least one scanning PHY must be enabled')
|
|
3286
3437
|
|
|
3287
|
-
await self.
|
|
3438
|
+
await self.send_sync_command(
|
|
3288
3439
|
hci.HCI_LE_Set_Extended_Scan_Parameters_Command(
|
|
3289
3440
|
own_address_type=own_address_type,
|
|
3290
3441
|
scanning_filter_policy=scanning_filter_policy,
|
|
@@ -3292,19 +3443,17 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3292
3443
|
scan_types=[scan_type] * scanning_phy_count,
|
|
3293
3444
|
scan_intervals=[int(scan_interval / 0.625)] * scanning_phy_count,
|
|
3294
3445
|
scan_windows=[int(scan_window / 0.625)] * scanning_phy_count,
|
|
3295
|
-
)
|
|
3296
|
-
check_result=True,
|
|
3446
|
+
)
|
|
3297
3447
|
)
|
|
3298
3448
|
|
|
3299
3449
|
# Enable scanning
|
|
3300
|
-
await self.
|
|
3450
|
+
await self.send_sync_command(
|
|
3301
3451
|
hci.HCI_LE_Set_Extended_Scan_Enable_Command(
|
|
3302
3452
|
enable=1,
|
|
3303
3453
|
filter_duplicates=1 if filter_duplicates else 0,
|
|
3304
3454
|
duration=0, # TODO allow other values
|
|
3305
3455
|
period=0, # TODO allow other values
|
|
3306
|
-
)
|
|
3307
|
-
check_result=True,
|
|
3456
|
+
)
|
|
3308
3457
|
)
|
|
3309
3458
|
else:
|
|
3310
3459
|
# Set the scanning parameters
|
|
@@ -3313,7 +3462,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3313
3462
|
if active
|
|
3314
3463
|
else hci.HCI_LE_Set_Scan_Parameters_Command.PASSIVE_SCANNING
|
|
3315
3464
|
)
|
|
3316
|
-
await self.
|
|
3465
|
+
await self.send_sync_command(
|
|
3317
3466
|
# pylint: disable=line-too-long
|
|
3318
3467
|
hci.HCI_LE_Set_Scan_Parameters_Command(
|
|
3319
3468
|
le_scan_type=scan_type,
|
|
@@ -3321,16 +3470,14 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3321
3470
|
le_scan_window=int(scan_window / 0.625),
|
|
3322
3471
|
own_address_type=own_address_type,
|
|
3323
3472
|
scanning_filter_policy=hci.HCI_LE_Set_Scan_Parameters_Command.BASIC_UNFILTERED_POLICY,
|
|
3324
|
-
)
|
|
3325
|
-
check_result=True,
|
|
3473
|
+
)
|
|
3326
3474
|
)
|
|
3327
3475
|
|
|
3328
3476
|
# Enable scanning
|
|
3329
|
-
await self.
|
|
3477
|
+
await self.send_sync_command(
|
|
3330
3478
|
hci.HCI_LE_Set_Scan_Enable_Command(
|
|
3331
3479
|
le_scan_enable=1, filter_duplicates=1 if filter_duplicates else 0
|
|
3332
|
-
)
|
|
3333
|
-
check_result=True,
|
|
3480
|
+
)
|
|
3334
3481
|
)
|
|
3335
3482
|
|
|
3336
3483
|
self.scanning_is_passive = not active
|
|
@@ -3339,18 +3486,16 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3339
3486
|
async def stop_scanning(self, legacy: bool = False) -> None:
|
|
3340
3487
|
# Disable scanning
|
|
3341
3488
|
if not legacy and self.supports_le_extended_advertising:
|
|
3342
|
-
await self.
|
|
3489
|
+
await self.send_sync_command(
|
|
3343
3490
|
hci.HCI_LE_Set_Extended_Scan_Enable_Command(
|
|
3344
3491
|
enable=0, filter_duplicates=0, duration=0, period=0
|
|
3345
|
-
)
|
|
3346
|
-
check_result=True,
|
|
3492
|
+
)
|
|
3347
3493
|
)
|
|
3348
3494
|
else:
|
|
3349
|
-
await self.
|
|
3495
|
+
await self.send_sync_command(
|
|
3350
3496
|
hci.HCI_LE_Set_Scan_Enable_Command(
|
|
3351
3497
|
le_scan_enable=0, filter_duplicates=0
|
|
3352
|
-
)
|
|
3353
|
-
check_result=True,
|
|
3498
|
+
)
|
|
3354
3499
|
)
|
|
3355
3500
|
|
|
3356
3501
|
self.scanning = False
|
|
@@ -3523,21 +3668,19 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3523
3668
|
periodic_advertising_sync.on_biginfo_advertising_report(report)
|
|
3524
3669
|
|
|
3525
3670
|
async def start_discovery(self, auto_restart: bool = True) -> None:
|
|
3526
|
-
await self.
|
|
3671
|
+
await self.send_sync_command(
|
|
3527
3672
|
hci.HCI_Write_Inquiry_Mode_Command(
|
|
3528
3673
|
inquiry_mode=hci.HCI_EXTENDED_INQUIRY_MODE
|
|
3529
|
-
)
|
|
3530
|
-
check_result=True,
|
|
3674
|
+
)
|
|
3531
3675
|
)
|
|
3532
3676
|
|
|
3533
3677
|
self.discovering = False
|
|
3534
|
-
await self.
|
|
3678
|
+
await self.send_async_command(
|
|
3535
3679
|
hci.HCI_Inquiry_Command(
|
|
3536
3680
|
lap=hci.HCI_GENERAL_INQUIRY_LAP,
|
|
3537
3681
|
inquiry_length=DEVICE_DEFAULT_INQUIRY_LENGTH,
|
|
3538
3682
|
num_responses=0, # Unlimited number of responses.
|
|
3539
|
-
)
|
|
3540
|
-
check_result=True,
|
|
3683
|
+
)
|
|
3541
3684
|
)
|
|
3542
3685
|
|
|
3543
3686
|
self.auto_restart_inquiry = auto_restart
|
|
@@ -3545,7 +3688,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3545
3688
|
|
|
3546
3689
|
async def stop_discovery(self) -> None:
|
|
3547
3690
|
if self.discovering:
|
|
3548
|
-
await self.
|
|
3691
|
+
await self.send_sync_command(hci.HCI_Inquiry_Cancel_Command())
|
|
3549
3692
|
self.auto_restart_inquiry = True
|
|
3550
3693
|
self.discovering = False
|
|
3551
3694
|
|
|
@@ -3573,9 +3716,8 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3573
3716
|
else:
|
|
3574
3717
|
scan_enable = 0x00
|
|
3575
3718
|
|
|
3576
|
-
return await self.
|
|
3577
|
-
hci.HCI_Write_Scan_Enable_Command(scan_enable=scan_enable)
|
|
3578
|
-
check_result=True,
|
|
3719
|
+
return await self.send_sync_command(
|
|
3720
|
+
hci.HCI_Write_Scan_Enable_Command(scan_enable=scan_enable)
|
|
3579
3721
|
)
|
|
3580
3722
|
|
|
3581
3723
|
async def set_discoverable(self, discoverable: bool = True) -> None:
|
|
@@ -3588,11 +3730,10 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3588
3730
|
)
|
|
3589
3731
|
|
|
3590
3732
|
# Update the controller
|
|
3591
|
-
await self.
|
|
3733
|
+
await self.send_sync_command(
|
|
3592
3734
|
hci.HCI_Write_Extended_Inquiry_Response_Command(
|
|
3593
3735
|
fec_required=0, extended_inquiry_response=self.inquiry_response
|
|
3594
|
-
)
|
|
3595
|
-
check_result=True,
|
|
3736
|
+
)
|
|
3596
3737
|
)
|
|
3597
3738
|
await self.set_scan_enable(
|
|
3598
3739
|
inquiry_scan_enabled=self.discoverable,
|
|
@@ -3607,287 +3748,168 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3607
3748
|
page_scan_enabled=self.connectable,
|
|
3608
3749
|
)
|
|
3609
3750
|
|
|
3610
|
-
async def
|
|
3751
|
+
async def connect_le(
|
|
3611
3752
|
self,
|
|
3612
3753
|
peer_address: hci.Address | str,
|
|
3613
|
-
transport: core.PhysicalTransport = PhysicalTransport.LE,
|
|
3614
3754
|
connection_parameters_preferences: (
|
|
3615
3755
|
dict[hci.Phy, ConnectionParametersPreferences] | None
|
|
3616
3756
|
) = None,
|
|
3617
3757
|
own_address_type: hci.OwnAddressType = hci.OwnAddressType.RANDOM,
|
|
3618
3758
|
timeout: float | None = DEVICE_DEFAULT_CONNECT_TIMEOUT,
|
|
3619
|
-
always_resolve: bool = False,
|
|
3620
3759
|
) -> Connection:
|
|
3621
|
-
'''
|
|
3622
|
-
Request a connection to a peer.
|
|
3623
|
-
|
|
3624
|
-
When the transport is BLE, this method cannot be called if there is already a
|
|
3625
|
-
pending connection.
|
|
3626
|
-
|
|
3627
|
-
Args:
|
|
3628
|
-
peer_address:
|
|
3629
|
-
hci.Address or name of the device to connect to.
|
|
3630
|
-
If a string is passed:
|
|
3631
|
-
If the string is an address followed by a `@` suffix, the `always_resolve`
|
|
3632
|
-
argument is implicitly set to True, so the connection is made to the
|
|
3633
|
-
address after resolution.
|
|
3634
|
-
If the string is any other address, the connection is made to that
|
|
3635
|
-
address (with or without address resolution, depending on the
|
|
3636
|
-
`always_resolve` argument).
|
|
3637
|
-
For any other string, a scan for devices using that string as their name
|
|
3638
|
-
is initiated, and a connection to the first matching device's address
|
|
3639
|
-
is made. In that case, `always_resolve` is ignored.
|
|
3640
|
-
|
|
3641
|
-
connection_parameters_preferences:
|
|
3642
|
-
(BLE only, ignored for BR/EDR)
|
|
3643
|
-
* None: use the 1M PHY with default parameters
|
|
3644
|
-
* map: each entry has a PHY as key and a ConnectionParametersPreferences
|
|
3645
|
-
object as value
|
|
3646
|
-
|
|
3647
|
-
own_address_type:
|
|
3648
|
-
(BLE only, ignored for BR/EDR)
|
|
3649
|
-
hci.OwnAddressType.RANDOM to use this device's random address, or
|
|
3650
|
-
hci.OwnAddressType.PUBLIC to use this device's public address.
|
|
3651
|
-
|
|
3652
|
-
timeout:
|
|
3653
|
-
Maximum time to wait for a connection to be established, in seconds.
|
|
3654
|
-
Pass None for an unlimited time.
|
|
3655
|
-
|
|
3656
|
-
always_resolve:
|
|
3657
|
-
(BLE only, ignored for BR/EDR)
|
|
3658
|
-
If True, always initiate a scan, resolving addresses, and connect to the
|
|
3659
|
-
address that resolves to `peer_address`.
|
|
3660
|
-
'''
|
|
3661
|
-
|
|
3662
|
-
# Check parameters
|
|
3663
|
-
if transport not in (PhysicalTransport.LE, PhysicalTransport.BR_EDR):
|
|
3664
|
-
raise InvalidArgumentError('invalid transport')
|
|
3665
|
-
transport = core.PhysicalTransport(transport)
|
|
3666
|
-
|
|
3667
|
-
# Adjust the transport automatically if we need to
|
|
3668
|
-
if transport == PhysicalTransport.LE and not self.le_enabled:
|
|
3669
|
-
transport = PhysicalTransport.BR_EDR
|
|
3670
|
-
elif transport == PhysicalTransport.BR_EDR and not self.classic_enabled:
|
|
3671
|
-
transport = PhysicalTransport.LE
|
|
3672
|
-
|
|
3673
3760
|
# Check that there isn't already a pending connection
|
|
3674
|
-
if
|
|
3761
|
+
if self.is_le_connecting:
|
|
3675
3762
|
raise InvalidStateError('connection already pending')
|
|
3676
3763
|
|
|
3764
|
+
try_resolve = not self.address_resolution_offload
|
|
3677
3765
|
if isinstance(peer_address, str):
|
|
3678
3766
|
try:
|
|
3679
|
-
|
|
3680
|
-
peer_address
|
|
3681
|
-
|
|
3682
|
-
)
|
|
3683
|
-
always_resolve = True
|
|
3684
|
-
logger.debug('forcing address resolution')
|
|
3685
|
-
else:
|
|
3686
|
-
peer_address = hci.Address.from_string_for_transport(
|
|
3687
|
-
peer_address, transport
|
|
3688
|
-
)
|
|
3767
|
+
peer_address = hci.Address.from_string_for_transport(
|
|
3768
|
+
peer_address, PhysicalTransport.LE
|
|
3769
|
+
)
|
|
3689
3770
|
except (InvalidArgumentError, ValueError):
|
|
3690
3771
|
# If the address is not parsable, assume it is a name instead
|
|
3691
|
-
always_resolve = False
|
|
3692
3772
|
logger.debug('looking for peer by name')
|
|
3693
3773
|
assert isinstance(peer_address, str)
|
|
3694
3774
|
peer_address = await self.find_peer_by_name(
|
|
3695
|
-
peer_address,
|
|
3775
|
+
peer_address, PhysicalTransport.LE
|
|
3696
3776
|
) # TODO: timeout
|
|
3697
|
-
|
|
3698
|
-
# All BR/EDR addresses should be public addresses
|
|
3699
|
-
if (
|
|
3700
|
-
transport == PhysicalTransport.BR_EDR
|
|
3701
|
-
and peer_address.address_type != hci.Address.PUBLIC_DEVICE_ADDRESS
|
|
3702
|
-
):
|
|
3703
|
-
raise InvalidArgumentError('BR/EDR addresses must be PUBLIC')
|
|
3777
|
+
try_resolve = False
|
|
3704
3778
|
|
|
3705
3779
|
assert isinstance(peer_address, hci.Address)
|
|
3706
3780
|
|
|
3707
|
-
if
|
|
3708
|
-
|
|
3709
|
-
|
|
3781
|
+
if (
|
|
3782
|
+
try_resolve
|
|
3783
|
+
and self.address_resolver is not None
|
|
3784
|
+
and self.address_resolver.can_resolve_to(peer_address)
|
|
3785
|
+
):
|
|
3786
|
+
# If we have an IRK for this address, we should resolve.
|
|
3787
|
+
logger.debug('have IRK for address, resolving...')
|
|
3788
|
+
peer_address = await self.find_peer_by_identity_address(
|
|
3710
3789
|
peer_address
|
|
3711
3790
|
) # TODO: timeout
|
|
3712
3791
|
|
|
3713
3792
|
def on_connection(connection):
|
|
3714
|
-
|
|
3715
|
-
# match BR/EDR connection event against peer address
|
|
3716
|
-
connection.transport == transport
|
|
3717
|
-
and connection.peer_address == peer_address
|
|
3718
|
-
):
|
|
3719
|
-
pending_connection.set_result(connection)
|
|
3793
|
+
pending_connection.set_result(connection)
|
|
3720
3794
|
|
|
3721
3795
|
def on_connection_failure(error: core.ConnectionError):
|
|
3722
|
-
|
|
3723
|
-
# match BR/EDR connection failure event against peer address
|
|
3724
|
-
error.transport == transport
|
|
3725
|
-
and error.peer_address == peer_address
|
|
3726
|
-
):
|
|
3727
|
-
pending_connection.set_exception(error)
|
|
3796
|
+
pending_connection.set_exception(error)
|
|
3728
3797
|
|
|
3729
|
-
# Create a future so that we can wait for the connection
|
|
3798
|
+
# Create a future so that we can wait for the connection result
|
|
3730
3799
|
pending_connection = asyncio.get_running_loop().create_future()
|
|
3731
3800
|
self.on(self.EVENT_CONNECTION, on_connection)
|
|
3732
3801
|
self.on(self.EVENT_CONNECTION_FAILURE, on_connection_failure)
|
|
3733
3802
|
|
|
3734
3803
|
try:
|
|
3735
3804
|
# Tell the controller to connect
|
|
3736
|
-
if
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
hci.HCI_LE_1M_PHY: ConnectionParametersPreferences.default
|
|
3741
|
-
}
|
|
3805
|
+
if connection_parameters_preferences is None:
|
|
3806
|
+
connection_parameters_preferences = {
|
|
3807
|
+
hci.HCI_LE_1M_PHY: ConnectionParametersPreferences.default
|
|
3808
|
+
}
|
|
3742
3809
|
|
|
3743
|
-
|
|
3810
|
+
self.connect_own_address_type = own_address_type
|
|
3744
3811
|
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
)
|
|
3812
|
+
if self.host.supports_command(
|
|
3813
|
+
hci.HCI_LE_EXTENDED_CREATE_CONNECTION_COMMAND
|
|
3814
|
+
):
|
|
3815
|
+
# Only keep supported PHYs
|
|
3816
|
+
phys = sorted(
|
|
3817
|
+
list(
|
|
3818
|
+
set(
|
|
3819
|
+
filter(
|
|
3820
|
+
self.supports_le_phy,
|
|
3821
|
+
connection_parameters_preferences.keys(),
|
|
3756
3822
|
)
|
|
3757
3823
|
)
|
|
3758
3824
|
)
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
phy_count = len(phys)
|
|
3763
|
-
initiating_phys = hci.phy_list_to_bits(phys)
|
|
3764
|
-
|
|
3765
|
-
connection_interval_mins = [
|
|
3766
|
-
int(
|
|
3767
|
-
connection_parameters_preferences[
|
|
3768
|
-
phy
|
|
3769
|
-
].connection_interval_min
|
|
3770
|
-
/ 1.25
|
|
3771
|
-
)
|
|
3772
|
-
for phy in phys
|
|
3773
|
-
]
|
|
3774
|
-
connection_interval_maxs = [
|
|
3775
|
-
int(
|
|
3776
|
-
connection_parameters_preferences[
|
|
3777
|
-
phy
|
|
3778
|
-
].connection_interval_max
|
|
3779
|
-
/ 1.25
|
|
3780
|
-
)
|
|
3781
|
-
for phy in phys
|
|
3782
|
-
]
|
|
3783
|
-
max_latencies = [
|
|
3784
|
-
connection_parameters_preferences[phy].max_latency
|
|
3785
|
-
for phy in phys
|
|
3786
|
-
]
|
|
3787
|
-
supervision_timeouts = [
|
|
3788
|
-
int(
|
|
3789
|
-
connection_parameters_preferences[phy].supervision_timeout
|
|
3790
|
-
/ 10
|
|
3791
|
-
)
|
|
3792
|
-
for phy in phys
|
|
3793
|
-
]
|
|
3794
|
-
min_ce_lengths = [
|
|
3795
|
-
int(
|
|
3796
|
-
connection_parameters_preferences[phy].min_ce_length / 0.625
|
|
3797
|
-
)
|
|
3798
|
-
for phy in phys
|
|
3799
|
-
]
|
|
3800
|
-
max_ce_lengths = [
|
|
3801
|
-
int(
|
|
3802
|
-
connection_parameters_preferences[phy].max_ce_length / 0.625
|
|
3803
|
-
)
|
|
3804
|
-
for phy in phys
|
|
3805
|
-
]
|
|
3825
|
+
)
|
|
3826
|
+
if not phys:
|
|
3827
|
+
raise InvalidArgumentError('at least one supported PHY needed')
|
|
3806
3828
|
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
scan_intervals=(
|
|
3815
|
-
int(DEVICE_DEFAULT_CONNECT_SCAN_INTERVAL / 0.625),
|
|
3816
|
-
)
|
|
3817
|
-
* phy_count,
|
|
3818
|
-
scan_windows=(
|
|
3819
|
-
int(DEVICE_DEFAULT_CONNECT_SCAN_WINDOW / 0.625),
|
|
3820
|
-
)
|
|
3821
|
-
* phy_count,
|
|
3822
|
-
connection_interval_mins=connection_interval_mins,
|
|
3823
|
-
connection_interval_maxs=connection_interval_maxs,
|
|
3824
|
-
max_latencies=max_latencies,
|
|
3825
|
-
supervision_timeouts=supervision_timeouts,
|
|
3826
|
-
min_ce_lengths=min_ce_lengths,
|
|
3827
|
-
max_ce_lengths=max_ce_lengths,
|
|
3828
|
-
),
|
|
3829
|
-
check_result=True,
|
|
3829
|
+
phy_count = len(phys)
|
|
3830
|
+
initiating_phys = hci.phy_list_to_bits(phys)
|
|
3831
|
+
|
|
3832
|
+
connection_interval_mins = [
|
|
3833
|
+
int(
|
|
3834
|
+
connection_parameters_preferences[phy].connection_interval_min
|
|
3835
|
+
/ 1.25
|
|
3830
3836
|
)
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3837
|
+
for phy in phys
|
|
3838
|
+
]
|
|
3839
|
+
connection_interval_maxs = [
|
|
3840
|
+
int(
|
|
3841
|
+
connection_parameters_preferences[phy].connection_interval_max
|
|
3842
|
+
/ 1.25
|
|
3843
|
+
)
|
|
3844
|
+
for phy in phys
|
|
3845
|
+
]
|
|
3846
|
+
max_latencies = [
|
|
3847
|
+
connection_parameters_preferences[phy].max_latency for phy in phys
|
|
3848
|
+
]
|
|
3849
|
+
supervision_timeouts = [
|
|
3850
|
+
int(connection_parameters_preferences[phy].supervision_timeout / 10)
|
|
3851
|
+
for phy in phys
|
|
3852
|
+
]
|
|
3853
|
+
min_ce_lengths = [
|
|
3854
|
+
int(connection_parameters_preferences[phy].min_ce_length / 0.625)
|
|
3855
|
+
for phy in phys
|
|
3856
|
+
]
|
|
3857
|
+
max_ce_lengths = [
|
|
3858
|
+
int(connection_parameters_preferences[phy].max_ce_length / 0.625)
|
|
3859
|
+
for phy in phys
|
|
3860
|
+
]
|
|
3861
|
+
|
|
3862
|
+
await self.send_async_command(
|
|
3863
|
+
hci.HCI_LE_Extended_Create_Connection_Command(
|
|
3864
|
+
initiator_filter_policy=0,
|
|
3865
|
+
own_address_type=own_address_type,
|
|
3866
|
+
peer_address_type=peer_address.address_type,
|
|
3867
|
+
peer_address=peer_address,
|
|
3868
|
+
initiating_phys=initiating_phys,
|
|
3869
|
+
scan_intervals=(
|
|
3870
|
+
int(DEVICE_DEFAULT_CONNECT_SCAN_INTERVAL / 0.625),
|
|
3871
|
+
)
|
|
3872
|
+
* phy_count,
|
|
3873
|
+
scan_windows=(int(DEVICE_DEFAULT_CONNECT_SCAN_WINDOW / 0.625),)
|
|
3874
|
+
* phy_count,
|
|
3875
|
+
connection_interval_mins=connection_interval_mins,
|
|
3876
|
+
connection_interval_maxs=connection_interval_maxs,
|
|
3877
|
+
max_latencies=max_latencies,
|
|
3878
|
+
supervision_timeouts=supervision_timeouts,
|
|
3879
|
+
min_ce_lengths=min_ce_lengths,
|
|
3880
|
+
max_ce_lengths=max_ce_lengths,
|
|
3860
3881
|
)
|
|
3861
|
-
else:
|
|
3862
|
-
# Save pending connection
|
|
3863
|
-
self.pending_connections[peer_address] = Connection(
|
|
3864
|
-
device=self,
|
|
3865
|
-
handle=0,
|
|
3866
|
-
transport=core.PhysicalTransport.BR_EDR,
|
|
3867
|
-
self_address=self.public_address,
|
|
3868
|
-
self_resolvable_address=None,
|
|
3869
|
-
peer_address=peer_address,
|
|
3870
|
-
peer_resolvable_address=None,
|
|
3871
|
-
role=hci.Role.CENTRAL,
|
|
3872
|
-
parameters=Connection.Parameters(0, 0, 0),
|
|
3873
3882
|
)
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3883
|
+
else:
|
|
3884
|
+
if hci.HCI_LE_1M_PHY not in connection_parameters_preferences:
|
|
3885
|
+
raise InvalidArgumentError('1M PHY preferences required')
|
|
3886
|
+
|
|
3887
|
+
prefs = connection_parameters_preferences[hci.HCI_LE_1M_PHY]
|
|
3888
|
+
await self.send_async_command(
|
|
3889
|
+
hci.HCI_LE_Create_Connection_Command(
|
|
3890
|
+
le_scan_interval=int(
|
|
3891
|
+
DEVICE_DEFAULT_CONNECT_SCAN_INTERVAL / 0.625
|
|
3892
|
+
),
|
|
3893
|
+
le_scan_window=int(DEVICE_DEFAULT_CONNECT_SCAN_WINDOW / 0.625),
|
|
3894
|
+
initiator_filter_policy=0,
|
|
3895
|
+
peer_address_type=peer_address.address_type,
|
|
3896
|
+
peer_address=peer_address,
|
|
3897
|
+
own_address_type=own_address_type,
|
|
3898
|
+
connection_interval_min=int(
|
|
3899
|
+
prefs.connection_interval_min / 1.25
|
|
3900
|
+
),
|
|
3901
|
+
connection_interval_max=int(
|
|
3902
|
+
prefs.connection_interval_max / 1.25
|
|
3903
|
+
),
|
|
3904
|
+
max_latency=prefs.max_latency,
|
|
3905
|
+
supervision_timeout=int(prefs.supervision_timeout / 10),
|
|
3906
|
+
min_ce_length=int(prefs.min_ce_length / 0.625),
|
|
3907
|
+
max_ce_length=int(prefs.max_ce_length / 0.625),
|
|
3908
|
+
)
|
|
3886
3909
|
)
|
|
3887
3910
|
|
|
3888
3911
|
# Wait for the connection process to complete
|
|
3889
|
-
|
|
3890
|
-
self.le_connecting = True
|
|
3912
|
+
self.le_connecting = True
|
|
3891
3913
|
|
|
3892
3914
|
if timeout is None:
|
|
3893
3915
|
return await utils.cancel_on_event(
|
|
@@ -3899,14 +3921,107 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3899
3921
|
asyncio.shield(pending_connection), timeout
|
|
3900
3922
|
)
|
|
3901
3923
|
except asyncio.TimeoutError:
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
await
|
|
3908
|
-
|
|
3924
|
+
await self.send_sync_command(
|
|
3925
|
+
hci.HCI_LE_Create_Connection_Cancel_Command()
|
|
3926
|
+
)
|
|
3927
|
+
|
|
3928
|
+
try:
|
|
3929
|
+
return await utils.cancel_on_event(
|
|
3930
|
+
self, Device.EVENT_FLUSH, pending_connection
|
|
3909
3931
|
)
|
|
3932
|
+
except core.ConnectionError as error:
|
|
3933
|
+
raise core.TimeoutError() from error
|
|
3934
|
+
finally:
|
|
3935
|
+
self.remove_listener(self.EVENT_CONNECTION, on_connection)
|
|
3936
|
+
self.remove_listener(self.EVENT_CONNECTION_FAILURE, on_connection_failure)
|
|
3937
|
+
self.le_connecting = False
|
|
3938
|
+
self.connect_own_address_type = None
|
|
3939
|
+
|
|
3940
|
+
async def connect_classic(
|
|
3941
|
+
self,
|
|
3942
|
+
peer_address: hci.Address | str,
|
|
3943
|
+
timeout: float | None = DEVICE_DEFAULT_CONNECT_TIMEOUT,
|
|
3944
|
+
) -> Connection:
|
|
3945
|
+
if isinstance(peer_address, str):
|
|
3946
|
+
try:
|
|
3947
|
+
peer_address = hci.Address.from_string_for_transport(
|
|
3948
|
+
peer_address, PhysicalTransport.BR_EDR
|
|
3949
|
+
)
|
|
3950
|
+
except (InvalidArgumentError, ValueError):
|
|
3951
|
+
# If the address is not parsable, assume it is a name instead
|
|
3952
|
+
logger.debug('looking for peer by name')
|
|
3953
|
+
assert isinstance(peer_address, str)
|
|
3954
|
+
peer_address = await self.find_peer_by_name(
|
|
3955
|
+
peer_address, PhysicalTransport.BR_EDR
|
|
3956
|
+
) # TODO: timeout
|
|
3957
|
+
else:
|
|
3958
|
+
# All BR/EDR addresses should be public addresses
|
|
3959
|
+
if peer_address.address_type != hci.Address.PUBLIC_DEVICE_ADDRESS:
|
|
3960
|
+
raise InvalidArgumentError('BR/EDR addresses must be PUBLIC')
|
|
3961
|
+
|
|
3962
|
+
assert isinstance(peer_address, hci.Address)
|
|
3963
|
+
|
|
3964
|
+
def on_connection(connection):
|
|
3965
|
+
if (
|
|
3966
|
+
# match BR/EDR connection event against peer address
|
|
3967
|
+
connection.transport == PhysicalTransport.BR_EDR
|
|
3968
|
+
and connection.peer_address == peer_address
|
|
3969
|
+
):
|
|
3970
|
+
pending_connection.set_result(connection)
|
|
3971
|
+
|
|
3972
|
+
def on_connection_failure(error: core.ConnectionError):
|
|
3973
|
+
if (
|
|
3974
|
+
# match BR/EDR connection failure event against peer address
|
|
3975
|
+
error.transport == PhysicalTransport.BR_EDR
|
|
3976
|
+
and error.peer_address == peer_address
|
|
3977
|
+
):
|
|
3978
|
+
pending_connection.set_exception(error)
|
|
3979
|
+
|
|
3980
|
+
# Create a future so that we can wait for the connection result
|
|
3981
|
+
pending_connection = asyncio.get_running_loop().create_future()
|
|
3982
|
+
self.on(self.EVENT_CONNECTION, on_connection)
|
|
3983
|
+
self.on(self.EVENT_CONNECTION_FAILURE, on_connection_failure)
|
|
3984
|
+
|
|
3985
|
+
try:
|
|
3986
|
+
# Save pending connection
|
|
3987
|
+
self.pending_connections[peer_address] = Connection(
|
|
3988
|
+
device=self,
|
|
3989
|
+
handle=0,
|
|
3990
|
+
transport=core.PhysicalTransport.BR_EDR,
|
|
3991
|
+
self_address=self.public_address,
|
|
3992
|
+
self_resolvable_address=None,
|
|
3993
|
+
peer_address=peer_address,
|
|
3994
|
+
peer_resolvable_address=None,
|
|
3995
|
+
role=hci.Role.CENTRAL,
|
|
3996
|
+
parameters=Connection.Parameters(0, 0, 0),
|
|
3997
|
+
)
|
|
3998
|
+
|
|
3999
|
+
# TODO: allow passing other settings
|
|
4000
|
+
await self.send_async_command(
|
|
4001
|
+
hci.HCI_Create_Connection_Command(
|
|
4002
|
+
bd_addr=peer_address,
|
|
4003
|
+
packet_type=0xCC18, # FIXME: change
|
|
4004
|
+
page_scan_repetition_mode=hci.HCI_R2_PAGE_SCAN_REPETITION_MODE,
|
|
4005
|
+
clock_offset=0x0000,
|
|
4006
|
+
allow_role_switch=0x01,
|
|
4007
|
+
reserved=0,
|
|
4008
|
+
)
|
|
4009
|
+
)
|
|
4010
|
+
|
|
4011
|
+
# Wait for the connection process to complete
|
|
4012
|
+
if timeout is None:
|
|
4013
|
+
return await utils.cancel_on_event(
|
|
4014
|
+
self, Device.EVENT_FLUSH, pending_connection
|
|
4015
|
+
)
|
|
4016
|
+
|
|
4017
|
+
try:
|
|
4018
|
+
return await asyncio.wait_for(
|
|
4019
|
+
asyncio.shield(pending_connection), timeout
|
|
4020
|
+
)
|
|
4021
|
+
except asyncio.TimeoutError:
|
|
4022
|
+
await self.send_sync_command(
|
|
4023
|
+
hci.HCI_Create_Connection_Cancel_Command(bd_addr=peer_address)
|
|
4024
|
+
)
|
|
3910
4025
|
|
|
3911
4026
|
try:
|
|
3912
4027
|
return await utils.cancel_on_event(
|
|
@@ -3917,11 +4032,78 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3917
4032
|
finally:
|
|
3918
4033
|
self.remove_listener(self.EVENT_CONNECTION, on_connection)
|
|
3919
4034
|
self.remove_listener(self.EVENT_CONNECTION_FAILURE, on_connection_failure)
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
4035
|
+
self.pending_connections.pop(peer_address, None)
|
|
4036
|
+
|
|
4037
|
+
async def connect(
|
|
4038
|
+
self,
|
|
4039
|
+
peer_address: hci.Address | str,
|
|
4040
|
+
transport: core.PhysicalTransport = PhysicalTransport.LE,
|
|
4041
|
+
connection_parameters_preferences: (
|
|
4042
|
+
dict[hci.Phy, ConnectionParametersPreferences] | None
|
|
4043
|
+
) = None,
|
|
4044
|
+
own_address_type: hci.OwnAddressType = hci.OwnAddressType.RANDOM,
|
|
4045
|
+
timeout: float | None = DEVICE_DEFAULT_CONNECT_TIMEOUT,
|
|
4046
|
+
always_resolve: bool = False,
|
|
4047
|
+
) -> Connection:
|
|
4048
|
+
'''
|
|
4049
|
+
Request a connection to a peer.
|
|
4050
|
+
|
|
4051
|
+
When the transport is BLE, this method cannot be called if there is already a
|
|
4052
|
+
pending connection.
|
|
4053
|
+
|
|
4054
|
+
Args:
|
|
4055
|
+
peer_address:
|
|
4056
|
+
hci.Address or name of the device to connect to.
|
|
4057
|
+
If a string is passed:
|
|
4058
|
+
[deprecated] If the string is an address followed by a `@` suffix, the
|
|
4059
|
+
`always_resolve`argument is implicitly set to True, so the connection is
|
|
4060
|
+
made to the address after resolution.
|
|
4061
|
+
If the string is any other address, the connection is made to that
|
|
4062
|
+
address (with or without address resolution, depending on the
|
|
4063
|
+
`always_resolve` argument).
|
|
4064
|
+
For any other string, a scan for devices using that string as their name
|
|
4065
|
+
is initiated, and a connection to the first matching device's address
|
|
4066
|
+
is made. In that case, `always_resolve` is ignored.
|
|
4067
|
+
|
|
4068
|
+
connection_parameters_preferences:
|
|
4069
|
+
(BLE only, ignored for BR/EDR)
|
|
4070
|
+
* None: use the 1M PHY with default parameters
|
|
4071
|
+
* map: each entry has a PHY as key and a ConnectionParametersPreferences
|
|
4072
|
+
object as value
|
|
4073
|
+
|
|
4074
|
+
own_address_type:
|
|
4075
|
+
(BLE only, ignored for BR/EDR)
|
|
4076
|
+
hci.OwnAddressType.RANDOM to use this device's random address, or
|
|
4077
|
+
hci.OwnAddressType.PUBLIC to use this device's public address.
|
|
4078
|
+
|
|
4079
|
+
timeout:
|
|
4080
|
+
Maximum time to wait for a connection to be established, in seconds.
|
|
4081
|
+
Pass None for an unlimited time.
|
|
4082
|
+
|
|
4083
|
+
always_resolve:
|
|
4084
|
+
[deprecated] (ignore)
|
|
4085
|
+
'''
|
|
4086
|
+
|
|
4087
|
+
# Connect using the appropriate transport
|
|
4088
|
+
# (auto-correct the transport based on declared capabilities)
|
|
4089
|
+
if transport == PhysicalTransport.LE or (
|
|
4090
|
+
self.le_enabled and not self.classic_enabled
|
|
4091
|
+
):
|
|
4092
|
+
return await self.connect_le(
|
|
4093
|
+
peer_address=peer_address,
|
|
4094
|
+
connection_parameters_preferences=connection_parameters_preferences,
|
|
4095
|
+
own_address_type=own_address_type,
|
|
4096
|
+
timeout=timeout,
|
|
4097
|
+
)
|
|
4098
|
+
|
|
4099
|
+
if transport == PhysicalTransport.BR_EDR or (
|
|
4100
|
+
self.classic_enabled and not self.le_enabled
|
|
4101
|
+
):
|
|
4102
|
+
return await self.connect_classic(
|
|
4103
|
+
peer_address=peer_address, timeout=timeout
|
|
4104
|
+
)
|
|
4105
|
+
|
|
4106
|
+
raise ValueError('invalid transport')
|
|
3925
4107
|
|
|
3926
4108
|
async def accept(
|
|
3927
4109
|
self,
|
|
@@ -4033,11 +4215,10 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4033
4215
|
|
|
4034
4216
|
try:
|
|
4035
4217
|
# Accept connection request
|
|
4036
|
-
await self.
|
|
4218
|
+
await self.send_async_command(
|
|
4037
4219
|
hci.HCI_Accept_Connection_Request_Command(
|
|
4038
4220
|
bd_addr=peer_address, role=role
|
|
4039
|
-
)
|
|
4040
|
-
check_result=True,
|
|
4221
|
+
)
|
|
4041
4222
|
)
|
|
4042
4223
|
|
|
4043
4224
|
# Wait for connection complete
|
|
@@ -4073,9 +4254,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4073
4254
|
if peer_address is None:
|
|
4074
4255
|
if not self.is_le_connecting:
|
|
4075
4256
|
return
|
|
4076
|
-
await self.
|
|
4077
|
-
hci.HCI_LE_Create_Connection_Cancel_Command(), check_result=True
|
|
4078
|
-
)
|
|
4257
|
+
await self.send_sync_command(hci.HCI_LE_Create_Connection_Cancel_Command())
|
|
4079
4258
|
|
|
4080
4259
|
# BR/EDR: try to cancel to ongoing connection
|
|
4081
4260
|
# NOTE: This API does not prevent from trying to cancel a connection which is
|
|
@@ -4092,9 +4271,8 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4092
4271
|
peer_address, PhysicalTransport.BR_EDR
|
|
4093
4272
|
) # TODO: timeout
|
|
4094
4273
|
|
|
4095
|
-
await self.
|
|
4096
|
-
hci.HCI_Create_Connection_Cancel_Command(bd_addr=peer_address)
|
|
4097
|
-
check_result=True,
|
|
4274
|
+
await self.send_sync_command(
|
|
4275
|
+
hci.HCI_Create_Connection_Cancel_Command(bd_addr=peer_address)
|
|
4098
4276
|
)
|
|
4099
4277
|
|
|
4100
4278
|
async def disconnect(
|
|
@@ -4112,11 +4290,10 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4112
4290
|
self.disconnecting = True
|
|
4113
4291
|
|
|
4114
4292
|
# Request a disconnection
|
|
4115
|
-
await self.
|
|
4293
|
+
await self.send_async_command(
|
|
4116
4294
|
hci.HCI_Disconnect_Command(
|
|
4117
4295
|
connection_handle=connection.handle, reason=reason
|
|
4118
|
-
)
|
|
4119
|
-
check_result=True,
|
|
4296
|
+
)
|
|
4120
4297
|
)
|
|
4121
4298
|
return await utils.cancel_on_event(
|
|
4122
4299
|
self, Device.EVENT_FLUSH, pending_disconnection
|
|
@@ -4140,13 +4317,12 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4140
4317
|
if tx_time < 0x0148 or tx_time > 0x4290:
|
|
4141
4318
|
raise InvalidArgumentError('tx_time must be between 0x0148 and 0x4290')
|
|
4142
4319
|
|
|
4143
|
-
|
|
4320
|
+
await self.send_sync_command(
|
|
4144
4321
|
hci.HCI_LE_Set_Data_Length_Command(
|
|
4145
4322
|
connection_handle=connection.handle,
|
|
4146
4323
|
tx_octets=tx_octets,
|
|
4147
4324
|
tx_time=tx_time,
|
|
4148
|
-
)
|
|
4149
|
-
check_result=True,
|
|
4325
|
+
)
|
|
4150
4326
|
)
|
|
4151
4327
|
|
|
4152
4328
|
async def update_connection_parameters(
|
|
@@ -4163,6 +4339,9 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4163
4339
|
'''
|
|
4164
4340
|
Request an update of the connection parameters.
|
|
4165
4341
|
|
|
4342
|
+
For short connection intervals (below 7.5 ms, introduced in Bluetooth 6.2),
|
|
4343
|
+
use `update_connection_parameters_with_subrate` instead.
|
|
4344
|
+
|
|
4166
4345
|
Args:
|
|
4167
4346
|
connection: The connection to update
|
|
4168
4347
|
connection_interval_min: Minimum interval, in milliseconds.
|
|
@@ -4203,36 +4382,163 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4203
4382
|
|
|
4204
4383
|
return
|
|
4205
4384
|
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
)
|
|
4216
|
-
|
|
4217
|
-
|
|
4385
|
+
pending_result = asyncio.get_running_loop().create_future()
|
|
4386
|
+
with closing(utils.EventWatcher()) as watcher:
|
|
4387
|
+
|
|
4388
|
+
@watcher.on(connection, connection.EVENT_CONNECTION_PARAMETERS_UPDATE)
|
|
4389
|
+
def _():
|
|
4390
|
+
pending_result.set_result(None)
|
|
4391
|
+
|
|
4392
|
+
@watcher.on(
|
|
4393
|
+
connection, connection.EVENT_CONNECTION_PARAMETERS_UPDATE_FAILURE
|
|
4394
|
+
)
|
|
4395
|
+
def _(error_code: int):
|
|
4396
|
+
pending_result.set_exception(hci.HCI_Error(error_code))
|
|
4397
|
+
|
|
4398
|
+
await self.send_async_command(
|
|
4399
|
+
hci.HCI_LE_Connection_Update_Command(
|
|
4400
|
+
connection_handle=connection.handle,
|
|
4401
|
+
connection_interval_min=connection_interval_min,
|
|
4402
|
+
connection_interval_max=connection_interval_max,
|
|
4403
|
+
max_latency=max_latency,
|
|
4404
|
+
supervision_timeout=supervision_timeout,
|
|
4405
|
+
min_ce_length=min_ce_length,
|
|
4406
|
+
max_ce_length=max_ce_length,
|
|
4407
|
+
)
|
|
4408
|
+
)
|
|
4409
|
+
|
|
4410
|
+
await connection.cancel_on_disconnection(pending_result)
|
|
4411
|
+
|
|
4412
|
+
async def update_connection_parameters_with_subrate(
|
|
4413
|
+
self,
|
|
4414
|
+
connection: Connection,
|
|
4415
|
+
connection_interval_min: float,
|
|
4416
|
+
connection_interval_max: float,
|
|
4417
|
+
subrate_min: int,
|
|
4418
|
+
subrate_max: int,
|
|
4419
|
+
max_latency: int,
|
|
4420
|
+
continuation_number: int,
|
|
4421
|
+
supervision_timeout: float,
|
|
4422
|
+
min_ce_length: float = 0.0,
|
|
4423
|
+
max_ce_length: float = 0.0,
|
|
4424
|
+
) -> None:
|
|
4425
|
+
'''
|
|
4426
|
+
Request a change of the connection parameters.
|
|
4427
|
+
This is similar to `update_connection_parameters` but also allows specifying
|
|
4428
|
+
the subrate parameters and supports shorter connection intervals (below
|
|
4429
|
+
7.5ms, as introduced in Bluetooth 6.2).
|
|
4430
|
+
|
|
4431
|
+
Args:
|
|
4432
|
+
connection: The connection to update
|
|
4433
|
+
connection_interval_min: Minimum interval, in milliseconds.
|
|
4434
|
+
connection_interval_max: Maximum interval, in milliseconds.
|
|
4435
|
+
subrate_min: Minimum subrate factor.
|
|
4436
|
+
subrate_max: Maximum subrate factor.
|
|
4437
|
+
max_latency: Max latency, in number of intervals.
|
|
4438
|
+
continuation_number: Continuation number.
|
|
4439
|
+
supervision_timeout: Supervision Timeout, in milliseconds.
|
|
4440
|
+
min_ce_length: Minimum connection event length, in milliseconds.
|
|
4441
|
+
max_ce_length: Maximum connection event length, in milliseconds.
|
|
4442
|
+
'''
|
|
4443
|
+
|
|
4444
|
+
# Convert the input parameters
|
|
4445
|
+
connection_interval_min = int(connection_interval_min / 0.125)
|
|
4446
|
+
connection_interval_max = int(connection_interval_max / 0.125)
|
|
4447
|
+
supervision_timeout = int(supervision_timeout / 10)
|
|
4448
|
+
min_ce_length = int(min_ce_length / 0.125)
|
|
4449
|
+
max_ce_length = int(max_ce_length / 0.125)
|
|
4450
|
+
|
|
4451
|
+
pending_result = asyncio.get_running_loop().create_future()
|
|
4452
|
+
with closing(utils.EventWatcher()) as watcher:
|
|
4453
|
+
|
|
4454
|
+
@watcher.on(connection, connection.EVENT_CONNECTION_PARAMETERS_UPDATE)
|
|
4455
|
+
def _():
|
|
4456
|
+
pending_result.set_result(None)
|
|
4457
|
+
|
|
4458
|
+
@watcher.on(
|
|
4459
|
+
connection, connection.EVENT_CONNECTION_PARAMETERS_UPDATE_FAILURE
|
|
4460
|
+
)
|
|
4461
|
+
def _(error_code: int):
|
|
4462
|
+
pending_result.set_exception(hci.HCI_Error(error_code))
|
|
4463
|
+
|
|
4464
|
+
await self.send_async_command(
|
|
4465
|
+
hci.HCI_LE_Connection_Rate_Request_Command(
|
|
4466
|
+
connection_handle=connection.handle,
|
|
4467
|
+
connection_interval_min=connection_interval_min,
|
|
4468
|
+
connection_interval_max=connection_interval_max,
|
|
4469
|
+
subrate_min=subrate_min,
|
|
4470
|
+
subrate_max=subrate_max,
|
|
4471
|
+
max_latency=max_latency,
|
|
4472
|
+
continuation_number=continuation_number,
|
|
4473
|
+
supervision_timeout=supervision_timeout,
|
|
4474
|
+
min_ce_length=min_ce_length,
|
|
4475
|
+
max_ce_length=max_ce_length,
|
|
4476
|
+
)
|
|
4477
|
+
)
|
|
4478
|
+
|
|
4479
|
+
await connection.cancel_on_disconnection(pending_result)
|
|
4480
|
+
|
|
4481
|
+
async def update_connection_subrate(
|
|
4482
|
+
self,
|
|
4483
|
+
connection: Connection,
|
|
4484
|
+
subrate_min: int,
|
|
4485
|
+
subrate_max: int,
|
|
4486
|
+
max_latency: int,
|
|
4487
|
+
continuation_number: int,
|
|
4488
|
+
supervision_timeout: float,
|
|
4489
|
+
) -> None:
|
|
4490
|
+
'''
|
|
4491
|
+
Request a change to the subrating factor and/or other parameters.
|
|
4492
|
+
|
|
4493
|
+
Args:
|
|
4494
|
+
connection: The connection to update
|
|
4495
|
+
subrate_min: Minimum subrate factor.
|
|
4496
|
+
subrate_max: Maximum subrate factor.
|
|
4497
|
+
max_latency: Max latency, in number of intervals.
|
|
4498
|
+
continuation_number: Continuation number.
|
|
4499
|
+
supervision_timeout: Supervision Timeout, in milliseconds.
|
|
4500
|
+
'''
|
|
4501
|
+
|
|
4502
|
+
pending_result = asyncio.get_running_loop().create_future()
|
|
4503
|
+
with closing(utils.EventWatcher()) as watcher:
|
|
4504
|
+
|
|
4505
|
+
@watcher.on(connection, connection.EVENT_CONNECTION_PARAMETERS_UPDATE)
|
|
4506
|
+
def _():
|
|
4507
|
+
pending_result.set_result(None)
|
|
4508
|
+
|
|
4509
|
+
@watcher.on(
|
|
4510
|
+
connection, connection.EVENT_CONNECTION_PARAMETERS_UPDATE_FAILURE
|
|
4511
|
+
)
|
|
4512
|
+
def _(error_code: int):
|
|
4513
|
+
pending_result.set_exception(hci.HCI_Error(error_code))
|
|
4514
|
+
|
|
4515
|
+
await self.send_async_command(
|
|
4516
|
+
hci.HCI_LE_Subrate_Request_Command(
|
|
4517
|
+
connection_handle=connection.handle,
|
|
4518
|
+
subrate_min=subrate_min,
|
|
4519
|
+
subrate_max=subrate_max,
|
|
4520
|
+
max_latency=max_latency,
|
|
4521
|
+
continuation_number=continuation_number,
|
|
4522
|
+
supervision_timeout=int(supervision_timeout / 10),
|
|
4523
|
+
)
|
|
4524
|
+
)
|
|
4525
|
+
|
|
4526
|
+
await connection.cancel_on_disconnection(pending_result)
|
|
4218
4527
|
|
|
4219
4528
|
async def get_connection_rssi(self, connection):
|
|
4220
|
-
result = await self.
|
|
4221
|
-
hci.HCI_Read_RSSI_Command(handle=connection.handle)
|
|
4529
|
+
result = await self.send_sync_command(
|
|
4530
|
+
hci.HCI_Read_RSSI_Command(handle=connection.handle)
|
|
4222
4531
|
)
|
|
4223
|
-
return result.
|
|
4532
|
+
return result.rssi
|
|
4224
4533
|
|
|
4225
4534
|
async def get_connection_phy(self, connection: Connection) -> ConnectionPHY:
|
|
4226
4535
|
if not self.host.supports_command(hci.HCI_LE_READ_PHY_COMMAND):
|
|
4227
4536
|
return ConnectionPHY(hci.Phy.LE_1M, hci.Phy.LE_1M)
|
|
4228
4537
|
|
|
4229
|
-
result = await self.
|
|
4230
|
-
hci.HCI_LE_Read_PHY_Command(connection_handle=connection.handle)
|
|
4231
|
-
check_result=True,
|
|
4232
|
-
)
|
|
4233
|
-
return ConnectionPHY(
|
|
4234
|
-
result.return_parameters.tx_phy, result.return_parameters.rx_phy
|
|
4538
|
+
result = await self.send_sync_command(
|
|
4539
|
+
hci.HCI_LE_Read_PHY_Command(connection_handle=connection.handle)
|
|
4235
4540
|
)
|
|
4541
|
+
return ConnectionPHY(result.tx_phy, result.rx_phy)
|
|
4236
4542
|
|
|
4237
4543
|
async def set_connection_phy(
|
|
4238
4544
|
self,
|
|
@@ -4249,60 +4555,139 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4249
4555
|
(1 if rx_phys is None else 0) << 1
|
|
4250
4556
|
)
|
|
4251
4557
|
|
|
4252
|
-
await self.
|
|
4558
|
+
await self.send_async_command(
|
|
4253
4559
|
hci.HCI_LE_Set_PHY_Command(
|
|
4254
4560
|
connection_handle=connection.handle,
|
|
4255
4561
|
all_phys=all_phys_bits,
|
|
4256
4562
|
tx_phys=hci.phy_list_to_bits(tx_phys),
|
|
4257
4563
|
rx_phys=hci.phy_list_to_bits(rx_phys),
|
|
4258
4564
|
phy_options=phy_options,
|
|
4259
|
-
)
|
|
4260
|
-
check_result=True,
|
|
4565
|
+
)
|
|
4261
4566
|
)
|
|
4262
4567
|
|
|
4263
4568
|
async def set_default_phy(
|
|
4264
4569
|
self,
|
|
4265
4570
|
tx_phys: Iterable[hci.Phy] | None = None,
|
|
4266
4571
|
rx_phys: Iterable[hci.Phy] | None = None,
|
|
4267
|
-
):
|
|
4572
|
+
) -> None:
|
|
4268
4573
|
all_phys_bits = (1 if tx_phys is None else 0) | (
|
|
4269
4574
|
(1 if rx_phys is None else 0) << 1
|
|
4270
4575
|
)
|
|
4271
4576
|
|
|
4272
|
-
|
|
4577
|
+
await self.send_sync_command(
|
|
4273
4578
|
hci.HCI_LE_Set_Default_PHY_Command(
|
|
4274
4579
|
all_phys=all_phys_bits,
|
|
4275
4580
|
tx_phys=hci.phy_list_to_bits(tx_phys),
|
|
4276
4581
|
rx_phys=hci.phy_list_to_bits(rx_phys),
|
|
4582
|
+
)
|
|
4583
|
+
)
|
|
4584
|
+
|
|
4585
|
+
async def set_default_connection_subrate(
|
|
4586
|
+
self,
|
|
4587
|
+
subrate_min: int,
|
|
4588
|
+
subrate_max: int,
|
|
4589
|
+
max_latency: int,
|
|
4590
|
+
continuation_number: int,
|
|
4591
|
+
supervision_timeout: float,
|
|
4592
|
+
) -> None:
|
|
4593
|
+
'''
|
|
4594
|
+
Set the default subrate parameters for new connections.
|
|
4595
|
+
|
|
4596
|
+
Args:
|
|
4597
|
+
subrate_min: Minimum subrate factor.
|
|
4598
|
+
subrate_max: Maximum subrate factor.
|
|
4599
|
+
max_latency: Max latency, in number of intervals.
|
|
4600
|
+
continuation_number: Continuation number.
|
|
4601
|
+
supervision_timeout: Supervision Timeout, in milliseconds.
|
|
4602
|
+
'''
|
|
4603
|
+
|
|
4604
|
+
# Convert the input parameters
|
|
4605
|
+
supervision_timeout = int(supervision_timeout / 10)
|
|
4606
|
+
|
|
4607
|
+
await self.send_command(
|
|
4608
|
+
hci.HCI_LE_Set_Default_Subrate_Command(
|
|
4609
|
+
subrate_min=subrate_min,
|
|
4610
|
+
subrate_max=subrate_max,
|
|
4611
|
+
max_latency=max_latency,
|
|
4612
|
+
continuation_number=continuation_number,
|
|
4613
|
+
supervision_timeout=supervision_timeout,
|
|
4277
4614
|
),
|
|
4278
4615
|
check_result=True,
|
|
4279
4616
|
)
|
|
4280
4617
|
|
|
4618
|
+
async def set_default_connection_parameters(
|
|
4619
|
+
self,
|
|
4620
|
+
connection_interval_min: float,
|
|
4621
|
+
connection_interval_max: float,
|
|
4622
|
+
subrate_min: int,
|
|
4623
|
+
subrate_max: int,
|
|
4624
|
+
max_latency: int,
|
|
4625
|
+
continuation_number: int,
|
|
4626
|
+
supervision_timeout: float,
|
|
4627
|
+
min_ce_length: float = 0.0,
|
|
4628
|
+
max_ce_length: float = 0.0,
|
|
4629
|
+
) -> None:
|
|
4630
|
+
'''
|
|
4631
|
+
Set the default connection parameters for new connections.
|
|
4632
|
+
|
|
4633
|
+
Args:
|
|
4634
|
+
connection_interval_min: Minimum interval, in milliseconds.
|
|
4635
|
+
connection_interval_max: Maximum interval, in milliseconds.
|
|
4636
|
+
subrate_min: Minimum subrate factor.
|
|
4637
|
+
subrate_max: Maximum subrate factor.
|
|
4638
|
+
max_latency: Max latency, in number of intervals.
|
|
4639
|
+
continuation_number: Continuation number.
|
|
4640
|
+
supervision_timeout: Supervision Timeout, in milliseconds.
|
|
4641
|
+
min_ce_length: Minimum connection event length, in milliseconds.
|
|
4642
|
+
max_ce_length: Maximum connection event length, in milliseconds.
|
|
4643
|
+
'''
|
|
4644
|
+
|
|
4645
|
+
# Convert the input parameters
|
|
4646
|
+
connection_interval_min = int(connection_interval_min / 0.125)
|
|
4647
|
+
connection_interval_max = int(connection_interval_max / 0.125)
|
|
4648
|
+
supervision_timeout = int(supervision_timeout / 10)
|
|
4649
|
+
min_ce_length = int(min_ce_length / 0.125)
|
|
4650
|
+
max_ce_length = int(max_ce_length / 0.125)
|
|
4651
|
+
|
|
4652
|
+
await self.send_sync_command(
|
|
4653
|
+
hci.HCI_LE_Set_Default_Rate_Parameters_Command(
|
|
4654
|
+
connection_interval_min=connection_interval_min,
|
|
4655
|
+
connection_interval_max=connection_interval_max,
|
|
4656
|
+
subrate_min=subrate_min,
|
|
4657
|
+
subrate_max=subrate_max,
|
|
4658
|
+
max_latency=max_latency,
|
|
4659
|
+
continuation_number=continuation_number,
|
|
4660
|
+
supervision_timeout=supervision_timeout,
|
|
4661
|
+
min_ce_length=min_ce_length,
|
|
4662
|
+
max_ce_length=max_ce_length,
|
|
4663
|
+
)
|
|
4664
|
+
)
|
|
4665
|
+
|
|
4281
4666
|
async def transfer_periodic_sync(
|
|
4282
4667
|
self, connection: Connection, sync_handle: int, service_data: int = 0
|
|
4283
4668
|
) -> None:
|
|
4284
|
-
|
|
4669
|
+
await self.send_sync_command(
|
|
4285
4670
|
hci.HCI_LE_Periodic_Advertising_Sync_Transfer_Command(
|
|
4286
4671
|
connection_handle=connection.handle,
|
|
4287
4672
|
service_data=service_data,
|
|
4288
4673
|
sync_handle=sync_handle,
|
|
4289
|
-
)
|
|
4290
|
-
check_result=True,
|
|
4674
|
+
)
|
|
4291
4675
|
)
|
|
4292
4676
|
|
|
4293
4677
|
async def transfer_periodic_set_info(
|
|
4294
4678
|
self, connection: Connection, advertising_handle: int, service_data: int = 0
|
|
4295
4679
|
) -> None:
|
|
4296
|
-
|
|
4680
|
+
await self.send_sync_command(
|
|
4297
4681
|
hci.HCI_LE_Periodic_Advertising_Set_Info_Transfer_Command(
|
|
4298
4682
|
connection_handle=connection.handle,
|
|
4299
4683
|
service_data=service_data,
|
|
4300
4684
|
advertising_handle=advertising_handle,
|
|
4301
|
-
)
|
|
4302
|
-
check_result=True,
|
|
4685
|
+
)
|
|
4303
4686
|
)
|
|
4304
4687
|
|
|
4305
|
-
async def find_peer_by_name(
|
|
4688
|
+
async def find_peer_by_name(
|
|
4689
|
+
self, name: str, transport=PhysicalTransport.LE
|
|
4690
|
+
) -> hci.Address:
|
|
4306
4691
|
"""
|
|
4307
4692
|
Scan for a peer with a given name and return its address.
|
|
4308
4693
|
"""
|
|
@@ -4320,6 +4705,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4320
4705
|
listener: Callable[..., None] | None = None
|
|
4321
4706
|
was_scanning = self.scanning
|
|
4322
4707
|
was_discovering = self.discovering
|
|
4708
|
+
event_name: str | None = None
|
|
4323
4709
|
try:
|
|
4324
4710
|
if transport == PhysicalTransport.LE:
|
|
4325
4711
|
event_name = 'advertisement'
|
|
@@ -4345,11 +4731,11 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4345
4731
|
if not self.discovering:
|
|
4346
4732
|
await self.start_discovery()
|
|
4347
4733
|
else:
|
|
4348
|
-
|
|
4734
|
+
raise ValueError('invalid transport')
|
|
4349
4735
|
|
|
4350
4736
|
return await utils.cancel_on_event(self, Device.EVENT_FLUSH, peer_address)
|
|
4351
4737
|
finally:
|
|
4352
|
-
if listener is not None:
|
|
4738
|
+
if listener is not None and event_name is not None:
|
|
4353
4739
|
self.remove_listener(event_name, listener)
|
|
4354
4740
|
|
|
4355
4741
|
if transport == PhysicalTransport.LE and not was_scanning:
|
|
@@ -4364,6 +4750,8 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4364
4750
|
Scan for a peer with a resolvable address that can be resolved to a given
|
|
4365
4751
|
identity address.
|
|
4366
4752
|
"""
|
|
4753
|
+
if self.address_resolver is None:
|
|
4754
|
+
raise InvalidStateError('no resolver')
|
|
4367
4755
|
|
|
4368
4756
|
# Create a future to wait for an address to be found
|
|
4369
4757
|
peer_address = asyncio.get_running_loop().create_future()
|
|
@@ -4375,7 +4763,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4375
4763
|
peer_address.set_result(address)
|
|
4376
4764
|
return
|
|
4377
4765
|
|
|
4378
|
-
if address.is_resolvable:
|
|
4766
|
+
if address.is_resolvable and self.address_resolver is not None:
|
|
4379
4767
|
resolved_address = self.address_resolver.resolve(address)
|
|
4380
4768
|
if resolved_address == identity_address:
|
|
4381
4769
|
if not peer_address.done():
|
|
@@ -4487,11 +4875,10 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4487
4875
|
pending_authentication.set_exception(hci.HCI_Error(error_code))
|
|
4488
4876
|
|
|
4489
4877
|
# Request the authentication
|
|
4490
|
-
await self.
|
|
4878
|
+
await self.send_async_command(
|
|
4491
4879
|
hci.HCI_Authentication_Requested_Command(
|
|
4492
4880
|
connection_handle=connection.handle
|
|
4493
|
-
)
|
|
4494
|
-
check_result=True,
|
|
4881
|
+
)
|
|
4495
4882
|
)
|
|
4496
4883
|
|
|
4497
4884
|
# Wait for the authentication to complete
|
|
@@ -4539,22 +4926,20 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4539
4926
|
if connection.role != hci.Role.CENTRAL:
|
|
4540
4927
|
raise InvalidStateError('only centrals can start encryption')
|
|
4541
4928
|
|
|
4542
|
-
await self.
|
|
4929
|
+
await self.send_async_command(
|
|
4543
4930
|
hci.HCI_LE_Enable_Encryption_Command(
|
|
4544
4931
|
connection_handle=connection.handle,
|
|
4545
4932
|
random_number=rand,
|
|
4546
4933
|
encrypted_diversifier=ediv,
|
|
4547
4934
|
long_term_key=ltk,
|
|
4548
|
-
)
|
|
4549
|
-
check_result=True,
|
|
4935
|
+
)
|
|
4550
4936
|
)
|
|
4551
4937
|
else:
|
|
4552
|
-
await self.
|
|
4938
|
+
await self.send_async_command(
|
|
4553
4939
|
hci.HCI_Set_Connection_Encryption_Command(
|
|
4554
4940
|
connection_handle=connection.handle,
|
|
4555
4941
|
encryption_enable=0x01 if enable else 0x00,
|
|
4556
|
-
)
|
|
4557
|
-
check_result=True,
|
|
4942
|
+
)
|
|
4558
4943
|
)
|
|
4559
4944
|
|
|
4560
4945
|
# Wait for the result
|
|
@@ -4586,15 +4971,13 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4586
4971
|
def _(error_code: int):
|
|
4587
4972
|
pending_role_change.set_exception(hci.HCI_Error(error_code))
|
|
4588
4973
|
|
|
4589
|
-
await self.
|
|
4590
|
-
hci.HCI_Switch_Role_Command(bd_addr=connection.peer_address, role=role)
|
|
4591
|
-
check_result=True,
|
|
4974
|
+
await self.send_async_command(
|
|
4975
|
+
hci.HCI_Switch_Role_Command(bd_addr=connection.peer_address, role=role)
|
|
4592
4976
|
)
|
|
4593
4977
|
await connection.cancel_on_disconnection(pending_role_change)
|
|
4594
4978
|
|
|
4595
4979
|
# [Classic only]
|
|
4596
4980
|
async def request_remote_name(self, remote: hci.Address | Connection) -> str:
|
|
4597
|
-
# Set up event handlers
|
|
4598
4981
|
pending_name: asyncio.Future[str] = asyncio.get_running_loop().create_future()
|
|
4599
4982
|
|
|
4600
4983
|
peer_address = (
|
|
@@ -4613,14 +4996,13 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4613
4996
|
if address == peer_address:
|
|
4614
4997
|
pending_name.set_exception(hci.HCI_Error(error_code))
|
|
4615
4998
|
|
|
4616
|
-
await self.
|
|
4999
|
+
await self.send_async_command(
|
|
4617
5000
|
hci.HCI_Remote_Name_Request_Command(
|
|
4618
5001
|
bd_addr=peer_address,
|
|
4619
5002
|
page_scan_repetition_mode=hci.HCI_Remote_Name_Request_Command.R2,
|
|
4620
5003
|
reserved=0,
|
|
4621
5004
|
clock_offset=0, # TODO investigate non-0 values
|
|
4622
|
-
)
|
|
4623
|
-
check_result=True,
|
|
5005
|
+
)
|
|
4624
5006
|
)
|
|
4625
5007
|
|
|
4626
5008
|
# Wait for the result
|
|
@@ -4631,7 +5013,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4631
5013
|
async def setup_cig(
|
|
4632
5014
|
self,
|
|
4633
5015
|
parameters: CigParameters,
|
|
4634
|
-
) ->
|
|
5016
|
+
) -> Sequence[int]:
|
|
4635
5017
|
"""Sends hci.HCI_LE_Set_CIG_Parameters_Command.
|
|
4636
5018
|
|
|
4637
5019
|
Args:
|
|
@@ -4640,7 +5022,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4640
5022
|
Returns:
|
|
4641
5023
|
List of created CIS handles corresponding to the same order of [cid_id].
|
|
4642
5024
|
"""
|
|
4643
|
-
response = await self.
|
|
5025
|
+
response = await self.send_sync_command(
|
|
4644
5026
|
hci.HCI_LE_Set_CIG_Parameters_Command(
|
|
4645
5027
|
cig_id=parameters.cig_id,
|
|
4646
5028
|
sdu_interval_c_to_p=parameters.sdu_interval_c_to_p,
|
|
@@ -4661,13 +5043,12 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4661
5043
|
phy_p_to_c=[cis.phy_p_to_c for cis in parameters.cis_parameters],
|
|
4662
5044
|
rtn_c_to_p=[cis.rtn_c_to_p for cis in parameters.cis_parameters],
|
|
4663
5045
|
rtn_p_to_c=[cis.rtn_p_to_c for cis in parameters.cis_parameters],
|
|
4664
|
-
)
|
|
4665
|
-
check_result=True,
|
|
5046
|
+
)
|
|
4666
5047
|
)
|
|
4667
5048
|
|
|
4668
5049
|
# Ideally, we should manage CIG lifecycle, but they are not useful for Unicast
|
|
4669
5050
|
# Server, so here it only provides a basic functionality for testing.
|
|
4670
|
-
cis_handles = response.
|
|
5051
|
+
cis_handles = response.connection_handle[:]
|
|
4671
5052
|
for cis, cis_handle in zip(parameters.cis_parameters, cis_handles):
|
|
4672
5053
|
self._pending_cis[cis_handle] = (cis.cis_id, parameters.cig_id)
|
|
4673
5054
|
|
|
@@ -4707,12 +5088,11 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4707
5088
|
watcher.on(
|
|
4708
5089
|
self, self.EVENT_CIS_ESTABLISHMENT_FAILURE, on_cis_establishment_failure
|
|
4709
5090
|
)
|
|
4710
|
-
await self.
|
|
5091
|
+
await self.send_async_command(
|
|
4711
5092
|
hci.HCI_LE_Create_CIS_Command(
|
|
4712
5093
|
cis_connection_handle=[p[0] for p in cis_acl_pairs],
|
|
4713
5094
|
acl_connection_handle=[p[1].handle for p in cis_acl_pairs],
|
|
4714
|
-
)
|
|
4715
|
-
check_result=True,
|
|
5095
|
+
)
|
|
4716
5096
|
)
|
|
4717
5097
|
|
|
4718
5098
|
return await asyncio.gather(*pending_cis_establishments.values())
|
|
@@ -4751,11 +5131,10 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4751
5131
|
on_establishment_failure,
|
|
4752
5132
|
)
|
|
4753
5133
|
|
|
4754
|
-
await self.
|
|
5134
|
+
await self.send_async_command(
|
|
4755
5135
|
hci.HCI_LE_Accept_CIS_Request_Command(
|
|
4756
5136
|
connection_handle=cis_link.handle
|
|
4757
|
-
)
|
|
4758
|
-
check_result=True,
|
|
5137
|
+
)
|
|
4759
5138
|
)
|
|
4760
5139
|
|
|
4761
5140
|
await pending_establishment
|
|
@@ -4767,11 +5146,10 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4767
5146
|
cis_link: CisLink,
|
|
4768
5147
|
reason: int = hci.HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR,
|
|
4769
5148
|
) -> None:
|
|
4770
|
-
await self.
|
|
5149
|
+
await self.send_sync_command(
|
|
4771
5150
|
hci.HCI_LE_Reject_CIS_Request_Command(
|
|
4772
5151
|
connection_handle=cis_link.handle, reason=reason
|
|
4773
|
-
)
|
|
4774
|
-
check_result=True,
|
|
5152
|
+
)
|
|
4775
5153
|
)
|
|
4776
5154
|
|
|
4777
5155
|
# [LE only]
|
|
@@ -4800,7 +5178,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4800
5178
|
)
|
|
4801
5179
|
|
|
4802
5180
|
try:
|
|
4803
|
-
await self.
|
|
5181
|
+
await self.send_async_command(
|
|
4804
5182
|
hci.HCI_LE_Create_BIG_Command(
|
|
4805
5183
|
big_handle=big_handle,
|
|
4806
5184
|
advertising_handle=advertising_set.advertising_handle,
|
|
@@ -4814,8 +5192,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4814
5192
|
framing=parameters.framing,
|
|
4815
5193
|
encryption=1 if parameters.broadcast_code else 0,
|
|
4816
5194
|
broadcast_code=parameters.broadcast_code or bytes(16),
|
|
4817
|
-
)
|
|
4818
|
-
check_result=True,
|
|
5195
|
+
)
|
|
4819
5196
|
)
|
|
4820
5197
|
await established
|
|
4821
5198
|
except hci.HCI_Error:
|
|
@@ -4855,7 +5232,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4855
5232
|
)
|
|
4856
5233
|
|
|
4857
5234
|
try:
|
|
4858
|
-
await self.
|
|
5235
|
+
await self.send_async_command(
|
|
4859
5236
|
hci.HCI_LE_BIG_Create_Sync_Command(
|
|
4860
5237
|
big_handle=big_handle,
|
|
4861
5238
|
sync_handle=pa_sync_handle,
|
|
@@ -4864,8 +5241,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4864
5241
|
mse=parameters.mse,
|
|
4865
5242
|
big_sync_timeout=parameters.big_sync_timeout,
|
|
4866
5243
|
bis=parameters.bis,
|
|
4867
|
-
)
|
|
4868
|
-
check_result=True,
|
|
5244
|
+
)
|
|
4869
5245
|
)
|
|
4870
5246
|
await established
|
|
4871
5247
|
except hci.HCI_Error:
|
|
@@ -4898,11 +5274,10 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4898
5274
|
|
|
4899
5275
|
watcher.on(self.host, 'le_remote_features', on_le_remote_features)
|
|
4900
5276
|
watcher.on(self.host, 'le_remote_features_failure', on_failure)
|
|
4901
|
-
await self.
|
|
5277
|
+
await self.send_async_command(
|
|
4902
5278
|
hci.HCI_LE_Read_Remote_Features_Command(
|
|
4903
5279
|
connection_handle=connection.handle
|
|
4904
|
-
)
|
|
4905
|
-
check_result=True,
|
|
5280
|
+
)
|
|
4906
5281
|
)
|
|
4907
5282
|
return await read_feature_future
|
|
4908
5283
|
|
|
@@ -4923,11 +5298,10 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4923
5298
|
'channel_sounding_capabilities_failure',
|
|
4924
5299
|
lambda status: complete_future.set_exception(hci.HCI_Error(status)),
|
|
4925
5300
|
)
|
|
4926
|
-
await self.
|
|
5301
|
+
await self.send_async_command(
|
|
4927
5302
|
hci.HCI_LE_CS_Read_Remote_Supported_Capabilities_Command(
|
|
4928
5303
|
connection_handle=connection.handle
|
|
4929
|
-
)
|
|
4930
|
-
check_result=True,
|
|
5304
|
+
)
|
|
4931
5305
|
)
|
|
4932
5306
|
return await complete_future
|
|
4933
5307
|
|
|
@@ -4941,14 +5315,13 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4941
5315
|
cs_sync_antenna_selection: int = 0xFF, # No Preference
|
|
4942
5316
|
max_tx_power: int = 0x04, # 4 dB
|
|
4943
5317
|
) -> None:
|
|
4944
|
-
await self.
|
|
5318
|
+
await self.send_sync_command(
|
|
4945
5319
|
hci.HCI_LE_CS_Set_Default_Settings_Command(
|
|
4946
5320
|
connection_handle=connection.handle,
|
|
4947
5321
|
role_enable=role_enable,
|
|
4948
5322
|
cs_sync_antenna_selection=cs_sync_antenna_selection,
|
|
4949
5323
|
max_tx_power=max_tx_power,
|
|
4950
|
-
)
|
|
4951
|
-
check_result=True,
|
|
5324
|
+
)
|
|
4952
5325
|
)
|
|
4953
5326
|
|
|
4954
5327
|
@utils.experimental('Only for testing.')
|
|
@@ -4997,7 +5370,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4997
5370
|
'channel_sounding_config_failure',
|
|
4998
5371
|
lambda status: complete_future.set_exception(hci.HCI_Error(status)),
|
|
4999
5372
|
)
|
|
5000
|
-
await self.
|
|
5373
|
+
await self.send_async_command(
|
|
5001
5374
|
hci.HCI_LE_CS_Create_Config_Command(
|
|
5002
5375
|
connection_handle=connection.handle,
|
|
5003
5376
|
config_id=config_id,
|
|
@@ -5017,8 +5390,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5017
5390
|
ch3c_shape=ch3c_shape,
|
|
5018
5391
|
ch3c_jump=ch3c_jump,
|
|
5019
5392
|
reserved=0x00,
|
|
5020
|
-
)
|
|
5021
|
-
check_result=True,
|
|
5393
|
+
)
|
|
5022
5394
|
)
|
|
5023
5395
|
return await complete_future
|
|
5024
5396
|
|
|
@@ -5038,11 +5410,10 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5038
5410
|
complete_future.set_exception(hci.HCI_Error(event.status))
|
|
5039
5411
|
|
|
5040
5412
|
watcher.once(self.host, 'cs_security', on_event)
|
|
5041
|
-
await self.
|
|
5413
|
+
await self.send_async_command(
|
|
5042
5414
|
hci.HCI_LE_CS_Security_Enable_Command(
|
|
5043
5415
|
connection_handle=connection.handle
|
|
5044
|
-
)
|
|
5045
|
-
check_result=True,
|
|
5416
|
+
)
|
|
5046
5417
|
)
|
|
5047
5418
|
return await complete_future
|
|
5048
5419
|
|
|
@@ -5064,7 +5435,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5064
5435
|
snr_control_initiator=hci.CsSnr.NOT_APPLIED,
|
|
5065
5436
|
snr_control_reflector=hci.CsSnr.NOT_APPLIED,
|
|
5066
5437
|
) -> None:
|
|
5067
|
-
await self.
|
|
5438
|
+
await self.send_sync_command(
|
|
5068
5439
|
hci.HCI_LE_CS_Set_Procedure_Parameters_Command(
|
|
5069
5440
|
connection_handle=connection.handle,
|
|
5070
5441
|
config_id=config.config_id,
|
|
@@ -5080,8 +5451,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5080
5451
|
preferred_peer_antenna=preferred_peer_antenna,
|
|
5081
5452
|
snr_control_initiator=snr_control_initiator,
|
|
5082
5453
|
snr_control_reflector=snr_control_reflector,
|
|
5083
|
-
)
|
|
5084
|
-
check_result=True,
|
|
5454
|
+
)
|
|
5085
5455
|
)
|
|
5086
5456
|
|
|
5087
5457
|
@utils.experimental('Only for testing.')
|
|
@@ -5103,13 +5473,12 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5103
5473
|
'channel_sounding_procedure_failure',
|
|
5104
5474
|
lambda x: complete_future.set_exception(hci.HCI_Error(x)),
|
|
5105
5475
|
)
|
|
5106
|
-
await self.
|
|
5476
|
+
await self.send_async_command(
|
|
5107
5477
|
hci.HCI_LE_CS_Procedure_Enable_Command(
|
|
5108
5478
|
connection_handle=connection.handle,
|
|
5109
5479
|
config_id=config.config_id,
|
|
5110
5480
|
enable=enabled,
|
|
5111
|
-
)
|
|
5112
|
-
check_result=True,
|
|
5481
|
+
)
|
|
5113
5482
|
)
|
|
5114
5483
|
return await complete_future
|
|
5115
5484
|
|
|
@@ -5740,12 +6109,14 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5740
6109
|
)[1 if pairing_config.bonding else 0][1 if pairing_config.mitm else 0]
|
|
5741
6110
|
|
|
5742
6111
|
# Respond
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
6112
|
+
utils.AsyncRunner.spawn(
|
|
6113
|
+
self.host.send_sync_command(
|
|
6114
|
+
hci.HCI_IO_Capability_Request_Reply_Command(
|
|
6115
|
+
bd_addr=connection.peer_address,
|
|
6116
|
+
io_capability=pairing_config.delegate.classic_io_capability,
|
|
6117
|
+
oob_data_present=0x00, # Not present
|
|
6118
|
+
authentication_requirements=authentication_requirements,
|
|
6119
|
+
)
|
|
5749
6120
|
)
|
|
5750
6121
|
)
|
|
5751
6122
|
|
|
@@ -5829,7 +6200,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5829
6200
|
async def reply() -> None:
|
|
5830
6201
|
try:
|
|
5831
6202
|
if await connection.cancel_on_disconnection(method()):
|
|
5832
|
-
await self.host.
|
|
6203
|
+
await self.host.send_sync_command(
|
|
5833
6204
|
hci.HCI_User_Confirmation_Request_Reply_Command(
|
|
5834
6205
|
bd_addr=connection.peer_address
|
|
5835
6206
|
)
|
|
@@ -5838,7 +6209,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5838
6209
|
except Exception:
|
|
5839
6210
|
logger.exception('exception while confirming')
|
|
5840
6211
|
|
|
5841
|
-
await self.host.
|
|
6212
|
+
await self.host.send_sync_command(
|
|
5842
6213
|
hci.HCI_User_Confirmation_Request_Negative_Reply_Command(
|
|
5843
6214
|
bd_addr=connection.peer_address
|
|
5844
6215
|
)
|
|
@@ -5859,7 +6230,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5859
6230
|
pairing_config.delegate.get_number()
|
|
5860
6231
|
)
|
|
5861
6232
|
if number is not None:
|
|
5862
|
-
await self.host.
|
|
6233
|
+
await self.host.send_sync_command(
|
|
5863
6234
|
hci.HCI_User_Passkey_Request_Reply_Command(
|
|
5864
6235
|
bd_addr=connection.peer_address, numeric_value=number
|
|
5865
6236
|
)
|
|
@@ -5868,7 +6239,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5868
6239
|
except Exception:
|
|
5869
6240
|
logger.exception('exception while asking for pass-key')
|
|
5870
6241
|
|
|
5871
|
-
await self.host.
|
|
6242
|
+
await self.host.send_sync_command(
|
|
5872
6243
|
hci.HCI_User_Passkey_Request_Negative_Reply_Command(
|
|
5873
6244
|
bd_addr=connection.peer_address
|
|
5874
6245
|
)
|
|
@@ -5911,7 +6282,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5911
6282
|
pin_code_len = len(pin_code)
|
|
5912
6283
|
if not 1 <= pin_code_len <= 16:
|
|
5913
6284
|
raise core.InvalidArgumentError("pin_code should be 1-16 bytes")
|
|
5914
|
-
await self.host.
|
|
6285
|
+
await self.host.send_sync_command(
|
|
5915
6286
|
hci.HCI_PIN_Code_Request_Reply_Command(
|
|
5916
6287
|
bd_addr=connection.peer_address,
|
|
5917
6288
|
pin_code_length=pin_code_len,
|
|
@@ -5920,17 +6291,19 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5920
6291
|
)
|
|
5921
6292
|
else:
|
|
5922
6293
|
logger.debug("delegate.get_string() returned None")
|
|
5923
|
-
await self.host.
|
|
6294
|
+
await self.host.send_sync_command(
|
|
5924
6295
|
hci.HCI_PIN_Code_Request_Negative_Reply_Command(
|
|
5925
6296
|
bd_addr=connection.peer_address
|
|
5926
6297
|
)
|
|
5927
6298
|
)
|
|
5928
6299
|
|
|
5929
|
-
|
|
6300
|
+
utils.AsyncRunner.spawn(get_pin_code())
|
|
5930
6301
|
else:
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
6302
|
+
utils.AsyncRunner.spawn(
|
|
6303
|
+
self.host.send_sync_command(
|
|
6304
|
+
hci.HCI_PIN_Code_Request_Negative_Reply_Command(
|
|
6305
|
+
bd_addr=connection.peer_address
|
|
6306
|
+
)
|
|
5934
6307
|
)
|
|
5935
6308
|
)
|
|
5936
6309
|
|
|
@@ -6191,7 +6564,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
6191
6564
|
):
|
|
6192
6565
|
logger.debug(
|
|
6193
6566
|
f'*** Connection Parameters Update: [0x{connection.handle:04X}] '
|
|
6194
|
-
f'{connection.peer_address} as {connection.role_name}
|
|
6567
|
+
f'{connection.peer_address} as {connection.role_name}'
|
|
6195
6568
|
)
|
|
6196
6569
|
if connection.parameters.connection_interval != connection_interval * 1.25:
|
|
6197
6570
|
connection.parameters = Connection.Parameters(
|
|
@@ -6215,7 +6588,41 @@ class Device(utils.CompositeEventEmitter):
|
|
|
6215
6588
|
self, connection: Connection, error: int
|
|
6216
6589
|
):
|
|
6217
6590
|
logger.debug(
|
|
6218
|
-
f'*** Connection Parameters Update
|
|
6591
|
+
f'*** Connection Parameters Update failed: [0x{connection.handle:04X}] '
|
|
6592
|
+
f'{connection.peer_address} as {connection.role_name}, '
|
|
6593
|
+
f'error={error}'
|
|
6594
|
+
)
|
|
6595
|
+
connection.emit(connection.EVENT_CONNECTION_PARAMETERS_UPDATE_FAILURE, error)
|
|
6596
|
+
|
|
6597
|
+
@host_event_handler
|
|
6598
|
+
@with_connection_from_handle
|
|
6599
|
+
def on_le_connection_rate_change(
|
|
6600
|
+
self,
|
|
6601
|
+
connection: Connection,
|
|
6602
|
+
connection_interval: int,
|
|
6603
|
+
subrate_factor: int,
|
|
6604
|
+
peripheral_latency: int,
|
|
6605
|
+
continuation_number: int,
|
|
6606
|
+
supervision_timeout: int,
|
|
6607
|
+
):
|
|
6608
|
+
logger.debug(
|
|
6609
|
+
f'*** Connection Rate Change: [0x{connection.handle:04X}] '
|
|
6610
|
+
f'{connection.peer_address} as {connection.role_name}'
|
|
6611
|
+
)
|
|
6612
|
+
connection.parameters = Connection.Parameters(
|
|
6613
|
+
connection_interval=connection_interval * 0.125,
|
|
6614
|
+
subrate_factor=subrate_factor,
|
|
6615
|
+
peripheral_latency=peripheral_latency,
|
|
6616
|
+
continuation_number=continuation_number,
|
|
6617
|
+
supervision_timeout=supervision_timeout * 10.0,
|
|
6618
|
+
)
|
|
6619
|
+
connection.emit(connection.EVENT_CONNECTION_PARAMETERS_UPDATE)
|
|
6620
|
+
|
|
6621
|
+
@host_event_handler
|
|
6622
|
+
@with_connection_from_handle
|
|
6623
|
+
def on_le_connection_rate_change_failure(self, connection: Connection, error: int):
|
|
6624
|
+
logger.debug(
|
|
6625
|
+
f'*** Connection Rate Change failed: [0x{connection.handle:04X}] '
|
|
6219
6626
|
f'{connection.peer_address} as {connection.role_name}, '
|
|
6220
6627
|
f'error={error}'
|
|
6221
6628
|
)
|
|
@@ -6258,7 +6665,25 @@ class Device(utils.CompositeEventEmitter):
|
|
|
6258
6665
|
subrate_factor,
|
|
6259
6666
|
continuation_number,
|
|
6260
6667
|
)
|
|
6261
|
-
connection.emit(connection.
|
|
6668
|
+
connection.emit(connection.EVENT_CONNECTION_PARAMETERS_UPDATE)
|
|
6669
|
+
|
|
6670
|
+
@host_event_handler
|
|
6671
|
+
@with_connection_from_handle
|
|
6672
|
+
def on_le_subrate_change_failure(self, connection: Connection, status: int):
|
|
6673
|
+
connection.emit(connection.EVENT_CONNECTION_PARAMETERS_UPDATE_FAILURE, status)
|
|
6674
|
+
|
|
6675
|
+
@host_event_handler
|
|
6676
|
+
@with_connection_from_handle
|
|
6677
|
+
def on_le_remote_features(
|
|
6678
|
+
self, connection: Connection, le_features: hci.LeFeatureMask
|
|
6679
|
+
):
|
|
6680
|
+
connection.peer_le_features = le_features
|
|
6681
|
+
connection.emit(connection.EVENT_LE_REMOTE_FEATURES_CHANGE)
|
|
6682
|
+
|
|
6683
|
+
@host_event_handler
|
|
6684
|
+
@with_connection_from_handle
|
|
6685
|
+
def on_le_remote_features_failure(self, connection: Connection, status: int):
|
|
6686
|
+
connection.emit(connection.EVENT_LE_REMOTE_FEATURES_CHANGE_FAILURE, status)
|
|
6262
6687
|
|
|
6263
6688
|
@host_event_handler
|
|
6264
6689
|
@with_connection_from_handle
|
|
@@ -6305,7 +6730,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
6305
6730
|
rtt_capability=event.rtt_capability,
|
|
6306
6731
|
rtt_aa_only_n=event.rtt_aa_only_n,
|
|
6307
6732
|
rtt_sounding_n=event.rtt_sounding_n,
|
|
6308
|
-
|
|
6733
|
+
rtt_random_sequence_n=event.rtt_random_sequence_n,
|
|
6309
6734
|
nadm_sounding_capability=event.nadm_sounding_capability,
|
|
6310
6735
|
nadm_random_capability=event.nadm_random_capability,
|
|
6311
6736
|
cs_sync_phys_supported=event.cs_sync_phys_supported,
|