bumble 0.0.218__py3-none-any.whl → 0.0.220__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
@@ -907,7 +907,7 @@ class PeriodicAdvertisingSync(utils.EventEmitter):
907
907
  hci.HCI_LE_Periodic_Advertising_Create_Sync_Command.Options.DUPLICATE_FILTERING_INITIALLY_ENABLED
908
908
  )
909
909
 
910
- response = await self.device.send_command(
910
+ await self.device.send_command(
911
911
  hci.HCI_LE_Periodic_Advertising_Create_Sync_Command(
912
912
  options=options,
913
913
  advertising_sid=self.sid,
@@ -916,10 +916,9 @@ class PeriodicAdvertisingSync(utils.EventEmitter):
916
916
  skip=self.skip,
917
917
  sync_timeout=int(self.sync_timeout * 100),
918
918
  sync_cte_type=0,
919
- )
919
+ ),
920
+ check_result=True,
920
921
  )
921
- if response.status != hci.HCI_Command_Status_Event.PENDING:
922
- raise hci.HCI_StatusError(response)
923
922
 
924
923
  self.state = self.State.PENDING
925
924
 
@@ -1915,16 +1914,13 @@ class Connection(utils.CompositeEventEmitter):
1915
1914
  """Idles the current task waiting for a disconnect or timeout"""
1916
1915
 
1917
1916
  abort = asyncio.get_running_loop().create_future()
1918
- self.on(self.EVENT_DISCONNECTION, abort.set_result)
1919
- self.on(self.EVENT_DISCONNECTION_FAILURE, abort.set_exception)
1917
+ with closing(utils.EventWatcher()) as watcher:
1918
+ watcher.on(self, self.EVENT_DISCONNECTION, abort.set_result)
1919
+ watcher.on(self, self.EVENT_DISCONNECTION_FAILURE, abort.set_exception)
1920
1920
 
1921
- try:
1922
1921
  await asyncio.wait_for(
1923
1922
  utils.cancel_on_event(self.device, Device.EVENT_FLUSH, abort), timeout
1924
1923
  )
1925
- finally:
1926
- self.remove_listener(self.EVENT_DISCONNECTION, abort.set_result)
1927
- self.remove_listener(self.EVENT_DISCONNECTION_FAILURE, abort.set_exception)
1928
1924
 
1929
1925
  async def set_data_length(self, tx_octets: int, tx_time: int) -> None:
1930
1926
  return await self.device.set_data_length(self, tx_octets, tx_time)
@@ -2374,11 +2370,7 @@ class Device(utils.CompositeEventEmitter):
2374
2370
  hci.Address.ANY: []
2375
2371
  } # Futures, by BD address OR [Futures] for hci.Address.ANY
2376
2372
 
2377
- # In Python <= 3.9 + Rust Runtime, asyncio.Lock cannot be properly initiated.
2378
- if sys.version_info >= (3, 10):
2379
- self._cis_lock = asyncio.Lock()
2380
- else:
2381
- self._cis_lock = AsyncExitStack()
2373
+ self._cis_lock = asyncio.Lock()
2382
2374
 
2383
2375
  # Own address type cache
2384
2376
  self.connect_own_address_type = None
@@ -2887,7 +2879,9 @@ class Device(utils.CompositeEventEmitter):
2887
2879
  self.address_resolver = smp.AddressResolver(resolving_keys)
2888
2880
 
2889
2881
  if self.address_resolution_offload or self.address_generation_offload:
2890
- await self.send_command(hci.HCI_LE_Clear_Resolving_List_Command())
2882
+ await self.send_command(
2883
+ hci.HCI_LE_Clear_Resolving_List_Command(), check_result=True
2884
+ )
2891
2885
 
2892
2886
  # Add an empty entry for non-directed address generation.
2893
2887
  await self.send_command(
@@ -2896,7 +2890,8 @@ class Device(utils.CompositeEventEmitter):
2896
2890
  peer_identity_address=hci.Address.ANY,
2897
2891
  peer_irk=bytes(16),
2898
2892
  local_irk=self.irk,
2899
- )
2893
+ ),
2894
+ check_result=True,
2900
2895
  )
