bumble 0.0.195__py3-none-any.whl → 0.0.198__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.
Files changed (50) hide show
  1. bumble/_version.py +2 -2
  2. bumble/apps/auracast.py +351 -66
  3. bumble/apps/console.py +5 -20
  4. bumble/apps/device_info.py +230 -0
  5. bumble/apps/gatt_dump.py +4 -0
  6. bumble/apps/lea_unicast/app.py +16 -17
  7. bumble/at.py +12 -6
  8. bumble/avc.py +8 -5
  9. bumble/avctp.py +3 -2
  10. bumble/avdtp.py +5 -1
  11. bumble/avrcp.py +2 -1
  12. bumble/codecs.py +17 -13
  13. bumble/colors.py +6 -2
  14. bumble/core.py +37 -7
  15. bumble/device.py +382 -111
  16. bumble/drivers/rtk.py +13 -8
  17. bumble/gatt.py +6 -1
  18. bumble/gatt_client.py +10 -4
  19. bumble/hci.py +50 -25
  20. bumble/hid.py +24 -28
  21. bumble/host.py +4 -0
  22. bumble/l2cap.py +24 -17
  23. bumble/link.py +8 -3
  24. bumble/profiles/ascs.py +739 -0
  25. bumble/profiles/bap.py +1 -874
  26. bumble/profiles/bass.py +440 -0
  27. bumble/profiles/csip.py +4 -4
  28. bumble/profiles/gap.py +110 -0
  29. bumble/profiles/heart_rate_service.py +4 -3
  30. bumble/profiles/le_audio.py +43 -9
  31. bumble/profiles/mcp.py +448 -0
  32. bumble/profiles/pacs.py +210 -0
  33. bumble/profiles/tmap.py +89 -0
  34. bumble/rfcomm.py +4 -2
  35. bumble/sdp.py +13 -11
  36. bumble/smp.py +20 -8
  37. bumble/snoop.py +5 -4
  38. bumble/transport/__init__.py +8 -2
  39. bumble/transport/android_emulator.py +9 -3
  40. bumble/transport/android_netsim.py +9 -7
  41. bumble/transport/common.py +46 -18
  42. bumble/transport/pyusb.py +2 -2
  43. bumble/transport/unix.py +56 -0
  44. bumble/transport/usb.py +57 -46
  45. {bumble-0.0.195.dist-info → bumble-0.0.198.dist-info}/METADATA +41 -41
  46. {bumble-0.0.195.dist-info → bumble-0.0.198.dist-info}/RECORD +50 -42
  47. {bumble-0.0.195.dist-info → bumble-0.0.198.dist-info}/WHEEL +1 -1
  48. {bumble-0.0.195.dist-info → bumble-0.0.198.dist-info}/LICENSE +0 -0
  49. {bumble-0.0.195.dist-info → bumble-0.0.198.dist-info}/entry_points.txt +0 -0
  50. {bumble-0.0.195.dist-info → bumble-0.0.198.dist-info}/top_level.txt +0 -0
bumble/drivers/rtk.py CHANGED
@@ -33,6 +33,7 @@ from typing import Tuple
33
33
  import weakref
34
34
 
35
35
 
