bumble 0.0.202__py3-none-any.whl → 0.0.204__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/gatt.py CHANGED
@@ -28,12 +28,15 @@ import functools
28
28
  import logging
29
29
  import struct
30
30
  from typing import (
31
+ Any,
31
32
  Callable,
32
33
  Dict,
33
34
  Iterable,
34
35
  List,
35
36
  Optional,
36
37
  Sequence,
38
+ SupportsBytes,
39
+ Type,
37
40
  Union,
38
41
  TYPE_CHECKING,
39
42
  )
@@ -41,6 +44,7 @@ from typing import (
41
44
  from bumble.colors import color
42
45
  from bumble.core import BaseBumbleError, UUID
43
46
  from bumble.att import Attribute, AttributeValue
47
+ from bumble.utils import ByteSerializable
44
48
 
45
49
  if TYPE_CHECKING:
46
50
  from bumble.gatt_client import AttributeProxy
@@ -343,7 +347,7 @@ class Service(Attribute):
343
347
  def __init__(
344
348
  self,
345
349
  uuid: Union[str, UUID],
346
- characteristics: List[Characteristic],
350
+ characteristics: Iterable[Characteristic],
347
351
  primary=True,
348
352
  included_services: Iterable[Service] = (),
349
353
  ) -> None:
@@ -362,7 +366,7 @@ class Service(Attribute):
362
366
  )
363
367
  self.uuid = uuid
364
368
  self.included_services = list(included_services)
365
- self.characteristics = characteristics[:]
369
+ self.characteristics = list(characteristics)
366
370
  self.primary = primary
367
371
 
368
372
  def get_advertising_data(self) -> Optional[bytes]:
@@ -393,7 +397,7 @@ class TemplateService(Service):
393
397
 
394
398
  def __init__(
395
399
  self,
396
- characteristics: List[Characteristic],
400
+ characteristics: Iterable[Characteristic],
397
401
  primary: bool = True,
398
402
  included_services: Iterable[Service] = (),
399
403
  ) -> None:
@@ -410,7 +414,7 @@ class IncludedServiceDeclaration(Attribute):
410
414
 
411
415
  def __init__(self, service: Service) -> None:
412
416
  declaration_bytes = struct.pack(
413
- '<HH2s', service.handle, service.end_group_handle, service.uuid.to_bytes()
417
+ '<HH2s', service.handle, service.end_group_handle, bytes(service.uuid)
414
418
  )
415
419
  super().__init__(
416
420
  GATT_INCLUDE_ATTRIBUTE_TYPE, Attribute.READABLE, declaration_bytes
@@ -490,7 +494,7 @@ class Characteristic(Attribute):
490
494
  uuid: Union[str, bytes, UUID],
491
495
  properties: Characteristic.Properties,
492
496
  permissions: Union[str, Attribute.Permissions],
493
- value: Union[str, bytes, CharacteristicValue] = b'',
497
+ value: Any = b'',
494
498
  descriptors: Sequence[Descriptor] = (),
495
499
  ):
496
500
  super().__init__(uuid, permissions, value)
@@ -525,7 +529,11 @@ class CharacteristicDeclaration(Attribute):
525
529
 
526
530
  characteristic: Characteristic
527
531
 