2901
2896
 
2902
2897
  for irk, address in resolving_keys:
@@ -2906,7 +2901,8 @@ class Device(utils.CompositeEventEmitter):
2906
2901
  peer_identity_address=address,
2907
2902
  peer_irk=irk,
2908
2903
  local_irk=self.irk,
2909
- )
2904
+ ),
2905
+ check_result=True,
2910
2906
  )
2911
2907
 
2912
2908
  def supports_le_features(self, feature: hci.LeFeatureMask) -> bool:
@@ -3501,16 +3497,15 @@ class Device(utils.CompositeEventEmitter):
3501
3497
  check_result=True,
3502
3498
  )
3503
3499
 
3504
- response = await self.send_command(
3500
+ self.discovering = False
3501
+ await self.send_command(
3505
3502
  hci.HCI_Inquiry_Command(
3506
3503
  lap=hci.HCI_GENERAL_INQUIRY_LAP,
3507
3504
  inquiry_length=DEVICE_DEFAULT_INQUIRY_LENGTH,
3508
3505
  num_responses=0, # Unlimited number of responses.
3509
- )
3506
+ ),
3507
+ check_result=True,
3510
3508
  )
3511
- if response.status != hci.HCI_Command_Status_Event.PENDING:
3512
- self.discovering = False
3513
- raise hci.HCI_StatusError(response)
3514
3509
 
3515
3510
  self.auto_restart_inquiry = auto_restart
3516
3511
  self.discovering = True
@@ -3546,7 +3541,8 @@ class Device(utils.CompositeEventEmitter):
3546
3541
  scan_enable = 0x00
3547
3542
 
3548
3543
  return await self.send_command(
3549
- hci.HCI_Write_Scan_Enable_Command(scan_enable=scan_enable)
3544
+ hci.HCI_Write_Scan_Enable_Command(scan_enable=scan_enable),
3545
+ check_result=True,
3550
3546
  )
3551
3547
 
3552
3548
  async def set_discoverable(self, discoverable: bool = True) -> None:
@@ -3775,7 +3771,7 @@ class Device(utils.CompositeEventEmitter):
3775
3771
  for phy in phys
3776
3772
  ]
3777
3773
 
3778
- result = await self.send_command(
3774
+ await self.send_command(
3779
3775
  hci.HCI_LE_Extended_Create_Connection_Command(
3780
3776
  initiator_filter_policy=0,
3781
3777
  own_address_type=own_address_type,
@@ -3796,14 +3792,15 @@ class Device(utils.CompositeEventEmitter):
3796
3792
  supervision_timeouts=supervision_timeouts,
3797
3793
  min_ce_lengths=min_ce_lengths,
3798
3794
  max_ce_lengths=max_ce_lengths,
3799
- )
3795
+ ),
3796
+ check_result=True,
3800
3797
  )
3801
3798
  else:
3802
3799
  if hci.HCI_LE_1M_PHY not in connection_parameters_preferences:
3803
3800
  raise InvalidArgumentError('1M PHY preferences required')
3804
3801
 
3805
3802
  prefs = connection_parameters_preferences[hci.HCI_LE_1M_PHY]
3806
- result = await self.send_command(
3803
+ await self.send_command(
3807
3804
  hci.HCI_LE_Create_Connection_Command(
3808
3805
  le_scan_interval=int(
3809
3806
  DEVICE_DEFAULT_CONNECT_SCAN_INTERVAL / 0.625
@@ -3825,7 +3822,8 @@ class Device(utils.CompositeEventEmitter):
3825
3822
  supervision_timeout=int(prefs.supervision_timeout / 10),
3826
3823
  min_ce_length=int(prefs.min_ce_length / 0.625),
3827
3824
  max_ce_length=int(prefs.max_ce_length / 0.625),
3828
- )
3825
+ ),
3826
+ check_result=True,
3829
3827
  )
3830
3828
  else:
3831
3829
  # Save pending connection
@@ -3842,7 +3840,7 @@ class Device(utils.CompositeEventEmitter):
3842
3840
  )