36
+ from bumble import core
36
37
  from bumble.hci import (
37
38
  hci_vendor_command_op_code,
38
39
  STATUS_SPEC,
@@ -49,6 +50,10 @@ from bumble.drivers import common
49
50
  logger = logging.getLogger(__name__)
50
51
 
51
52
 
53
+ class RtkFirmwareError(core.BaseBumbleError):
54
+ """Error raised when RTK firmware initialization fails."""
55
+
56
+
52
57
  # -----------------------------------------------------------------------------
53
58
  # Constants
54
59
  # -----------------------------------------------------------------------------
@@ -208,15 +213,15 @@ class Firmware:
208
213
  extension_sig = bytes([0x51, 0x04, 0xFD, 0x77])
209
214
 
210
215
  if not firmware.startswith(RTK_EPATCH_SIGNATURE):
211
- raise ValueError("Firmware does not start with epatch signature")
216
+ raise RtkFirmwareError("Firmware does not start with epatch signature")
212
217
 
213
218
  if not firmware.endswith(extension_sig):
214
- raise ValueError("Firmware does not end with extension sig")
219
+ raise RtkFirmwareError("Firmware does not end with extension sig")
215
220
 
216
221
  # The firmware should start with a 14 byte header.
217
222
  epatch_header_size = 14
218
223
  if len(firmware) < epatch_header_size:
219
- raise ValueError("Firmware too short")
224
+ raise RtkFirmwareError("Firmware too short")
220
225
 
221
226
  # Look for the "project ID", starting from the end.
222
227
  offset = len(firmware) - len(extension_sig)
@@ -230,7 +235,7 @@ class Firmware:
230
235
  break
231
236
 
232
237
  if length == 0:
233
- raise ValueError("Invalid 0-length instruction")
238
+ raise RtkFirmwareError("Invalid 0-length instruction")
234
239
 
235
240
  if opcode == 0 and length == 1:
236
241
  project_id = firmware[offset - 1]
@@ -239,7 +244,7 @@ class Firmware:
239
244
  offset -= length
240
245
 
241
246
  if project_id < 0:
242
- raise ValueError("Project ID not found")
247
+ raise RtkFirmwareError("Project ID not found")
243
248
 
244
249
  self.project_id = project_id
245
250
 
@@ -252,7 +257,7 @@ class Firmware:
252
257
  # <PatchLength_1><PatchLength_2>...<PatchLength_N> (16 bits each)
253
258
  # <PatchOffset_1><PatchOffset_2>...<PatchOffset_N> (32 bits each)
254
259
  if epatch_header_size + 8 * num_patches > len(firmware):
255
- raise ValueError("Firmware too short")
260
+ raise RtkFirmwareError("Firmware too short")
256
261
  chip_id_table_offset = epatch_header_size
257
262
  patch_length_table_offset = chip_id_table_offset + 2 * num_patches
258
263
  patch_offset_table_offset = chip_id_table_offset + 4 * num_patches
@@ -266,7 +271,7 @@ class Firmware:
266
271
  "<I", firmware, patch_offset_table_offset + 4 * patch_index
267
272
  )
268
273
  if patch_offset + patch_length > len(firmware):
269
- raise ValueError("Firmware too short")
274
+ raise RtkFirmwareError("Firmware too short")
270
275
 
271
276
  # Get the SVN version for the patch
272
277
  (svn_version,) = struct.unpack_from(
@@ -645,7 +650,7 @@ class Driver(common.Driver):
645
650
  ):
646
651
  return await self.download_for_rtl8723b()
647
652
 
648
- raise ValueError("ROM not supported")
653
+ raise RtkFirmwareError("ROM not supported")
649
654
 
650
655
  async def init_controller(self):
651
656
  await self.download_firmware()
bumble/gatt.py CHANGED
@@ -39,7 +39,7 @@ from typing import (
39
39
  )
40
40
 
41
41
  from bumble.colors import color
42
- from bumble.core import UUID
42
+ from bumble.core import BaseBumbleError, UUID
43
43
  from bumble.att import Attribute, AttributeValue
44
44
 
45
45
  if TYPE_CHECKING:
@@ -320,6 +320,11 @@ def show_services(services: Iterable[Service]) -> None:
320
320
  print(color(' ' + str(descriptor), 'green'))
321
321
 
322
322
 
323
+ # -----------------------------------------------------------------------------
324
+ class InvalidServiceError(BaseBumbleError):
325
+ """The service is not compliant with the spec/profile"""
326
+
327
+
323
328
  # -----------------------------------------------------------------------------
324
329
  class Service(Attribute):
325
330
  '''
bumble/gatt_client.py CHANGED
@@ -253,7 +253,7 @@ class ProfileServiceProxy:
253
253
  SERVICE_CLASS: Type[TemplateService]
254
254
 
255
255
  @classmethod
256
- def from_client(cls, client: Client) -> ProfileServiceProxy:
256
+ def from_client(cls, client: Client) -> Optional[ProfileServiceProxy]:
257
257
  return ServiceProxy.from_client(cls, client, cls.SERVICE_CLASS.UUID)
258
258
 
259
259
 
@@ -283,6 +283,8 @@ class Client:
283
283
  self.services = []
284
284
  self.cached_values = {}
285
285
 
286
+ connection.on('disconnection', self.on_disconnection)
287
+
286
288
  def send_gatt_pdu(self, pdu: bytes) -> None:
287
289
  self.connection.send_l2cap_pdu(ATT_CID, pdu)
288
290
 
@@ -331,9 +333,9 @@ class Client:
331
333
  async def request_mtu(self, mtu: int) -> int:
332
334
  # Check the range
333
335
  if mtu < ATT_DEFAULT_MTU:
334
- raise ValueError(f'MTU must be >= {ATT_DEFAULT_MTU}')
336
+ raise core.InvalidArgumentError(f'MTU must be >= {ATT_DEFAULT_MTU}')
335
337
  if mtu > 0xFFFF:
336
- raise ValueError('MTU must be <= 0xFFFF')
338
+ raise core.InvalidArgumentError('MTU must be <= 0xFFFF')
337
339
 
338
340
  # We can only send one request per connection
339
341
  if self.mtu_exchange_done:
@@ -405,7 +407,7 @@ class Client:
405
407
  if not already_known:
406
408
  self.services.append(service)
407
409
 
408
- async def discover_services(self, uuids: Iterable[UUID] = []) -> List[ServiceProxy]:
410
+ async def discover_services(self, uuids: Iterable[UUID] = ()) -> List[ServiceProxy]:
409
411
  '''