528
- def __init__(self, characteristic: Characteristic, value_handle: int) -> None:
532
+ def __init__(
533
+ self,
534
+ characteristic: Characteristic,
535
+ value_handle: int,
536
+ ) -> None:
529
537
  declaration_bytes = (
530
538
  struct.pack('<BH', characteristic.properties, value_handle)
531
539
  + characteristic.uuid.to_pdu_bytes()
@@ -705,7 +713,7 @@ class MappedCharacteristicAdapter(PackedCharacteristicAdapter):
705
713
  '''
706
714
  Adapter that packs/unpacks characteristic values according to a standard
707
715
  Python `struct` format.
708
- The adapted `read_value` and `write_value` methods return/accept aa dictionary which
716
+ The adapted `read_value` and `write_value` methods return/accept a dictionary which
709
717
  is packed/unpacked according to format, with the arguments extracted from the
710
718
  dictionary by key, in the same order as they occur in the `keys` parameter.
711
719
  '''
@@ -735,6 +743,24 @@ class UTF8CharacteristicAdapter(CharacteristicAdapter):
735
743
  return value.decode('utf-8')
736
744
 
737
745
 
746
+ # -----------------------------------------------------------------------------
747
+ class SerializableCharacteristicAdapter(CharacteristicAdapter):
748
+ '''
749
+ Adapter that converts any class to/from bytes using the class'
750
+ `to_bytes` and `__bytes__` methods, respectively.
751
+ '''
752
+
753
+ def __init__(self, characteristic, cls: Type[ByteSerializable]):
754
+ super().__init__(characteristic)
755
+ self.cls = cls
756
+
757
+ def encode_value(self, value: SupportsBytes) -> bytes:
758
+ return bytes(value)
759
+
760
+ def decode_value(self, value: bytes) -> Any:
761
+ return self.cls.from_bytes(value)
762
+
763
+
738
764
  # -----------------------------------------------------------------------------
739
765
  class Descriptor(Attribute):
740
766
  '''
bumble/gatt_client.py CHANGED
@@ -292,7 +292,7 @@ class Client:
292
292
  logger.debug(
293
293
  f'GATT Command from client: [0x{self.connection.handle:04X}] {command}'
294
294
  )
295
- self.send_gatt_pdu(command.to_bytes())
295
+ self.send_gatt_pdu(bytes(command))
296
296
 
297
297
  async def send_request(self, request: ATT_PDU):
298
298
  logger.debug(
@@ -310,7 +310,7 @@ class Client:
310
310
  self.pending_request = request
311
311
 
312
312
  try:
313
- self.send_gatt_pdu(request.to_bytes())
313
+ self.send_gatt_pdu(bytes(request))
314
314
  response = await asyncio.wait_for(
315
315
  self.pending_response, GATT_REQUEST_TIMEOUT
316
316
  )
@@ -328,7 +328,7 @@ class Client:
328
328
  f'GATT Confirmation from client: [0x{self.connection.handle:04X}] '
329
329
  f'{confirmation}'
330
330
  )
331
- self.send_gatt_pdu(confirmation.to_bytes())
331
+ self.send_gatt_pdu(bytes(confirmation))
332
332
 
333
333
  async def request_mtu(self, mtu: int) -> int:
334
334
  # Check the range
bumble/gatt_server.py CHANGED
@@ -28,7 +28,17 @@ import asyncio
28
28
  import logging
29
29
  from collections import defaultdict
30
30
  import struct
31
- from typing import List, Tuple, Optional, TypeVar, Type, Dict, Iterable, TYPE_CHECKING
31
+ from typing import (
32
+ Dict,
33
+ Iterable,
34
+ List,
35
+ Optional,
36
+ Tuple,
37
+ TypeVar,
38
+ Type,
39
+ Union,
40
+ TYPE_CHECKING,
41
+ )
32
42
  from pyee import EventEmitter
33
43
 
34
44
  from bumble.colors import color
@@ -68,6 +78,7 @@ from bumble.gatt import (
68
78
  GATT_REQUEST_TIMEOUT,
69
79
  GATT_SECONDARY_SERVICE_ATTRIBUTE_TYPE,
70
80
  Characteristic,
81
+ CharacteristicAdapter,
71
82
  CharacteristicDeclaration,
72
83
  CharacteristicValue,
73
84
  IncludedServiceDeclaration,
@@ -353,7 +364,7 @@ class Server(EventEmitter):
353
364
  logger.debug(
354
365
  f'GATT Response from server: [0x{connection.handle:04X}] {response}'
355
366
  )
356
- self.send_gatt_pdu(connection.handle, response.to_bytes())
367
+ self.send_gatt_pdu(connection.handle, bytes(response))
357
368
 
358
369
  async def notify_subscriber(
359
370
  self,
@@ -450,7 +461,7 @@ class Server(EventEmitter):
450
461
  )
451
462
 
452
463
  try:
453
- self.send_gatt_pdu(connection.handle, indication.to_bytes())
464
+ self.send_gatt_pdu(connection.handle, bytes(indication))
454
465
  await asyncio.wait_for(pending_confirmation, GATT_REQUEST_TIMEOUT)
455
466
  except asyncio.TimeoutError as error:
456
467
  logger.warning(color('!!! GATT Indicate timeout', 'red'))
bumble/hci.py CHANGED
@@ -915,6 +915,8 @@ HCI_SUPPORTED_COMMANDS_MASKS = {
915
915
  HCI_READ_CURRENT_IAC_LAP_COMMAND : 1 << (11*8+3),
916
916
  HCI_WRITE_CURRENT_IAC_LAP_COMMAND : 1 << (11*8+4),
917
917
  HCI_SET_AFH_HOST_CHANNEL_CLASSIFICATION_COMMAND : 1 << (12*8+1),
918
+ HCI_LE_CS_READ_REMOTE_FAE_TABLE_COMMAND : 1 << (12*8+2),
919
+ HCI_LE_CS_WRITE_CACHED_REMOTE_FAE_TABLE_COMMAND : 1 << (12*8+3),
918
920
  HCI_READ_INQUIRY_SCAN_TYPE_COMMAND : 1 << (12*8+4),
919
921
  HCI_WRITE_INQUIRY_SCAN_TYPE_COMMAND : 1 << (12*8+5),
920
922
  HCI_READ_INQUIRY_MODE_COMMAND : 1 << (12*8+6),
@@ -940,6 +942,8 @@ HCI_SUPPORTED_COMMANDS_MASKS = {
940
942
  HCI_SETUP_SYNCHRONOUS_CONNECTION_COMMAND : 1 << (16*8+3),
941
943
  HCI_ACCEPT_SYNCHRONOUS_CONNECTION_REQUEST_COMMAND : 1 << (16*8+4),
942
944
  HCI_REJECT_SYNCHRONOUS_CONNECTION_REQUEST_COMMAND : 1 << (16*8+5),
945
+ HCI_LE_CS_CREATE_CONFIG_COMMAND : 1 << (16*8+6),
946
+ HCI_LE_CS_REMOVE_CONFIG_COMMAND : 1 << (16*8+7),
943
947
  HCI_READ_EXTENDED_INQUIRY_RESPONSE_COMMAND : 1 << (17*8+0),
944
948
  HCI_WRITE_EXTENDED_INQUIRY_RESPONSE_COMMAND : 1 << (17*8+1),
945
949
  HCI_REFRESH_ENCRYPTION_KEY_COMMAND : 1 << (17*8+2),
@@ -963,13 +967,20 @@ HCI_SUPPORTED_COMMANDS_MASKS = {
963
967
  HCI_SEND_KEYPRESS_NOTIFICATION_COMMAND : 1 << (20*8+2),
964
968
  HCI_IO_CAPABILITY_REQUEST_NEGATIVE_REPLY_COMMAND : 1 << (20*8+3),
965
969
  HCI_READ_ENCRYPTION_KEY_SIZE_COMMAND : 1 << (20*8+4),
970
+ HCI_LE_CS_READ_LOCAL_SUPPORTED_CAPABILITIES_COMMAND : 1 << (20*8+5),
971
+ HCI_LE_CS_READ_REMOTE_SUPPORTED_CAPABILITIES_COMMAND : 1 << (20*8+6),
972
+ HCI_LE_CS_WRITE_CACHED_REMOTE_SUPPORTED_CAPABILITIES : 1 << (20*8+7),
966
973
  HCI_SET_EVENT_MASK_PAGE_2_COMMAND : 1 << (22*8+2),
967
974
  HCI_READ_FLOW_CONTROL_MODE_COMMAND : 1 << (23*8+0),
968
975
  HCI_WRITE_FLOW_CONTROL_MODE_COMMAND : 1 << (23*8+1),
969
976
  HCI_READ_DATA_BLOCK_SIZE_COMMAND : 1 << (23*8+2),
977
+ HCI_LE_CS_TEST_COMMAND : 1 << (23*8+3),
978
+ HCI_LE_CS_TEST_END_COMMAND : 1 << (23*8+4),
970
979
  HCI_READ_ENHANCED_TRANSMIT_POWER_LEVEL_COMMAND : 1 << (24*8+0),
980
+ HCI_LE_CS_SECURITY_ENABLE_COMMAND : 1 << (24*8+1),
971
981
  HCI_READ_LE_HOST_SUPPORT_COMMAND : 1 << (24*8+5),
972
982
  HCI_WRITE_LE_HOST_SUPPORT_COMMAND : 1 << (24*8+6),
983
+ HCI_LE_CS_SET_DEFAULT_SETTINGS_COMMAND : 1 << (24*8+7),
973
984
  HCI_LE_SET_EVENT_MASK_COMMAND : 1 << (25*8+0),
974
985
  HCI_LE_READ_BUFFER_SIZE_COMMAND : 1 << (25*8+1),
975
986
  HCI_LE_READ_LOCAL_SUPPORTED_FEATURES_COMMAND : 1 << (25*8+2),
@@ -1000,6 +1011,10 @@ HCI_SUPPORTED_COMMANDS_MASKS = {
1000
1011
  HCI_LE_RECEIVER_TEST_COMMAND : 1 << (28*8+4),
1001
1012
  HCI_LE_TRANSMITTER_TEST_COMMAND : 1 << (28*8+5),
1002
1013
  HCI_LE_TEST_END_COMMAND : 1 << (28*8+6),
1014
+ HCI_LE_ENABLE_MONITORING_ADVERTISERS_COMMAND : 1 << (28*8+7),
1015
+ HCI_LE_CS_SET_CHANNEL_CLASSIFICATION_COMMAND : 1 << (29*8+0),
1016
+ HCI_LE_CS_SET_PROCEDURE_PARAMETERS_COMMAND : 1 << (29*8+1),
1017
+ HCI_LE_CS_PROCEDURE_ENABLE_COMMAND : 1 << (29*8+2),
1003
1018
  HCI_ENHANCED_SETUP_SYNCHRONOUS_CONNECTION_COMMAND : 1 << (29*8+3),
1004
1019
  HCI_ENHANCED_ACCEPT_SYNCHRONOUS_CONNECTION_REQUEST_COMMAND : 1 << (29*8+4),
1005
1020
  HCI_READ_LOCAL_SUPPORTED_CODECS_COMMAND : 1 << (29*8+5),
@@ -1136,11 +1151,21 @@ HCI_SUPPORTED_COMMANDS_MASKS = {
1136
1151
  HCI_LE_SET_DEFAULT_SUBRATE_COMMAND : 1 << (46*8+0),
1137
1152
  HCI_LE_SUBRATE_REQUEST_COMMAND : 1 << (46*8+1),
1138
1153
  HCI_LE_SET_EXTENDED_ADVERTISING_PARAMETERS_V2_COMMAND : 1 << (46*8+2),
1154
+ HCI_LE_SET_DECISION_DATA_COMMAND : 1 << (46*8+3),
1155
+ HCI_LE_SET_DECISION_INSTRUCTIONS_COMMAND : 1 << (46*8+4),
1139
1156
  HCI_LE_SET_PERIODIC_ADVERTISING_SUBEVENT_DATA_COMMAND : 1 << (46*8+5),
1140
1157
  HCI_LE_SET_PERIODIC_ADVERTISING_RESPONSE_DATA_COMMAND : 1 << (46*8+6),
1141
1158
  HCI_LE_SET_PERIODIC_SYNC_SUBEVENT_COMMAND : 1 << (46*8+7),
1142
1159
  HCI_LE_EXTENDED_CREATE_CONNECTION_V2_COMMAND : 1 << (47*8+0),
1143
1160
  HCI_LE_SET_PERIODIC_ADVERTISING_PARAMETERS_V2_COMMAND : 1 << (47*8+1),
1161
+ HCI_LE_READ_ALL_LOCAL_SUPPORTED_FEATURES_COMMAND : 1 << (47*8+2),
1162
+ HCI_LE_READ_ALL_REMOTE_FEATURES_COMMAND : 1 << (47*8+3),
1163
+ HCI_LE_SET_HOST_FEATURE_V2_COMMAND : 1 << (47*8+4),
1164
+ HCI_LE_ADD_DEVICE_TO_MONITORED_ADVERTISERS_LIST_COMMAND : 1 << (47*8+5),
1165
+ HCI_LE_REMOVE_DEVICE_FROM_MONITORED_ADVERTISERS_LIST_COMMAND : 1 << (47*8+6),
1166
+ HCI_LE_CLEAR_MONITORED_ADVERTISERS_LIST_COMMAND : 1 << (47*8+7),
1167
+ HCI_LE_READ_MONITORED_ADVERTISERS_LIST_SIZE_COMMAND : 1 << (48*8+0),
1168
+ HCI_LE_FRAME_SPACE_UPDATE_COMMAND : 1 << (48*8+1),
1144
1169
  }
1145
1170
 
1146
1171
  # LE Supported Features
@@ -1457,7 +1482,7 @@ class CodingFormat:
1457
1482
  vendor_specific_codec_id: int = 0
1458
1483
 
1459
1484
  @classmethod
1460
- def parse_from_bytes(cls, data: bytes, offset: int):
1485
+ def parse_from_bytes(cls, data: bytes, offset: int) -> tuple[int, CodingFormat]:
1461
1486
  (codec_id, company_id, vendor_specific_codec_id) = struct.unpack_from(
1462
1487
  '<BHH', data, offset
1463
1488
  )
@@ -1467,14 +1492,15 @@ class CodingFormat:
1467
1492
  vendor_specific_codec_id=vendor_specific_codec_id,
1468
1493
  )
1469
1494
 
1470
- def to_bytes(self) -> bytes:
1495
+ @classmethod
1496
+ def from_bytes(cls, data: bytes) -> CodingFormat:
1497
+ return cls.parse_from_bytes(data, 0)[1]
1498
+
1499
+ def __bytes__(self) -> bytes:
1471
1500
  return struct.pack(
1472
1501
  '<BHH', self.codec_id, self.company_id, self.vendor_specific_codec_id
1473
1502
  )
1474
1503
 
1475
- def __bytes__(self) -> bytes:
1476
- return self.to_bytes()
1477
-
1478
1504
 
1479
1505
  # -----------------------------------------------------------------------------
1480
1506
  class HCI_Constant:
@@ -1691,7 +1717,7 @@ class HCI_Object:
1691
1717
  field_length = len(field_bytes)
1692
1718
  field_bytes = bytes([field_length]) + field_bytes
1693
1719
  elif isinstance(field_value, (bytes, bytearray)) or hasattr(
1694
- field_value, 'to_bytes'
1720
+ field_value, '__bytes__'
1695
1721
  ):
1696
1722
  field_bytes = bytes(field_value)
1697
1723
  if isinstance(field_type, int) and 4 < field_type <= 256:
@@ -1736,7 +1762,7 @@ class HCI_Object:
1736
1762
  def from_bytes(cls, data, offset, fields):
1737
1763
  return cls(fields, **cls.dict_from_bytes(data, offset, fields))
1738
1764
 
1739
- def to_bytes(self):
1765
+ def __bytes__(self):
1740
1766
  return HCI_Object.dict_to_bytes(self.__dict__, self.fields)
1741
1767
 
1742
1768
  @staticmethod
@@ -1831,9 +1857,6 @@ class HCI_Object:
1831
1857
  for field_name, field_value in field_strings
1832
1858
  )
1833
1859
 
1834
- def __bytes__(self):
1835
- return self.to_bytes()
1836
-
1837
1860
  def __init__(self, fields, **kwargs):
1838
1861
  self.fields = fields
1839
1862
  self.init_from_fields(self, fields, kwargs)
@@ -2008,9 +2031,6 @@ class Address:
2008
2031
  def is_static(self):
2009
2032
  return self.is_random and (self.address_bytes[5] >> 6 == 3)
2010
2033
 
2011
- def to_bytes(self):
2012
- return self.address_bytes
2013
-
2014
2034
  def to_string(self, with_type_qualifier=True):
2015
2035
  '''
2016
2036
  String representation of the address, MSB first, with an optional type
@@ -2022,7 +2042,7 @@ class Address:
2022
2042
  return result + '/P'
2023
2043
 
2024
2044
  def __bytes__(self):
2025
- return self.to_bytes()
2045
+ return self.address_bytes
2026
2046
 
2027
2047
  def __hash__(self):
2028
2048
  return hash(self.address_bytes)
@@ -2228,16 +2248,13 @@ class HCI_Command(HCI_Packet):
2228
2248
  self.op_code = op_code
2229
2249
  self.parameters = parameters
2230
2250
 
2231
- def to_bytes(self):
2251
+ def __bytes__(self):
2232
2252
  parameters = b'' if self.parameters is None else self.parameters
2233
2253
  return (
2234
2254
  struct.pack('<BHB', HCI_COMMAND_PACKET, self.op_code, len(parameters))
2235
2255
  + parameters
2236
2256
  )
2237
2257
 
2238
- def __bytes__(self):
2239
- return self.to_bytes()
2240
-
2241
2258
  def __str__(self):
2242
2259
  result = color(self.name, 'green')
2243
2260
  if fields := getattr(self, 'fields', None):
@@ -4302,6 +4319,61 @@ class HCI_LE_Clear_Advertising_Sets_Command(HCI_Command):
4302
4319
  '''
4303
4320
 
4304
4321
 
4322
+ # -----------------------------------------------------------------------------
4323
+ @HCI_Command.command(
4324
+ [
4325
+ ('advertising_handle', 1),
4326
+ ('periodic_advertising_interval_min', 2),
4327
+ ('periodic_advertising_interval_max', 2),
4328
+ ('periodic_advertising_properties', 2),
4329
+ ]
4330
+ )
4331
+ class HCI_LE_Set_Periodic_Advertising_Parameters_Command(HCI_Command):
4332
+ '''
4333
+ See Bluetooth spec @ 7.8.61 LE Set Periodic Advertising Parameters command
4334
+ '''
4335
+
4336
+ class Properties(enum.IntFlag):
4337
+ INCLUDE_TX_POWER = 1 << 6
4338
+
4339
+ advertising_handle: int
4340
+ periodic_advertising_interval_min: int
4341
+ periodic_advertising_interval_max: int
4342
+ periodic_advertising_properties: int
4343
+
4344
+
4345
+ # -----------------------------------------------------------------------------
4346
+ @HCI_Command.command(
4347
+ [
4348
+ ('advertising_handle', 1),
4349
+ (
4350
+ 'operation',
4351
+ {
4352
+ 'size': 1,
4353
+ 'mapper': lambda x: HCI_LE_Set_Extended_Advertising_Data_Command.Operation(
4354
+ x
4355
+ ).name,
4356
+ },
4357
+ ),
4358
+ (
4359
+ 'advertising_data',
4360
+ {
4361
+ 'parser': HCI_Object.parse_length_prefixed_bytes,
4362
+ 'serializer': HCI_Object.serialize_length_prefixed_bytes,
4363
+ },
4364
+ ),
4365
+ ]
4366
+ )
4367
+ class HCI_LE_Set_Periodic_Advertising_Data_Command(HCI_Command):
4368
+ '''
4369
+ See Bluetooth spec @ 7.8.62 LE Set Periodic Advertising Data command
4370
+ '''
4371
+
4372
+ advertising_handle: int
4373
+ operation: int
4374
+ advertising_data: bytes
4375
+
4376
+
4305
4377
  # -----------------------------------------------------------------------------
4306
4378
  @HCI_Command.command([('enable', 1), ('advertising_handle', 1)])
4307
4379
  class HCI_LE_Set_Periodic_Advertising_Enable_Command(HCI_Command):
@@ -4996,6 +5068,7 @@ class HCI_Event(HCI_Packet):
4996
5068
  hci_packet_type = HCI_EVENT_PACKET
4997
5069
  event_names: Dict[int, str] = {}
4998
5070
  event_classes: Dict[int, Type[HCI_Event]] = {}
5071
+ vendor_factory: Optional[Callable[[bytes], Optional[HCI_Event]]] = None
4999
5072
 
5000
5073
  @staticmethod
5001
5074
  def event(fields=()):
@@ -5053,37 +5126,41 @@ class HCI_Event(HCI_Packet):
5053
5126
 
5054
5127
  return event_class
5055
5128
 
5056
- @staticmethod
5057
- def from_bytes(packet: bytes) -> HCI_Event:
5129
+ @classmethod
5130
+ def from_bytes(cls, packet: bytes) -> HCI_Event:
5058
5131
  event_code = packet[1]
5059
5132
  length = packet[2]
5060
5133
  parameters = packet[3:]
5061
5134
  if len(parameters) != length:
5062
5135
  raise InvalidPacketError('invalid packet length')
5063
5136
 
5064
- cls: Any
5137
+ subclass: Any
5065
5138
  if event_code == HCI_LE_META_EVENT:
5066
5139
  # We do this dispatch here and not in the subclass in order to avoid call
5067
5140
  # loops
5068
5141
  subevent_code = parameters[0]
5069
- cls = HCI_LE_Meta_Event.subevent_classes.get(subevent_code)
5070
- if cls is None:
5142
+ subclass = HCI_LE_Meta_Event.subevent_classes.get(subevent_code)
5143
+ if subclass is None:
5071
5144
  # No class registered, just use a generic class instance
5072
5145
  return HCI_LE_Meta_Event(subevent_code, parameters)
5073
5146
  elif event_code == HCI_VENDOR_EVENT:
5074
- subevent_code = parameters[0]
5075
- cls = HCI_Vendor_Event.subevent_classes.get(subevent_code)
5076
- if cls is None:
5077
- # No class registered, just use a generic class instance
5078
- return HCI_Vendor_Event(subevent_code, parameters)
5147
+ # Invoke all the registered factories to see if any of them can handle
5148
+ # the event
5149
+ if cls.vendor_factory:
5150
+ if event := cls.vendor_factory(parameters):
5151
+ return event
5152
+
5153
+ # No factory, or the factory could not create an instance,
5154
+ # return a generic vendor event
5155
+ return HCI_Event(event_code, parameters)
5079
5156
  else:
5080
- cls = HCI_Event.event_classes.get(event_code)
5081
- if cls is None:
5157
+ subclass = HCI_Event.event_classes.get(event_code)
5158
+ if subclass is None:
5082
5159
  # No class registered, just use a generic class instance
5083
5160
  return HCI_Event(event_code, parameters)
5084
5161
 
5085
5162
  # Invoke the factory to create a new instance
5086
- return cls.from_parameters(parameters) # type: ignore
5163
+ return subclass.from_parameters(parameters) # type: ignore
5087
5164
 
5088
5165
  @classmethod
5089
5166
  def from_parameters(cls, parameters):
@@ -5106,13 +5183,10 @@ class HCI_Event(HCI_Packet):
5106
5183
  self.event_code = event_code
5107
5184
  self.parameters = parameters
5108
5185
 
5109
- def to_bytes(self):
5186
+ def __bytes__(self):
5110
5187
  parameters = b'' if self.parameters is None else self.parameters
5111
5188
  return bytes([HCI_EVENT_PACKET, self.event_code, len(parameters)]) + parameters
5112
5189
 
5113
- def __bytes__(self):
5114
- return self.to_bytes()
5115
-
5116
5190
  def __str__(self):
5117
5191
  result = color(self.name, 'magenta')
5118
5192
  if fields := getattr(self, 'fields', None):
@@ -5129,11 +5203,11 @@ HCI_Event.register_events(globals())
5129
5203
  # -----------------------------------------------------------------------------
5130
5204
  class HCI_Extended_Event(HCI_Event):
5131
5205
  '''
5132
- HCI_Event subclass for events that has a subevent code.
5206
+ HCI_Event subclass for events that have a subevent code.
5133
5207
  '''
5134
5208
 
5135
5209
  subevent_names: Dict[int, str] = {}
5136
- subevent_classes: Dict[int, Type[HCI_Extended_Event]]
5210
+ subevent_classes: Dict[int, Type[HCI_Extended_Event]] = {}
5137
5211
 
5138
5212
  @classmethod
5139
5213
  def event(cls, fields=()):
@@ -5184,7 +5258,22 @@ class HCI_Extended_Event(HCI_Event):
5184
5258
  cls.subevent_names.update(cls.subevent_map(symbols))
5185
5259
 
5186
5260
  @classmethod
5187
- def from_parameters(cls, parameters):
5261
+ def subclass_from_parameters(
5262
+ cls, parameters: bytes
5263
+ ) -> Optional[HCI_Extended_Event]:
5264
+ """
5265
+ Factory method that parses the subevent code, finds a registered subclass,
5266
+ and creates an instance if found.
5267
+ """
5268
+ subevent_code = parameters[0]
5269
+ if subclass := cls.subevent_classes.get(subevent_code):
5270
+ return subclass.from_parameters(parameters)
5271
+
5272
+ return None
5273
+
5274
+ @classmethod
5275
+ def from_parameters(cls, parameters: bytes) -> HCI_Extended_Event:
5276
+ """Factory method for subclasses (the subevent code has already been parsed)"""
5188
5277
  self = cls.__new__(cls)
5189
5278
  HCI_Extended_Event.__init__(self, self.subevent_code, parameters)
5190
5279
  if fields := getattr(self, 'fields', None):
@@ -5225,12 +5314,6 @@ class HCI_LE_Meta_Event(HCI_Extended_Event):
5225
5314
  HCI_LE_Meta_Event.register_subevents(globals())
5226
5315
 
5227
5316
 
5228
- # -----------------------------------------------------------------------------
5229
- class HCI_Vendor_Event(HCI_Extended_Event):
5230
- event_code: int = HCI_VENDOR_EVENT
5231
- subevent_classes = {}
5232
-
5233
-
5234
5317
  # -----------------------------------------------------------------------------
5235
5318
  @HCI_LE_Meta_Event.event(
5236
5319
  [
@@ -6104,8 +6187,9 @@ class HCI_Command_Complete_Event(HCI_Event):
6104
6187
  See Bluetooth spec @ 7.7.14 Command Complete Event
6105
6188
  '''
6106
6189
 
6107
- return_parameters = b''
6190
+ num_hci_command_packets: int
6108
6191
  command_opcode: int
6192
+ return_parameters = b''
6109
6193
 
6110
6194
  def map_return_parameters(self, return_parameters):
6111
6195
  '''Map simple 'status' return parameters to their named constant form'''
@@ -6641,6 +6725,14 @@ class HCI_Remote_Host_Supported_Features_Notification_Event(HCI_Event):
6641
6725
  '''
6642
6726
 
6643
6727
 
6728
+ # -----------------------------------------------------------------------------
6729
+ @HCI_Event.event([('data', "*")])
6730
+ class HCI_Vendor_Event(HCI_Event):
6731
+ '''
6732
+ See Bluetooth spec @ 5.4.4 HCI Event packet
6733
+ '''
6734
+
6735
+
6644
6736
  # -----------------------------------------------------------------------------
6645
6737
  class HCI_AclDataPacket(HCI_Packet):
6646
6738
  '''
@@ -6663,7 +6755,7 @@ class HCI_AclDataPacket(HCI_Packet):
6663
6755
  connection_handle, pb_flag, bc_flag, data_total_length, data
6664
6756
  )