3843
3841
 
3844
3842
  # TODO: allow passing other settings
3845
- result = await self.send_command(
3843
+ await self.send_command(
3846
3844
  hci.HCI_Create_Connection_Command(
3847
3845
  bd_addr=peer_address,
3848
3846
  packet_type=0xCC18, # FIXME: change
@@ -3850,12 +3848,10 @@ class Device(utils.CompositeEventEmitter):
3850
3848
  clock_offset=0x0000,
3851
3849
  allow_role_switch=0x01,
3852
3850
  reserved=0,
3853
- )
3851
+ ),
3852
+ check_result=True,
3854
3853
  )
3855
3854
 
3856
- if result.status != hci.HCI_Command_Status_Event.PENDING:
3857
- raise hci.HCI_StatusError(result)
3858
-
3859
3855
  # Wait for the connection process to complete
3860
3856
  if transport == PhysicalTransport.LE:
3861
3857
  self.le_connecting = True
@@ -4007,7 +4003,8 @@ class Device(utils.CompositeEventEmitter):
4007
4003
  await self.send_command(
4008
4004
  hci.HCI_Accept_Connection_Request_Command(
4009
4005
  bd_addr=peer_address, role=role
4010
- )
4006
+ ),
4007
+ check_result=True,
4011
4008
  )
4012
4009
 
4013
4010
  # Wait for connection complete
@@ -4077,19 +4074,17 @@ class Device(utils.CompositeEventEmitter):
4077
4074
  connection.EVENT_DISCONNECTION_FAILURE, pending_disconnection.set_exception
4078
4075
  )
4079
4076
 
4080
- # Request a disconnection
4081
- result = await self.send_command(
4082
- hci.HCI_Disconnect_Command(
4083
- connection_handle=connection.handle, reason=reason
4084
- )
4085
- )
4086
-
4087
4077
  try:
4088
- if result.status != hci.HCI_Command_Status_Event.PENDING:
4089
- raise hci.HCI_StatusError(result)
4090
-
4091
4078
  # Wait for the disconnection process to complete
4092
4079
  self.disconnecting = True
4080
+
4081
+ # Request a disconnection
4082
+ await self.send_command(
4083
+ hci.HCI_Disconnect_Command(
4084
+ connection_handle=connection.handle, reason=reason
4085
+ ),
4086
+ check_result=True,
4087
+ )
4093
4088
  return await utils.cancel_on_event(
4094
4089
  self, Device.EVENT_FLUSH, pending_disconnection
4095
4090
  )
@@ -4175,7 +4170,7 @@ class Device(utils.CompositeEventEmitter):
4175
4170
 
4176
4171
  return
4177
4172
 
4178
- result = await self.send_command(
4173
+ await self.send_command(
4179
4174
  hci.HCI_LE_Connection_Update_Command(
4180
4175
  connection_handle=connection.handle,
4181
4176
  connection_interval_min=connection_interval_min,
@@ -4184,10 +4179,9 @@ class Device(utils.CompositeEventEmitter):
4184
4179
  supervision_timeout=supervision_timeout,
4185
4180
  min_ce_length=min_ce_length,
4186
4181
  max_ce_length=max_ce_length,
4187
- )
4182
+ ),
4183
+ check_result=True,
4188
4184
  )
4189
- if result.status != hci.HCI_Command_Status_Event.PENDING:
4190
- raise hci.HCI_StatusError(result)
4191
4185
 
4192
4186
  async def get_connection_rssi(self, connection):
4193
4187
  result = await self.send_command(
@@ -4222,23 +4216,17 @@ class Device(utils.CompositeEventEmitter):
4222
4216
  (1 if rx_phys is None else 0) << 1
4223
4217
  )
4224
4218
 
4225
- result = await self.send_command(
4219
+ await self.send_command(
4226
4220
  hci.HCI_LE_Set_PHY_Command(
4227
4221
  connection_handle=connection.handle,
4228
4222
  all_phys=all_phys_bits,
4229
4223
  tx_phys=hci.phy_list_to_bits(tx_phys),
4230
4224
  rx_phys=hci.phy_list_to_bits(rx_phys),
4231
4225
  phy_options=phy_options,
4232
- )
4226
+ ),
4227
+ check_result=True,
4233
4228
  )
