bumble 0.0.222__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/apps/usb_probe.py +15 -2
- bumble/att.py +97 -32
- bumble/avctp.py +1 -1
- bumble/avdtp.py +3 -3
- bumble/avrcp.py +366 -190
- bumble/bridge.py +10 -2
- bumble/controller.py +14 -1
- bumble/core.py +1 -1
- bumble/device.py +999 -577
- bumble/drivers/intel.py +45 -39
- bumble/drivers/rtk.py +102 -43
- bumble/gatt.py +2 -2
- bumble/gatt_client.py +5 -4
- bumble/gatt_server.py +100 -1
- bumble/hci.py +1367 -844
- bumble/hid.py +2 -2
- bumble/host.py +339 -157
- bumble/l2cap.py +13 -6
- bumble/pandora/l2cap.py +1 -1
- bumble/profiles/battery_service.py +25 -34
- bumble/profiles/heart_rate_service.py +130 -121
- bumble/rfcomm.py +1 -1
- bumble/sdp.py +2 -2
- 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.222.dist-info → bumble-0.0.224.dist-info}/METADATA +4 -3
- {bumble-0.0.222.dist-info → bumble-0.0.224.dist-info}/RECORD +43 -43
- {bumble-0.0.222.dist-info → bumble-0.0.224.dist-info}/WHEEL +1 -1
- {bumble-0.0.222.dist-info → bumble-0.0.224.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.222.dist-info → bumble-0.0.224.dist-info}/licenses/LICENSE +0 -0
- {bumble-0.0.222.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
|
|
@@ -1383,10 +1367,7 @@ class Peer:
|
|
|
1383
1367
|
def create_service_proxy(
|
|
1384
1368
|
self, proxy_class: type[_PROXY_CLASS]
|
|
1385
1369
|
) -> _PROXY_CLASS | None:
|
|
1386
|
-
|
|
1387
|
-
return cast(_PROXY_CLASS, proxy)
|
|
1388
|
-
|
|
1389
|
-
return None
|
|
1370
|
+
return proxy_class.from_client(self.gatt_client)
|
|
1390
1371
|
|
|
1391
1372
|
async def discover_service_and_create_proxy(
|
|
1392
1373
|
self, proxy_class: type[_PROXY_CLASS]
|
|
@@ -1406,7 +1387,7 @@ class Peer:
|
|
|
1406
1387
|
async def request_name(self) -> str:
|
|
1407
1388
|
return await self.connection.request_remote_name()
|
|
1408
1389
|
|
|
1409
|
-
async def __aenter__(self):
|
|
1390
|
+
async def __aenter__(self) -> Self:
|
|
1410
1391
|
await self.discover_services()
|
|
1411
1392
|
for service in self.services:
|
|
1412
1393
|
await service.discover_characteristics()
|
|
@@ -1428,8 +1409,8 @@ class ConnectionParametersPreferences:
|
|
|
1428
1409
|
connection_interval_max: float = DEVICE_DEFAULT_CONNECTION_INTERVAL_MAX
|
|
1429
1410
|
max_latency: int = DEVICE_DEFAULT_CONNECTION_MAX_LATENCY
|
|
1430
1411
|
supervision_timeout: int = DEVICE_DEFAULT_CONNECTION_SUPERVISION_TIMEOUT
|
|
1431
|
-
min_ce_length:
|
|
1432
|
-
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
|
|
1433
1414
|
|
|
1434
1415
|
|
|
1435
1416
|
ConnectionParametersPreferences.default = ConnectionParametersPreferences()
|
|
@@ -1499,7 +1480,7 @@ class _IsoLink:
|
|
|
1499
1480
|
async with self._data_path_lock:
|
|
1500
1481
|
if direction in self.data_paths:
|
|
1501
1482
|
return
|
|
1502
|
-
await self.device.
|
|
1483
|
+
await self.device.send_sync_command(
|
|
1503
1484
|
hci.HCI_LE_Setup_ISO_Data_Path_Command(
|
|
1504
1485
|
connection_handle=self.handle,
|
|
1505
1486
|
data_path_direction=direction,
|
|
@@ -1507,8 +1488,7 @@ class _IsoLink:
|
|
|
1507
1488
|
codec_id=codec_id or hci.CodingFormat(hci.CodecID.TRANSPARENT),
|
|
1508
1489
|
controller_delay=controller_delay,
|
|
1509
1490
|
codec_configuration=codec_configuration,
|
|
1510
|
-
)
|
|
1511
|
-
check_result=True,
|
|
1491
|
+
)
|
|
1512
1492
|
)
|
|
1513
1493
|
self.data_paths.add(direction)
|
|
1514
1494
|
|
|
@@ -1525,14 +1505,13 @@ class _IsoLink:
|
|
|
1525
1505
|
directions_to_remove = set(directions).intersection(self.data_paths)
|
|
1526
1506
|
if not directions_to_remove:
|
|
1527
1507
|
return
|
|
1528
|
-
await self.device.
|
|
1508
|
+
await self.device.send_sync_command(
|
|
1529
1509
|
hci.HCI_LE_Remove_ISO_Data_Path_Command(
|
|
1530
1510
|
connection_handle=self.handle,
|
|
1531
1511
|
data_path_direction=sum(
|
|
1532
1512
|
1 << direction for direction in directions_to_remove
|
|
1533
1513
|
),
|
|
1534
|
-
)
|
|
1535
|
-
check_result=True,
|
|
1514
|
+
)
|
|
1536
1515
|
)
|
|
1537
1516
|
self.data_paths.difference_update(directions_to_remove)
|
|
1538
1517
|
|
|
@@ -1541,14 +1520,13 @@ class _IsoLink:
|
|
|
1541
1520
|
self.device.host.send_iso_sdu(connection_handle=self.handle, sdu=sdu)
|
|
1542
1521
|
|
|
1543
1522
|
async def get_tx_time_stamp(self) -> tuple[int, int, int]:
|
|
1544
|
-
response = await self.device.
|
|
1545
|
-
hci.HCI_LE_Read_ISO_TX_Sync_Command(connection_handle=self.handle)
|
|
1546
|
-
check_result=True,
|
|
1523
|
+
response = await self.device.send_sync_command(
|
|
1524
|
+
hci.HCI_LE_Read_ISO_TX_Sync_Command(connection_handle=self.handle)
|
|
1547
1525
|
)
|
|
1548
1526
|
return (
|
|
1549
|
-
response.
|
|
1550
|
-
response.
|
|
1551
|
-
response.
|
|
1527
|
+
response.packet_sequence_number,
|
|
1528
|
+
response.tx_time_stamp,
|
|
1529
|
+
response.time_offset,
|
|
1552
1530
|
)
|
|
1553
1531
|
|
|
1554
1532
|
@property
|
|
@@ -1719,7 +1697,7 @@ class Connection(utils.CompositeEventEmitter):
|
|
|
1719
1697
|
peer_address: hci.Address
|
|
1720
1698
|
peer_name: str | None
|
|
1721
1699
|
peer_resolvable_address: hci.Address | None
|
|
1722
|
-
peer_le_features: hci.LeFeatureMask
|
|
1700
|
+
peer_le_features: hci.LeFeatureMask
|
|
1723
1701
|
role: hci.Role
|
|
1724
1702
|
parameters: Parameters
|
|
1725
1703
|
encryption: int
|
|
@@ -1772,8 +1750,8 @@ class Connection(utils.CompositeEventEmitter):
|
|
|
1772
1750
|
EVENT_CIS_REQUEST = "cis_request"
|
|
1773
1751
|
EVENT_CIS_ESTABLISHMENT = "cis_establishment"
|
|
1774
1752
|
EVENT_CIS_ESTABLISHMENT_FAILURE = "cis_establishment_failure"
|
|
1775
|
-
|
|
1776
|
-
|
|
1753
|
+
EVENT_LE_REMOTE_FEATURES_CHANGE = "le_remote_features_change"
|
|
1754
|
+
EVENT_LE_REMOTE_FEATURES_CHANGE_FAILURE = "le_remote_features_change_failure"
|
|
1777
1755
|
|
|
1778
1756
|
@utils.composite_listener
|
|
1779
1757
|
class Listener:
|
|
@@ -1851,14 +1829,14 @@ class Connection(utils.CompositeEventEmitter):
|
|
|
1851
1829
|
self.authenticated = False
|
|
1852
1830
|
self.sc = False
|
|
1853
1831
|
self.att_mtu = att.ATT_DEFAULT_MTU
|
|
1854
|
-
self.data_length = DEVICE_DEFAULT_DATA_LENGTH
|
|
1832
|
+
self.data_length: tuple[int, int, int, int] = DEVICE_DEFAULT_DATA_LENGTH
|
|
1855
1833
|
self.gatt_client = gatt_client.Client(self) # Per-connection client
|
|
1856
1834
|
self.gatt_server = (
|
|
1857
1835
|
device.gatt_server
|
|
1858
1836
|
) # By default, use the device's shared server
|
|
1859
1837
|
self.pairing_peer_io_capability = None
|
|
1860
1838
|
self.pairing_peer_authentication_requirements = None
|
|
1861
|
-
self.peer_le_features =
|
|
1839
|
+
self.peer_le_features = hci.LeFeatureMask(0)
|
|
1862
1840
|
self.cs_configs = {}
|
|
1863
1841
|
self.cs_procedures = {}
|
|
1864
1842
|
|
|
@@ -1940,16 +1918,21 @@ class Connection(utils.CompositeEventEmitter):
|
|
|
1940
1918
|
connection_interval_max: float,
|
|
1941
1919
|
max_latency: int,
|
|
1942
1920
|
supervision_timeout: float,
|
|
1921
|
+
min_ce_length: float = 0.0,
|
|
1922
|
+
max_ce_length: float = 0.0,
|
|
1943
1923
|
use_l2cap=False,
|
|
1944
1924
|
) -> None:
|
|
1945
1925
|
"""
|
|
1946
|
-
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.
|
|
1947
1930
|
|
|
1948
1931
|
Args:
|
|
1949
1932
|
connection_interval_min: Minimum interval, in milliseconds.
|
|
1950
1933
|
connection_interval_max: Maximum interval, in milliseconds.
|
|
1951
|
-
max_latency:
|
|
1952
|
-
supervision_timeout: Timeout, in milliseconds.
|
|
1934
|
+
max_latency: Max latency, in number of intervals.
|
|
1935
|
+
supervision_timeout: Supervision Timeout, in milliseconds.
|
|
1953
1936
|
use_l2cap: Request the update via L2CAP.
|
|
1954
1937
|
"""
|
|
1955
1938
|
return await self.device.update_connection_parameters(
|
|
@@ -1959,6 +1942,77 @@ class Connection(utils.CompositeEventEmitter):
|
|
|
1959
1942
|
max_latency,
|
|
1960
1943
|
supervision_timeout,
|
|
1961
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,
|
|
1962
2016
|
)
|
|
1963
2017
|
|
|
1964
2018
|
async def set_phy(
|
|
@@ -2065,6 +2119,7 @@ class DeviceConfiguration:
|
|
|
2065
2119
|
le_privacy_enabled: bool = False
|
|
2066
2120
|
le_rpa_timeout: int = DEVICE_DEFAULT_LE_RPA_TIMEOUT
|
|
2067
2121
|
le_subrate_enabled: bool = False
|
|
2122
|
+
le_shorter_connection_intervals_enabled: bool = False
|
|
2068
2123
|
classic_enabled: bool = False
|
|
2069
2124
|
classic_sc_enabled: bool = True
|
|
2070
2125
|
classic_ssp_enabled: bool = True
|
|
@@ -2371,7 +2426,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2371
2426
|
self._host = None
|
|
2372
2427
|
self.powered_on = False
|
|
2373
2428
|
self.auto_restart_inquiry = True
|
|
2374
|
-
self.command_timeout = 10 # seconds
|
|
2429
|
+
self.command_timeout = 10.0 # seconds
|
|
2375
2430
|
self.gatt_server = gatt_server.Server(self)
|
|
2376
2431
|
self.sdp_server = sdp.Server(self)
|
|
2377
2432
|
self.l2cap_channel_manager = l2cap.ChannelManager(
|
|
@@ -2419,6 +2474,9 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2419
2474
|
self.le_rpa_timeout = config.le_rpa_timeout
|
|
2420
2475
|
self.le_rpa_periodic_update_task: asyncio.Task | None = None
|
|
2421
2476
|
self.le_subrate_enabled = config.le_subrate_enabled
|
|
2477
|
+
self.le_shorter_connection_intervals_enabled = (
|
|
2478
|
+
config.le_shorter_connection_intervals_enabled
|
|
2479
|
+
)
|
|
2422
2480
|
self.classic_enabled = config.classic_enabled
|
|
2423
2481
|
self.cis_enabled = config.cis_enabled
|
|
2424
2482
|
self.classic_sc_enabled = config.classic_sc_enabled
|
|
@@ -2677,10 +2735,88 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2677
2735
|
def send_l2cap_pdu(self, connection_handle: int, cid: int, pdu: bytes) -> None:
|
|
2678
2736
|
self.host.send_l2cap_pdu(connection_handle, cid, pdu)
|
|
2679
2737
|
|
|
2680
|
-
|
|
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
|
+
'''
|
|
2681
2817
|
try:
|
|
2682
|
-
return await
|
|
2683
|
-
|
|
2818
|
+
return await self.host.send_async_command(
|
|
2819
|
+
command, check_status, self.command_timeout
|
|
2684
2820
|
)
|
|
2685
2821
|
except asyncio.TimeoutError as error:
|
|
2686
2822
|
logger.warning(f'!!! Command {command.name} timed out')
|
|
@@ -2691,12 +2827,12 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2691
2827
|
await self.host.reset()
|
|
2692
2828
|
|
|
2693
2829
|
# Try to get the public address from the controller
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
logger.debug(
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
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')
|
|
2700
2836
|
|
|
2701
2837
|
# Instantiate the Key Store (we do this here rather than at __init__ time
|
|
2702
2838
|
# because some Key Store implementations use the public address as a namespace)
|
|
@@ -2710,12 +2846,11 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2710
2846
|
)
|
|
2711
2847
|
|
|
2712
2848
|
if self.host.supports_command(hci.HCI_WRITE_LE_HOST_SUPPORT_COMMAND):
|
|
2713
|
-
await self.
|
|
2849
|
+
await self.send_sync_command(
|
|
2714
2850
|
hci.HCI_Write_LE_Host_Support_Command(
|
|
2715
2851
|
le_supported_host=int(self.le_enabled),
|
|
2716
2852
|
simultaneous_le_host=int(self.le_simultaneous_enabled),
|
|
2717
|
-
)
|
|
2718
|
-
check_result=True,
|
|
2853
|
+
)
|
|
2719
2854
|
)
|
|
2720
2855
|
|
|
2721
2856
|
if self.le_enabled:
|
|
@@ -2742,11 +2877,10 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2742
2877
|
'yellow',
|
|
2743
2878
|
)
|
|
2744
2879
|
)
|
|
2745
|
-
await self.
|
|
2880
|
+
await self.send_sync_command(
|
|
2746
2881
|
hci.HCI_LE_Set_Random_Address_Command(
|
|
2747
2882
|
random_address=self.random_address
|
|
2748
|
-
)
|
|
2749
|
-
check_result=True,
|
|
2883
|
+
)
|
|
2750
2884
|
)
|
|
2751
2885
|
|
|
2752
2886
|
# Load the address resolving list
|
|
@@ -2755,81 +2889,100 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2755
2889
|
|
|
2756
2890
|
# Enable address resolution
|
|
2757
2891
|
if self.address_resolution_offload:
|
|
2758
|
-
await self.
|
|
2892
|
+
await self.send_sync_command(
|
|
2759
2893
|
hci.HCI_LE_Set_Address_Resolution_Enable_Command(
|
|
2760
2894
|
address_resolution_enable=1
|
|
2761
|
-
)
|
|
2762
|
-
check_result=True,
|
|
2895
|
+
)
|
|
2763
2896
|
)
|
|
2764
2897
|
|
|
2765
|
-
if self.cis_enabled
|
|
2766
|
-
|
|
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(
|
|
2767
2902
|
hci.HCI_LE_Set_Host_Feature_Command(
|
|
2768
2903
|
bit_number=hci.LeFeature.CONNECTED_ISOCHRONOUS_STREAM,
|
|
2769
2904
|
bit_value=1,
|
|
2770
|
-
)
|
|
2771
|
-
check_result=True,
|
|
2905
|
+
)
|
|
2772
2906
|
)
|
|
2773
2907
|
|
|
2774
|
-
if
|
|
2775
|
-
|
|
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(
|
|
2776
2916
|
hci.HCI_LE_Set_Host_Feature_Command(
|
|
2777
2917
|
bit_number=hci.LeFeature.CONNECTION_SUBRATING_HOST_SUPPORT,
|
|
2778
2918
|
bit_value=1,
|
|
2779
|
-
)
|
|
2780
|
-
check_result=True,
|
|
2919
|
+
)
|
|
2781
2920
|
)
|
|
2782
2921
|
|
|
2783
|
-
if self.config.channel_sounding_enabled
|
|
2784
|
-
|
|
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(
|
|
2785
2926
|
hci.HCI_LE_Set_Host_Feature_Command(
|
|
2786
2927
|
bit_number=hci.LeFeature.CHANNEL_SOUNDING_HOST_SUPPORT,
|
|
2787
2928
|
bit_value=1,
|
|
2788
|
-
)
|
|
2789
|
-
check_result=True,
|
|
2929
|
+
)
|
|
2790
2930
|
)
|
|
2791
|
-
result = await self.
|
|
2792
|
-
hci.HCI_LE_CS_Read_Local_Supported_Capabilities_Command()
|
|
2793
|
-
check_result=True,
|
|
2931
|
+
result = await self.send_sync_command(
|
|
2932
|
+
hci.HCI_LE_CS_Read_Local_Supported_Capabilities_Command()
|
|
2794
2933
|
)
|
|
2795
2934
|
self.cs_capabilities = ChannelSoundingCapabilities(
|
|
2796
|
-
num_config_supported=result.
|
|
2797
|
-
max_consecutive_procedures_supported=result.
|
|
2798
|
-
num_antennas_supported=result.
|
|
2799
|
-
max_antenna_paths_supported=result.
|
|
2800
|
-
roles_supported=result.
|
|
2801
|
-
modes_supported=result.
|
|
2802
|
-
rtt_capability=result.
|
|
2803
|
-
rtt_aa_only_n=result.
|
|
2804
|
-
rtt_sounding_n=result.
|
|
2805
|
-
|
|
2806
|
-
nadm_sounding_capability=result.
|
|
2807
|
-
nadm_random_capability=result.
|
|
2808
|
-
cs_sync_phys_supported=result.
|
|
2809
|
-
subfeatures_supported=result.
|
|
2810
|
-
t_ip1_times_supported=result.
|
|
2811
|
-
t_ip2_times_supported=result.
|
|
2812
|
-
t_fcs_times_supported=result.
|
|
2813
|
-
t_pm_times_supported=result.
|
|
2814
|
-
t_sw_time_supported=result.
|
|
2815
|
-
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
|
+
)
|
|
2816
2969
|
)
|
|
2817
2970
|
|
|
2818
2971
|
if self.classic_enabled:
|
|
2819
|
-
await self.
|
|
2972
|
+
await self.send_sync_command_raw(
|
|
2820
2973
|
hci.HCI_Write_Local_Name_Command(local_name=self.name.encode('utf8'))
|
|
2821
2974
|
)
|
|
2822
|
-
await self.
|
|
2975
|
+
await self.send_sync_command_raw(
|
|
2823
2976
|
hci.HCI_Write_Class_Of_Device_Command(
|
|
2824
2977
|
class_of_device=self.class_of_device
|
|
2825
2978
|
)
|
|
2826
2979
|
)
|
|
2827
|
-
await self.
|
|
2980
|
+
await self.send_sync_command_raw(
|
|
2828
2981
|
hci.HCI_Write_Simple_Pairing_Mode_Command(
|
|
2829
2982
|
simple_pairing_mode=int(self.classic_ssp_enabled)
|
|
2830
2983
|
)
|
|
2831
2984
|
)
|
|
2832
|
-
await self.
|
|
2985
|
+
await self.send_sync_command_raw(
|
|
2833
2986
|
hci.HCI_Write_Secure_Connections_Host_Support_Command(
|
|
2834
2987
|
secure_connections_host_support=int(self.classic_sc_enabled)
|
|
2835
2988
|
)
|
|
@@ -2841,17 +2994,15 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2841
2994
|
if self.host.supports_lmp_features(
|
|
2842
2995
|
hci.LmpFeatureMask.INTERLACED_PAGE_SCAN
|
|
2843
2996
|
):
|
|
2844
|
-
await self.
|
|
2845
|
-
hci.HCI_Write_Page_Scan_Type_Command(page_scan_type=1)
|
|
2846
|
-
check_result=True,
|
|
2997
|
+
await self.send_sync_command(
|
|
2998
|
+
hci.HCI_Write_Page_Scan_Type_Command(page_scan_type=1)
|
|
2847
2999
|
)
|
|
2848
3000
|
|
|
2849
3001
|
if self.host.supports_lmp_features(
|
|
2850
3002
|
hci.LmpFeatureMask.INTERLACED_INQUIRY_SCAN
|
|
2851
3003
|
):
|
|
2852
|
-
await self.
|
|
2853
|
-
hci.HCI_Write_Inquiry_Scan_Type_Command(scan_type=1)
|
|
2854
|
-
check_result=True,
|
|
3004
|
+
await self.send_sync_command(
|
|
3005
|
+
hci.HCI_Write_Inquiry_Scan_Type_Command(scan_type=1)
|
|
2855
3006
|
)
|
|
2856
3007
|
|
|
2857
3008
|
# Done
|
|
@@ -2883,15 +3034,17 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2883
3034
|
return False
|
|
2884
3035
|
|
|
2885
3036
|
random_address = hci.Address.generate_private_address(self.irk)
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
3037
|
+
try:
|
|
3038
|
+
await self.send_sync_command(
|
|
3039
|
+
hci.HCI_LE_Set_Random_Address_Command(
|
|
3040
|
+
random_address=self.random_address
|
|
3041
|
+
)
|
|
3042
|
+
)
|
|
2890
3043
|
logger.info(f'new RPA: {random_address}')
|
|
2891
3044
|
self.random_address = random_address
|
|
2892
3045
|
return True
|
|
2893
|
-
|
|
2894
|
-
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}')
|
|
2895
3048
|
return False
|
|
2896
3049
|
|
|
2897
3050
|
async def _run_rpa_periodic_update(self) -> None:
|
|
@@ -2909,30 +3062,26 @@ class Device(utils.CompositeEventEmitter):
|
|
|
2909
3062
|
self.address_resolver = smp.AddressResolver(resolving_keys)
|
|
2910
3063
|
|
|
2911
3064
|
if self.address_resolution_offload or self.address_generation_offload:
|
|
2912
|
-
await self.
|
|
2913
|
-
hci.HCI_LE_Clear_Resolving_List_Command(), check_result=True
|
|
2914
|
-
)
|
|
3065
|
+
await self.send_sync_command(hci.HCI_LE_Clear_Resolving_List_Command())
|
|
2915
3066
|
|
|
2916
3067
|
# Add an empty entry for non-directed address generation.
|
|
2917
|
-
await self.
|
|
3068
|
+
await self.send_sync_command(
|
|
2918
3069
|
hci.HCI_LE_Add_Device_To_Resolving_List_Command(
|
|
2919
3070
|
peer_identity_address_type=hci.Address.ANY.address_type,
|
|
2920
3071
|
peer_identity_address=hci.Address.ANY,
|
|
2921
3072
|
peer_irk=bytes(16),
|
|
2922
3073
|
local_irk=self.irk,
|
|
2923
|
-
)
|
|
2924
|
-
check_result=True,
|
|
3074
|
+
)
|
|
2925
3075
|
)
|
|
2926
3076
|
|
|
2927
3077
|
for irk, address in resolving_keys:
|
|
2928
|
-
await self.
|
|
3078
|
+
await self.send_sync_command(
|
|
2929
3079
|
hci.HCI_LE_Add_Device_To_Resolving_List_Command(
|
|
2930
3080
|
peer_identity_address_type=address.address_type,
|
|
2931
3081
|
peer_identity_address=address,
|
|
2932
3082
|
peer_irk=irk,
|
|
2933
3083
|
local_irk=self.irk,
|
|
2934
|
-
)
|
|
2935
|
-
check_result=True,
|
|
3084
|
+
)
|
|
2936
3085
|
)
|
|
2937
3086
|
|
|
2938
3087
|
def supports_le_features(self, feature: hci.LeFeatureMask) -> bool:
|
|
@@ -3200,11 +3349,10 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3200
3349
|
except hci.HCI_Error as error:
|
|
3201
3350
|
# Remove the advertising set so that it doesn't stay dangling in the
|
|
3202
3351
|
# controller.
|
|
3203
|
-
await self.
|
|
3352
|
+
await self.send_sync_command(
|
|
3204
3353
|
hci.HCI_LE_Remove_Advertising_Set_Command(
|
|
3205
3354
|
advertising_handle=advertising_handle
|
|
3206
|
-
)
|
|
3207
|
-
check_result=False,
|
|
3355
|
+
)
|
|
3208
3356
|
)
|
|
3209
3357
|
raise error
|
|
3210
3358
|
|
|
@@ -3287,7 +3435,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3287
3435
|
if scanning_phy_count == 0:
|
|
3288
3436
|
raise InvalidArgumentError('at least one scanning PHY must be enabled')
|
|
3289
3437
|
|
|
3290
|
-
await self.
|
|
3438
|
+
await self.send_sync_command(
|
|
3291
3439
|
hci.HCI_LE_Set_Extended_Scan_Parameters_Command(
|
|
3292
3440
|
own_address_type=own_address_type,
|
|
3293
3441
|
scanning_filter_policy=scanning_filter_policy,
|
|
@@ -3295,19 +3443,17 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3295
3443
|
scan_types=[scan_type] * scanning_phy_count,
|
|
3296
3444
|
scan_intervals=[int(scan_interval / 0.625)] * scanning_phy_count,
|
|
3297
3445
|
scan_windows=[int(scan_window / 0.625)] * scanning_phy_count,
|
|
3298
|
-
)
|
|
3299
|
-
check_result=True,
|
|
3446
|
+
)
|
|
3300
3447
|
)
|
|
3301
3448
|
|
|
3302
3449
|
# Enable scanning
|
|
3303
|
-
await self.
|
|
3450
|
+
await self.send_sync_command(
|
|
3304
3451
|
hci.HCI_LE_Set_Extended_Scan_Enable_Command(
|
|
3305
3452
|
enable=1,
|
|
3306
3453
|
filter_duplicates=1 if filter_duplicates else 0,
|
|
3307
3454
|
duration=0, # TODO allow other values
|
|
3308
3455
|
period=0, # TODO allow other values
|
|
3309
|
-
)
|
|
3310
|
-
check_result=True,
|
|
3456
|
+
)
|
|
3311
3457
|
)
|
|
3312
3458
|
else:
|
|
3313
3459
|
# Set the scanning parameters
|
|
@@ -3316,7 +3462,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3316
3462
|
if active
|
|
3317
3463
|
else hci.HCI_LE_Set_Scan_Parameters_Command.PASSIVE_SCANNING
|
|
3318
3464
|
)
|
|
3319
|
-
await self.
|
|
3465
|
+
await self.send_sync_command(
|
|
3320
3466
|
# pylint: disable=line-too-long
|
|
3321
3467
|
hci.HCI_LE_Set_Scan_Parameters_Command(
|
|
3322
3468
|
le_scan_type=scan_type,
|
|
@@ -3324,16 +3470,14 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3324
3470
|
le_scan_window=int(scan_window / 0.625),
|
|
3325
3471
|
own_address_type=own_address_type,
|
|
3326
3472
|
scanning_filter_policy=hci.HCI_LE_Set_Scan_Parameters_Command.BASIC_UNFILTERED_POLICY,
|
|
3327
|
-
)
|
|
3328
|
-
check_result=True,
|
|
3473
|
+
)
|
|
3329
3474
|
)
|
|
3330
3475
|
|
|
3331
3476
|
# Enable scanning
|
|
3332
|
-
await self.
|
|
3477
|
+
await self.send_sync_command(
|
|
3333
3478
|
hci.HCI_LE_Set_Scan_Enable_Command(
|
|
3334
3479
|
le_scan_enable=1, filter_duplicates=1 if filter_duplicates else 0
|
|
3335
|
-
)
|
|
3336
|
-
check_result=True,
|
|
3480
|
+
)
|
|
3337
3481
|
)
|
|
3338
3482
|
|
|
3339
3483
|
self.scanning_is_passive = not active
|
|
@@ -3342,18 +3486,16 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3342
3486
|
async def stop_scanning(self, legacy: bool = False) -> None:
|
|
3343
3487
|
# Disable scanning
|
|
3344
3488
|
if not legacy and self.supports_le_extended_advertising:
|
|
3345
|
-
await self.
|
|
3489
|
+
await self.send_sync_command(
|
|
3346
3490
|
hci.HCI_LE_Set_Extended_Scan_Enable_Command(
|
|
3347
3491
|
enable=0, filter_duplicates=0, duration=0, period=0
|
|
3348
|
-
)
|
|
3349
|
-
check_result=True,
|
|
3492
|
+
)
|
|
3350
3493
|
)
|
|
3351
3494
|
else:
|
|
3352
|
-
await self.
|
|
3495
|
+
await self.send_sync_command(
|
|
3353
3496
|
hci.HCI_LE_Set_Scan_Enable_Command(
|
|
3354
3497
|
le_scan_enable=0, filter_duplicates=0
|
|
3355
|
-
)
|
|
3356
|
-
check_result=True,
|
|
3498
|
+
)
|
|
3357
3499
|
)
|
|
3358
3500
|
|
|
3359
3501
|
self.scanning = False
|
|
@@ -3526,21 +3668,19 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3526
3668
|
periodic_advertising_sync.on_biginfo_advertising_report(report)
|
|
3527
3669
|
|
|
3528
3670
|
async def start_discovery(self, auto_restart: bool = True) -> None:
|
|
3529
|
-
await self.
|
|
3671
|
+
await self.send_sync_command(
|
|
3530
3672
|
hci.HCI_Write_Inquiry_Mode_Command(
|
|
3531
3673
|
inquiry_mode=hci.HCI_EXTENDED_INQUIRY_MODE
|
|
3532
|
-
)
|
|
3533
|
-
check_result=True,
|
|
3674
|
+
)
|
|
3534
3675
|
)
|
|
3535
3676
|
|
|
3536
3677
|
self.discovering = False
|
|
3537
|
-
await self.
|
|
3678
|
+
await self.send_async_command(
|
|
3538
3679
|
hci.HCI_Inquiry_Command(
|
|
3539
3680
|
lap=hci.HCI_GENERAL_INQUIRY_LAP,
|
|
3540
3681
|
inquiry_length=DEVICE_DEFAULT_INQUIRY_LENGTH,
|
|
3541
3682
|
num_responses=0, # Unlimited number of responses.
|
|
3542
|
-
)
|
|
3543
|
-
check_result=True,
|
|
3683
|
+
)
|
|
3544
3684
|
)
|
|
3545
3685
|
|
|
3546
3686
|
self.auto_restart_inquiry = auto_restart
|
|
@@ -3548,7 +3688,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3548
3688
|
|
|
3549
3689
|
async def stop_discovery(self) -> None:
|
|
3550
3690
|
if self.discovering:
|
|
3551
|
-
await self.
|
|
3691
|
+
await self.send_sync_command(hci.HCI_Inquiry_Cancel_Command())
|
|
3552
3692
|
self.auto_restart_inquiry = True
|
|
3553
3693
|
self.discovering = False
|
|
3554
3694
|
|
|
@@ -3576,9 +3716,8 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3576
3716
|
else:
|
|
3577
3717
|
scan_enable = 0x00
|
|
3578
3718
|
|
|
3579
|
-
return await self.
|
|
3580
|
-
hci.HCI_Write_Scan_Enable_Command(scan_enable=scan_enable)
|
|
3581
|
-
check_result=True,
|
|
3719
|
+
return await self.send_sync_command(
|
|
3720
|
+
hci.HCI_Write_Scan_Enable_Command(scan_enable=scan_enable)
|
|
3582
3721
|
)
|
|
3583
3722
|
|
|
3584
3723
|
async def set_discoverable(self, discoverable: bool = True) -> None:
|
|
@@ -3591,11 +3730,10 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3591
3730
|
)
|
|
3592
3731
|
|
|
3593
3732
|
# Update the controller
|
|
3594
|
-
await self.
|
|
3733
|
+
await self.send_sync_command(
|
|
3595
3734
|
hci.HCI_Write_Extended_Inquiry_Response_Command(
|
|
3596
3735
|
fec_required=0, extended_inquiry_response=self.inquiry_response
|
|
3597
|
-
)
|
|
3598
|
-
check_result=True,
|
|
3736
|
+
)
|
|
3599
3737
|
)
|
|
3600
3738
|
await self.set_scan_enable(
|
|
3601
3739
|
inquiry_scan_enabled=self.discoverable,
|
|
@@ -3610,287 +3748,168 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3610
3748
|
page_scan_enabled=self.connectable,
|
|
3611
3749
|
)
|
|
3612
3750
|
|
|
3613
|
-
async def
|
|
3751
|
+
async def connect_le(
|
|
3614
3752
|
self,
|
|
3615
3753
|
peer_address: hci.Address | str,
|
|
3616
|
-
transport: core.PhysicalTransport = PhysicalTransport.LE,
|
|
3617
3754
|
connection_parameters_preferences: (
|
|
3618
3755
|
dict[hci.Phy, ConnectionParametersPreferences] | None
|
|
3619
3756
|
) = None,
|
|
3620
3757
|
own_address_type: hci.OwnAddressType = hci.OwnAddressType.RANDOM,
|
|
3621
3758
|
timeout: float | None = DEVICE_DEFAULT_CONNECT_TIMEOUT,
|
|
3622
|
-
always_resolve: bool = False,
|
|
3623
3759
|
) -> Connection:
|
|
3624
|
-
'''
|
|
3625
|
-
Request a connection to a peer.
|
|
3626
|
-
|
|
3627
|
-
When the transport is BLE, this method cannot be called if there is already a
|
|
3628
|
-
pending connection.
|
|
3629
|
-
|
|
3630
|
-
Args:
|
|
3631
|
-
peer_address:
|
|
3632
|
-
hci.Address or name of the device to connect to.
|
|
3633
|
-
If a string is passed:
|
|
3634
|
-
If the string is an address followed by a `@` suffix, the `always_resolve`
|
|
3635
|
-
argument is implicitly set to True, so the connection is made to the
|
|
3636
|
-
address after resolution.
|
|
3637
|
-
If the string is any other address, the connection is made to that
|
|
3638
|
-
address (with or without address resolution, depending on the
|
|
3639
|
-
`always_resolve` argument).
|
|
3640
|
-
For any other string, a scan for devices using that string as their name
|
|
3641
|
-
is initiated, and a connection to the first matching device's address
|
|
3642
|
-
is made. In that case, `always_resolve` is ignored.
|
|
3643
|
-
|
|
3644
|
-
connection_parameters_preferences:
|
|
3645
|
-
(BLE only, ignored for BR/EDR)
|
|
3646
|
-
* None: use the 1M PHY with default parameters
|
|
3647
|
-
* map: each entry has a PHY as key and a ConnectionParametersPreferences
|
|
3648
|
-
object as value
|
|
3649
|
-
|
|
3650
|
-
own_address_type:
|
|
3651
|
-
(BLE only, ignored for BR/EDR)
|
|
3652
|
-
hci.OwnAddressType.RANDOM to use this device's random address, or
|
|
3653
|
-
hci.OwnAddressType.PUBLIC to use this device's public address.
|
|
3654
|
-
|
|
3655
|
-
timeout:
|
|
3656
|
-
Maximum time to wait for a connection to be established, in seconds.
|
|
3657
|
-
Pass None for an unlimited time.
|
|
3658
|
-
|
|
3659
|
-
always_resolve:
|
|
3660
|
-
(BLE only, ignored for BR/EDR)
|
|
3661
|
-
If True, always initiate a scan, resolving addresses, and connect to the
|
|
3662
|
-
address that resolves to `peer_address`.
|
|
3663
|
-
'''
|
|
3664
|
-
|
|
3665
|
-
# Check parameters
|
|
3666
|
-
if transport not in (PhysicalTransport.LE, PhysicalTransport.BR_EDR):
|
|
3667
|
-
raise InvalidArgumentError('invalid transport')
|
|
3668
|
-
transport = core.PhysicalTransport(transport)
|
|
3669
|
-
|
|
3670
|
-
# Adjust the transport automatically if we need to
|
|
3671
|
-
if transport == PhysicalTransport.LE and not self.le_enabled:
|
|
3672
|
-
transport = PhysicalTransport.BR_EDR
|
|
3673
|
-
elif transport == PhysicalTransport.BR_EDR and not self.classic_enabled:
|
|
3674
|
-
transport = PhysicalTransport.LE
|
|
3675
|
-
|
|
3676
3760
|
# Check that there isn't already a pending connection
|
|
3677
|
-
if
|
|
3761
|
+
if self.is_le_connecting:
|
|
3678
3762
|
raise InvalidStateError('connection already pending')
|
|
3679
3763
|
|
|
3764
|
+
try_resolve = not self.address_resolution_offload
|
|
3680
3765
|
if isinstance(peer_address, str):
|
|
3681
3766
|
try:
|
|
3682
|
-
|
|
3683
|
-
peer_address
|
|
3684
|
-
|
|
3685
|
-
)
|
|
3686
|
-
always_resolve = True
|
|
3687
|
-
logger.debug('forcing address resolution')
|
|
3688
|
-
else:
|
|
3689
|
-
peer_address = hci.Address.from_string_for_transport(
|
|
3690
|
-
peer_address, transport
|
|
3691
|
-
)
|
|
3767
|
+
peer_address = hci.Address.from_string_for_transport(
|
|
3768
|
+
peer_address, PhysicalTransport.LE
|
|
3769
|
+
)
|
|
3692
3770
|
except (InvalidArgumentError, ValueError):
|
|
3693
3771
|
# If the address is not parsable, assume it is a name instead
|
|
3694
|
-
always_resolve = False
|
|
3695
3772
|
logger.debug('looking for peer by name')
|
|
3696
3773
|
assert isinstance(peer_address, str)
|
|
3697
3774
|
peer_address = await self.find_peer_by_name(
|
|
3698
|
-
peer_address,
|
|
3775
|
+
peer_address, PhysicalTransport.LE
|
|
3699
3776
|
) # TODO: timeout
|
|
3700
|
-
|
|
3701
|
-
# All BR/EDR addresses should be public addresses
|
|
3702
|
-
if (
|
|
3703
|
-
transport == PhysicalTransport.BR_EDR
|
|
3704
|
-
and peer_address.address_type != hci.Address.PUBLIC_DEVICE_ADDRESS
|
|
3705
|
-
):
|
|
3706
|
-
raise InvalidArgumentError('BR/EDR addresses must be PUBLIC')
|
|
3777
|
+
try_resolve = False
|
|
3707
3778
|
|
|
3708
3779
|
assert isinstance(peer_address, hci.Address)
|
|
3709
3780
|
|
|
3710
|
-
if
|
|
3711
|
-
|
|
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...')
|
|
3712
3788
|
peer_address = await self.find_peer_by_identity_address(
|
|
3713
3789
|
peer_address
|
|
3714
3790
|
) # TODO: timeout
|
|
3715
3791
|
|
|
3716
3792
|
def on_connection(connection):
|
|
3717
|
-
|
|
3718
|
-
# match BR/EDR connection event against peer address
|
|
3719
|
-
connection.transport == transport
|
|
3720
|
-
and connection.peer_address == peer_address
|
|
3721
|
-
):
|
|
3722
|
-
pending_connection.set_result(connection)
|
|
3793
|
+
pending_connection.set_result(connection)
|
|
3723
3794
|
|
|
3724
3795
|
def on_connection_failure(error: core.ConnectionError):
|
|
3725
|
-
|
|
3726
|
-
# match BR/EDR connection failure event against peer address
|
|
3727
|
-
error.transport == transport
|
|
3728
|
-
and error.peer_address == peer_address
|
|
3729
|
-
):
|
|
3730
|
-
pending_connection.set_exception(error)
|
|
3796
|
+
pending_connection.set_exception(error)
|
|
3731
3797
|
|
|
3732
|
-
# Create a future so that we can wait for the connection
|
|
3798
|
+
# Create a future so that we can wait for the connection result
|
|
3733
3799
|
pending_connection = asyncio.get_running_loop().create_future()
|
|
3734
3800
|
self.on(self.EVENT_CONNECTION, on_connection)
|
|
3735
3801
|
self.on(self.EVENT_CONNECTION_FAILURE, on_connection_failure)
|
|
3736
3802
|
|
|
3737
3803
|
try:
|
|
3738
3804
|
# Tell the controller to connect
|
|
3739
|
-
if
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
hci.HCI_LE_1M_PHY: ConnectionParametersPreferences.default
|
|
3744
|
-
}
|
|
3805
|
+
if connection_parameters_preferences is None:
|
|
3806
|
+
connection_parameters_preferences = {
|
|
3807
|
+
hci.HCI_LE_1M_PHY: ConnectionParametersPreferences.default
|
|
3808
|
+
}
|
|
3745
3809
|
|
|
3746
|
-
|
|
3810
|
+
self.connect_own_address_type = own_address_type
|
|
3747
3811
|
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
)
|
|
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(),
|
|
3759
3822
|
)
|
|
3760
3823
|
)
|
|
3761
3824
|
)
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
phy_count = len(phys)
|
|
3766
|
-
initiating_phys = hci.phy_list_to_bits(phys)
|
|
3767
|
-
|
|
3768
|
-
connection_interval_mins = [
|
|
3769
|
-
int(
|
|
3770
|
-
connection_parameters_preferences[
|
|
3771
|
-
phy
|
|
3772
|
-
].connection_interval_min
|
|
3773
|
-
/ 1.25
|
|
3774
|
-
)
|
|
3775
|
-
for phy in phys
|
|
3776
|
-
]
|
|
3777
|
-
connection_interval_maxs = [
|
|
3778
|
-
int(
|
|
3779
|
-
connection_parameters_preferences[
|
|
3780
|
-
phy
|
|
3781
|
-
].connection_interval_max
|
|
3782
|
-
/ 1.25
|
|
3783
|
-
)
|
|
3784
|
-
for phy in phys
|
|
3785
|
-
]
|
|
3786
|
-
max_latencies = [
|
|
3787
|
-
connection_parameters_preferences[phy].max_latency
|
|
3788
|
-
for phy in phys
|
|
3789
|
-
]
|
|
3790
|
-
supervision_timeouts = [
|
|
3791
|
-
int(
|
|
3792
|
-
connection_parameters_preferences[phy].supervision_timeout
|
|
3793
|
-
/ 10
|
|
3794
|
-
)
|
|
3795
|
-
for phy in phys
|
|
3796
|
-
]
|
|
3797
|
-
min_ce_lengths = [
|
|
3798
|
-
int(
|
|
3799
|
-
connection_parameters_preferences[phy].min_ce_length / 0.625
|
|
3800
|
-
)
|
|
3801
|
-
for phy in phys
|
|
3802
|
-
]
|
|
3803
|
-
max_ce_lengths = [
|
|
3804
|
-
int(
|
|
3805
|
-
connection_parameters_preferences[phy].max_ce_length / 0.625
|
|
3806
|
-
)
|
|
3807
|
-
for phy in phys
|
|
3808
|
-
]
|
|
3825
|
+
)
|
|
3826
|
+
if not phys:
|
|
3827
|
+
raise InvalidArgumentError('at least one supported PHY needed')
|
|
3809
3828
|
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
scan_intervals=(
|
|
3818
|
-
int(DEVICE_DEFAULT_CONNECT_SCAN_INTERVAL / 0.625),
|
|
3819
|
-
)
|
|
3820
|
-
* phy_count,
|
|
3821
|
-
scan_windows=(
|
|
3822
|
-
int(DEVICE_DEFAULT_CONNECT_SCAN_WINDOW / 0.625),
|
|
3823
|
-
)
|
|
3824
|
-
* phy_count,
|
|
3825
|
-
connection_interval_mins=connection_interval_mins,
|
|
3826
|
-
connection_interval_maxs=connection_interval_maxs,
|
|
3827
|
-
max_latencies=max_latencies,
|
|
3828
|
-
supervision_timeouts=supervision_timeouts,
|
|
3829
|
-
min_ce_lengths=min_ce_lengths,
|
|
3830
|
-
max_ce_lengths=max_ce_lengths,
|
|
3831
|
-
),
|
|
3832
|
-
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
|
|
3833
3836
|
)
|
|
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
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
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,
|
|
3863
3881
|
)
|
|
3864
|
-
else:
|
|
3865
|
-
# Save pending connection
|
|
3866
|
-
self.pending_connections[peer_address] = Connection(
|
|
3867
|
-
device=self,
|
|
3868
|
-
handle=0,
|
|
3869
|
-
transport=core.PhysicalTransport.BR_EDR,
|
|
3870
|
-
self_address=self.public_address,
|
|
3871
|
-
self_resolvable_address=None,
|
|
3872
|
-
peer_address=peer_address,
|
|
3873
|
-
peer_resolvable_address=None,
|
|
3874
|
-
role=hci.Role.CENTRAL,
|
|
3875
|
-
parameters=Connection.Parameters(0, 0, 0),
|
|
3876
3882
|
)
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
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
|
+
)
|
|
3889
3909
|
)
|
|
3890
3910
|
|
|
3891
3911
|
# Wait for the connection process to complete
|
|
3892
|
-
|
|
3893
|
-
self.le_connecting = True
|
|
3912
|
+
self.le_connecting = True
|
|
3894
3913
|
|
|
3895
3914
|
if timeout is None:
|
|
3896
3915
|
return await utils.cancel_on_event(
|
|
@@ -3902,14 +3921,107 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3902
3921
|
asyncio.shield(pending_connection), timeout
|
|
3903
3922
|
)
|
|
3904
3923
|
except asyncio.TimeoutError:
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
await
|
|
3911
|
-
|
|
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
|
|
3912
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
|
+
)
|
|
3913
4025
|
|
|
3914
4026
|
try:
|
|
3915
4027
|
return await utils.cancel_on_event(
|
|
@@ -3920,11 +4032,78 @@ class Device(utils.CompositeEventEmitter):
|
|
|
3920
4032
|
finally:
|
|
3921
4033
|
self.remove_listener(self.EVENT_CONNECTION, on_connection)
|
|
3922
4034
|
self.remove_listener(self.EVENT_CONNECTION_FAILURE, on_connection_failure)
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
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')
|
|
3928
4107
|
|
|
3929
4108
|
async def accept(
|
|
3930
4109
|
self,
|
|
@@ -4036,11 +4215,10 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4036
4215
|
|
|
4037
4216
|
try:
|
|
4038
4217
|
# Accept connection request
|
|
4039
|
-
await self.
|
|
4218
|
+
await self.send_async_command(
|
|
4040
4219
|
hci.HCI_Accept_Connection_Request_Command(
|
|
4041
4220
|
bd_addr=peer_address, role=role
|
|
4042
|
-
)
|
|
4043
|
-
check_result=True,
|
|
4221
|
+
)
|
|
4044
4222
|
)
|
|
4045
4223
|
|
|
4046
4224
|
# Wait for connection complete
|
|
@@ -4076,9 +4254,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4076
4254
|
if peer_address is None:
|
|
4077
4255
|
if not self.is_le_connecting:
|
|
4078
4256
|
return
|
|
4079
|
-
await self.
|
|
4080
|
-
hci.HCI_LE_Create_Connection_Cancel_Command(), check_result=True
|
|
4081
|
-
)
|
|
4257
|
+
await self.send_sync_command(hci.HCI_LE_Create_Connection_Cancel_Command())
|
|
4082
4258
|
|
|
4083
4259
|
# BR/EDR: try to cancel to ongoing connection
|
|
4084
4260
|
# NOTE: This API does not prevent from trying to cancel a connection which is
|
|
@@ -4095,9 +4271,8 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4095
4271
|
peer_address, PhysicalTransport.BR_EDR
|
|
4096
4272
|
) # TODO: timeout
|
|
4097
4273
|
|
|
4098
|
-
await self.
|
|
4099
|
-
hci.HCI_Create_Connection_Cancel_Command(bd_addr=peer_address)
|
|
4100
|
-
check_result=True,
|
|
4274
|
+
await self.send_sync_command(
|
|
4275
|
+
hci.HCI_Create_Connection_Cancel_Command(bd_addr=peer_address)
|
|
4101
4276
|
)
|
|
4102
4277
|
|
|
4103
4278
|
async def disconnect(
|
|
@@ -4115,11 +4290,10 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4115
4290
|
self.disconnecting = True
|
|
4116
4291
|
|
|
4117
4292
|
# Request a disconnection
|
|
4118
|
-
await self.
|
|
4293
|
+
await self.send_async_command(
|
|
4119
4294
|
hci.HCI_Disconnect_Command(
|
|
4120
4295
|
connection_handle=connection.handle, reason=reason
|
|
4121
|
-
)
|
|
4122
|
-
check_result=True,
|
|
4296
|
+
)
|
|
4123
4297
|
)
|
|
4124
4298
|
return await utils.cancel_on_event(
|
|
4125
4299
|
self, Device.EVENT_FLUSH, pending_disconnection
|
|
@@ -4143,13 +4317,12 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4143
4317
|
if tx_time < 0x0148 or tx_time > 0x4290:
|
|
4144
4318
|
raise InvalidArgumentError('tx_time must be between 0x0148 and 0x4290')
|
|
4145
4319
|
|
|
4146
|
-
|
|
4320
|
+
await self.send_sync_command(
|
|
4147
4321
|
hci.HCI_LE_Set_Data_Length_Command(
|
|
4148
4322
|
connection_handle=connection.handle,
|
|
4149
4323
|
tx_octets=tx_octets,
|
|
4150
4324
|
tx_time=tx_time,
|
|
4151
|
-
)
|
|
4152
|
-
check_result=True,
|
|
4325
|
+
)
|
|
4153
4326
|
)
|
|
4154
4327
|
|
|
4155
4328
|
async def update_connection_parameters(
|
|
@@ -4166,6 +4339,9 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4166
4339
|
'''
|
|
4167
4340
|
Request an update of the connection parameters.
|
|
4168
4341
|
|
|
4342
|
+
For short connection intervals (below 7.5 ms, introduced in Bluetooth 6.2),
|
|
4343
|
+
use `update_connection_parameters_with_subrate` instead.
|
|
4344
|
+
|
|
4169
4345
|
Args:
|
|
4170
4346
|
connection: The connection to update
|
|
4171
4347
|
connection_interval_min: Minimum interval, in milliseconds.
|
|
@@ -4206,36 +4382,163 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4206
4382
|
|
|
4207
4383
|
return
|
|
4208
4384
|
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
)
|
|
4219
|
-
|
|
4220
|
-
|
|
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)
|
|
4221
4527
|
|
|
4222
4528
|
async def get_connection_rssi(self, connection):
|
|
4223
|
-
result = await self.
|
|
4224
|
-
hci.HCI_Read_RSSI_Command(handle=connection.handle)
|
|
4529
|
+
result = await self.send_sync_command(
|
|
4530
|
+
hci.HCI_Read_RSSI_Command(handle=connection.handle)
|
|
4225
4531
|
)
|
|
4226
|
-
return result.
|
|
4532
|
+
return result.rssi
|
|
4227
4533
|
|
|
4228
4534
|
async def get_connection_phy(self, connection: Connection) -> ConnectionPHY:
|
|
4229
4535
|
if not self.host.supports_command(hci.HCI_LE_READ_PHY_COMMAND):
|
|
4230
4536
|
return ConnectionPHY(hci.Phy.LE_1M, hci.Phy.LE_1M)
|
|
4231
4537
|
|
|
4232
|
-
result = await self.
|
|
4233
|
-
hci.HCI_LE_Read_PHY_Command(connection_handle=connection.handle)
|
|
4234
|
-
check_result=True,
|
|
4235
|
-
)
|
|
4236
|
-
return ConnectionPHY(
|
|
4237
|
-
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)
|
|
4238
4540
|
)
|
|
4541
|
+
return ConnectionPHY(result.tx_phy, result.rx_phy)
|
|
4239
4542
|
|
|
4240
4543
|
async def set_connection_phy(
|
|
4241
4544
|
self,
|
|
@@ -4252,60 +4555,139 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4252
4555
|
(1 if rx_phys is None else 0) << 1
|
|
4253
4556
|
)
|
|
4254
4557
|
|
|
4255
|
-
await self.
|
|
4558
|
+
await self.send_async_command(
|
|
4256
4559
|
hci.HCI_LE_Set_PHY_Command(
|
|
4257
4560
|
connection_handle=connection.handle,
|
|
4258
4561
|
all_phys=all_phys_bits,
|
|
4259
4562
|
tx_phys=hci.phy_list_to_bits(tx_phys),
|
|
4260
4563
|
rx_phys=hci.phy_list_to_bits(rx_phys),
|
|
4261
4564
|
phy_options=phy_options,
|
|
4262
|
-
)
|
|
4263
|
-
check_result=True,
|
|
4565
|
+
)
|
|
4264
4566
|
)
|
|
4265
4567
|
|
|
4266
4568
|
async def set_default_phy(
|
|
4267
4569
|
self,
|
|
4268
4570
|
tx_phys: Iterable[hci.Phy] | None = None,
|
|
4269
4571
|
rx_phys: Iterable[hci.Phy] | None = None,
|
|
4270
|
-
):
|
|
4572
|
+
) -> None:
|
|
4271
4573
|
all_phys_bits = (1 if tx_phys is None else 0) | (
|
|
4272
4574
|
(1 if rx_phys is None else 0) << 1
|
|
4273
4575
|
)
|
|
4274
4576
|
|
|
4275
|
-
|
|
4577
|
+
await self.send_sync_command(
|
|
4276
4578
|
hci.HCI_LE_Set_Default_PHY_Command(
|
|
4277
4579
|
all_phys=all_phys_bits,
|
|
4278
4580
|
tx_phys=hci.phy_list_to_bits(tx_phys),
|
|
4279
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,
|
|
4280
4614
|
),
|
|
4281
4615
|
check_result=True,
|
|
4282
4616
|
)
|
|
4283
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
|
+
|
|
4284
4666
|
async def transfer_periodic_sync(
|
|
4285
4667
|
self, connection: Connection, sync_handle: int, service_data: int = 0
|
|
4286
4668
|
) -> None:
|
|
4287
|
-
|
|
4669
|
+
await self.send_sync_command(
|
|
4288
4670
|
hci.HCI_LE_Periodic_Advertising_Sync_Transfer_Command(
|
|
4289
4671
|
connection_handle=connection.handle,
|
|
4290
4672
|
service_data=service_data,
|
|
4291
4673
|
sync_handle=sync_handle,
|
|
4292
|
-
)
|
|
4293
|
-
check_result=True,
|
|
4674
|
+
)
|
|
4294
4675
|
)
|
|
4295
4676
|
|
|
4296
4677
|
async def transfer_periodic_set_info(
|
|
4297
4678
|
self, connection: Connection, advertising_handle: int, service_data: int = 0
|
|
4298
4679
|
) -> None:
|
|
4299
|
-
|
|
4680
|
+
await self.send_sync_command(
|
|
4300
4681
|
hci.HCI_LE_Periodic_Advertising_Set_Info_Transfer_Command(
|
|
4301
4682
|
connection_handle=connection.handle,
|
|
4302
4683
|
service_data=service_data,
|
|
4303
4684
|
advertising_handle=advertising_handle,
|
|
4304
|
-
)
|
|
4305
|
-
check_result=True,
|
|
4685
|
+
)
|
|
4306
4686
|
)
|
|
4307
4687
|
|
|
4308
|
-
async def find_peer_by_name(
|
|
4688
|
+
async def find_peer_by_name(
|
|
4689
|
+
self, name: str, transport=PhysicalTransport.LE
|
|
4690
|
+
) -> hci.Address:
|
|
4309
4691
|
"""
|
|
4310
4692
|
Scan for a peer with a given name and return its address.
|
|
4311
4693
|
"""
|
|
@@ -4323,6 +4705,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4323
4705
|
listener: Callable[..., None] | None = None
|
|
4324
4706
|
was_scanning = self.scanning
|
|
4325
4707
|
was_discovering = self.discovering
|
|
4708
|
+
event_name: str | None = None
|
|
4326
4709
|
try:
|
|
4327
4710
|
if transport == PhysicalTransport.LE:
|
|
4328
4711
|
event_name = 'advertisement'
|
|
@@ -4348,11 +4731,11 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4348
4731
|
if not self.discovering:
|
|
4349
4732
|
await self.start_discovery()
|
|
4350
4733
|
else:
|
|
4351
|
-
|
|
4734
|
+
raise ValueError('invalid transport')
|
|
4352
4735
|
|
|
4353
4736
|
return await utils.cancel_on_event(self, Device.EVENT_FLUSH, peer_address)
|
|
4354
4737
|
finally:
|
|
4355
|
-
if listener is not None:
|
|
4738
|
+
if listener is not None and event_name is not None:
|
|
4356
4739
|
self.remove_listener(event_name, listener)
|
|
4357
4740
|
|
|
4358
4741
|
if transport == PhysicalTransport.LE and not was_scanning:
|
|
@@ -4367,6 +4750,8 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4367
4750
|
Scan for a peer with a resolvable address that can be resolved to a given
|
|
4368
4751
|
identity address.
|
|
4369
4752
|
"""
|
|
4753
|
+
if self.address_resolver is None:
|
|
4754
|
+
raise InvalidStateError('no resolver')
|
|
4370
4755
|
|
|
4371
4756
|
# Create a future to wait for an address to be found
|
|
4372
4757
|
peer_address = asyncio.get_running_loop().create_future()
|
|
@@ -4378,7 +4763,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4378
4763
|
peer_address.set_result(address)
|
|
4379
4764
|
return
|
|
4380
4765
|
|
|
4381
|
-
if address.is_resolvable:
|
|
4766
|
+
if address.is_resolvable and self.address_resolver is not None:
|
|
4382
4767
|
resolved_address = self.address_resolver.resolve(address)
|
|
4383
4768
|
if resolved_address == identity_address:
|
|
4384
4769
|
if not peer_address.done():
|
|
@@ -4490,11 +4875,10 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4490
4875
|
pending_authentication.set_exception(hci.HCI_Error(error_code))
|
|
4491
4876
|
|
|
4492
4877
|
# Request the authentication
|
|
4493
|
-
await self.
|
|
4878
|
+
await self.send_async_command(
|
|
4494
4879
|
hci.HCI_Authentication_Requested_Command(
|
|
4495
4880
|
connection_handle=connection.handle
|
|
4496
|
-
)
|
|
4497
|
-
check_result=True,
|
|
4881
|
+
)
|
|
4498
4882
|
)
|
|
4499
4883
|
|
|
4500
4884
|
# Wait for the authentication to complete
|
|
@@ -4542,22 +4926,20 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4542
4926
|
if connection.role != hci.Role.CENTRAL:
|
|
4543
4927
|
raise InvalidStateError('only centrals can start encryption')
|
|
4544
4928
|
|
|
4545
|
-
await self.
|
|
4929
|
+
await self.send_async_command(
|
|
4546
4930
|
hci.HCI_LE_Enable_Encryption_Command(
|
|
4547
4931
|
connection_handle=connection.handle,
|
|
4548
4932
|
random_number=rand,
|
|
4549
4933
|
encrypted_diversifier=ediv,
|
|
4550
4934
|
long_term_key=ltk,
|
|
4551
|
-
)
|
|
4552
|
-
check_result=True,
|
|
4935
|
+
)
|
|
4553
4936
|
)
|
|
4554
4937
|
else:
|
|
4555
|
-
await self.
|
|
4938
|
+
await self.send_async_command(
|
|
4556
4939
|
hci.HCI_Set_Connection_Encryption_Command(
|
|
4557
4940
|
connection_handle=connection.handle,
|
|
4558
4941
|
encryption_enable=0x01 if enable else 0x00,
|
|
4559
|
-
)
|
|
4560
|
-
check_result=True,
|
|
4942
|
+
)
|
|
4561
4943
|
)
|
|
4562
4944
|
|
|
4563
4945
|
# Wait for the result
|
|
@@ -4589,15 +4971,13 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4589
4971
|
def _(error_code: int):
|
|
4590
4972
|
pending_role_change.set_exception(hci.HCI_Error(error_code))
|
|
4591
4973
|
|
|
4592
|
-
await self.
|
|
4593
|
-
hci.HCI_Switch_Role_Command(bd_addr=connection.peer_address, role=role)
|
|
4594
|
-
check_result=True,
|
|
4974
|
+
await self.send_async_command(
|
|
4975
|
+
hci.HCI_Switch_Role_Command(bd_addr=connection.peer_address, role=role)
|
|
4595
4976
|
)
|
|
4596
4977
|
await connection.cancel_on_disconnection(pending_role_change)
|
|
4597
4978
|
|
|
4598
4979
|
# [Classic only]
|
|
4599
4980
|
async def request_remote_name(self, remote: hci.Address | Connection) -> str:
|
|
4600
|
-
# Set up event handlers
|
|
4601
4981
|
pending_name: asyncio.Future[str] = asyncio.get_running_loop().create_future()
|
|
4602
4982
|
|
|
4603
4983
|
peer_address = (
|
|
@@ -4616,14 +4996,13 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4616
4996
|
if address == peer_address:
|
|
4617
4997
|
pending_name.set_exception(hci.HCI_Error(error_code))
|
|
4618
4998
|
|
|
4619
|
-
await self.
|
|
4999
|
+
await self.send_async_command(
|
|
4620
5000
|
hci.HCI_Remote_Name_Request_Command(
|
|
4621
5001
|
bd_addr=peer_address,
|
|
4622
5002
|
page_scan_repetition_mode=hci.HCI_Remote_Name_Request_Command.R2,
|
|
4623
5003
|
reserved=0,
|
|
4624
5004
|
clock_offset=0, # TODO investigate non-0 values
|
|
4625
|
-
)
|
|
4626
|
-
check_result=True,
|
|
5005
|
+
)
|
|
4627
5006
|
)
|
|
4628
5007
|
|
|
4629
5008
|
# Wait for the result
|
|
@@ -4634,7 +5013,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4634
5013
|
async def setup_cig(
|
|
4635
5014
|
self,
|
|
4636
5015
|
parameters: CigParameters,
|
|
4637
|
-
) ->
|
|
5016
|
+
) -> Sequence[int]:
|
|
4638
5017
|
"""Sends hci.HCI_LE_Set_CIG_Parameters_Command.
|
|
4639
5018
|
|
|
4640
5019
|
Args:
|
|
@@ -4643,7 +5022,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4643
5022
|
Returns:
|
|
4644
5023
|
List of created CIS handles corresponding to the same order of [cid_id].
|
|
4645
5024
|
"""
|
|
4646
|
-
response = await self.
|
|
5025
|
+
response = await self.send_sync_command(
|
|
4647
5026
|
hci.HCI_LE_Set_CIG_Parameters_Command(
|
|
4648
5027
|
cig_id=parameters.cig_id,
|
|
4649
5028
|
sdu_interval_c_to_p=parameters.sdu_interval_c_to_p,
|
|
@@ -4664,13 +5043,12 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4664
5043
|
phy_p_to_c=[cis.phy_p_to_c for cis in parameters.cis_parameters],
|
|
4665
5044
|
rtn_c_to_p=[cis.rtn_c_to_p for cis in parameters.cis_parameters],
|
|
4666
5045
|
rtn_p_to_c=[cis.rtn_p_to_c for cis in parameters.cis_parameters],
|
|
4667
|
-
)
|
|
4668
|
-
check_result=True,
|
|
5046
|
+
)
|
|
4669
5047
|
)
|
|
4670
5048
|
|
|
4671
5049
|
# Ideally, we should manage CIG lifecycle, but they are not useful for Unicast
|
|
4672
5050
|
# Server, so here it only provides a basic functionality for testing.
|
|
4673
|
-
cis_handles = response.
|
|
5051
|
+
cis_handles = response.connection_handle[:]
|
|
4674
5052
|
for cis, cis_handle in zip(parameters.cis_parameters, cis_handles):
|
|
4675
5053
|
self._pending_cis[cis_handle] = (cis.cis_id, parameters.cig_id)
|
|
4676
5054
|
|
|
@@ -4710,12 +5088,11 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4710
5088
|
watcher.on(
|
|
4711
5089
|
self, self.EVENT_CIS_ESTABLISHMENT_FAILURE, on_cis_establishment_failure
|
|
4712
5090
|
)
|
|
4713
|
-
await self.
|
|
5091
|
+
await self.send_async_command(
|
|
4714
5092
|
hci.HCI_LE_Create_CIS_Command(
|
|
4715
5093
|
cis_connection_handle=[p[0] for p in cis_acl_pairs],
|
|
4716
5094
|
acl_connection_handle=[p[1].handle for p in cis_acl_pairs],
|
|
4717
|
-
)
|
|
4718
|
-
check_result=True,
|
|
5095
|
+
)
|
|
4719
5096
|
)
|
|
4720
5097
|
|
|
4721
5098
|
return await asyncio.gather(*pending_cis_establishments.values())
|
|
@@ -4754,11 +5131,10 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4754
5131
|
on_establishment_failure,
|
|
4755
5132
|
)
|
|
4756
5133
|
|
|
4757
|
-
await self.
|
|
5134
|
+
await self.send_async_command(
|
|
4758
5135
|
hci.HCI_LE_Accept_CIS_Request_Command(
|
|
4759
5136
|
connection_handle=cis_link.handle
|
|
4760
|
-
)
|
|
4761
|
-
check_result=True,
|
|
5137
|
+
)
|
|
4762
5138
|
)
|
|
4763
5139
|
|
|
4764
5140
|
await pending_establishment
|
|
@@ -4770,11 +5146,10 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4770
5146
|
cis_link: CisLink,
|
|
4771
5147
|
reason: int = hci.HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR,
|
|
4772
5148
|
) -> None:
|
|
4773
|
-
await self.
|
|
5149
|
+
await self.send_sync_command(
|
|
4774
5150
|
hci.HCI_LE_Reject_CIS_Request_Command(
|
|
4775
5151
|
connection_handle=cis_link.handle, reason=reason
|
|
4776
|
-
)
|
|
4777
|
-
check_result=True,
|
|
5152
|
+
)
|
|
4778
5153
|
)
|
|
4779
5154
|
|
|
4780
5155
|
# [LE only]
|
|
@@ -4803,7 +5178,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4803
5178
|
)
|
|
4804
5179
|
|
|
4805
5180
|
try:
|
|
4806
|
-
await self.
|
|
5181
|
+
await self.send_async_command(
|
|
4807
5182
|
hci.HCI_LE_Create_BIG_Command(
|
|
4808
5183
|
big_handle=big_handle,
|
|
4809
5184
|
advertising_handle=advertising_set.advertising_handle,
|
|
@@ -4817,8 +5192,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4817
5192
|
framing=parameters.framing,
|
|
4818
5193
|
encryption=1 if parameters.broadcast_code else 0,
|
|
4819
5194
|
broadcast_code=parameters.broadcast_code or bytes(16),
|
|
4820
|
-
)
|
|
4821
|
-
check_result=True,
|
|
5195
|
+
)
|
|
4822
5196
|
)
|
|
4823
5197
|
await established
|
|
4824
5198
|
except hci.HCI_Error:
|
|
@@ -4858,7 +5232,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4858
5232
|
)
|
|
4859
5233
|
|
|
4860
5234
|
try:
|
|
4861
|
-
await self.
|
|
5235
|
+
await self.send_async_command(
|
|
4862
5236
|
hci.HCI_LE_BIG_Create_Sync_Command(
|
|
4863
5237
|
big_handle=big_handle,
|
|
4864
5238
|
sync_handle=pa_sync_handle,
|
|
@@ -4867,8 +5241,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4867
5241
|
mse=parameters.mse,
|
|
4868
5242
|
big_sync_timeout=parameters.big_sync_timeout,
|
|
4869
5243
|
bis=parameters.bis,
|
|
4870
|
-
)
|
|
4871
|
-
check_result=True,
|
|
5244
|
+
)
|
|
4872
5245
|
)
|
|
4873
5246
|
await established
|
|
4874
5247
|
except hci.HCI_Error:
|
|
@@ -4901,11 +5274,10 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4901
5274
|
|
|
4902
5275
|
watcher.on(self.host, 'le_remote_features', on_le_remote_features)
|
|
4903
5276
|
watcher.on(self.host, 'le_remote_features_failure', on_failure)
|
|
4904
|
-
await self.
|
|
5277
|
+
await self.send_async_command(
|
|
4905
5278
|
hci.HCI_LE_Read_Remote_Features_Command(
|
|
4906
5279
|
connection_handle=connection.handle
|
|
4907
|
-
)
|
|
4908
|
-
check_result=True,
|
|
5280
|
+
)
|
|
4909
5281
|
)
|
|
4910
5282
|
return await read_feature_future
|
|
4911
5283
|
|
|
@@ -4926,11 +5298,10 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4926
5298
|
'channel_sounding_capabilities_failure',
|
|
4927
5299
|
lambda status: complete_future.set_exception(hci.HCI_Error(status)),
|
|
4928
5300
|
)
|
|
4929
|
-
await self.
|
|
5301
|
+
await self.send_async_command(
|
|
4930
5302
|
hci.HCI_LE_CS_Read_Remote_Supported_Capabilities_Command(
|
|
4931
5303
|
connection_handle=connection.handle
|
|
4932
|
-
)
|
|
4933
|
-
check_result=True,
|
|
5304
|
+
)
|
|
4934
5305
|
)
|
|
4935
5306
|
return await complete_future
|
|
4936
5307
|
|
|
@@ -4944,14 +5315,13 @@ class Device(utils.CompositeEventEmitter):
|
|
|
4944
5315
|
cs_sync_antenna_selection: int = 0xFF, # No Preference
|
|
4945
5316
|
max_tx_power: int = 0x04, # 4 dB
|
|
4946
5317
|
) -> None:
|
|
4947
|
-
await self.
|
|
5318
|
+
await self.send_sync_command(
|
|
4948
5319
|
hci.HCI_LE_CS_Set_Default_Settings_Command(
|
|
4949
5320
|
connection_handle=connection.handle,
|
|
4950
5321
|
role_enable=role_enable,
|
|
4951
5322
|
cs_sync_antenna_selection=cs_sync_antenna_selection,
|
|
4952
5323
|
max_tx_power=max_tx_power,
|
|
4953
|
-
)
|
|
4954
|
-
check_result=True,
|
|
5324
|
+
)
|
|
4955
5325
|
)
|
|
4956
5326
|
|
|
4957
5327
|
@utils.experimental('Only for testing.')
|
|
@@ -5000,7 +5370,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5000
5370
|
'channel_sounding_config_failure',
|
|
5001
5371
|
lambda status: complete_future.set_exception(hci.HCI_Error(status)),
|
|
5002
5372
|
)
|
|
5003
|
-
await self.
|
|
5373
|
+
await self.send_async_command(
|
|
5004
5374
|
hci.HCI_LE_CS_Create_Config_Command(
|
|
5005
5375
|
connection_handle=connection.handle,
|
|
5006
5376
|
config_id=config_id,
|
|
@@ -5020,8 +5390,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5020
5390
|
ch3c_shape=ch3c_shape,
|
|
5021
5391
|
ch3c_jump=ch3c_jump,
|
|
5022
5392
|
reserved=0x00,
|
|
5023
|
-
)
|
|
5024
|
-
check_result=True,
|
|
5393
|
+
)
|
|
5025
5394
|
)
|
|
5026
5395
|
return await complete_future
|
|
5027
5396
|
|
|
@@ -5041,11 +5410,10 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5041
5410
|
complete_future.set_exception(hci.HCI_Error(event.status))
|
|
5042
5411
|
|
|
5043
5412
|
watcher.once(self.host, 'cs_security', on_event)
|
|
5044
|
-
await self.
|
|
5413
|
+
await self.send_async_command(
|
|
5045
5414
|
hci.HCI_LE_CS_Security_Enable_Command(
|
|
5046
5415
|
connection_handle=connection.handle
|
|
5047
|
-
)
|
|
5048
|
-
check_result=True,
|
|
5416
|
+
)
|
|
5049
5417
|
)
|
|
5050
5418
|
return await complete_future
|
|
5051
5419
|
|
|
@@ -5067,7 +5435,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5067
5435
|
snr_control_initiator=hci.CsSnr.NOT_APPLIED,
|
|
5068
5436
|
snr_control_reflector=hci.CsSnr.NOT_APPLIED,
|
|
5069
5437
|
) -> None:
|
|
5070
|
-
await self.
|
|
5438
|
+
await self.send_sync_command(
|
|
5071
5439
|
hci.HCI_LE_CS_Set_Procedure_Parameters_Command(
|
|
5072
5440
|
connection_handle=connection.handle,
|
|
5073
5441
|
config_id=config.config_id,
|
|
@@ -5083,8 +5451,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5083
5451
|
preferred_peer_antenna=preferred_peer_antenna,
|
|
5084
5452
|
snr_control_initiator=snr_control_initiator,
|
|
5085
5453
|
snr_control_reflector=snr_control_reflector,
|
|
5086
|
-
)
|
|
5087
|
-
check_result=True,
|
|
5454
|
+
)
|
|
5088
5455
|
)
|
|
5089
5456
|
|
|
5090
5457
|
@utils.experimental('Only for testing.')
|
|
@@ -5106,13 +5473,12 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5106
5473
|
'channel_sounding_procedure_failure',
|
|
5107
5474
|
lambda x: complete_future.set_exception(hci.HCI_Error(x)),
|
|
5108
5475
|
)
|
|
5109
|
-
await self.
|
|
5476
|
+
await self.send_async_command(
|
|
5110
5477
|
hci.HCI_LE_CS_Procedure_Enable_Command(
|
|
5111
5478
|
connection_handle=connection.handle,
|
|
5112
5479
|
config_id=config.config_id,
|
|
5113
5480
|
enable=enabled,
|
|
5114
|
-
)
|
|
5115
|
-
check_result=True,
|
|
5481
|
+
)
|
|
5116
5482
|
)
|
|
5117
5483
|
return await complete_future
|
|
5118
5484
|
|
|
@@ -5743,12 +6109,14 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5743
6109
|
)[1 if pairing_config.bonding else 0][1 if pairing_config.mitm else 0]
|
|
5744
6110
|
|
|
5745
6111
|
# Respond
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
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
|
+
)
|
|
5752
6120
|
)
|
|
5753
6121
|
)
|
|
5754
6122
|
|
|
@@ -5832,7 +6200,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5832
6200
|
async def reply() -> None:
|
|
5833
6201
|
try:
|
|
5834
6202
|
if await connection.cancel_on_disconnection(method()):
|
|
5835
|
-
await self.host.
|
|
6203
|
+
await self.host.send_sync_command(
|
|
5836
6204
|
hci.HCI_User_Confirmation_Request_Reply_Command(
|
|
5837
6205
|
bd_addr=connection.peer_address
|
|
5838
6206
|
)
|
|
@@ -5841,7 +6209,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5841
6209
|
except Exception:
|
|
5842
6210
|
logger.exception('exception while confirming')
|
|
5843
6211
|
|
|
5844
|
-
await self.host.
|
|
6212
|
+
await self.host.send_sync_command(
|
|
5845
6213
|
hci.HCI_User_Confirmation_Request_Negative_Reply_Command(
|
|
5846
6214
|
bd_addr=connection.peer_address
|
|
5847
6215
|
)
|
|
@@ -5862,7 +6230,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5862
6230
|
pairing_config.delegate.get_number()
|
|
5863
6231
|
)
|
|
5864
6232
|
if number is not None:
|
|
5865
|
-
await self.host.
|
|
6233
|
+
await self.host.send_sync_command(
|
|
5866
6234
|
hci.HCI_User_Passkey_Request_Reply_Command(
|
|
5867
6235
|
bd_addr=connection.peer_address, numeric_value=number
|
|
5868
6236
|
)
|
|
@@ -5871,7 +6239,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5871
6239
|
except Exception:
|
|
5872
6240
|
logger.exception('exception while asking for pass-key')
|
|
5873
6241
|
|
|
5874
|
-
await self.host.
|
|
6242
|
+
await self.host.send_sync_command(
|
|
5875
6243
|
hci.HCI_User_Passkey_Request_Negative_Reply_Command(
|
|
5876
6244
|
bd_addr=connection.peer_address
|
|
5877
6245
|
)
|
|
@@ -5914,7 +6282,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5914
6282
|
pin_code_len = len(pin_code)
|
|
5915
6283
|
if not 1 <= pin_code_len <= 16:
|
|
5916
6284
|
raise core.InvalidArgumentError("pin_code should be 1-16 bytes")
|
|
5917
|
-
await self.host.
|
|
6285
|
+
await self.host.send_sync_command(
|
|
5918
6286
|
hci.HCI_PIN_Code_Request_Reply_Command(
|
|
5919
6287
|
bd_addr=connection.peer_address,
|
|
5920
6288
|
pin_code_length=pin_code_len,
|
|
@@ -5923,17 +6291,19 @@ class Device(utils.CompositeEventEmitter):
|
|
|
5923
6291
|
)
|
|
5924
6292
|
else:
|
|
5925
6293
|
logger.debug("delegate.get_string() returned None")
|
|
5926
|
-
await self.host.
|
|
6294
|
+
await self.host.send_sync_command(
|
|
5927
6295
|
hci.HCI_PIN_Code_Request_Negative_Reply_Command(
|
|
5928
6296
|
bd_addr=connection.peer_address
|
|
5929
6297
|
)
|
|
5930
6298
|
)
|
|
5931
6299
|
|
|
5932
|
-
|
|
6300
|
+
utils.AsyncRunner.spawn(get_pin_code())
|
|
5933
6301
|
else:
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
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
|
+
)
|
|
5937
6307
|
)
|
|
5938
6308
|
)
|
|
5939
6309
|
|
|
@@ -6194,7 +6564,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
6194
6564
|
):
|
|
6195
6565
|
logger.debug(
|
|
6196
6566
|
f'*** Connection Parameters Update: [0x{connection.handle:04X}] '
|
|
6197
|
-
f'{connection.peer_address} as {connection.role_name}
|
|
6567
|
+
f'{connection.peer_address} as {connection.role_name}'
|
|
6198
6568
|
)
|
|
6199
6569
|
if connection.parameters.connection_interval != connection_interval * 1.25:
|
|
6200
6570
|
connection.parameters = Connection.Parameters(
|
|
@@ -6218,7 +6588,41 @@ class Device(utils.CompositeEventEmitter):
|
|
|
6218
6588
|
self, connection: Connection, error: int
|
|
6219
6589
|
):
|
|
6220
6590
|
logger.debug(
|
|
6221
|
-
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}] '
|
|
6222
6626
|
f'{connection.peer_address} as {connection.role_name}, '
|
|
6223
6627
|
f'error={error}'
|
|
6224
6628
|
)
|
|
@@ -6261,7 +6665,25 @@ class Device(utils.CompositeEventEmitter):
|
|
|
6261
6665
|
subrate_factor,
|
|
6262
6666
|
continuation_number,
|
|
6263
6667
|
)
|
|
6264
|
-
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)
|
|
6265
6687
|
|
|
6266
6688
|
@host_event_handler
|
|
6267
6689
|
@with_connection_from_handle
|
|
@@ -6308,7 +6730,7 @@ class Device(utils.CompositeEventEmitter):
|
|
|
6308
6730
|
rtt_capability=event.rtt_capability,
|
|
6309
6731
|
rtt_aa_only_n=event.rtt_aa_only_n,
|
|
6310
6732
|
rtt_sounding_n=event.rtt_sounding_n,
|
|
6311
|
-
|
|
6733
|
+
rtt_random_sequence_n=event.rtt_random_sequence_n,
|
|
6312
6734
|
nadm_sounding_capability=event.nadm_sounding_capability,
|
|
6313
6735
|
nadm_random_capability=event.nadm_random_capability,
|
|
6314
6736
|
cs_sync_phys_supported=event.cs_sync_phys_supported,
|