6665
6757
 
6666
- def to_bytes(self):
6758
+ def __bytes__(self):
6667
6759
  h = (self.pb_flag << 12) | (self.bc_flag << 14) | self.connection_handle
6668
6760
  return (
6669
6761
  struct.pack('<BHH', HCI_ACL_DATA_PACKET, h, self.data_total_length)
@@ -6677,9 +6769,6 @@ class HCI_AclDataPacket(HCI_Packet):
6677
6769
  self.data_total_length = data_total_length
6678
6770
  self.data = data
6679
6771
 
6680
- def __bytes__(self):
6681
- return self.to_bytes()
6682
-
6683
6772
  def __str__(self):
6684
6773
  return (
6685
6774
  f'{color("ACL", "blue")}: '
@@ -6713,7 +6802,7 @@ class HCI_SynchronousDataPacket(HCI_Packet):
6713
6802
  connection_handle, packet_status, data_total_length, data
6714
6803
  )
6715
6804
 
6716
- def to_bytes(self) -> bytes:
6805
+ def __bytes__(self) -> bytes:
6717
6806
  h = (self.packet_status << 12) | self.connection_handle
6718
6807
  return (
6719
6808
  struct.pack('<BHB', HCI_SYNCHRONOUS_DATA_PACKET, h, self.data_total_length)
@@ -6732,9 +6821,6 @@ class HCI_SynchronousDataPacket(HCI_Packet):
6732
6821
  self.data_total_length = data_total_length
6733
6822
  self.data = data
6734
6823
 
6735
- def __bytes__(self) -> bytes:
6736
- return self.to_bytes()
6737
-
6738
6824
  def __str__(self) -> str:
6739
6825
  return (
6740
6826
  f'{color("SCO", "blue")}: '
@@ -6807,9 +6893,6 @@ class HCI_IsoDataPacket(HCI_Packet):
6807
6893
  )
6808
6894
 
6809
6895
  def __bytes__(self) -> bytes:
6810
- return self.to_bytes()
6811
-
6812
- def to_bytes(self) -> bytes:
6813
6896
  fmt = '<BHH'
6814
6897
  args = [
6815
6898
  HCI_ISO_DATA_PACKET,