4234
4229
 
4235
- if result.status != hci.HCI_COMMAND_STATUS_PENDING:
4236
- logger.warning(
4237
- 'HCI_LE_Set_PHY_Command failed: '
4238
- f'{hci.HCI_Constant.error_name(result.status)}'
4239
- )
4240
- raise hci.HCI_StatusError(result)
4241
-
4242
4230
  async def set_default_phy(
4243
4231
  self,
4244
4232
  tx_phys: Optional[Iterable[hci.Phy]] = None,
@@ -4455,43 +4443,26 @@ class Device(utils.CompositeEventEmitter):
4455
4443
  async def authenticate(self, connection: Connection) -> None:
4456
4444
  # Set up event handlers
4457
4445
  pending_authentication = asyncio.get_running_loop().create_future()
4446
+ with closing(utils.EventWatcher()) as watcher:
4458
4447
 
4459
- def on_authentication():
4460
- pending_authentication.set_result(None)
4461
-
4462
- def on_authentication_failure(error_code):
4463
- pending_authentication.set_exception(hci.HCI_Error(error_code))
4448
+ @watcher.on(connection, connection.EVENT_CONNECTION_AUTHENTICATION)
4449
+ def on_authentication() -> None:
4450
+ pending_authentication.set_result(None)
4464
4451
 
4465
- connection.on(connection.EVENT_CONNECTION_AUTHENTICATION, on_authentication)
4466
- connection.on(
4467
- connection.EVENT_CONNECTION_AUTHENTICATION_FAILURE,
4468
- on_authentication_failure,
4469
- )
4452
+ @watcher.on(connection, connection.EVENT_CONNECTION_AUTHENTICATION_FAILURE)
4453
+ def on_authentication_failure(error_code: int) -> None:
4454
+ pending_authentication.set_exception(hci.HCI_Error(error_code))
4470
4455
 
4471
- # Request the authentication
4472
- try:
4473
- result = await self.send_command(
4456
+ # Request the authentication
4457
+ await self.send_command(
4474
4458
  hci.HCI_Authentication_Requested_Command(
4475
4459
  connection_handle=connection.handle
4476
- )
4460
+ ),
4461
+ check_result=True,
4477
4462
  )
4478
- if result.status != hci.HCI_COMMAND_STATUS_PENDING:
4479
- logger.warning(
4480
- 'HCI_Authentication_Requested_Command failed: '
4481
- f'{hci.HCI_Constant.error_name(result.status)}'
4482
- )
4483
- raise hci.HCI_StatusError(result)
4484
4463
 
4485
4464
  # Wait for the authentication to complete
4486
4465
  await connection.cancel_on_disconnection(pending_authentication)
4487
- finally:
4488
- connection.remove_listener(
4489
- connection.EVENT_CONNECTION_AUTHENTICATION, on_authentication
4490
- )
4491
- connection.remove_listener(
4492
- connection.EVENT_CONNECTION_AUTHENTICATION_FAILURE,
4493
- on_authentication_failure,
4494
- )
4495
4466
 
4496
4467
  async def encrypt(self, connection: Connection, enable: bool = True):
4497
4468
  if not enable and connection.transport == PhysicalTransport.LE:
@@ -4500,21 +4471,17 @@ class Device(utils.CompositeEventEmitter):
4500
4471
  # Set up event handlers
4501
4472
  pending_encryption = asyncio.get_running_loop().create_future()
4502
4473
 
4503
- def on_encryption_change():
4504
- pending_encryption.set_result(None)
4474
+ # Request the encryption
4475
+ with closing(utils.EventWatcher()) as watcher:
4505
4476
 
4506
- def on_encryption_failure(error_code: int):
4507
- pending_encryption.set_exception(hci.HCI_Error(error_code))
4477
+ @watcher.on(connection, connection.EVENT_CONNECTION_ENCRYPTION_CHANGE)
4478
+ def _() -> None:
4479
+ pending_encryption.set_result(None)
4508
4480
 
4509
- connection.on(
4510
- connection.EVENT_CONNECTION_ENCRYPTION_CHANGE, on_encryption_change
4511
- )
4512
- connection.on(
4513
- connection.EVENT_CONNECTION_ENCRYPTION_FAILURE, on_encryption_failure
4514
- )
4481
+ @watcher.on(connection, connection.EVENT_CONNECTION_ENCRYPTION_FAILURE)
4482
+ def _(error_code: int):
4483
+ pending_encryption.set_exception(hci.HCI_Error(error_code))
4515
4484
 
4516
- # Request the encryption
4517
- try:
4518
4485
  if connection.transport == PhysicalTransport.LE:
4519
4486
  # Look for a key in the key store
4520
4487
  if self.keystore is None:
@@ -4539,45 +4506,26 @@ class Device(utils.CompositeEventEmitter):
4539
4506
  if connection.role != hci.Role.CENTRAL:
4540
4507
  raise InvalidStateError('only centrals can start encryption')
4541
4508
 
4542
- result = await self.send_command(
4509
+ await self.send_command(
4543
4510
  hci.HCI_LE_Enable_Encryption_Command(
4544
4511
  connection_handle=connection.handle,
4545
4512
  random_number=rand,
4546
4513
  encrypted_diversifier=ediv,
4547
4514
  long_term_key=ltk,
4548
- )
4515
+ ),
4516
+ check_result=True,
4549
4517
  )
4550
-
4551
- if result.status != hci.HCI_COMMAND_STATUS_PENDING:
4552
- logger.warning(
4553
- 'HCI_LE_Enable_Encryption_Command failed: '
4554
- f'{hci.HCI_Constant.error_name(result.status)}'
4555
- )
4556
- raise hci.HCI_StatusError(result)
4557
4518
  else:
4558
- result = await self.send_command(
4519
+ await self.send_command(
4559
4520
  hci.HCI_Set_Connection_Encryption_Command(
4560
4521
  connection_handle=connection.handle,
4561
4522
  encryption_enable=0x01 if enable else 0x00,
4562
- )
4523
+ ),
4524
+ check_result=True,
4563
4525
  )
4564
4526
 
4565
- if result.status != hci.HCI_COMMAND_STATUS_PENDING:
4566
- logger.warning(
4567
- 'HCI_Set_Connection_Encryption_Command failed: '
4568
- f'{hci.HCI_Constant.error_name(result.status)}'
4569
- )
4570
- raise hci.HCI_StatusError(result)
4571
-
4572
4527
  # Wait for the result
4573
4528
  await connection.cancel_on_disconnection(pending_encryption)
4574
- finally:
4575
- connection.remove_listener(
4576
- connection.EVENT_CONNECTION_ENCRYPTION_CHANGE, on_encryption_change
4577
- )
4578
- connection.remove_listener(
4579
- connection.EVENT_CONNECTION_ENCRYPTION_FAILURE, on_encryption_failure
4580
- )
4581
4529
 
4582
4530
  async def update_keys(self, address: str, keys: PairingKeys) -> None:
4583
4531
  if self.keystore is None:
@@ -4595,80 +4543,55 @@ class Device(utils.CompositeEventEmitter):
4595
4543
  async def switch_role(self, connection: Connection, role: hci.Role):
4596
4544
  pending_role_change = asyncio.get_running_loop().create_future()
4597
4545
 
4598
- def on_role_change(new_role: hci.Role):
4599
- pending_role_change.set_result(new_role)
4546
+ with closing(utils.EventWatcher()) as watcher:
4600
4547
 
4601
- def on_role_change_failure(error_code: int):
4602
- pending_role_change.set_exception(hci.HCI_Error(error_code))
4548
+ @watcher.on(connection, connection.EVENT_ROLE_CHANGE)
4549
+ def _(new_role: hci.Role):
4550
+ pending_role_change.set_result(new_role)
4603
4551
 
4604
- connection.on(connection.EVENT_ROLE_CHANGE, on_role_change)
4605
- connection.on(connection.EVENT_ROLE_CHANGE_FAILURE, on_role_change_failure)
4552
+ @watcher.on(connection, connection.EVENT_ROLE_CHANGE_FAILURE)
4553
+ def _(error_code: int):
4554
+ pending_role_change.set_exception(hci.HCI_Error(error_code))
4606
4555
 
4607
- try:
4608
- result = await self.send_command(
4609
- hci.HCI_Switch_Role_Command(bd_addr=connection.peer_address, role=role)
4556
+ await self.send_command(
4557
+ hci.HCI_Switch_Role_Command(bd_addr=connection.peer_address, role=role),
4558
+ check_result=True,
4610
4559
  )
4611
- if result.status != hci.HCI_COMMAND_STATUS_PENDING:
4612
- logger.warning(
4613
- 'HCI_Switch_Role_Command failed: '
4614
- f'{hci.HCI_Constant.error_name(result.status)}'
4615
- )
4616
- raise hci.HCI_StatusError(result)
4617
4560
  await connection.cancel_on_disconnection(pending_role_change)
4618
- finally:
4619
- connection.remove_listener(connection.EVENT_ROLE_CHANGE, on_role_change)
4620
- connection.remove_listener(
4621
- connection.EVENT_ROLE_CHANGE_FAILURE, on_role_change_failure
4622
- )
4623
4561
 
4624
4562
  # [Classic only]
4625
4563
  async def request_remote_name(self, remote: Union[hci.Address, Connection]) -> str:
4626
4564
  # Set up event handlers
4627
- pending_name = asyncio.get_running_loop().create_future()
4565
+ pending_name: asyncio.Future[str] = asyncio.get_running_loop().create_future()
4628
4566
 
4629
4567
  peer_address = (
4630
4568
  remote if isinstance(remote, hci.Address) else remote.peer_address
4631
4569
  )
4632
4570
 
4633
- handler = self.on(
4634
- self.EVENT_REMOTE_NAME,
4635
- lambda address, remote_name: (
4636
- pending_name.set_result(remote_name)
4637
- if address == peer_address
4638
- else None
4639
- ),
4640
- )
4641
- failure_handler = self.on(
4642
- self.EVENT_REMOTE_NAME_FAILURE,
4643
- lambda address, error_code: (
4644
- pending_name.set_exception(hci.HCI_Error(error_code))
4645
- if address == peer_address
4646
- else None
4647
- ),
4648
- )
4571
+ with closing(utils.EventWatcher()) as watcher:
4649
4572
 
4650
- try:
4651
- result = await self.send_command(
4573
+ @watcher.on(self, self.EVENT_REMOTE_NAME)
4574
+ def _(address: hci.Address, remote_name: str) -> None:
4575
+ if address == peer_address:
4576
+ pending_name.set_result(remote_name)
4577
+
4578
+ @watcher.on(self, self.EVENT_REMOTE_NAME_FAILURE)
4579
+ def _(address: hci.Address, error_code: int) -> None:
4580
+ if address == peer_address:
4581
+ pending_name.set_exception(hci.HCI_Error(error_code))
4582
+
4583
+ await self.send_command(
4652
4584
  hci.HCI_Remote_Name_Request_Command(
4653
4585
  bd_addr=peer_address,
4654
4586
  page_scan_repetition_mode=hci.HCI_Remote_Name_Request_Command.R2,
4655
4587
  reserved=0,
4656
4588
  clock_offset=0, # TODO investigate non-0 values
4657
- )
4589
+ ),
4590
+ check_result=True,
4658
4591
  )
4659
4592
 
4660
- if result.status != hci.HCI_COMMAND_STATUS_PENDING:
4661
- logger.warning(
4662
- 'HCI_Remote_Name_Request_Command failed: '
4663
- f'{hci.HCI_Constant.error_name(result.status)}'
4664
- )
4665
- raise hci.HCI_StatusError(result)
4666
-
4667
4593
  # Wait for the result
4668
4594
  return await utils.cancel_on_event(self, Device.EVENT_FLUSH, pending_name)
4669
- finally:
4670
- self.remove_listener(self.EVENT_REMOTE_NAME, handler)
4671
- self.remove_listener(self.EVENT_REMOTE_NAME_FAILURE, failure_handler)
4672
4595
 
4673
4596
  # [LE only]
4674
4597
  @utils.experimental('Only for testing.')
@@ -4684,8 +4607,6 @@ class Device(utils.CompositeEventEmitter):
4684
4607
  Returns:
4685
4608
  List of created CIS handles corresponding to the same order of [cid_id].
4686
4609
  """
4687
- num_cis = len(parameters.cis_parameters)
4688
-
4689
4610
  response = await self.send_command(
4690
4611
  hci.HCI_LE_Set_CIG_Parameters_Command(
4691
4612
  cig_id=parameters.cig_id,
@@ -5753,9 +5674,7 @@ class Device(utils.CompositeEventEmitter):
5753
5674
 
5754
5675
  @host_event_handler
5755
5676
  @with_connection_from_handle
5756
- def on_connection_authentication_failure(
5757
- self, connection: Connection, error: core.ConnectionError
5758
- ):
5677
+ def on_connection_authentication_failure(self, connection: Connection, error: int):
5759
5678
  logger.debug(
5760
5679
  f'*** Connection Authentication Failure: [0x{connection.handle:04X}] '
5761
5680
  f'{connection.peer_address} as {connection.role_name}, error={error}'
@@ -5810,11 +5729,15 @@ class Device(utils.CompositeEventEmitter):
5810
5729
  # [Classic only]
5811
5730
  @host_event_handler
5812
5731
  @with_connection_from_address
5813
- def on_authentication_user_confirmation_request(self, connection, code) -> None:
5732
+ def on_authentication_user_confirmation_request(
5733
+ self, connection: Connection, code: int
5734
+ ) -> None:
5814
5735
  # Ask what the pairing config should be for this connection
5815
5736
  pairing_config = self.pairing_config_factory(connection)
5816
5737
  io_capability = pairing_config.delegate.classic_io_capability
5817
5738
  peer_io_capability = connection.pairing_peer_io_capability
5739
+ if peer_io_capability is None:
5740
+ raise core.InvalidStateError("Unknown pairing_peer_io_capability")
5818
5741
 
5819
5742
  async def confirm() -> bool:
5820
5743
  # Ask the user to confirm the pairing, without display
@@ -5941,15 +5864,16 @@ class Device(utils.CompositeEventEmitter):
5941
5864
  # Respond
5942
5865
  if io_capability == hci.IoCapability.KEYBOARD_ONLY:
5943
5866
  # Ask the user to enter a string
5944
- async def get_pin_code():
5945
- pin_code = await connection.cancel_on_disconnection(
5867
+ async def get_pin_code() -> None:
5868
+ pin_code_str = await connection.cancel_on_disconnection(
5946
5869
  pairing_config.delegate.get_string(16)
5947
5870
  )
5948
5871
 
5949
- if pin_code is not None:
5950
- pin_code = bytes(pin_code, encoding='utf-8')
5872
+ if pin_code_str is not None:
5873
+ pin_code = bytes(pin_code_str, encoding='utf-8')
5951
5874
  pin_code_len = len(pin_code)
5952
- assert 0 < pin_code_len <= 16, "pin_code should be 1-16 bytes"
5875
+ if not 1 <= pin_code_len <= 16:
5876
+ raise core.InvalidArgumentError("pin_code should be 1-16 bytes")
5953
5877
  await self.host.send_command(
5954
5878
  hci.HCI_PIN_Code_Request_Reply_Command(
5955
5879
  bd_addr=connection.peer_address,
bumble/drivers/rtk.py CHANGED
@@ -115,12 +115,14 @@ RTK_USB_PRODUCTS = {
115
115
  # Realtek 8761BUV
116
116
  (0x0B05, 0x190E),
117
117
  (0x0BDA, 0x8771),
118
+ (0x0BDA, 0x877B),
119
+ (0x0BDA, 0xA728),
120
+ (0x0BDA, 0xA729),
118
121
  (0x2230, 0x0016),
119
122
  (0x2357, 0x0604),
120
123
  (0x2550, 0x8761),
121
124
  (0x2B89, 0x8761),
122
125
  (0x7392, 0xC611),
123
- (0x0BDA, 0x877B),
124
126
  # Realtek 8821AE
125
127
  (0x0B05, 0x17DC),
126
128
  (0x13D3, 0x3414),
bumble/hci.py CHANGED
@@ -3441,6 +3441,17 @@ class HCI_Write_Synchronous_Flow_Control_Enable_Command(HCI_Command):
3441
3441
  synchronous_flow_control_enable: int = field(metadata=metadata(1))
3442
3442
 
3443
3443
 
3444
+ # -----------------------------------------------------------------------------
3445
+ @HCI_Command.command
3446
+ @dataclasses.dataclass
3447
+ class HCI_Set_Controller_To_Host_Flow_Control_Command(HCI_Command):
3448
+ '''
3449
+ See Bluetooth spec @ 7.3.38 Set Controller To Host Flow Control command
3450
+ '''
3451
+
3452
+ flow_control_enable: int = field(metadata=metadata(1))
3453
+
3454
+
3444
3455
  # -----------------------------------------------------------------------------
3445
3456
  @HCI_Command.command
3446
3457
  @dataclasses.dataclass
@@ -4338,6 +4349,15 @@ class HCI_LE_Write_Suggested_Default_Data_Length_Command(HCI_Command):
4338
4349
  suggested_max_tx_time: int = field(metadata=metadata(2))
4339
4350
 
4340
4351
 
4352
+ # -----------------------------------------------------------------------------
4353
+ @HCI_Command.command
4354
+ @dataclasses.dataclass
4355
+ class HCI_LE_Read_Local_P_256_Public_Key_Command(HCI_Command):
4356
+ '''
4357
+ See Bluetooth spec @ 7.8.36 LE LE Read Local P-256 Public Key command
4358
+ '''
4359
+
4360
+
4341
4361
  # -----------------------------------------------------------------------------
4342
4362
  @HCI_Command.command
4343
4363
  @dataclasses.dataclass
@@ -4365,6 +4385,15 @@ class HCI_LE_Clear_Resolving_List_Command(HCI_Command):
4365
4385
  '''
4366
4386
 
4367
4387
 
4388
+ # -----------------------------------------------------------------------------
4389
+ @HCI_Command.command
4390
+ @dataclasses.dataclass
4391
+ class HCI_LE_Read_Resolving_List_Size_Command(HCI_Command):
4392
+ '''
4393
+ See Bluetooth spec @ 7.8.41 LE Read Resolving List Size command
4394
+ '''
4395
+
4396
+
4368
4397
  # -----------------------------------------------------------------------------
4369
4398
  @HCI_Command.command
4370
4399
  @dataclasses.dataclass
@@ -5028,6 +5057,15 @@ class HCI_LE_Periodic_Advertising_Terminate_Sync_Command(HCI_Command):
5028
5057
  sync_handle: int = field(metadata=metadata(2))
5029
5058
 
5030
5059
 
5060
+ # -----------------------------------------------------------------------------
5061
+ @HCI_Command.command
5062
+ @dataclasses.dataclass
5063
+ class HCI_LE_Read_Transmit_Power_Command(HCI_Command):
5064
+ '''
5065
+ See Bluetooth spec @ 7.8.74 LE Read Transmit Power command
5066
+ '''
5067
+
5068
+
5031
5069
  # -----------------------------------------------------------------------------
5032
5070
  @HCI_Command.command
5033
5071
  @dataclasses.dataclass
bumble/hid.py CHANGED
@@ -246,7 +246,7 @@ class HID(ABC, utils.EventEmitter):
246
246
  # Create a new L2CAP connection - interrupt channel
247
247
  try:
248
248
  channel = await self.connection.create_l2cap_channel(
249
- l2cap.ClassicChannelSpec(HID_CONTROL_PSM)
249
+ l2cap.ClassicChannelSpec(HID_INTERRUPT_PSM)
250
250
  )
251
251
  channel.sink = self.on_intr_pdu
252
252
  self.l2cap_intr_channel = channel