Allow register HCI packets with custom names

This commit is contained in:
Josh Wu
2025-07-19 20:46:33 +08:00
parent d9d971b8b3
commit fc3fd7f25b
2 changed files with 55 additions and 27 deletions

View File

@@ -2291,7 +2291,7 @@ class HCI_Command(HCI_Packet):
hci_packet_type = HCI_COMMAND_PACKET hci_packet_type = HCI_COMMAND_PACKET
command_names: dict[int, str] = {} command_names: dict[int, str] = {}
command_classes: dict[int, type[HCI_Command]] = {} command_classes: dict[int, type[HCI_Command]] = {}
op_code: int = -1 op_code: int
fields: Fields = () fields: Fields = ()
return_parameters_fields: Fields = () return_parameters_fields: Fields = ()
_parameters: bytes = b'' _parameters: bytes = b''
@@ -2304,10 +2304,14 @@ class HCI_Command(HCI_Packet):
Decorator used to declare and register subclasses Decorator used to declare and register subclasses
''' '''
subclass.name = subclass.__name__.upper() # Subclasses may set parameters as ClassVar, or inferred from class name.
subclass.op_code = key_with_value(subclass.command_names, subclass.name) if not hasattr(subclass, 'name'):
if subclass.op_code is None: subclass.name = subclass.__name__.upper()
raise KeyError(f'command {subclass.name} not found in command_names') if not hasattr(subclass, 'op_code'):
op_code = key_with_value(subclass.command_names, subclass.name)
if op_code is None:
raise KeyError(f'command {subclass.name} not found in command_names')
subclass.op_code = op_code
if dataclasses.is_dataclass(subclass): if dataclasses.is_dataclass(subclass):
subclass.fields = HCI_Object.fields_from_dataclass(subclass) subclass.fields = HCI_Object.fields_from_dataclass(subclass)
@@ -2350,11 +2354,12 @@ class HCI_Command(HCI_Packet):
command.parameters = parameters command.parameters = parameters
return command return command
@staticmethod @classmethod
def command_name(op_code): def command_name(cls, op_code: int) -> str:
name = HCI_Command.command_names.get(op_code) if name := cls.command_names.get(op_code):
if name is not None:
return name return name
if (subclass := cls.command_classes.get(op_code)) and subclass.name:
return subclass.name
return f'[OGF=0x{op_code >> 10:02x}, OCF=0x{op_code & 0x3FF:04x}]' return f'[OGF=0x{op_code >> 10:02x}, OCF=0x{op_code & 0x3FF:04x}]'
@classmethod @classmethod
@@ -5598,7 +5603,7 @@ class HCI_Event(HCI_Packet):
event_names: dict[int, str] = {} event_names: dict[int, str] = {}
event_classes: dict[int, type[HCI_Event]] = {} event_classes: dict[int, type[HCI_Event]] = {}
vendor_factories: list[Callable[[bytes], Optional[HCI_Event]]] = [] vendor_factories: list[Callable[[bytes], Optional[HCI_Event]]] = []
event_code: int = -1 event_code: int
fields: Fields = () fields: Fields = ()
_parameters: bytes = b'' _parameters: bytes = b''
@@ -5609,12 +5614,17 @@ class HCI_Event(HCI_Packet):
''' '''
Decorator used to declare and register subclasses Decorator used to declare and register subclasses
''' '''
# Subclasses may set parameters as ClassVar, or inferred from class name.
if not hasattr(subclass, 'name'):
subclass.name = subclass.__name__.upper()
if not hasattr(subclass, 'event_code'):
event_code = key_with_value(subclass.event_names, subclass.name)
if event_code is None:
raise KeyError(f'event {subclass.name} not found in event_names')
subclass.event_code = event_code
subclass.name = subclass.__name__.upper() if dataclasses.is_dataclass(subclass):
subclass.event_code = key_with_value(subclass.event_names, subclass.name) subclass.fields = HCI_Object.fields_from_dataclass(subclass)
subclass.fields = HCI_Object.fields_from_dataclass(subclass)
if subclass.event_code is None:
raise KeyError(f'event {subclass.name} not found in event_names')
# Register a factory for this class # Register a factory for this class
cls.event_classes[subclass.event_code] = subclass cls.event_classes[subclass.event_code] = subclass
@@ -5630,9 +5640,11 @@ class HCI_Event(HCI_Packet):
and event_name.endswith('_EVENT') and event_name.endswith('_EVENT')
} }
@staticmethod @classmethod
def event_name(event_code): def event_name(cls, event_code: int) -> str:
return name_or_number(HCI_Event.event_names, event_code) if (subclass := cls.event_classes.get(event_code)) and subclass.name:
return subclass.name
return name_or_number(cls.event_names, event_code)
@staticmethod @staticmethod
def register_events(symbols: dict[str, Any]) -> None: def register_events(symbols: dict[str, Any]) -> None:
@@ -5758,7 +5770,7 @@ class HCI_Extended_Event(HCI_Event):
subevent_names: dict[int, str] = {} subevent_names: dict[int, str] = {}
subevent_classes: dict[int, type[HCI_Extended_Event]] = {} subevent_classes: dict[int, type[HCI_Extended_Event]] = {}
subevent_code: int = -1 subevent_code: int
_parameters: bytes = b'' _parameters: bytes = b''
_ExtendedEvent = TypeVar("_ExtendedEvent", bound="HCI_Extended_Event") _ExtendedEvent = TypeVar("_ExtendedEvent", bound="HCI_Extended_Event")
@@ -5769,14 +5781,20 @@ class HCI_Extended_Event(HCI_Event):
''' '''
Decorator used to declare and register subclasses Decorator used to declare and register subclasses
''' '''
subclass.name = subclass.__name__.upper() # Subclasses may set parameters as ClassVar, or inferred from class name.
subclass.subevent_code = key_with_value(subclass.subevent_names, subclass.name) if not hasattr(subclass, 'name'):
if subclass.subevent_code is None: subclass.name = subclass.__name__.upper()
raise KeyError(f'subevent {subclass.name} not found in subevent_names') if not hasattr(subclass, 'subevent_code'):
subevent_code = key_with_value(subclass.subevent_names, subclass.name)
if subevent_code is None:
raise KeyError(f'subevent {subclass.name} not found in subevent_names')
subclass.subevent_code = subevent_code
if dataclasses.is_dataclass(subclass):
subclass.fields = HCI_Object.fields_from_dataclass(subclass)
# Register a factory for this class # Register a factory for this class
cls.subevent_classes[subclass.subevent_code] = subclass cls.subevent_classes[subclass.subevent_code] = subclass
subclass.fields = HCI_Object.fields_from_dataclass(subclass)
return subclass return subclass
@@ -5793,10 +5811,11 @@ class HCI_Extended_Event(HCI_Event):
self._parameters = parameters self._parameters = parameters
@classmethod @classmethod
def subevent_name(cls, subevent_code): def subevent_name(cls, subevent_code: int) -> str:
subevent_name = cls.subevent_names.get(subevent_code) if subevent_name := cls.subevent_names.get(subevent_code):
if subevent_name is not None:
return subevent_name return subevent_name
if (subclass := cls.subevent_classes.get(subevent_code)) and subclass.name:
return subclass.name
return f'{cls.__name__.upper()}[0x{subevent_code:02X}]' return f'{cls.__name__.upper()}[0x{subevent_code:02X}]'

View File

@@ -238,6 +238,15 @@ def test_HCI_Command():
command = hci.HCI_Command(op_code=0x5566, parameters=bytes.fromhex('AABBCC')) command = hci.HCI_Command(op_code=0x5566, parameters=bytes.fromhex('AABBCC'))
basic_check(command) basic_check(command)
@hci.HCI_Command.command
class CustomCommand(hci.HCI_Command):
op_code = 0x7788
name = 'Custom Command'
basic_check(CustomCommand())
assert CustomCommand().op_code == 0x7788
assert CustomCommand().name == 'Custom Command'
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@pytest.mark.parametrize( @pytest.mark.parametrize(