410
412
  See Vol 3, Part G - 4.4.1 Discover All Primary Services
411
413
  '''
@@ -1072,6 +1074,10 @@ class Client:
1072
1074
  )
1073
1075
  )
1074
1076
 
1077
+ def on_disconnection(self, _) -> None:
1078
+ if self.pending_response and not self.pending_response.done():
1079
+ self.pending_response.cancel()
1080
+
1075
1081
  def on_gatt_pdu(self, att_pdu: ATT_PDU) -> None:
1076
1082
  logger.debug(
1077
1083
  f'GATT Response to client: [0x{self.connection.handle:04X}] {att_pdu}'
bumble/hci.py CHANGED
@@ -31,6 +31,8 @@ from bumble.core import (
31
31
  BT_BR_EDR_TRANSPORT,
32
32
  AdvertisingData,
33
33
  DeviceClass,
34
+ InvalidArgumentError,
35
+ InvalidPacketError,
34
36
  ProtocolError,
35
37
  bit_flags_to_strings,
36
38
  name_or_number,
@@ -92,14 +94,14 @@ def map_class_of_device(class_of_device):
92
94
  )
93
95
 
94
96
 
95
- def phy_list_to_bits(phys):
97
+ def phy_list_to_bits(phys: Optional[Iterable[int]]) -> int:
96
98
  if phys is None:
97
99
  return 0
98
100
 
99
101
  phy_bits = 0
100
102
  for phy in phys:
101
103
  if phy not in HCI_LE_PHY_TYPE_TO_BIT:
102
- raise ValueError('invalid PHY')
104
+ raise InvalidArgumentError('invalid PHY')
103
105
  phy_bits |= 1 << HCI_LE_PHY_TYPE_TO_BIT[phy]
104
106
  return phy_bits
105
107
 
@@ -1553,7 +1555,7 @@ class HCI_Object:
1553
1555
  new_offset, field_value = field_type(data, offset)
1554
1556
  return (field_value, new_offset - offset)
1555
1557
 
1556
- raise ValueError(f'unknown field type {field_type}')
1558
+ raise InvalidArgumentError(f'unknown field type {field_type}')
1557
1559
 
1558
1560
  @staticmethod
1559
1561
  def dict_from_bytes(data, offset, fields):
@@ -1622,7 +1624,7 @@ class HCI_Object:
1622
1624
  if 0 <= field_value <= 255:
1623
1625
  field_bytes = bytes([field_value])
1624
1626
  else:
1625
- raise ValueError('value too large for *-typed field')
1627
+ raise InvalidArgumentError('value too large for *-typed field')
1626
1628
  else:
1627
1629
  field_bytes = bytes(field_value)
1628
1630
  elif field_type == 'v':
@@ -1641,7 +1643,9 @@ class HCI_Object:
1641
1643
  elif len(field_bytes) > field_type:
1642
1644
  field_bytes = field_bytes[:field_type]
1643
1645
  else:
1644
- raise ValueError(f"don't know how to serialize type {type(field_value)}")
1646
+ raise InvalidArgumentError(
1647
+ f"don't know how to serialize type {type(field_value)}"
1648
+ )
1645
1649
 
1646
1650
  return field_bytes
1647
1651
 
@@ -1835,6 +1839,12 @@ class Address:
1835
1839
  data, offset, Address.PUBLIC_DEVICE_ADDRESS
1836
1840
  )
1837
1841
 
1842
+ @staticmethod
1843
+ def parse_random_address(data, offset):
1844
+ return Address.parse_address_with_type(
1845
+ data, offset, Address.RANDOM_DEVICE_ADDRESS
1846
+ )
1847
+
1838
1848
  @staticmethod
1839
1849
  def parse_address_with_type(data, offset, address_type):
1840
1850
  return offset + 6, Address(data[offset : offset + 6], address_type)
@@ -1905,7 +1915,7 @@ class Address:
1905
1915
  self.address_bytes = bytes(reversed(bytes.fromhex(address)))
1906
1916
 
1907
1917
  if len(self.address_bytes) != 6:
1908
- raise ValueError('invalid address length')
1918
+ raise InvalidArgumentError('invalid address length')
1909
1919
 
1910
1920
  self.address_type = address_type
1911
1921
 
@@ -1961,7 +1971,8 @@ class Address:
1961
1971
 
1962
1972
  def __eq__(self, other):
1963
1973
  return (
1964
- self.address_bytes == other.address_bytes
1974
+ isinstance(other, Address)
1975
+ and self.address_bytes == other.address_bytes
1965
1976
  and self.is_public == other.is_public
1966
1977
  )
1967
1978
 
@@ -2108,7 +2119,7 @@ class HCI_Command(HCI_Packet):
2108
2119
  op_code, length = struct.unpack_from('<HB', packet, 1)
2109
2120
  parameters = packet[4:]
2110
2121
  if len(parameters) != length:
2111
- raise ValueError('invalid packet length')
2122
+ raise InvalidPacketError('invalid packet length')
2112
2123
 
2113
2124
  # Look for a registered class
2114
2125
  cls = HCI_Command.command_classes.get(op_code)
@@ -4518,18 +4529,6 @@ class HCI_LE_Periodic_Advertising_Terminate_Sync_Command(HCI_Command):
4518
4529
  '''
