mirror of
https://github.com/google/bumble.git
synced 2026-05-09 04:08:02 +00:00
add support for field arrays in hci packet definitions
This commit is contained in:
451
bumble/hci.py
451
bumble/hci.py
@@ -1445,8 +1445,14 @@ class HCI_Object:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def init_from_fields(hci_object, fields, values):
|
def init_from_fields(hci_object, fields, values):
|
||||||
if isinstance(values, dict):
|
if isinstance(values, dict):
|
||||||
for field_name, _ in fields:
|
for field in fields:
|
||||||
setattr(hci_object, field_name, values[field_name])
|
if isinstance(field, list):
|
||||||
|
# The field is an array, up-level the array field names
|
||||||
|
for sub_field_name, _ in field:
|
||||||
|
setattr(hci_object, sub_field_name, values[sub_field_name])
|
||||||
|
else:
|
||||||
|
field_name = field[0]
|
||||||
|
setattr(hci_object, field_name, values[field_name])
|
||||||
else:
|
else:
|
||||||
for field_name, field_value in zip(fields, values):
|
for field_name, field_value in zip(fields, values):
|
||||||
setattr(hci_object, field_name, field_value)
|
setattr(hci_object, field_name, field_value)
|
||||||
@@ -1456,133 +1462,161 @@ class HCI_Object:
|
|||||||
parsed = HCI_Object.dict_from_bytes(data, offset, fields)
|
parsed = HCI_Object.dict_from_bytes(data, offset, fields)
|
||||||
HCI_Object.init_from_fields(hci_object, parsed.keys(), parsed.values())
|
HCI_Object.init_from_fields(hci_object, parsed.keys(), parsed.values())
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_field(data, offset, field_type):
|
||||||
|
# The field_type may be a dictionary with a mapper, parser, and/or size
|
||||||
|
if isinstance(field_type, dict):
|
||||||
|
if 'size' in field_type:
|
||||||
|
field_type = field_type['size']
|
||||||
|
elif 'parser' in field_type:
|
||||||
|
field_type = field_type['parser']
|
||||||
|
|
||||||
|
# Parse the field
|
||||||
|
if field_type == '*':
|
||||||
|
# The rest of the bytes
|
||||||
|
field_value = data[offset:]
|
||||||
|
return (field_value, len(field_value))
|
||||||
|
if field_type == 1:
|
||||||
|
# 8-bit unsigned
|
||||||
|
return (data[offset], 1)
|
||||||
|
if field_type == -1:
|
||||||
|
# 8-bit signed
|
||||||
|
return (struct.unpack_from('b', data, offset)[0], 1)
|
||||||
|
if field_type == 2:
|
||||||
|
# 16-bit unsigned
|
||||||
|
return (struct.unpack_from('<H', data, offset)[0], 2)
|
||||||
|
if field_type == '>2':
|
||||||
|
# 16-bit unsigned big-endian
|
||||||
|
return (struct.unpack_from('>H', data, offset)[0], 2)
|
||||||
|
if field_type == -2:
|
||||||
|
# 16-bit signed
|
||||||
|
return (struct.unpack_from('<h', data, offset)[0], 2)
|
||||||
|
if field_type == 3:
|
||||||
|
# 24-bit unsigned
|
||||||
|
padded = data[offset : offset + 3] + bytes([0])
|
||||||
|
return (struct.unpack('<I', padded)[0], 3)
|
||||||
|
if field_type == 4:
|
||||||
|
# 32-bit unsigned
|
||||||
|
return (struct.unpack_from('<I', data, offset)[0], 4)
|
||||||
|
if field_type == '>4':
|
||||||
|
# 32-bit unsigned big-endian
|
||||||
|
return (struct.unpack_from('>I', data, offset)[0], 4)
|
||||||
|
if isinstance(field_type, int) and 4 < field_type <= 256:
|
||||||
|
# Byte array (from 5 up to 256 bytes)
|
||||||
|
return (data[offset : offset + field_type], field_type)
|
||||||
|
if callable(field_type):
|
||||||
|
new_offset, field_value = field_type(data, offset)
|
||||||
|
return (field_value, new_offset - offset)
|
||||||
|
|
||||||
|
raise ValueError(f'unknown field type {field_type}')
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def dict_from_bytes(data, offset, fields):
|
def dict_from_bytes(data, offset, fields):
|
||||||
result = collections.OrderedDict()
|
result = collections.OrderedDict()
|
||||||
for (field_name, field_type) in fields:
|
for field in fields:
|
||||||
# The field_type may be a dictionary with a mapper, parser, and/or size
|
if isinstance(field, list):
|
||||||
if isinstance(field_type, dict):
|
# This is an array field, starting with a 1-byte item count.
|
||||||
if 'size' in field_type:
|
item_count = data[offset]
|
||||||
field_type = field_type['size']
|
|
||||||
elif 'parser' in field_type:
|
|
||||||
field_type = field_type['parser']
|
|
||||||
|
|
||||||
# Parse the field
|
|
||||||
if field_type == '*':
|
|
||||||
# The rest of the bytes
|
|
||||||
field_value = data[offset:]
|
|
||||||
offset += len(field_value)
|
|
||||||
elif field_type == 1:
|
|
||||||
# 8-bit unsigned
|
|
||||||
field_value = data[offset]
|
|
||||||
offset += 1
|
offset += 1
|
||||||
elif field_type == -1:
|
for _ in range(item_count):
|
||||||
# 8-bit signed
|
for sub_field_name, sub_field_type in field:
|
||||||
field_value = struct.unpack_from('b', data, offset)[0]
|
value, size = HCI_Object.parse_field(
|
||||||
offset += 1
|
data, offset, sub_field_type
|
||||||
elif field_type == 2:
|
)
|
||||||
# 16-bit unsigned
|
result.setdefault(sub_field_name, []).append(value)
|
||||||
field_value = struct.unpack_from('<H', data, offset)[0]
|
offset += size
|
||||||
offset += 2
|
continue
|
||||||
elif field_type == '>2':
|
|
||||||
# 16-bit unsigned big-endian
|
|
||||||
field_value = struct.unpack_from('>H', data, offset)[0]
|
|
||||||
offset += 2
|
|
||||||
elif field_type == -2:
|
|
||||||
# 16-bit signed
|
|
||||||
field_value = struct.unpack_from('<h', data, offset)[0]
|
|
||||||
offset += 2
|
|
||||||
elif field_type == 3:
|
|
||||||
# 24-bit unsigned
|
|
||||||
padded = data[offset : offset + 3] + bytes([0])
|
|
||||||
field_value = struct.unpack('<I', padded)[0]
|
|
||||||
offset += 3
|
|
||||||
elif field_type == 4:
|
|
||||||
# 32-bit unsigned
|
|
||||||
field_value = struct.unpack_from('<I', data, offset)[0]
|
|
||||||
offset += 4
|
|
||||||
elif field_type == '>4':
|
|
||||||
# 32-bit unsigned big-endian
|
|
||||||
field_value = struct.unpack_from('>I', data, offset)[0]
|
|
||||||
offset += 4
|
|
||||||
elif isinstance(field_type, int) and 4 < field_type <= 256:
|
|
||||||
# Byte array (from 5 up to 256 bytes)
|
|
||||||
field_value = data[offset : offset + field_type]
|
|
||||||
offset += field_type
|
|
||||||
elif callable(field_type):
|
|
||||||
offset, field_value = field_type(data, offset)
|
|
||||||
else:
|
|
||||||
raise ValueError(f'unknown field type {field_type}')
|
|
||||||
|
|
||||||
|
field_name, field_type = field
|
||||||
|
field_value, field_size = HCI_Object.parse_field(data, offset, field_type)
|
||||||
result[field_name] = field_value
|
result[field_name] = field_value
|
||||||
|
offset += field_size
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def serialize_field(field_value, field_type):
|
||||||
|
# The field_type may be a dictionary with a mapper, parser, serializer,
|
||||||
|
# and/or size
|
||||||
|
serializer = None
|
||||||
|
if isinstance(field_type, dict):
|
||||||
|
if 'serializer' in field_type:
|
||||||
|
serializer = field_type['serializer']
|
||||||
|
if 'size' in field_type:
|
||||||
|
field_type = field_type['size']
|
||||||
|
|
||||||
|
# Serialize the field
|
||||||
|
if serializer:
|
||||||
|
field_bytes = serializer(field_value)
|
||||||
|
elif field_type == 1:
|
||||||
|
# 8-bit unsigned
|
||||||
|
field_bytes = bytes([field_value])
|
||||||
|
elif field_type == -1:
|
||||||
|
# 8-bit signed
|
||||||
|
field_bytes = struct.pack('b', field_value)
|
||||||
|
elif field_type == 2:
|
||||||
|
# 16-bit unsigned
|
||||||
|
field_bytes = struct.pack('<H', field_value)
|
||||||
|
elif field_type == '>2':
|
||||||
|
# 16-bit unsigned big-endian
|
||||||
|
field_bytes = struct.pack('>H', field_value)
|
||||||
|
elif field_type == -2:
|
||||||
|
# 16-bit signed
|
||||||
|
field_bytes = struct.pack('<h', field_value)
|
||||||
|
elif field_type == 3:
|
||||||
|
# 24-bit unsigned
|
||||||
|
field_bytes = struct.pack('<I', field_value)[0:3]
|
||||||
|
elif field_type == 4:
|
||||||
|
# 32-bit unsigned
|
||||||
|
field_bytes = struct.pack('<I', field_value)
|
||||||
|
elif field_type == '>4':
|
||||||
|
# 32-bit unsigned big-endian
|
||||||
|
field_bytes = struct.pack('>I', field_value)
|
||||||
|
elif field_type == '*':
|
||||||
|
if isinstance(field_value, int):
|
||||||
|
if 0 <= field_value <= 255:
|
||||||
|
field_bytes = bytes([field_value])
|
||||||
|
else:
|
||||||
|
raise ValueError('value too large for *-typed field')
|
||||||
|
else:
|
||||||
|
field_bytes = bytes(field_value)
|
||||||
|
elif isinstance(field_value, (bytes, bytearray)) or hasattr(
|
||||||
|
field_value, 'to_bytes'
|
||||||
|
):
|
||||||
|
field_bytes = bytes(field_value)
|
||||||
|
if isinstance(field_type, int) and 4 < field_type <= 256:
|
||||||
|
# Truncate or pad with zeros if the field is too long or too short
|
||||||
|
if len(field_bytes) < field_type:
|
||||||
|
field_bytes += bytes(field_type - len(field_bytes))
|
||||||
|
elif len(field_bytes) > field_type:
|
||||||
|
field_bytes = field_bytes[:field_type]
|
||||||
|
else:
|
||||||
|
raise ValueError(f"don't know how to serialize type {type(field_value)}")
|
||||||
|
|
||||||
|
return field_bytes
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def dict_to_bytes(hci_object, fields):
|
def dict_to_bytes(hci_object, fields):
|
||||||
result = bytearray()
|
result = bytearray()
|
||||||
for (field_name, field_type) in fields:
|
for field in fields:
|
||||||
# The field_type may be a dictionary with a mapper, parser, serializer,
|
if isinstance(field, list):
|
||||||
# and/or size
|
# The field is an array. The serialized form starts with a 1-byte
|
||||||
serializer = None
|
# item count. We use the length of the first array field as the
|
||||||
if isinstance(field_type, dict):
|
# array count, since all array fields have the same number of items.
|
||||||
if 'serializer' in field_type:
|
item_count = len(hci_object[field[0][0]])
|
||||||
serializer = field_type['serializer']
|
result += bytes([item_count]) + b''.join(
|
||||||
if 'size' in field_type:
|
b''.join(
|
||||||
field_type = field_type['size']
|
HCI_Object.serialize_field(
|
||||||
|
hci_object[sub_field_name][i], sub_field_type
|
||||||
# Serialize the field
|
)
|
||||||
field_value = hci_object[field_name]
|
for sub_field_name, sub_field_type in field
|
||||||
if serializer:
|
)
|
||||||
field_bytes = serializer(field_value)
|
for i in range(item_count)
|
||||||
elif field_type == 1:
|
|
||||||
# 8-bit unsigned
|
|
||||||
field_bytes = bytes([field_value])
|
|
||||||
elif field_type == -1:
|
|
||||||
# 8-bit signed
|
|
||||||
field_bytes = struct.pack('b', field_value)
|
|
||||||
elif field_type == 2:
|
|
||||||
# 16-bit unsigned
|
|
||||||
field_bytes = struct.pack('<H', field_value)
|
|
||||||
elif field_type == '>2':
|
|
||||||
# 16-bit unsigned big-endian
|
|
||||||
field_bytes = struct.pack('>H', field_value)
|
|
||||||
elif field_type == -2:
|
|
||||||
# 16-bit signed
|
|
||||||
field_bytes = struct.pack('<h', field_value)
|
|
||||||
elif field_type == 3:
|
|
||||||
# 24-bit unsigned
|
|
||||||
field_bytes = struct.pack('<I', field_value)[0:3]
|
|
||||||
elif field_type == 4:
|
|
||||||
# 32-bit unsigned
|
|
||||||
field_bytes = struct.pack('<I', field_value)
|
|
||||||
elif field_type == '>4':
|
|
||||||
# 32-bit unsigned big-endian
|
|
||||||
field_bytes = struct.pack('>I', field_value)
|
|
||||||
elif field_type == '*':
|
|
||||||
if isinstance(field_value, int):
|
|
||||||
if 0 <= field_value <= 255:
|
|
||||||
field_bytes = bytes([field_value])
|
|
||||||
else:
|
|
||||||
raise ValueError('value too large for *-typed field')
|
|
||||||
else:
|
|
||||||
field_bytes = bytes(field_value)
|
|
||||||
elif isinstance(field_value, (bytes, bytearray)) or hasattr(
|
|
||||||
field_value, 'to_bytes'
|
|
||||||
):
|
|
||||||
field_bytes = bytes(field_value)
|
|
||||||
if isinstance(field_type, int) and 4 < field_type <= 256:
|
|
||||||
# Truncate or Pad with zeros if the field is too long or too short
|
|
||||||
if len(field_bytes) < field_type:
|
|
||||||
field_bytes += bytes(field_type - len(field_bytes))
|
|
||||||
elif len(field_bytes) > field_type:
|
|
||||||
field_bytes = field_bytes[:field_type]
|
|
||||||
else:
|
|
||||||
raise ValueError(
|
|
||||||
f"don't know how to serialize type {type(field_value)}"
|
|
||||||
)
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
result += field_bytes
|
(field_name, field_type) = field
|
||||||
|
result += HCI_Object.serialize_field(hci_object[field_name], field_type)
|
||||||
|
|
||||||
return bytes(result)
|
return bytes(result)
|
||||||
|
|
||||||
@@ -1617,48 +1651,73 @@ class HCI_Object:
|
|||||||
return str(value)
|
return str(value)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def format_fields(hci_object, keys, indentation='', value_mappers=None):
|
def stringify_field(
|
||||||
if not keys:
|
field_name, field_type, field_value, indentation, value_mappers
|
||||||
return ''
|
):
|
||||||
|
value_mapper = None
|
||||||
|
if isinstance(field_type, dict):
|
||||||
|
# Get the value mapper from the specifier
|
||||||
|
value_mapper = field_type.get('mapper')
|
||||||
|
|
||||||
# Measure the widest field name
|
# Check if there's a matching mapper passed
|
||||||
max_field_name_length = max(
|
if value_mappers:
|
||||||
(len(key[0] if isinstance(key, tuple) else key) for key in keys)
|
value_mapper = value_mappers.get(field_name, value_mapper)
|
||||||
|
|
||||||
|
# Map the value if we have a mapper
|
||||||
|
if value_mapper is not None:
|
||||||
|
field_value = value_mapper(field_value)
|
||||||
|
|
||||||
|
# Get the string representation of the value
|
||||||
|
return HCI_Object.format_field_value(
|
||||||
|
field_value, indentation=indentation + ' '
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def format_fields(hci_object, fields, indentation='', value_mappers=None):
|
||||||
|
if not fields:
|
||||||
|
return ''
|
||||||
|
|
||||||
# Build array of formatted key:value pairs
|
# Build array of formatted key:value pairs
|
||||||
fields = []
|
field_strings = []
|
||||||
for key in keys:
|
for field in fields:
|
||||||
value_mapper = None
|
if isinstance(field, list):
|
||||||
if isinstance(key, tuple):
|
for sub_field in field:
|
||||||
# The key has an associated specifier
|
sub_field_name, sub_field_type = sub_field
|
||||||
key, specifier = key
|
item_count = len(hci_object[sub_field_name])
|
||||||
|
for i in range(item_count):
|
||||||
|
field_strings.append(
|
||||||
|
(
|
||||||
|
f'{sub_field_name}[{i}]',
|
||||||
|
HCI_Object.stringify_field(
|
||||||
|
sub_field_name,
|
||||||
|
sub_field_type,
|
||||||
|
hci_object[sub_field_name][i],
|
||||||
|
indentation,
|
||||||
|
value_mappers,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
# Get the value mapper from the specifier
|
field_name, field_type = field
|
||||||
if isinstance(specifier, dict):
|
field_value = hci_object[field_name]
|
||||||
value_mapper = specifier.get('mapper')
|
field_strings.append(
|
||||||
|
(
|
||||||
# Get the value for the field
|
field_name,
|
||||||
value = hci_object[key]
|
HCI_Object.stringify_field(
|
||||||
|
field_name, field_type, field_value, indentation, value_mappers
|
||||||
# Check if there's a matching mapper passed
|
),
|
||||||
if value_mappers:
|
),
|
||||||
value_mapper = value_mappers.get(key, value_mapper)
|
|
||||||
|
|
||||||
# Map the value if we have a mapper
|
|
||||||
if value_mapper is not None:
|
|
||||||
value = value_mapper(value)
|
|
||||||
|
|
||||||
# Get the string representation of the value
|
|
||||||
value_str = HCI_Object.format_field_value(
|
|
||||||
value, indentation=indentation + ' '
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add the field to the formatted result
|
# Measure the widest field name
|
||||||
key_str = color(f'{key + ":":{1 + max_field_name_length}}', 'cyan')
|
max_field_name_length = max(len(s[0]) for s in field_strings)
|
||||||
fields.append(f'{indentation}{key_str} {value_str}')
|
sep = ':'
|
||||||
|
return '\n'.join(
|
||||||
return '\n'.join(fields)
|
f'{indentation}'
|
||||||
|
f'{color(f"{field_name + sep:{1 + max_field_name_length}}", "cyan")} {field_value}'
|
||||||
|
for field_name, field_value in field_strings
|
||||||
|
)
|
||||||
|
|
||||||
def __bytes__(self):
|
def __bytes__(self):
|
||||||
return self.to_bytes()
|
return self.to_bytes()
|
||||||
@@ -3769,9 +3828,7 @@ class HCI_LE_Set_Extended_Advertising_Parameters_Command(HCI_Command):
|
|||||||
'advertising_data',
|
'advertising_data',
|
||||||
{
|
{
|
||||||
'parser': HCI_Object.parse_length_prefixed_bytes,
|
'parser': HCI_Object.parse_length_prefixed_bytes,
|
||||||
'serializer': functools.partial(
|
'serializer': HCI_Object.serialize_length_prefixed_bytes,
|
||||||
HCI_Object.serialize_length_prefixed_bytes
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -3819,9 +3876,7 @@ class HCI_LE_Set_Extended_Advertising_Data_Command(HCI_Command):
|
|||||||
'scan_response_data',
|
'scan_response_data',
|
||||||
{
|
{
|
||||||
'parser': HCI_Object.parse_length_prefixed_bytes,
|
'parser': HCI_Object.parse_length_prefixed_bytes,
|
||||||
'serializer': functools.partial(
|
'serializer': HCI_Object.serialize_length_prefixed_bytes,
|
||||||
HCI_Object.serialize_length_prefixed_bytes
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -3849,73 +3904,21 @@ class HCI_LE_Set_Extended_Scan_Response_Data_Command(HCI_Command):
|
|||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@HCI_Command.command(fields=None)
|
@HCI_Command.command(
|
||||||
|
[
|
||||||
|
('enable', 1),
|
||||||
|
[
|
||||||
|
('advertising_handles', 1),
|
||||||
|
('durations', 2),
|
||||||
|
('max_extended_advertising_events', 1),
|
||||||
|
],
|
||||||
|
]
|
||||||
|
)
|
||||||
class HCI_LE_Set_Extended_Advertising_Enable_Command(HCI_Command):
|
class HCI_LE_Set_Extended_Advertising_Enable_Command(HCI_Command):
|
||||||
'''
|
'''
|
||||||
See Bluetooth spec @ 7.8.56 LE Set Extended Advertising Enable Command
|
See Bluetooth spec @ 7.8.56 LE Set Extended Advertising Enable Command
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_parameters(cls, parameters):
|
|
||||||
enable = parameters[0]
|
|
||||||
num_sets = parameters[1]
|
|
||||||
advertising_handles = []
|
|
||||||
durations = []
|
|
||||||
max_extended_advertising_events = []
|
|
||||||
offset = 2
|
|
||||||
for _ in range(num_sets):
|
|
||||||
advertising_handles.append(parameters[offset])
|
|
||||||
durations.append(struct.unpack_from('<H', parameters, offset + 1)[0])
|
|
||||||
max_extended_advertising_events.append(parameters[offset + 3])
|
|
||||||
offset += 4
|
|
||||||
|
|
||||||
return cls(
|
|
||||||
enable, advertising_handles, durations, max_extended_advertising_events
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self, enable, advertising_handles, durations, max_extended_advertising_events
|
|
||||||
):
|
|
||||||
super().__init__(HCI_LE_SET_EXTENDED_ADVERTISING_ENABLE_COMMAND)
|
|
||||||
self.enable = enable
|
|
||||||
self.advertising_handles = advertising_handles
|
|
||||||
self.durations = durations
|
|
||||||
self.max_extended_advertising_events = max_extended_advertising_events
|
|
||||||
|
|
||||||
self.parameters = bytes([enable, len(advertising_handles)]) + b''.join(
|
|
||||||
[
|
|
||||||
struct.pack(
|
|
||||||
'<BHB',
|
|
||||||
advertising_handles[i],
|
|
||||||
durations[i],
|
|
||||||
max_extended_advertising_events[i],
|
|
||||||
)
|
|
||||||
for i in range(len(advertising_handles))
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
fields = [('enable:', self.enable)]
|
|
||||||
for i, advertising_handle in enumerate(self.advertising_handles):
|
|
||||||
fields.append(
|
|
||||||
(f'advertising_handle[{i}]: ', advertising_handle)
|
|
||||||
)
|
|
||||||
fields.append((f'duration[{i}]: ', self.durations[i]))
|
|
||||||
fields.append(
|
|
||||||
(
|
|
||||||
f'max_extended_advertising_events[{i}]:',
|
|
||||||
self.max_extended_advertising_events[i],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
color(self.name, 'green')
|
|
||||||
+ ':\n'
|
|
||||||
+ '\n'.join(
|
|
||||||
[color(field[0], 'cyan') + ' ' + str(field[1]) for field in fields]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@HCI_Command.command(
|
@HCI_Command.command(
|
||||||
@@ -4066,7 +4069,10 @@ class HCI_LE_Set_Extended_Scan_Parameters_Command(HCI_Command):
|
|||||||
color(self.name, 'green')
|
color(self.name, 'green')
|
||||||
+ ':\n'
|
+ ':\n'
|
||||||
+ '\n'.join(
|
+ '\n'.join(
|
||||||
[color(field[0], 'cyan') + ' ' + str(field[1]) for field in fields]
|
[
|
||||||
|
color(' ' + field[0], 'cyan') + ' ' + str(field[1])
|
||||||
|
for field in fields
|
||||||
|
]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -4242,7 +4248,10 @@ class HCI_LE_Extended_Create_Connection_Command(HCI_Command):
|
|||||||
color(self.name, 'green')
|
color(self.name, 'green')
|
||||||
+ ':\n'
|
+ ':\n'
|
||||||
+ '\n'.join(
|
+ '\n'.join(
|
||||||
[color(field[0], 'cyan') + ' ' + str(field[1]) for field in fields]
|
[
|
||||||
|
color(' ' + field[0], 'cyan') + ' ' + str(field[1])
|
||||||
|
for field in fields
|
||||||
|
]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -5205,7 +5214,7 @@ class HCI_Number_Of_Completed_Packets_Event(HCI_Event):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
lines = [
|
lines = [
|
||||||
color(self.name, 'magenta') + ':',
|
color(self.name, 'magenta') + ':',
|
||||||
color(' number_of_handles: ', 'cyan')
|
color(' number_of_handles: ', 'cyan')
|
||||||
+ f'{len(self.connection_handles)}',
|
+ f'{len(self.connection_handles)}',
|
||||||
]
|
]
|
||||||
for i, connection_handle in enumerate(self.connection_handles):
|
for i, connection_handle in enumerate(self.connection_handles):
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ from bumble.hci import (
|
|||||||
HCI_LE_Set_Advertising_Parameters_Command,
|
HCI_LE_Set_Advertising_Parameters_Command,
|
||||||
HCI_LE_Set_Default_PHY_Command,
|
HCI_LE_Set_Default_PHY_Command,
|
||||||
HCI_LE_Set_Event_Mask_Command,
|
HCI_LE_Set_Event_Mask_Command,
|
||||||
|
HCI_LE_Set_Extended_Advertising_Enable_Command,
|
||||||
HCI_LE_Set_Extended_Scan_Parameters_Command,
|
HCI_LE_Set_Extended_Scan_Parameters_Command,
|
||||||
HCI_LE_Set_Random_Address_Command,
|
HCI_LE_Set_Random_Address_Command,
|
||||||
HCI_LE_Set_Scan_Enable_Command,
|
HCI_LE_Set_Scan_Enable_Command,
|
||||||
@@ -422,6 +423,25 @@ def test_HCI_LE_Set_Extended_Scan_Parameters_Command():
|
|||||||
basic_check(command)
|
basic_check(command)
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
def test_HCI_LE_Set_Extended_Advertising_Enable_Command():
|
||||||
|
command = HCI_Packet.from_bytes(
|
||||||
|
bytes.fromhex('0139200e010301050008020600090307000a')
|
||||||
|
)
|
||||||
|
assert command.enable == 1
|
||||||
|
assert command.advertising_handles == [1, 2, 3]
|
||||||
|
assert command.durations == [5, 6, 7]
|
||||||
|
assert command.max_extended_advertising_events == [8, 9, 10]
|
||||||
|
|
||||||
|
command = HCI_LE_Set_Extended_Advertising_Enable_Command(
|
||||||
|
enable=1,
|
||||||
|
advertising_handles=[1, 2, 3],
|
||||||
|
durations=[5, 6, 7],
|
||||||
|
max_extended_advertising_events=[8, 9, 10],
|
||||||
|
)
|
||||||
|
basic_check(command)
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
def test_address():
|
def test_address():
|
||||||
a = Address('C4:F2:17:1A:1D:BB')
|
a = Address('C4:F2:17:1A:1D:BB')
|
||||||
@@ -478,6 +498,7 @@ def run_test_commands():
|
|||||||
test_HCI_LE_Read_Remote_Features_Command()
|
test_HCI_LE_Read_Remote_Features_Command()
|
||||||
test_HCI_LE_Set_Default_PHY_Command()
|
test_HCI_LE_Set_Default_PHY_Command()
|
||||||
test_HCI_LE_Set_Extended_Scan_Parameters_Command()
|
test_HCI_LE_Set_Extended_Scan_Parameters_Command()
|
||||||
|
test_HCI_LE_Set_Extended_Advertising_Enable_Command()
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user