bumble 0.0.223__py3-none-any.whl → 0.0.224__py3-none-any.whl

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