4519
4530
 
4520
4531
 
4521
- # -----------------------------------------------------------------------------
4522
- @HCI_Command.command([('sync_handle', 2), ('enable', 1)])
4523
- class HCI_LE_Set_Periodic_Advertising_Receive_Enable_Command(HCI_Command):
4524
- '''
4525
- See Bluetooth spec @ 7.8.88 LE Set Periodic Advertising Receive Enable Command
4526
- '''
4527
-
4528
- class Enable(enum.IntFlag):
4529
- REPORTING_ENABLED = 1 << 0
4530
- DUPLICATE_FILTERING_ENABLED = 1 << 1
4531
-
4532
-
4533
4532
  # -----------------------------------------------------------------------------
4534
4533
  @HCI_Command.command(
4535
4534
  [
@@ -4565,6 +4564,32 @@ class HCI_LE_Set_Privacy_Mode_Command(HCI_Command):
4565
4564
  return name_or_number(cls.PRIVACY_MODE_NAMES, privacy_mode)
4566
4565
 
4567
4566
 
4567
+ # -----------------------------------------------------------------------------
4568
+ @HCI_Command.command([('sync_handle', 2), ('enable', 1)])
4569
+ class HCI_LE_Set_Periodic_Advertising_Receive_Enable_Command(HCI_Command):
4570
+ '''
4571
+ See Bluetooth spec @ 7.8.88 LE Set Periodic Advertising Receive Enable Command
4572
+ '''
4573
+
4574
+ class Enable(enum.IntFlag):
4575
+ REPORTING_ENABLED = 1 << 0
4576
+ DUPLICATE_FILTERING_ENABLED = 1 << 1
4577
+
4578
+
4579
+ # -----------------------------------------------------------------------------
4580
+ @HCI_Command.command(
4581
+ fields=[('connection_handle', 2), ('service_data', 2), ('sync_handle', 2)],
4582
+ return_parameters_fields=[
4583
+ ('status', STATUS_SPEC),
4584
+ ('connection_handle', 2),
4585
+ ],
4586
+ )
4587
+ class HCI_LE_Periodic_Advertising_Sync_Transfer_Command(HCI_Command):
4588
+ '''
4589
+ See Bluetooth spec @ 7.8.89 LE Periodic Advertising Sync Transfer Command
4590
+ '''
4591
+
4592
+
4568
4593
  # -----------------------------------------------------------------------------
4569
4594
  @HCI_Command.command(
4570
4595
  fields=[
@@ -4807,7 +4832,7 @@ class HCI_Event(HCI_Packet):
4807
4832
  length = packet[2]
4808
4833
  parameters = packet[3:]
4809
4834
  if len(parameters) != length:
4810
- raise ValueError('invalid packet length')
4835
+ raise InvalidPacketError('invalid packet length')
4811
4836
 
4812
4837
  cls: Any
4813
4838
  if event_code == HCI_LE_META_EVENT:
@@ -5174,8 +5199,8 @@ class HCI_LE_Data_Length_Change_Event(HCI_LE_Meta_Event):
5174
5199
  ),
5175
5200
  ('peer_address_type', Address.ADDRESS_TYPE_SPEC),
5176
5201
  ('peer_address', Address.parse_address_preceded_by_type),
5177
- ('local_resolvable_private_address', Address.parse_address),
5178
- ('peer_resolvable_private_address', Address.parse_address),
5202
+ ('local_resolvable_private_address', Address.parse_random_address),
5203
+ ('peer_resolvable_private_address', Address.parse_random_address),
5179
5204
  ('connection_interval', 2),
5180
5205
  ('peripheral_latency', 2),
5181
5206
  ('supervision_timeout', 2),
@@ -6342,7 +6367,7 @@ class HCI_AclDataPacket(HCI_Packet):
6342
6367
  bc_flag = (h >> 14) & 3
6343
6368
  data = packet[5:]
6344
6369
  if len(data) != data_total_length:
6345
- raise ValueError('invalid packet length')
6370
+ raise InvalidPacketError('invalid packet length')
6346
6371
  return HCI_AclDataPacket(
6347
6372
  connection_handle, pb_flag, bc_flag, data_total_length, data
6348
6373
  )
@@ -6390,7 +6415,7 @@ class HCI_SynchronousDataPacket(HCI_Packet):
6390
6415
  packet_status = (h >> 12) & 0b11
6391
6416
  data = packet[4:]
6392
6417
  if len(data) != data_total_length:
6393
- raise ValueError(
6418
+ raise InvalidPacketError(
6394
6419
  f'invalid packet length {len(data)} != {data_total_length}'
6395
6420
  )
6396
6421
  return HCI_SynchronousDataPacket(
bumble/hid.py CHANGED
@@ -23,13 +23,12 @@ import struct
23
23
 
24
24
  from abc import ABC, abstractmethod
25
25
  from pyee import EventEmitter
26
- from typing import Optional, Callable, TYPE_CHECKING
26
+ from typing import Optional, Callable
27
27
  from typing_extensions import override
28
28
 
29
29
  from bumble import l2cap, device
30
- from bumble.colors import color
31
30
  from bumble.core import InvalidStateError, ProtocolError
32
- from .hci import Address
31
+ from bumble.hci import Address
33
32
 
34
33
 
35
34
  # -----------------------------------------------------------------------------
@@ -220,31 +219,27 @@ class HID(ABC, EventEmitter):
220
219
  async def connect_control_channel(self) -> None:
221
220
  # Create a new L2CAP connection - control channel
222
221
  try:
223
- self.l2cap_ctrl_channel = await self.device.l2cap_channel_manager.connect(
222
+ channel = await self.device.l2cap_channel_manager.connect(
224
223
  self.connection, HID_CONTROL_PSM
225
224
  )
225
+ channel.sink = self.on_ctrl_pdu
226
+ self.l2cap_ctrl_channel = channel
226
227
  except ProtocolError:
227
228
  logging.exception(f'L2CAP connection failed.')
228
229
  raise
229
230
 
230
- assert self.l2cap_ctrl_channel is not None
231
- # Become a sink for the L2CAP channel
232
- self.l2cap_ctrl_channel.sink = self.on_ctrl_pdu
233
-
234
231
  async def connect_interrupt_channel(self) -> None:
235
232
  # Create a new L2CAP connection - interrupt channel
236
233
  try:
237
- self.l2cap_intr_channel = await self.device.l2cap_channel_manager.connect(
234
+ channel = await self.device.l2cap_channel_manager.connect(
238
235
  self.connection, HID_INTERRUPT_PSM
239
236
  )
237
+ channel.sink = self.on_intr_pdu
238
+ self.l2cap_intr_channel = channel
240
239
  except ProtocolError:
241
240
  logging.exception(f'L2CAP connection failed.')
242
241
  raise
243
242
 
244
- assert self.l2cap_intr_channel is not None
245
- # Become a sink for the L2CAP channel
246
- self.l2cap_intr_channel.sink = self.on_intr_pdu
247
-
248
243
  async def disconnect_interrupt_channel(self) -> None:
249
244
  if self.l2cap_intr_channel is None:
250
245
  raise InvalidStateError('invalid state')
@@ -334,17 +329,18 @@ class Device(HID):
334
329
  ERR_INVALID_PARAMETER = 0x04
335
330
  SUCCESS = 0xFF
336
331
 
332
+ @dataclass
337
333
  class GetSetStatus:
338
- def __init__(self) -> None:
339
- self.data = bytearray()
340
- self.status = 0
334
+ data: bytes = b''
335
+ status: int = 0
336
+
337
+ get_report_cb: Optional[Callable[[int, int, int], GetSetStatus]] = None
338
+ set_report_cb: Optional[Callable[[int, int, int, bytes], GetSetStatus]] = None
339
+ get_protocol_cb: Optional[Callable[[], GetSetStatus]] = None
340
+ set_protocol_cb: Optional[Callable[[int], GetSetStatus]] = None
341
341
 
342
342
  def __init__(self, device: device.Device) -> None:
343
343
  super().__init__(device, HID.Role.DEVICE)
344
- get_report_cb: Optional[Callable[[int, int, int], None]] = None
345
- set_report_cb: Optional[Callable[[int, int, int, bytes], None]] = None
346
- get_protocol_cb: Optional[Callable[[], None]] = None
347
- set_protocol_cb: Optional[Callable[[int], None]] = None
348
344
 
349
345
  @override
350
346
  def on_ctrl_pdu(self, pdu: bytes) -> None:
@@ -410,7 +406,6 @@ class Device(HID):
410
406
  buffer_size = 0
411
407
 
412
408
  ret = self.get_report_cb(report_id, report_type, buffer_size)
413
- assert ret is not None
414
409
  if ret.status == self.GetSetReturn.FAILURE:
415
410
  self.send_handshake_message(Message.Handshake.ERR_UNKNOWN)
416
411
  elif ret.status == self.GetSetReturn.SUCCESS:
@@ -428,7 +423,9 @@ class Device(HID):
428
423
  elif ret.status == self.GetSetReturn.ERR_UNSUPPORTED_REQUEST:
429
424
  self.send_handshake_message(Message.Handshake.ERR_UNSUPPORTED_REQUEST)
430
425
 
431
- def register_get_report_cb(self, cb: Callable[[int, int, int], None]) -> None:
426
+ def register_get_report_cb(
427
+ self, cb: Callable[[int, int, int], Device.GetSetStatus]
428
+ ) -> None:
432
429
  self.get_report_cb = cb
433
430
  logger.debug("GetReport callback registered successfully")
434
431
 
@@ -442,7 +439,6 @@ class Device(HID):
442
439
  report_data = pdu[2:]
443
440
  report_size = len(report_data) + 1
444
441
  ret = self.set_report_cb(report_id, report_type, report_size, report_data)
445
- assert ret is not None
446
442
  if ret.status == self.GetSetReturn.SUCCESS:
447
443
  self.send_handshake_message(Message.Handshake.SUCCESSFUL)
448
444
  elif ret.status == self.GetSetReturn.ERR_INVALID_PARAMETER:
@@ -453,7 +449,7 @@ class Device(HID):
453
449
  self.send_handshake_message(Message.Handshake.ERR_UNSUPPORTED_REQUEST)
454
450
 
455
451
  def register_set_report_cb(
456
- self, cb: Callable[[int, int, int, bytes], None]
452
+ self, cb: Callable[[int, int, int, bytes], Device.GetSetStatus]
457
453
  ) -> None:
458
454
  self.set_report_cb = cb
459
455
  logger.debug("SetReport callback registered successfully")
@@ -464,13 +460,12 @@ class Device(HID):
464
460
  self.send_handshake_message(Message.Handshake.ERR_UNSUPPORTED_REQUEST)
465
461
  return
466
462
  ret = self.get_protocol_cb()
467
- assert ret is not None
468
463
  if ret.status == self.GetSetReturn.SUCCESS:
469
464
  self.send_control_data(Message.ReportType.OTHER_REPORT, ret.data)
470
465
  else:
471
466
  self.send_handshake_message(Message.Handshake.ERR_UNSUPPORTED_REQUEST)
472
467
 
473
- def register_get_protocol_cb(self, cb: Callable[[], None]) -> None:
468
+ def register_get_protocol_cb(self, cb: Callable[[], Device.GetSetStatus]) -> None:
474
469
  self.get_protocol_cb = cb
475
470
  logger.debug("GetProtocol callback registered successfully")
476
471
 
@@ -480,13 +475,14 @@ class Device(HID):
480
475
  self.send_handshake_message(Message.Handshake.ERR_UNSUPPORTED_REQUEST)
481
476
  return
482
477
  ret = self.set_protocol_cb(pdu[0] & 0x01)
483
- assert ret is not None
484
478
  if ret.status == self.GetSetReturn.SUCCESS:
485
479
  self.send_handshake_message(Message.Handshake.SUCCESSFUL)
486
480
  else:
487
481
  self.send_handshake_message(Message.Handshake.ERR_UNSUPPORTED_REQUEST)
488
482
 
489
- def register_set_protocol_cb(self, cb: Callable[[int], None]) -> None:
483
+ def register_set_protocol_cb(
484
+ self, cb: Callable[[int], Device.GetSetStatus]
485
+ ) -> None:
490
486
  self.set_protocol_cb = cb
491
487
  logger.debug("SetProtocol callback registered successfully")
492
488
 
bumble/host.py CHANGED
@@ -772,6 +772,8 @@ class Host(AbortableEventEmitter):
772
772
  event.connection_handle,
773
773
  BT_LE_TRANSPORT,
774
774
  event.peer_address,
775
+ getattr(event, 'local_resolvable_private_address', None),
776
+ getattr(event, 'peer_resolvable_private_address', None),
775
777
  event.role,
776
778
  connection_parameters,
777
779
  )
@@ -817,6 +819,8 @@ class Host(AbortableEventEmitter):
817
819
  event.bd_addr,
818
820
  None,
819
821
  None,
822
+ None,
823
+ None,
820
824
  )
821
825
  else:
822
826
  logger.debug(f'### BR/EDR CONNECTION FAILED: {event.status}')
bumble/l2cap.py CHANGED
@@ -41,7 +41,14 @@ from typing import (
41
41
 
42
42
  from .utils import deprecated
43
43
  from .colors import color
44
- from .core import BT_CENTRAL_ROLE, InvalidStateError, ProtocolError
44
+ from .core import (
45
+ BT_CENTRAL_ROLE,
46
+ InvalidStateError,
47
+ InvalidArgumentError,
48
+ InvalidPacketError,
49
+ OutOfResourcesError,
50
+ ProtocolError,
51
+ )
45
52
  from .hci import (
46
53
  HCI_LE_Connection_Update_Command,
47
54
  HCI_Object,
@@ -189,17 +196,17 @@ class LeCreditBasedChannelSpec:
189
196
  self.max_credits < 1
190
197
  or self.max_credits > L2CAP_LE_CREDIT_BASED_CONNECTION_MAX_CREDITS
191
198
  ):
192
- raise ValueError('max credits out of range')
199
+ raise InvalidArgumentError('max credits out of range')
193
200
  if (
194
201
  self.mtu < L2CAP_LE_CREDIT_BASED_CONNECTION_MIN_MTU
195
202
  or self.mtu > L2CAP_LE_CREDIT_BASED_CONNECTION_MAX_MTU
196
203
  ):
197
- raise ValueError('MTU out of range')
204
+ raise InvalidArgumentError('MTU out of range')
198
205
  if (
199
206
  self.mps < L2CAP_LE_CREDIT_BASED_CONNECTION_MIN_MPS
200
207
  or self.mps > L2CAP_LE_CREDIT_BASED_CONNECTION_MAX_MPS
201
208
  ):
202
- raise ValueError('MPS out of range')
209
+ raise InvalidArgumentError('MPS out of range')
203
210
 
204
211
 
205
212
  class L2CAP_PDU:
@@ -211,7 +218,7 @@ class L2CAP_PDU:
211
218
  def from_bytes(data: bytes) -> L2CAP_PDU:
212
219
  # Check parameters
213
220
  if len(data) < 4:
214
- raise ValueError('not enough data for L2CAP header')
221
+ raise InvalidPacketError('not enough data for L2CAP header')
215
222
 
216
223
  _, l2cap_pdu_cid = struct.unpack_from('<HH', data, 0)
217
224
  l2cap_pdu_payload = data[4:]
@@ -816,7 +823,7 @@ class ClassicChannel(EventEmitter):
816
823
 
817
824
  # Check that we can start a new connection
818
825
  if self.connection_result:
819
- raise RuntimeError('connection already pending')
826
+ raise InvalidStateError('connection already pending')
820
827
 
821
828
  self._change_state(self.State.WAIT_CONNECT_RSP)
822
829
  self.send_control_frame(
@@ -1129,7 +1136,7 @@ class LeCreditBasedChannel(EventEmitter):
1129
1136
  # Check that we can start a new connection
1130
1137
  identifier = self.manager.next_identifier(self.connection)
1131
1138
  if identifier in self.manager.le_coc_requests:
1132
- raise RuntimeError('too many concurrent connection requests')
1139
+ raise InvalidStateError('too many concurrent connection requests')
1133
1140
 
1134
1141
  self._change_state(self.State.CONNECTING)
1135
1142
  request = L2CAP_LE_Credit_Based_Connection_Request(
@@ -1516,7 +1523,7 @@ class ChannelManager:
1516
1523
  if cid not in channels:
1517
1524
  return cid
1518
1525
 
1519
- raise RuntimeError('no free CID available')
1526
+ raise OutOfResourcesError('no free CID available')
1520
1527
 
1521
1528
  @staticmethod
1522
1529
  def find_free_le_cid(channels: Iterable[int]) -> int:
@@ -1529,7 +1536,7 @@ class ChannelManager:
1529
1536
  if cid not in channels:
1530
1537
  return cid
1531
1538
 
1532
- raise RuntimeError('no free CID')
1539
+ raise OutOfResourcesError('no free CID')
1533
1540
 
1534
1541
  def next_identifier(self, connection: Connection) -> int:
1535
1542
  identifier = (self.identifiers.setdefault(connection.handle, 0) + 1) % 256
@@ -1576,15 +1583,15 @@ class ChannelManager:
1576
1583
  else:
1577
1584
  # Check that the PSM isn't already in use
1578
1585
  if spec.psm in self.servers:
1579
- raise ValueError('PSM already in use')
1586
+ raise InvalidArgumentError('PSM already in use')
1580
1587
 
1581
1588
  # Check that the PSM is valid
1582
1589
  if spec.psm % 2 == 0:
1583
- raise ValueError('invalid PSM (not odd)')
1590
+ raise InvalidArgumentError('invalid PSM (not odd)')
1584
1591
  check = spec.psm >> 8
1585
1592
  while check:
1586
1593
  if check % 2 != 0:
1587
- raise ValueError('invalid PSM')
1594
+ raise InvalidArgumentError('invalid PSM')
1588
1595
  check >>= 8
1589
1596
 
1590
1597
  self.servers[spec.psm] = ClassicChannelServer(self, spec.psm, handler, spec.mtu)
@@ -1626,7 +1633,7 @@ class ChannelManager:
1626
1633
  else:
1627
1634
  # Check that the PSM isn't already in use
1628
1635
  if spec.psm in self.le_coc_servers:
1629
- raise ValueError('PSM already in use')
1636
+ raise InvalidArgumentError('PSM already in use')
1630
1637
 
1631
1638
  self.le_coc_servers[spec.psm] = LeCreditBasedChannelServer(
1632
1639
  self,
@@ -2154,10 +2161,10 @@ class ChannelManager:
2154
2161
  connection_channels = self.channels.setdefault(connection.handle, {})
2155
2162
  source_cid = self.find_free_le_cid(connection_channels)
2156
2163
  if source_cid is None: # Should never happen!
2157
- raise RuntimeError('all CIDs already in use')
2164
+ raise OutOfResourcesError('all CIDs already in use')
2158
2165
 
2159
2166
  if spec.psm is None:
2160
- raise ValueError('PSM cannot be None')
2167
+ raise InvalidArgumentError('PSM cannot be None')
2161
2168
 
2162
2169
  # Create the channel
2163
2170
  logger.debug(f'creating coc channel with cid={source_cid} for psm {spec.psm}')
@@ -2206,10 +2213,10 @@ class ChannelManager:
2206
2213
  connection_channels = self.channels.setdefault(connection.handle, {})
2207
2214
  source_cid = self.find_free_br_edr_cid(connection_channels)
2208
2215
  if source_cid is None: # Should never happen!
2209
- raise RuntimeError('all CIDs already in use')
2216
+ raise OutOfResourcesError('all CIDs already in use')
2210
2217
 
2211
2218
  if spec.psm is None:
2212
- raise ValueError('PSM cannot be None')
2219
+ raise InvalidArgumentError('PSM cannot be None')
2213
2220
 
2214
2221
  # Create the channel
2215
2222
  logger.debug(
bumble/link.py CHANGED
@@ -19,7 +19,12 @@ import logging
19
19
  import asyncio
20
20
  from functools import partial
21
21
 
22
- from bumble.core import BT_PERIPHERAL_ROLE, BT_BR_EDR_TRANSPORT, BT_LE_TRANSPORT
22
+ from bumble.core import (
23
+ BT_PERIPHERAL_ROLE,
24
+ BT_BR_EDR_TRANSPORT,
25
+ BT_LE_TRANSPORT,
26
+ InvalidStateError,
27
+ )
23
28
  from bumble.colors import color
24
29
  from bumble.hci import (
25
30
  Address,
@@ -405,12 +410,12 @@ class RemoteLink:
405
410
 
406
411
  def add_controller(self, controller):
407
412
  if self.controller:
408
- raise ValueError('controller already set')
413
+ raise InvalidStateError('controller already set')
409
414
  self.controller = controller
410
415
 
411
416
  def remove_controller(self, controller):
412
417
  if self.controller != controller:
413
- raise ValueError('controller mismatch')
418
+ raise InvalidStateError('controller mismatch')
414
419
  self.controller = None
415
420
 
416
421
  def get_pending_connection(self):