diff --git a/apps/controller_info.py b/apps/controller_info.py index b4a1d87b..778b43aa 100644 --- a/apps/controller_info.py +++ b/apps/controller_info.py @@ -34,11 +34,7 @@ HCI_READ_BD_ADDR_COMMAND, HCI_READ_BUFFER_SIZE_COMMAND, HCI_READ_LOCAL_NAME_COMMAND, - HCI_SUCCESS, - CodecID, HCI_Command, - HCI_Command_Complete_Event, - HCI_Command_Status_Event, HCI_LE_Read_Buffer_Size_Command, HCI_LE_Read_Buffer_Size_V2_Command, HCI_LE_Read_Maximum_Advertising_Data_Length_Command, @@ -59,34 +55,23 @@ from bumble.transport import open_transport -# ----------------------------------------------------------------------------- -def command_succeeded(response): - if isinstance(response, HCI_Command_Status_Event): - return response.status == HCI_SUCCESS - if isinstance(response, HCI_Command_Complete_Event): - return response.return_parameters.status == HCI_SUCCESS - return False - - # ----------------------------------------------------------------------------- async def get_classic_info(host: Host) -> None: if host.supports_command(HCI_READ_BD_ADDR_COMMAND): - response = await host.send_command(HCI_Read_BD_ADDR_Command()) - if command_succeeded(response): - print() - print( - color('Public Address:', 'yellow'), - response.return_parameters.bd_addr.to_string(False), - ) + response1 = await host.send_sync_command(HCI_Read_BD_ADDR_Command()) + print() + print( + color('Public Address:', 'yellow'), + response1.bd_addr.to_string(False), + ) if host.supports_command(HCI_READ_LOCAL_NAME_COMMAND): - response = await host.send_command(HCI_Read_Local_Name_Command()) - if command_succeeded(response): - print() - print( - color('Local Name:', 'yellow'), - map_null_terminated_utf8_string(response.return_parameters.local_name), - ) + response2 = await host.send_sync_command(HCI_Read_Local_Name_Command()) + print() + print( + color('Local Name:', 'yellow'), + map_null_terminated_utf8_string(response2.local_name), + ) # ----------------------------------------------------------------------------- @@ -94,52 +79,50 @@ async def get_le_info(host: Host) -> None: print() if host.supports_command(HCI_LE_READ_NUMBER_OF_SUPPORTED_ADVERTISING_SETS_COMMAND): - response = await host.send_command( + response1 = await host.send_sync_command( HCI_LE_Read_Number_Of_Supported_Advertising_Sets_Command() ) - if command_succeeded(response): - print( - color('LE Number Of Supported Advertising Sets:', 'yellow'), - response.return_parameters.num_supported_advertising_sets, - '\n', - ) + print( + color('LE Number Of Supported Advertising Sets:', 'yellow'), + response1.num_supported_advertising_sets, + '\n', + ) if host.supports_command(HCI_LE_READ_MAXIMUM_ADVERTISING_DATA_LENGTH_COMMAND): - response = await host.send_command( + response2 = await host.send_sync_command( HCI_LE_Read_Maximum_Advertising_Data_Length_Command() ) - if command_succeeded(response): - print( - color('LE Maximum Advertising Data Length:', 'yellow'), - response.return_parameters.max_advertising_data_length, - '\n', - ) + print( + color('LE Maximum Advertising Data Length:', 'yellow'), + response2.max_advertising_data_length, + '\n', + ) if host.supports_command(HCI_LE_READ_MAXIMUM_DATA_LENGTH_COMMAND): - response = await host.send_command(HCI_LE_Read_Maximum_Data_Length_Command()) - if command_succeeded(response): - print( - color('Maximum Data Length:', 'yellow'), - ( - f'tx:{response.return_parameters.supported_max_tx_octets}/' - f'{response.return_parameters.supported_max_tx_time}, ' - f'rx:{response.return_parameters.supported_max_rx_octets}/' - f'{response.return_parameters.supported_max_rx_time}' - ), - '\n', - ) + response3 = await host.send_sync_command( + HCI_LE_Read_Maximum_Data_Length_Command() + ) + print( + color('Maximum Data Length:', 'yellow'), + ( + f'tx:{response3.supported_max_tx_octets}/' + f'{response3.supported_max_tx_time}, ' + f'rx:{response3.supported_max_rx_octets}/' + f'{response3.supported_max_rx_time}' + ), + '\n', + ) if host.supports_command(HCI_LE_READ_SUGGESTED_DEFAULT_DATA_LENGTH_COMMAND): - response = await host.send_command( + response4 = await host.send_sync_command( HCI_LE_Read_Suggested_Default_Data_Length_Command() ) - if command_succeeded(response): - print( - color('Suggested Default Data Length:', 'yellow'), - f'{response.return_parameters.suggested_max_tx_octets}/' - f'{response.return_parameters.suggested_max_tx_time}', - '\n', - ) + print( + color('Suggested Default Data Length:', 'yellow'), + f'{response4.suggested_max_tx_octets}/' + f'{response4.suggested_max_tx_time}', + '\n', + ) print(color('LE Features:', 'yellow')) for feature in host.supported_le_features: @@ -151,37 +134,31 @@ async def get_flow_control_info(host: Host) -> None: print() if host.supports_command(HCI_READ_BUFFER_SIZE_COMMAND): - response = await host.send_command( - HCI_Read_Buffer_Size_Command(), check_result=True - ) + response1 = await host.send_sync_command(HCI_Read_Buffer_Size_Command()) print( color('ACL Flow Control:', 'yellow'), - f'{response.return_parameters.hc_total_num_acl_data_packets} ' - f'packets of size {response.return_parameters.hc_acl_data_packet_length}', + f'{response1.hc_total_num_acl_data_packets} ' + f'packets of size {response1.hc_acl_data_packet_length}', ) if host.supports_command(HCI_LE_READ_BUFFER_SIZE_V2_COMMAND): - response = await host.send_command( - HCI_LE_Read_Buffer_Size_V2_Command(), check_result=True - ) + response2 = await host.send_sync_command(HCI_LE_Read_Buffer_Size_V2_Command()) print( color('LE ACL Flow Control:', 'yellow'), - f'{response.return_parameters.total_num_le_acl_data_packets} ' - f'packets of size {response.return_parameters.le_acl_data_packet_length}', + f'{response2.total_num_le_acl_data_packets} ' + f'packets of size {response2.le_acl_data_packet_length}', ) print( color('LE ISO Flow Control:', 'yellow'), - f'{response.return_parameters.total_num_iso_data_packets} ' - f'packets of size {response.return_parameters.iso_data_packet_length}', + f'{response2.total_num_iso_data_packets} ' + f'packets of size {response2.iso_data_packet_length}', ) elif host.supports_command(HCI_LE_READ_BUFFER_SIZE_COMMAND): - response = await host.send_command( - HCI_LE_Read_Buffer_Size_Command(), check_result=True - ) + response3 = await host.send_sync_command(HCI_LE_Read_Buffer_Size_Command()) print( color('LE ACL Flow Control:', 'yellow'), - f'{response.return_parameters.total_num_le_acl_data_packets} ' - f'packets of size {response.return_parameters.le_acl_data_packet_length}', + f'{response3.total_num_le_acl_data_packets} ' + f'packets of size {response3.le_acl_data_packet_length}', ) @@ -190,52 +167,44 @@ async def get_codecs_info(host: Host) -> None: print() if host.supports_command(HCI_Read_Local_Supported_Codecs_V2_Command.op_code): - response = await host.send_command( - HCI_Read_Local_Supported_Codecs_V2_Command(), check_result=True + response1 = await host.send_sync_command( + HCI_Read_Local_Supported_Codecs_V2_Command() ) print(color('Codecs:', 'yellow')) for codec_id, transport in zip( - response.return_parameters.standard_codec_ids, - response.return_parameters.standard_codec_transports, + response1.standard_codec_ids, + response1.standard_codec_transports, ): - transport_name = HCI_Read_Local_Supported_Codecs_V2_Command.Transport( - transport - ).name - codec_name = CodecID(codec_id).name - print(f' {codec_name} - {transport_name}') + print(f' {codec_id.name} - {transport.name}') - for codec_id, transport in zip( - response.return_parameters.vendor_specific_codec_ids, - response.return_parameters.vendor_specific_codec_transports, + for vendor_codec_id, vendor_transport in zip( + response1.vendor_specific_codec_ids, + response1.vendor_specific_codec_transports, ): - transport_name = HCI_Read_Local_Supported_Codecs_V2_Command.Transport( - transport - ).name - company = name_or_number(COMPANY_IDENTIFIERS, codec_id >> 16) - print(f' {company} / {codec_id & 0xFFFF} - {transport_name}') + company = name_or_number(COMPANY_IDENTIFIERS, vendor_codec_id >> 16) + print(f' {company} / {vendor_codec_id & 0xFFFF} - {vendor_transport.name}') - if not response.return_parameters.standard_codec_ids: + if not response1.standard_codec_ids: print(' No standard codecs') - if not response.return_parameters.vendor_specific_codec_ids: + if not response1.vendor_specific_codec_ids: print(' No Vendor-specific codecs') if host.supports_command(HCI_Read_Local_Supported_Codecs_Command.op_code): - response = await host.send_command( - HCI_Read_Local_Supported_Codecs_Command(), check_result=True + response2 = await host.send_sync_command( + HCI_Read_Local_Supported_Codecs_Command() ) print(color('Codecs (BR/EDR):', 'yellow')) - for codec_id in response.return_parameters.standard_codec_ids: - codec_name = CodecID(codec_id).name - print(f' {codec_name}') + for codec_id in response2.standard_codec_ids: + print(f' {codec_id.name}') - for codec_id in response.return_parameters.vendor_specific_codec_ids: - company = name_or_number(COMPANY_IDENTIFIERS, codec_id >> 16) - print(f' {company} / {codec_id & 0xFFFF}') + for vendor_codec_id in response2.vendor_specific_codec_ids: + company = name_or_number(COMPANY_IDENTIFIERS, vendor_codec_id >> 16) + print(f' {company} / {vendor_codec_id & 0xFFFF}') - if not response.return_parameters.standard_codec_ids: + if not response2.standard_codec_ids: print(' No standard codecs') - if not response.return_parameters.vendor_specific_codec_ids: + if not response2.vendor_specific_codec_ids: print(' No Vendor-specific codecs') diff --git a/apps/controller_loopback.py b/apps/controller_loopback.py index ab8ba2c3..89a5c165 100644 --- a/apps/controller_loopback.py +++ b/apps/controller_loopback.py @@ -85,7 +85,7 @@ def on_l2cap_pdu(self, connection_handle: int, cid: int, pdu: bytes): print(color('@@@ Received last packet', 'green')) self.done.set() - async def run(self): + async def run(self) -> None: """Run a loopback throughput test""" print(color('>>> Connecting to HCI...', 'green')) async with await open_transport(self.transport) as ( @@ -100,11 +100,15 @@ async def run(self): # make sure data can fit in one l2cap pdu l2cap_header_size = 4 - max_packet_size = ( + packet_queue = ( host.acl_packet_queue if host.acl_packet_queue else host.le_acl_packet_queue - ).max_packet_size - l2cap_header_size + ) + if packet_queue is None: + print(color('!!! No packet queue', 'red')) + return + max_packet_size = packet_queue.max_packet_size - l2cap_header_size if self.packet_size > max_packet_size: print( color( @@ -128,20 +132,18 @@ async def run(self): loopback_mode = LoopbackMode.LOCAL print(color('### Setting loopback mode', 'blue')) - await host.send_command( + await host.send_sync_command( HCI_Write_Loopback_Mode_Command(loopback_mode=LoopbackMode.LOCAL), - check_result=True, ) print(color('### Checking loopback mode', 'blue')) - response = await host.send_command( - HCI_Read_Loopback_Mode_Command(), check_result=True - ) - if response.return_parameters.loopback_mode != loopback_mode: + response = await host.send_sync_command(HCI_Read_Loopback_Mode_Command()) + if response.loopback_mode != loopback_mode: print(color('!!! Loopback mode mismatch', 'red')) return await self.connection_event.wait() + assert self.connection_handle is not None print(color('### Connected', 'cyan')) print(color('=== Start sending', 'magenta')) diff --git a/bumble/controller.py b/bumble/controller.py index c3b84464..bbb36673 100644 --- a/bumble/controller.py +++ b/bumble/controller.py @@ -421,7 +421,7 @@ def on_hci_command_packet(self, command: hci.HCI_Command) -> None: hci.HCI_Command_Complete_Event( num_hci_command_packets=1, command_opcode=command.op_code, - return_parameters=result, + return_parameters=hci.HCI_GenericReturnParameters(data=result), ) ) diff --git a/bumble/core.py b/bumble/core.py index d47ec6a7..8f68a2b4 100644 --- a/bumble/core.py +++ b/bumble/core.py @@ -923,7 +923,7 @@ class DeviceClass: # pylint: enable=line-too-long @staticmethod - def split_class_of_device(class_of_device): + def split_class_of_device(class_of_device: int) -> tuple[int, int, int]: # Split the bit fields of the composite class of device value into: # (service_classes, major_device_class, minor_device_class) return ( diff --git a/bumble/device.py b/bumble/device.py index a0e41b90..8f216689 100644 --- a/bumble/device.py +++ b/bumble/device.py @@ -79,6 +79,7 @@ from bumble.transport.common import TransportSink, TransportSource _T = TypeVar('_T') +_RP = TypeVar('_RP', bound=hci.HCI_ReturnParameters) # ----------------------------------------------------------------------------- # Logging @@ -373,24 +374,22 @@ class LegacyAdvertiser: async def start(self) -> None: # Set/update the advertising data if the advertising type allows it if self.advertising_type.has_data: - await self.device.send_command( + await self.device.send_sync_command( hci.HCI_LE_Set_Advertising_Data_Command( advertising_data=self.device.advertising_data - ), - check_result=True, + ) ) # Set/update the scan response data if the advertising is scannable if self.advertising_type.is_scannable: - await self.device.send_command( + await self.device.send_sync_command( hci.HCI_LE_Set_Scan_Response_Data_Command( scan_response_data=self.device.scan_response_data - ), - check_result=True, + ) ) # Set the advertising parameters - await self.device.send_command( + await self.device.send_sync_command( hci.HCI_LE_Set_Advertising_Parameters_Command( advertising_interval_min=int( self.device.advertising_interval_min / 0.625 @@ -404,21 +403,18 @@ async def start(self) -> None: peer_address=self.peer_address, advertising_channel_map=7, advertising_filter_policy=0, - ), - check_result=True, + ) ) # Enable advertising - await self.device.send_command( - hci.HCI_LE_Set_Advertising_Enable_Command(advertising_enable=1), - check_result=True, + await self.device.send_sync_command( + hci.HCI_LE_Set_Advertising_Enable_Command(advertising_enable=1) ) async def stop(self) -> None: # Disable advertising - await self.device.send_command( - hci.HCI_LE_Set_Advertising_Enable_Command(advertising_enable=0), - check_result=True, + await self.device.send_sync_command( + hci.HCI_LE_Set_Advertising_Enable_Command(advertising_enable=0) ) @@ -635,7 +631,7 @@ async def set_advertising_parameters( "connectable and scannable" ) - response = await self.device.send_command( + response = await self.device.send_sync_command( hci.HCI_LE_Set_Extended_Advertising_Parameters_Command( advertising_handle=self.advertising_handle, advertising_event_properties=int( @@ -668,22 +664,20 @@ async def set_advertising_parameters( scan_request_notification_enable=( 1 if advertising_parameters.enable_scan_request_notifications else 0 ), - ), - check_result=True, + ) ) - self.selected_tx_power = response.return_parameters.selected_tx_power + self.selected_tx_power = response.selected_tx_power self.advertising_parameters = advertising_parameters async def set_advertising_data(self, advertising_data: bytes) -> None: # pylint: disable=line-too-long - await self.device.send_command( + await self.device.send_sync_command( hci.HCI_LE_Set_Extended_Advertising_Data_Command( advertising_handle=self.advertising_handle, operation=hci.HCI_LE_Set_Extended_Advertising_Data_Command.Operation.COMPLETE_DATA, fragment_preference=hci.HCI_LE_Set_Extended_Advertising_Parameters_Command.SHOULD_NOT_FRAGMENT, advertising_data=advertising_data, - ), - check_result=True, + ) ) self.advertising_data = advertising_data @@ -699,21 +693,20 @@ async def set_scan_response_data(self, scan_response_data: bytes) -> None: ) return - await self.device.send_command( + await self.device.send_sync_command( hci.HCI_LE_Set_Extended_Scan_Response_Data_Command( advertising_handle=self.advertising_handle, operation=hci.HCI_LE_Set_Extended_Advertising_Data_Command.Operation.COMPLETE_DATA, fragment_preference=hci.HCI_LE_Set_Extended_Advertising_Parameters_Command.SHOULD_NOT_FRAGMENT, scan_response_data=scan_response_data, - ), - check_result=True, + ) ) self.scan_response_data = scan_response_data async def set_periodic_advertising_parameters( self, advertising_parameters: PeriodicAdvertisingParameters ) -> None: - await self.device.send_command( + await self.device.send_sync_command( hci.HCI_LE_Set_Periodic_Advertising_Parameters_Command( advertising_handle=self.advertising_handle, periodic_advertising_interval_min=int( @@ -723,29 +716,26 @@ async def set_periodic_advertising_parameters( advertising_parameters.periodic_advertising_interval_max / 1.25 ), periodic_advertising_properties=advertising_parameters.periodic_advertising_properties, - ), - check_result=True, + ) ) self.periodic_advertising_parameters = advertising_parameters async def set_periodic_advertising_data(self, advertising_data: bytes) -> None: - await self.device.send_command( + await self.device.send_sync_command( hci.HCI_LE_Set_Periodic_Advertising_Data_Command( advertising_handle=self.advertising_handle, operation=hci.HCI_LE_Set_Extended_Advertising_Data_Command.Operation.COMPLETE_DATA, advertising_data=advertising_data, - ), - check_result=True, + ) ) self.periodic_advertising_data = advertising_data async def set_random_address(self, random_address: hci.Address) -> None: - await self.device.send_command( + await self.device.send_sync_command( hci.HCI_LE_Set_Advertising_Set_Random_Address_Command( advertising_handle=self.advertising_handle, random_address=(random_address or self.device.random_address), - ), - check_result=True, + ) ) async def start( @@ -761,28 +751,26 @@ async def start( max_advertising_events: Maximum number of events to advertise for. Use 0 (the default) for an unlimited number of advertisements. """ - await self.device.send_command( + await self.device.send_sync_command( hci.HCI_LE_Set_Extended_Advertising_Enable_Command( enable=1, advertising_handles=[self.advertising_handle], durations=[round(duration * 100)], max_extended_advertising_events=[max_advertising_events], - ), - check_result=True, + ) ) self.enabled = True self.emit(self.EVENT_START) async def stop(self) -> None: - await self.device.send_command( + await self.device.send_sync_command( hci.HCI_LE_Set_Extended_Advertising_Enable_Command( enable=0, advertising_handles=[self.advertising_handle], durations=[0], max_extended_advertising_events=[0], - ), - check_result=True, + ) ) self.enabled = False @@ -791,12 +779,11 @@ async def stop(self) -> None: async def start_periodic(self, include_adi: bool = False) -> None: if self.periodic_enabled: return - await self.device.send_command( + await self.device.send_sync_command( hci.HCI_LE_Set_Periodic_Advertising_Enable_Command( enable=1 | (2 if include_adi else 0), advertising_handle=self.advertising_handle, - ), - check_result=True, + ) ) self.periodic_enabled = True @@ -805,23 +792,21 @@ async def start_periodic(self, include_adi: bool = False) -> None: async def stop_periodic(self) -> None: if not self.periodic_enabled: return - await self.device.send_command( + await self.device.send_sync_command( hci.HCI_LE_Set_Periodic_Advertising_Enable_Command( enable=0, advertising_handle=self.advertising_handle, - ), - check_result=True, + ) ) self.periodic_enabled = False self.emit(self.EVENT_STOP_PERIODIC) async def remove(self) -> None: - await self.device.send_command( + await self.device.send_sync_command( hci.HCI_LE_Remove_Advertising_Set_Command( advertising_handle=self.advertising_handle - ), - check_result=True, + ) ) del self.device.extended_advertising_sets[self.advertising_handle] @@ -916,7 +901,7 @@ async def establish(self) -> None: hci.HCI_LE_Periodic_Advertising_Create_Sync_Command.Options.DUPLICATE_FILTERING_INITIALLY_ENABLED ) - await self.device.send_command( + await self.device.send_async_command( hci.HCI_LE_Periodic_Advertising_Create_Sync_Command( options=options, advertising_sid=self.sid, @@ -925,8 +910,7 @@ async def establish(self) -> None: skip=self.skip, sync_timeout=int(self.sync_timeout * 100), sync_cte_type=0, - ), - check_result=True, + ) ) self.state = self.State.PENDING @@ -937,18 +921,20 @@ async def terminate(self) -> None: if self.state == self.State.PENDING: self.state = self.State.CANCELLED - response = await self.device.send_command( - hci.HCI_LE_Periodic_Advertising_Create_Sync_Cancel_Command(), - ) - if response.return_parameters == hci.HCI_SUCCESS: + try: + await self.device.send_sync_command( + hci.HCI_LE_Periodic_Advertising_Create_Sync_Cancel_Command() + ) if self in self.device.periodic_advertising_syncs: self.device.periodic_advertising_syncs.remove(self) + except hci.HCI_Error: + pass return if self.state in (self.State.ESTABLISHED, self.State.ERROR, self.State.LOST): self.state = self.State.TERMINATED if self.sync_handle is not None: - await self.device.send_command( + await self.device.send_sync_command( hci.HCI_LE_Periodic_Advertising_Terminate_Sync_Command( sync_handle=self.sync_handle ) @@ -1118,11 +1104,10 @@ async def terminate( with closing(utils.EventWatcher()) as watcher: terminated = asyncio.Event() watcher.once(self, Big.Event.TERMINATION, lambda _: terminated.set()) - await self.device.send_command( + await self.device.send_async_command( hci.HCI_LE_Terminate_BIG_Command( big_handle=self.big_handle, reason=reason - ), - check_result=True, + ) ) await terminated.wait() @@ -1174,9 +1159,8 @@ async def terminate(self) -> None: logger.error('BIG Sync %d is not active.', self.big_handle) return - await self.device.send_command( - hci.HCI_LE_BIG_Terminate_Sync_Command(big_handle=self.big_handle), - check_result=True, + await self.device.send_sync_command( + hci.HCI_LE_BIG_Terminate_Sync_Command(big_handle=self.big_handle) ) self.state = BigSync.State.TERMINATED @@ -1499,7 +1483,7 @@ async def setup_data_path( async with self._data_path_lock: if direction in self.data_paths: return - await self.device.send_command( + await self.device.send_sync_command( hci.HCI_LE_Setup_ISO_Data_Path_Command( connection_handle=self.handle, data_path_direction=direction, @@ -1507,8 +1491,7 @@ async def setup_data_path( codec_id=codec_id or hci.CodingFormat(hci.CodecID.TRANSPARENT), controller_delay=controller_delay, codec_configuration=codec_configuration, - ), - check_result=True, + ) ) self.data_paths.add(direction) @@ -1525,14 +1508,13 @@ async def remove_data_path(self, directions: Iterable[_IsoLink.Direction]) -> No directions_to_remove = set(directions).intersection(self.data_paths) if not directions_to_remove: return - await self.device.send_command( + await self.device.send_sync_command( hci.HCI_LE_Remove_ISO_Data_Path_Command( connection_handle=self.handle, data_path_direction=sum( 1 << direction for direction in directions_to_remove ), - ), - check_result=True, + ) ) self.data_paths.difference_update(directions_to_remove) @@ -1541,14 +1523,13 @@ def write(self, sdu: bytes) -> None: self.device.host.send_iso_sdu(connection_handle=self.handle, sdu=sdu) async def get_tx_time_stamp(self) -> tuple[int, int, int]: - response = await self.device.host.send_command( - hci.HCI_LE_Read_ISO_TX_Sync_Command(connection_handle=self.handle), - check_result=True, + response = await self.device.host.send_sync_command( + hci.HCI_LE_Read_ISO_TX_Sync_Command(connection_handle=self.handle) ) return ( - response.return_parameters.packet_sequence_number, - response.return_parameters.tx_time_stamp, - response.return_parameters.time_offset, + response.packet_sequence_number, + response.tx_time_stamp, + response.time_offset, ) @property @@ -2371,7 +2352,7 @@ def __init__( self._host = None self.powered_on = False self.auto_restart_inquiry = True - self.command_timeout = 10 # seconds + self.command_timeout = 10.0 # seconds self.gatt_server = gatt_server.Server(self) self.sdp_server = sdp.Server(self) self.l2cap_channel_manager = l2cap.ChannelManager( @@ -2677,10 +2658,73 @@ def create_l2cap_server( def send_l2cap_pdu(self, connection_handle: int, cid: int, pdu: bytes) -> None: self.host.send_l2cap_pdu(connection_handle, cid, pdu) - async def send_command(self, command: hci.HCI_Command, check_result: bool = False): + @overload + async def send_command( + self, command: hci.HCI_SyncCommand, check_result: bool = False + ) -> hci.HCI_Command_Complete_Event: ... + + @overload + async def send_command( + self, command: hci.HCI_AsyncCommand, check_result: bool = False + ) -> hci.HCI_Command_Status_Event: ... + + async def send_command( + self, + command: hci.HCI_SyncCommand | hci.HCI_AsyncCommand, + check_result: bool = False, + ) -> hci.HCI_Command_Complete_Event | hci.HCI_Command_Status_Event: + ''' + Send a synchronous or asynchronous command via the host. + + NOTE: use `send_sync_command` instead for synchronous commands. + ''' + try: + return await self.host.send_command( + command, check_result, self.command_timeout + ) + except asyncio.TimeoutError as error: + logger.warning(f'!!! Command {command.name} timed out') + raise CommandTimeoutError() from error + + async def send_sync_command( + self, command: hci.HCI_SyncCommand[_RP], check_status: bool = True + ) -> _RP: + ''' + Send a synchronous command via the host. + + Params: + command: the command to send. + check_status: If `True`, check the `status` field of the response's + `return_parameters` and raise and exception if not equal to `SUCCESS`. + + Returns: + An instance of the return parameters class associated with the command class. + ''' + try: + return await self.host.send_sync_command( + command, check_status, self.command_timeout + ) + except asyncio.TimeoutError as error: + logger.warning(f'!!! Command {command.name} timed out') + raise CommandTimeoutError() from error + + async def send_async_command( + self, command: hci.HCI_AsyncCommand, check_status: bool = True + ) -> hci.HCI_ErrorCode: + ''' + Send an asynchronous command via the host. + + Params: + command: the command to send. + check_status: If `True`, check the `status` field of the response and + raise and exception if not equal to `PENDING`. + + Returns: + An instance of the return parameters class associated with the command class. + ''' try: - return await asyncio.wait_for( - self.host.send_command(command, check_result), self.command_timeout + return await self.host.send_async_command( + command, check_status, self.command_timeout ) except asyncio.TimeoutError as error: logger.warning(f'!!! Command {command.name} timed out') @@ -2691,12 +2735,12 @@ async def power_on(self) -> None: await self.host.reset() # Try to get the public address from the controller - response = await self.send_command(hci.HCI_Read_BD_ADDR_Command()) - if response.return_parameters.status == hci.HCI_SUCCESS: - logger.debug( - color(f'BD_ADDR: {response.return_parameters.bd_addr}', 'yellow') - ) - self.public_address = response.return_parameters.bd_addr + response = await self.host.send_sync_command( + hci.HCI_Read_BD_ADDR_Command(), check_status=False + ) + if response.status == hci.HCI_SUCCESS: + logger.debug(color(f'BD_ADDR: {response.bd_addr}', 'yellow')) + self.public_address = response.bd_addr # Instantiate the Key Store (we do this here rather than at __init__ time # because some Key Store implementations use the public address as a namespace) @@ -2710,12 +2754,11 @@ async def power_on(self) -> None: ) if self.host.supports_command(hci.HCI_WRITE_LE_HOST_SUPPORT_COMMAND): - await self.send_command( + await self.send_sync_command( hci.HCI_Write_LE_Host_Support_Command( le_supported_host=int(self.le_enabled), simultaneous_le_host=int(self.le_simultaneous_enabled), - ), - check_result=True, + ) ) if self.le_enabled: @@ -2742,11 +2785,10 @@ async def power_on(self) -> None: 'yellow', ) ) - await self.send_command( + await self.send_sync_command( hci.HCI_LE_Set_Random_Address_Command( random_address=self.random_address - ), - check_result=True, + ) ) # Load the address resolving list @@ -2755,84 +2797,83 @@ async def power_on(self) -> None: # Enable address resolution if self.address_resolution_offload: - await self.send_command( + await self.send_sync_command( hci.HCI_LE_Set_Address_Resolution_Enable_Command( address_resolution_enable=1 - ), - check_result=True, + ) ) if self.cis_enabled: - await self.send_command( + await self.send_sync_command( hci.HCI_LE_Set_Host_Feature_Command( bit_number=hci.LeFeature.CONNECTED_ISOCHRONOUS_STREAM, bit_value=1, - ), - check_result=True, + ) ) if self.le_subrate_enabled: - await self.send_command( + await self.send_sync_command( hci.HCI_LE_Set_Host_Feature_Command( bit_number=hci.LeFeature.CONNECTION_SUBRATING_HOST_SUPPORT, bit_value=1, - ), - check_result=True, + ) ) if self.config.channel_sounding_enabled: - await self.send_command( + await self.send_sync_command( hci.HCI_LE_Set_Host_Feature_Command( bit_number=hci.LeFeature.CHANNEL_SOUNDING_HOST_SUPPORT, bit_value=1, - ), - check_result=True, + ) ) - result = await self.send_command( - hci.HCI_LE_CS_Read_Local_Supported_Capabilities_Command(), - check_result=True, + result = await self.send_sync_command( + hci.HCI_LE_CS_Read_Local_Supported_Capabilities_Command() ) self.cs_capabilities = ChannelSoundingCapabilities( - num_config_supported=result.return_parameters.num_config_supported, - max_consecutive_procedures_supported=result.return_parameters.max_consecutive_procedures_supported, - num_antennas_supported=result.return_parameters.num_antennas_supported, - max_antenna_paths_supported=result.return_parameters.max_antenna_paths_supported, - roles_supported=result.return_parameters.roles_supported, - modes_supported=result.return_parameters.modes_supported, - rtt_capability=result.return_parameters.rtt_capability, - rtt_aa_only_n=result.return_parameters.rtt_aa_only_n, - rtt_sounding_n=result.return_parameters.rtt_sounding_n, - rtt_random_payload_n=result.return_parameters.rtt_random_payload_n, - nadm_sounding_capability=result.return_parameters.nadm_sounding_capability, - nadm_random_capability=result.return_parameters.nadm_random_capability, - cs_sync_phys_supported=result.return_parameters.cs_sync_phys_supported, - subfeatures_supported=result.return_parameters.subfeatures_supported, - t_ip1_times_supported=result.return_parameters.t_ip1_times_supported, - t_ip2_times_supported=result.return_parameters.t_ip2_times_supported, - t_fcs_times_supported=result.return_parameters.t_fcs_times_supported, - t_pm_times_supported=result.return_parameters.t_pm_times_supported, - t_sw_time_supported=result.return_parameters.t_sw_time_supported, - tx_snr_capability=result.return_parameters.tx_snr_capability, + num_config_supported=result.num_config_supported, + max_consecutive_procedures_supported=result.max_consecutive_procedures_supported, + num_antennas_supported=result.num_antennas_supported, + max_antenna_paths_supported=result.max_antenna_paths_supported, + roles_supported=result.roles_supported, + modes_supported=result.modes_supported, + rtt_capability=result.rtt_capability, + rtt_aa_only_n=result.rtt_aa_only_n, + rtt_sounding_n=result.rtt_sounding_n, + rtt_random_payload_n=result.rtt_random_payload_n, + nadm_sounding_capability=result.nadm_sounding_capability, + nadm_random_capability=result.nadm_random_capability, + cs_sync_phys_supported=result.cs_sync_phys_supported, + subfeatures_supported=result.subfeatures_supported, + t_ip1_times_supported=result.t_ip1_times_supported, + t_ip2_times_supported=result.t_ip2_times_supported, + t_fcs_times_supported=result.t_fcs_times_supported, + t_pm_times_supported=result.t_pm_times_supported, + t_sw_time_supported=result.t_sw_time_supported, + tx_snr_capability=result.tx_snr_capability, ) if self.classic_enabled: - await self.send_command( - hci.HCI_Write_Local_Name_Command(local_name=self.name.encode('utf8')) + await self.send_sync_command( + hci.HCI_Write_Local_Name_Command(local_name=self.name.encode('utf8')), + check_status=False, ) - await self.send_command( + await self.send_sync_command( hci.HCI_Write_Class_Of_Device_Command( class_of_device=self.class_of_device - ) + ), + check_status=False, ) - await self.send_command( + await self.send_sync_command( hci.HCI_Write_Simple_Pairing_Mode_Command( simple_pairing_mode=int(self.classic_ssp_enabled) - ) + ), + check_status=False, ) - await self.send_command( + await self.send_sync_command( hci.HCI_Write_Secure_Connections_Host_Support_Command( secure_connections_host_support=int(self.classic_sc_enabled) - ) + ), + check_status=False, ) await self.set_connectable(self.connectable) await self.set_discoverable(self.discoverable) @@ -2841,17 +2882,15 @@ async def power_on(self) -> None: if self.host.supports_lmp_features( hci.LmpFeatureMask.INTERLACED_PAGE_SCAN ): - await self.send_command( - hci.HCI_Write_Page_Scan_Type_Command(page_scan_type=1), - check_result=True, + await self.send_sync_command( + hci.HCI_Write_Page_Scan_Type_Command(page_scan_type=1) ) if self.host.supports_lmp_features( hci.LmpFeatureMask.INTERLACED_INQUIRY_SCAN ): - await self.send_command( - hci.HCI_Write_Inquiry_Scan_Type_Command(scan_type=1), - check_result=True, + await self.send_sync_command( + hci.HCI_Write_Inquiry_Scan_Type_Command(scan_type=1) ) # Done @@ -2883,15 +2922,17 @@ async def update_rpa(self) -> bool: return False random_address = hci.Address.generate_private_address(self.irk) - response = await self.send_command( - hci.HCI_LE_Set_Random_Address_Command(random_address=self.random_address) - ) - if response.return_parameters == hci.HCI_SUCCESS: + try: + await self.send_sync_command( + hci.HCI_LE_Set_Random_Address_Command( + random_address=self.random_address + ) + ) logger.info(f'new RPA: {random_address}') self.random_address = random_address return True - else: - logger.warning(f'failed to set RPA: {response.return_parameters}') + except hci.HCI_Error as error: + logger.warning(f'failed to set RPA: {error.error_code!r}') return False async def _run_rpa_periodic_update(self) -> None: @@ -2909,30 +2950,26 @@ async def refresh_resolving_list(self) -> None: self.address_resolver = smp.AddressResolver(resolving_keys) if self.address_resolution_offload or self.address_generation_offload: - await self.send_command( - hci.HCI_LE_Clear_Resolving_List_Command(), check_result=True - ) + await self.send_sync_command(hci.HCI_LE_Clear_Resolving_List_Command()) # Add an empty entry for non-directed address generation. - await self.send_command( + await self.send_sync_command( hci.HCI_LE_Add_Device_To_Resolving_List_Command( peer_identity_address_type=hci.Address.ANY.address_type, peer_identity_address=hci.Address.ANY, peer_irk=bytes(16), local_irk=self.irk, - ), - check_result=True, + ) ) for irk, address in resolving_keys: - await self.send_command( + await self.send_sync_command( hci.HCI_LE_Add_Device_To_Resolving_List_Command( peer_identity_address_type=address.address_type, peer_identity_address=address, peer_irk=irk, local_irk=self.irk, - ), - check_result=True, + ) ) def supports_le_features(self, feature: hci.LeFeatureMask) -> bool: @@ -3200,11 +3237,10 @@ async def create_advertising_set( except hci.HCI_Error as error: # Remove the advertising set so that it doesn't stay dangling in the # controller. - await self.send_command( + await self.send_sync_command( hci.HCI_LE_Remove_Advertising_Set_Command( advertising_handle=advertising_handle - ), - check_result=False, + ) ) raise error @@ -3287,7 +3323,7 @@ async def start_scanning( if scanning_phy_count == 0: raise InvalidArgumentError('at least one scanning PHY must be enabled') - await self.send_command( + await self.send_sync_command( hci.HCI_LE_Set_Extended_Scan_Parameters_Command( own_address_type=own_address_type, scanning_filter_policy=scanning_filter_policy, @@ -3295,19 +3331,17 @@ async def start_scanning( scan_types=[scan_type] * scanning_phy_count, scan_intervals=[int(scan_interval / 0.625)] * scanning_phy_count, scan_windows=[int(scan_window / 0.625)] * scanning_phy_count, - ), - check_result=True, + ) ) # Enable scanning - await self.send_command( + await self.send_sync_command( hci.HCI_LE_Set_Extended_Scan_Enable_Command( enable=1, filter_duplicates=1 if filter_duplicates else 0, duration=0, # TODO allow other values period=0, # TODO allow other values - ), - check_result=True, + ) ) else: # Set the scanning parameters @@ -3316,7 +3350,7 @@ async def start_scanning( if active else hci.HCI_LE_Set_Scan_Parameters_Command.PASSIVE_SCANNING ) - await self.send_command( + await self.send_sync_command( # pylint: disable=line-too-long hci.HCI_LE_Set_Scan_Parameters_Command( le_scan_type=scan_type, @@ -3324,16 +3358,14 @@ async def start_scanning( le_scan_window=int(scan_window / 0.625), own_address_type=own_address_type, scanning_filter_policy=hci.HCI_LE_Set_Scan_Parameters_Command.BASIC_UNFILTERED_POLICY, - ), - check_result=True, + ) ) # Enable scanning - await self.send_command( + await self.send_sync_command( hci.HCI_LE_Set_Scan_Enable_Command( le_scan_enable=1, filter_duplicates=1 if filter_duplicates else 0 - ), - check_result=True, + ) ) self.scanning_is_passive = not active @@ -3342,18 +3374,16 @@ async def start_scanning( async def stop_scanning(self, legacy: bool = False) -> None: # Disable scanning if not legacy and self.supports_le_extended_advertising: - await self.send_command( + await self.send_sync_command( hci.HCI_LE_Set_Extended_Scan_Enable_Command( enable=0, filter_duplicates=0, duration=0, period=0 - ), - check_result=True, + ) ) else: - await self.send_command( + await self.send_sync_command( hci.HCI_LE_Set_Scan_Enable_Command( le_scan_enable=0, filter_duplicates=0 - ), - check_result=True, + ) ) self.scanning = False @@ -3526,21 +3556,19 @@ def on_biginfo_advertising_report( periodic_advertising_sync.on_biginfo_advertising_report(report) async def start_discovery(self, auto_restart: bool = True) -> None: - await self.send_command( + await self.send_sync_command( hci.HCI_Write_Inquiry_Mode_Command( inquiry_mode=hci.HCI_EXTENDED_INQUIRY_MODE - ), - check_result=True, + ) ) self.discovering = False - await self.send_command( + await self.send_async_command( hci.HCI_Inquiry_Command( lap=hci.HCI_GENERAL_INQUIRY_LAP, inquiry_length=DEVICE_DEFAULT_INQUIRY_LENGTH, num_responses=0, # Unlimited number of responses. - ), - check_result=True, + ) ) self.auto_restart_inquiry = auto_restart @@ -3548,7 +3576,7 @@ async def start_discovery(self, auto_restart: bool = True) -> None: async def stop_discovery(self) -> None: if self.discovering: - await self.send_command(hci.HCI_Inquiry_Cancel_Command(), check_result=True) + await self.send_sync_command(hci.HCI_Inquiry_Cancel_Command()) self.auto_restart_inquiry = True self.discovering = False @@ -3576,9 +3604,8 @@ async def set_scan_enable( else: scan_enable = 0x00 - return await self.send_command( - hci.HCI_Write_Scan_Enable_Command(scan_enable=scan_enable), - check_result=True, + return await self.send_sync_command( + hci.HCI_Write_Scan_Enable_Command(scan_enable=scan_enable) ) async def set_discoverable(self, discoverable: bool = True) -> None: @@ -3591,11 +3618,10 @@ async def set_discoverable(self, discoverable: bool = True) -> None: ) # Update the controller - await self.send_command( + await self.send_sync_command( hci.HCI_Write_Extended_Inquiry_Response_Command( fec_required=0, extended_inquiry_response=self.inquiry_response - ), - check_result=True, + ) ) await self.set_scan_enable( inquiry_scan_enabled=self.discoverable, @@ -3807,7 +3833,7 @@ def on_connection_failure(error: core.ConnectionError): for phy in phys ] - await self.send_command( + await self.send_async_command( hci.HCI_LE_Extended_Create_Connection_Command( initiator_filter_policy=0, own_address_type=own_address_type, @@ -3828,15 +3854,14 @@ def on_connection_failure(error: core.ConnectionError): supervision_timeouts=supervision_timeouts, min_ce_lengths=min_ce_lengths, max_ce_lengths=max_ce_lengths, - ), - check_result=True, + ) ) else: if hci.HCI_LE_1M_PHY not in connection_parameters_preferences: raise InvalidArgumentError('1M PHY preferences required') prefs = connection_parameters_preferences[hci.HCI_LE_1M_PHY] - await self.send_command( + await self.send_async_command( hci.HCI_LE_Create_Connection_Command( le_scan_interval=int( DEVICE_DEFAULT_CONNECT_SCAN_INTERVAL / 0.625 @@ -3858,8 +3883,7 @@ def on_connection_failure(error: core.ConnectionError): supervision_timeout=int(prefs.supervision_timeout / 10), min_ce_length=int(prefs.min_ce_length / 0.625), max_ce_length=int(prefs.max_ce_length / 0.625), - ), - check_result=True, + ) ) else: # Save pending connection @@ -3876,7 +3900,7 @@ def on_connection_failure(error: core.ConnectionError): ) # TODO: allow passing other settings - await self.send_command( + await self.send_async_command( hci.HCI_Create_Connection_Command( bd_addr=peer_address, packet_type=0xCC18, # FIXME: change @@ -3884,8 +3908,7 @@ def on_connection_failure(error: core.ConnectionError): clock_offset=0x0000, allow_role_switch=0x01, reserved=0, - ), - check_result=True, + ) ) # Wait for the connection process to complete @@ -3903,11 +3926,11 @@ def on_connection_failure(error: core.ConnectionError): ) except asyncio.TimeoutError: if transport == PhysicalTransport.LE: - await self.send_command( + await self.send_sync_command( hci.HCI_LE_Create_Connection_Cancel_Command() ) else: - await self.send_command( + await self.send_sync_command( hci.HCI_Create_Connection_Cancel_Command(bd_addr=peer_address) ) @@ -4036,11 +4059,10 @@ def on_connection_failure(error: core.ConnectionError): try: # Accept connection request - await self.send_command( + await self.send_async_command( hci.HCI_Accept_Connection_Request_Command( bd_addr=peer_address, role=role - ), - check_result=True, + ) ) # Wait for connection complete @@ -4076,9 +4098,7 @@ async def cancel_connection(self, peer_address=None): if peer_address is None: if not self.is_le_connecting: return - await self.send_command( - hci.HCI_LE_Create_Connection_Cancel_Command(), check_result=True - ) + await self.send_sync_command(hci.HCI_LE_Create_Connection_Cancel_Command()) # BR/EDR: try to cancel to ongoing connection # NOTE: This API does not prevent from trying to cancel a connection which is @@ -4095,9 +4115,8 @@ async def cancel_connection(self, peer_address=None): peer_address, PhysicalTransport.BR_EDR ) # TODO: timeout - await self.send_command( - hci.HCI_Create_Connection_Cancel_Command(bd_addr=peer_address), - check_result=True, + await self.send_sync_command( + hci.HCI_Create_Connection_Cancel_Command(bd_addr=peer_address) ) async def disconnect( @@ -4115,11 +4134,10 @@ async def disconnect( self.disconnecting = True # Request a disconnection - await self.send_command( + await self.send_async_command( hci.HCI_Disconnect_Command( connection_handle=connection.handle, reason=reason - ), - check_result=True, + ) ) return await utils.cancel_on_event( self, Device.EVENT_FLUSH, pending_disconnection @@ -4143,13 +4161,12 @@ async def set_data_length( if tx_time < 0x0148 or tx_time > 0x4290: raise InvalidArgumentError('tx_time must be between 0x0148 and 0x4290') - return await self.send_command( + await self.send_sync_command( hci.HCI_LE_Set_Data_Length_Command( connection_handle=connection.handle, tx_octets=tx_octets, tx_time=tx_time, - ), - check_result=True, + ) ) async def update_connection_parameters( @@ -4206,7 +4223,7 @@ async def update_connection_parameters( return - await self.send_command( + await self.send_async_command( hci.HCI_LE_Connection_Update_Command( connection_handle=connection.handle, connection_interval_min=connection_interval_min, @@ -4215,27 +4232,23 @@ async def update_connection_parameters( supervision_timeout=supervision_timeout, min_ce_length=min_ce_length, max_ce_length=max_ce_length, - ), - check_result=True, + ) ) async def get_connection_rssi(self, connection): - result = await self.send_command( - hci.HCI_Read_RSSI_Command(handle=connection.handle), check_result=True + result = await self.send_sync_command( + hci.HCI_Read_RSSI_Command(handle=connection.handle) ) - return result.return_parameters.rssi + return result.rssi async def get_connection_phy(self, connection: Connection) -> ConnectionPHY: if not self.host.supports_command(hci.HCI_LE_READ_PHY_COMMAND): return ConnectionPHY(hci.Phy.LE_1M, hci.Phy.LE_1M) - result = await self.send_command( - hci.HCI_LE_Read_PHY_Command(connection_handle=connection.handle), - check_result=True, - ) - return ConnectionPHY( - result.return_parameters.tx_phy, result.return_parameters.rx_phy + result = await self.send_sync_command( + hci.HCI_LE_Read_PHY_Command(connection_handle=connection.handle) ) + return ConnectionPHY(result.tx_phy, result.rx_phy) async def set_connection_phy( self, @@ -4252,57 +4265,53 @@ async def set_connection_phy( (1 if rx_phys is None else 0) << 1 ) - await self.send_command( + await self.send_async_command( hci.HCI_LE_Set_PHY_Command( connection_handle=connection.handle, all_phys=all_phys_bits, tx_phys=hci.phy_list_to_bits(tx_phys), rx_phys=hci.phy_list_to_bits(rx_phys), phy_options=phy_options, - ), - check_result=True, + ) ) async def set_default_phy( self, tx_phys: Iterable[hci.Phy] | None = None, rx_phys: Iterable[hci.Phy] | None = None, - ): + ) -> None: all_phys_bits = (1 if tx_phys is None else 0) | ( (1 if rx_phys is None else 0) << 1 ) - return await self.send_command( + await self.send_sync_command( hci.HCI_LE_Set_Default_PHY_Command( all_phys=all_phys_bits, tx_phys=hci.phy_list_to_bits(tx_phys), rx_phys=hci.phy_list_to_bits(rx_phys), - ), - check_result=True, + ) ) async def transfer_periodic_sync( self, connection: Connection, sync_handle: int, service_data: int = 0 ) -> None: - return await self.send_command( + await self.send_sync_command( hci.HCI_LE_Periodic_Advertising_Sync_Transfer_Command( connection_handle=connection.handle, service_data=service_data, sync_handle=sync_handle, - ), - check_result=True, + ) ) async def transfer_periodic_set_info( self, connection: Connection, advertising_handle: int, service_data: int = 0 ) -> None: - return await self.send_command( + await self.send_sync_command( hci.HCI_LE_Periodic_Advertising_Set_Info_Transfer_Command( connection_handle=connection.handle, service_data=service_data, advertising_handle=advertising_handle, - ), - check_result=True, + ) ) async def find_peer_by_name(self, name: str, transport=PhysicalTransport.LE): @@ -4490,11 +4499,10 @@ def on_authentication_failure(error_code: int) -> None: pending_authentication.set_exception(hci.HCI_Error(error_code)) # Request the authentication - await self.send_command( + await self.send_async_command( hci.HCI_Authentication_Requested_Command( connection_handle=connection.handle - ), - check_result=True, + ) ) # Wait for the authentication to complete @@ -4542,22 +4550,20 @@ def _(error_code: int): if connection.role != hci.Role.CENTRAL: raise InvalidStateError('only centrals can start encryption') - await self.send_command( + await self.send_async_command( hci.HCI_LE_Enable_Encryption_Command( connection_handle=connection.handle, random_number=rand, encrypted_diversifier=ediv, long_term_key=ltk, - ), - check_result=True, + ) ) else: - await self.send_command( + await self.send_async_command( hci.HCI_Set_Connection_Encryption_Command( connection_handle=connection.handle, encryption_enable=0x01 if enable else 0x00, - ), - check_result=True, + ) ) # Wait for the result @@ -4589,9 +4595,8 @@ def _(new_role: hci.Role): def _(error_code: int): pending_role_change.set_exception(hci.HCI_Error(error_code)) - await self.send_command( - hci.HCI_Switch_Role_Command(bd_addr=connection.peer_address, role=role), - check_result=True, + await self.send_async_command( + hci.HCI_Switch_Role_Command(bd_addr=connection.peer_address, role=role) ) await connection.cancel_on_disconnection(pending_role_change) @@ -4616,14 +4621,13 @@ def _(address: hci.Address, error_code: int) -> None: if address == peer_address: pending_name.set_exception(hci.HCI_Error(error_code)) - await self.send_command( + await self.send_async_command( hci.HCI_Remote_Name_Request_Command( bd_addr=peer_address, page_scan_repetition_mode=hci.HCI_Remote_Name_Request_Command.R2, reserved=0, clock_offset=0, # TODO investigate non-0 values - ), - check_result=True, + ) ) # Wait for the result @@ -4634,7 +4638,7 @@ def _(address: hci.Address, error_code: int) -> None: async def setup_cig( self, parameters: CigParameters, - ) -> list[int]: + ) -> Sequence[int]: """Sends hci.HCI_LE_Set_CIG_Parameters_Command. Args: @@ -4643,7 +4647,7 @@ async def setup_cig( Returns: List of created CIS handles corresponding to the same order of [cid_id]. """ - response = await self.send_command( + response = await self.send_sync_command( hci.HCI_LE_Set_CIG_Parameters_Command( cig_id=parameters.cig_id, sdu_interval_c_to_p=parameters.sdu_interval_c_to_p, @@ -4664,13 +4668,12 @@ async def setup_cig( phy_p_to_c=[cis.phy_p_to_c for cis in parameters.cis_parameters], rtn_c_to_p=[cis.rtn_c_to_p for cis in parameters.cis_parameters], rtn_p_to_c=[cis.rtn_p_to_c for cis in parameters.cis_parameters], - ), - check_result=True, + ) ) # Ideally, we should manage CIG lifecycle, but they are not useful for Unicast # Server, so here it only provides a basic functionality for testing. - cis_handles = response.return_parameters.connection_handle[:] + cis_handles = response.connection_handle[:] for cis, cis_handle in zip(parameters.cis_parameters, cis_handles): self._pending_cis[cis_handle] = (cis.cis_id, parameters.cig_id) @@ -4710,12 +4713,11 @@ def on_cis_establishment_failure(cis_link: CisLink, status: int) -> None: watcher.on( self, self.EVENT_CIS_ESTABLISHMENT_FAILURE, on_cis_establishment_failure ) - await self.send_command( + await self.send_async_command( hci.HCI_LE_Create_CIS_Command( cis_connection_handle=[p[0] for p in cis_acl_pairs], acl_connection_handle=[p[1].handle for p in cis_acl_pairs], - ), - check_result=True, + ) ) return await asyncio.gather(*pending_cis_establishments.values()) @@ -4754,11 +4756,10 @@ def on_establishment_failure(status: int) -> None: on_establishment_failure, ) - await self.send_command( + await self.send_async_command( hci.HCI_LE_Accept_CIS_Request_Command( connection_handle=cis_link.handle - ), - check_result=True, + ) ) await pending_establishment @@ -4770,11 +4771,10 @@ async def reject_cis_request( cis_link: CisLink, reason: int = hci.HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR, ) -> None: - await self.send_command( + await self.send_sync_command( hci.HCI_LE_Reject_CIS_Request_Command( connection_handle=cis_link.handle, reason=reason - ), - check_result=True, + ) ) # [LE only] @@ -4803,7 +4803,7 @@ async def create_big( ) try: - await self.send_command( + await self.send_async_command( hci.HCI_LE_Create_BIG_Command( big_handle=big_handle, advertising_handle=advertising_set.advertising_handle, @@ -4817,8 +4817,7 @@ async def create_big( framing=parameters.framing, encryption=1 if parameters.broadcast_code else 0, broadcast_code=parameters.broadcast_code or bytes(16), - ), - check_result=True, + ) ) await established except hci.HCI_Error: @@ -4858,7 +4857,7 @@ async def create_big_sync( ) try: - await self.send_command( + await self.send_async_command( hci.HCI_LE_BIG_Create_Sync_Command( big_handle=big_handle, sync_handle=pa_sync_handle, @@ -4867,8 +4866,7 @@ async def create_big_sync( mse=parameters.mse, big_sync_timeout=parameters.big_sync_timeout, bis=parameters.bis, - ), - check_result=True, + ) ) await established except hci.HCI_Error: @@ -4901,11 +4899,10 @@ def on_failure(handle: int, status: int): watcher.on(self.host, 'le_remote_features', on_le_remote_features) watcher.on(self.host, 'le_remote_features_failure', on_failure) - await self.send_command( + await self.send_async_command( hci.HCI_LE_Read_Remote_Features_Command( connection_handle=connection.handle - ), - check_result=True, + ) ) return await read_feature_future @@ -4926,11 +4923,10 @@ async def get_remote_cs_capabilities( 'channel_sounding_capabilities_failure', lambda status: complete_future.set_exception(hci.HCI_Error(status)), ) - await self.send_command( + await self.send_async_command( hci.HCI_LE_CS_Read_Remote_Supported_Capabilities_Command( connection_handle=connection.handle - ), - check_result=True, + ) ) return await complete_future @@ -4944,14 +4940,13 @@ async def set_default_cs_settings( cs_sync_antenna_selection: int = 0xFF, # No Preference max_tx_power: int = 0x04, # 4 dB ) -> None: - await self.send_command( + await self.send_sync_command( hci.HCI_LE_CS_Set_Default_Settings_Command( connection_handle=connection.handle, role_enable=role_enable, cs_sync_antenna_selection=cs_sync_antenna_selection, max_tx_power=max_tx_power, - ), - check_result=True, + ) ) @utils.experimental('Only for testing.') @@ -5000,7 +4995,7 @@ async def create_cs_config( 'channel_sounding_config_failure', lambda status: complete_future.set_exception(hci.HCI_Error(status)), ) - await self.send_command( + await self.send_async_command( hci.HCI_LE_CS_Create_Config_Command( connection_handle=connection.handle, config_id=config_id, @@ -5020,8 +5015,7 @@ async def create_cs_config( ch3c_shape=ch3c_shape, ch3c_jump=ch3c_jump, reserved=0x00, - ), - check_result=True, + ) ) return await complete_future @@ -5041,11 +5035,10 @@ def on_event(event: hci.HCI_LE_CS_Security_Enable_Complete_Event) -> None: complete_future.set_exception(hci.HCI_Error(event.status)) watcher.once(self.host, 'cs_security', on_event) - await self.send_command( + await self.send_async_command( hci.HCI_LE_CS_Security_Enable_Command( connection_handle=connection.handle - ), - check_result=True, + ) ) return await complete_future @@ -5067,7 +5060,7 @@ async def set_cs_procedure_parameters( snr_control_initiator=hci.CsSnr.NOT_APPLIED, snr_control_reflector=hci.CsSnr.NOT_APPLIED, ) -> None: - await self.send_command( + await self.send_sync_command( hci.HCI_LE_CS_Set_Procedure_Parameters_Command( connection_handle=connection.handle, config_id=config.config_id, @@ -5083,8 +5076,7 @@ async def set_cs_procedure_parameters( preferred_peer_antenna=preferred_peer_antenna, snr_control_initiator=snr_control_initiator, snr_control_reflector=snr_control_reflector, - ), - check_result=True, + ) ) @utils.experimental('Only for testing.') @@ -5106,13 +5098,12 @@ async def enable_cs_procedure( 'channel_sounding_procedure_failure', lambda x: complete_future.set_exception(hci.HCI_Error(x)), ) - await self.send_command( + await self.send_async_command( hci.HCI_LE_CS_Procedure_Enable_Command( connection_handle=connection.handle, config_id=config.config_id, enable=enabled, - ), - check_result=True, + ) ) return await complete_future @@ -5743,12 +5734,14 @@ def on_authentication_io_capability_request(self, connection: Connection): )[1 if pairing_config.bonding else 0][1 if pairing_config.mitm else 0] # Respond - self.host.send_command_sync( - hci.HCI_IO_Capability_Request_Reply_Command( - bd_addr=connection.peer_address, - io_capability=pairing_config.delegate.classic_io_capability, - oob_data_present=0x00, # Not present - authentication_requirements=authentication_requirements, + utils.AsyncRunner.spawn( + self.host.send_sync_command( + hci.HCI_IO_Capability_Request_Reply_Command( + bd_addr=connection.peer_address, + io_capability=pairing_config.delegate.classic_io_capability, + oob_data_present=0x00, # Not present + authentication_requirements=authentication_requirements, + ) ) ) @@ -5832,7 +5825,7 @@ async def na() -> bool: async def reply() -> None: try: if await connection.cancel_on_disconnection(method()): - await self.host.send_command( + await self.host.send_sync_command( hci.HCI_User_Confirmation_Request_Reply_Command( bd_addr=connection.peer_address ) @@ -5841,7 +5834,7 @@ async def reply() -> None: except Exception: logger.exception('exception while confirming') - await self.host.send_command( + await self.host.send_sync_command( hci.HCI_User_Confirmation_Request_Negative_Reply_Command( bd_addr=connection.peer_address ) @@ -5862,7 +5855,7 @@ async def reply() -> None: pairing_config.delegate.get_number() ) if number is not None: - await self.host.send_command( + await self.host.send_sync_command( hci.HCI_User_Passkey_Request_Reply_Command( bd_addr=connection.peer_address, numeric_value=number ) @@ -5871,7 +5864,7 @@ async def reply() -> None: except Exception: logger.exception('exception while asking for pass-key') - await self.host.send_command( + await self.host.send_sync_command( hci.HCI_User_Passkey_Request_Negative_Reply_Command( bd_addr=connection.peer_address ) @@ -5914,7 +5907,7 @@ async def get_pin_code() -> None: pin_code_len = len(pin_code) if not 1 <= pin_code_len <= 16: raise core.InvalidArgumentError("pin_code should be 1-16 bytes") - await self.host.send_command( + await self.host.send_sync_command( hci.HCI_PIN_Code_Request_Reply_Command( bd_addr=connection.peer_address, pin_code_length=pin_code_len, @@ -5923,17 +5916,19 @@ async def get_pin_code() -> None: ) else: logger.debug("delegate.get_string() returned None") - await self.host.send_command( + await self.host.send_sync_command( hci.HCI_PIN_Code_Request_Negative_Reply_Command( bd_addr=connection.peer_address ) ) - asyncio.create_task(get_pin_code()) + utils.AsyncRunner.spawn(get_pin_code()) else: - self.host.send_command_sync( - hci.HCI_PIN_Code_Request_Negative_Reply_Command( - bd_addr=connection.peer_address + utils.AsyncRunner.spawn( + self.host.send_sync_command( + hci.HCI_PIN_Code_Request_Negative_Reply_Command( + bd_addr=connection.peer_address + ) ) ) diff --git a/bumble/drivers/intel.py b/bumble/drivers/intel.py index 486951bd..71888b80 100644 --- a/bumble/drivers/intel.py +++ b/bumble/drivers/intel.py @@ -89,52 +89,55 @@ hci.HCI_Command.register_commands(globals()) -@hci.HCI_Command.command @dataclasses.dataclass -class HCI_Intel_Read_Version_Command(hci.HCI_Command): - param0: int = dataclasses.field(metadata=hci.metadata(1)) +class HCI_Intel_Read_Version_ReturnParameters(hci.HCI_StatusReturnParameters): + tlv: bytes = hci.field(metadata=hci.metadata('*')) + - return_parameters_fields = [ - ("status", hci.STATUS_SPEC), - ("tlv", "*"), - ] +@hci.HCI_SyncCommand.sync_command(HCI_Intel_Read_Version_ReturnParameters) +@dataclasses.dataclass +class HCI_Intel_Read_Version_Command( + hci.HCI_SyncCommand[HCI_Intel_Read_Version_ReturnParameters] +): + param0: int = dataclasses.field(metadata=hci.metadata(1)) -@hci.HCI_Command.command +@hci.HCI_SyncCommand.sync_command(hci.HCI_StatusReturnParameters) @dataclasses.dataclass -class Hci_Intel_Secure_Send_Command(hci.HCI_Command): +class Hci_Intel_Secure_Send_Command( + hci.HCI_SyncCommand[hci.HCI_StatusReturnParameters] +): data_type: int = dataclasses.field(metadata=hci.metadata(1)) data: bytes = dataclasses.field(metadata=hci.metadata("*")) - return_parameters_fields = [ - ("status", 1), - ] + +@dataclasses.dataclass +class HCI_Intel_Reset_ReturnParameters(hci.HCI_ReturnParameters): + data: bytes = hci.field(metadata=hci.metadata('*')) -@hci.HCI_Command.command +@hci.HCI_SyncCommand.sync_command(HCI_Intel_Reset_ReturnParameters) @dataclasses.dataclass -class HCI_Intel_Reset_Command(hci.HCI_Command): +class HCI_Intel_Reset_Command(hci.HCI_SyncCommand[HCI_Intel_Reset_ReturnParameters]): reset_type: int = dataclasses.field(metadata=hci.metadata(1)) patch_enable: int = dataclasses.field(metadata=hci.metadata(1)) ddc_reload: int = dataclasses.field(metadata=hci.metadata(1)) boot_option: int = dataclasses.field(metadata=hci.metadata(1)) boot_address: int = dataclasses.field(metadata=hci.metadata(4)) - return_parameters_fields = [ - ("data", "*"), - ] +@dataclasses.dataclass +class HCI_Intel_Write_Device_Config_ReturnParameters(hci.HCI_StatusReturnParameters): + params: bytes = hci.field(metadata=hci.metadata('*')) -@hci.HCI_Command.command + +@hci.HCI_SyncCommand.sync_command(HCI_Intel_Write_Device_Config_ReturnParameters) @dataclasses.dataclass -class Hci_Intel_Write_Device_Config_Command(hci.HCI_Command): +class HCI_Intel_Write_Device_Config_Command( + hci.HCI_SyncCommand[HCI_Intel_Write_Device_Config_ReturnParameters] +): data: bytes = dataclasses.field(metadata=hci.metadata("*")) - return_parameters_fields = [ - ("status", hci.STATUS_SPEC), - ("params", "*"), - ] - # ----------------------------------------------------------------------------- # Functions @@ -402,7 +405,7 @@ def on_packet(self, packet: bytes) -> None: self.host.on_hci_event_packet(event) return - if not event.return_parameters == hci.HCI_SUCCESS: + if not event.return_parameters.status == hci.HCI_SUCCESS: raise DriverError("HCI_Command_Complete_Event error") if self.max_in_flight_firmware_load_commands != event.num_hci_command_packets: @@ -641,8 +644,8 @@ async def load_device_config(self, ddc_data: bytes) -> None: while ddc_data: ddc_len = 1 + ddc_data[0] ddc_payload = ddc_data[:ddc_len] - await self.host.send_command( - Hci_Intel_Write_Device_Config_Command(data=ddc_payload) + await self.host.send_sync_command( + HCI_Intel_Write_Device_Config_Command(data=ddc_payload) ) ddc_data = ddc_data[ddc_len:] @@ -660,31 +663,26 @@ async def reboot_bootloader(self) -> None: async def read_device_info(self) -> dict[ValueType, Any]: self.host.ready = True - response = await self.host.send_command(hci.HCI_Reset_Command()) - if not ( - isinstance(response, hci.HCI_Command_Complete_Event) - and response.return_parameters - in (hci.HCI_UNKNOWN_HCI_COMMAND_ERROR, hci.HCI_SUCCESS) - ): + response1 = await self.host.send_sync_command( + hci.HCI_Reset_Command(), check_status=False + ) + if response1.status not in (hci.HCI_UNKNOWN_HCI_COMMAND_ERROR, hci.HCI_SUCCESS): # When the controller is in operational mode, the response is a # successful response. # When the controller is in bootloader mode, # HCI_UNKNOWN_HCI_COMMAND_ERROR is the expected response. Anything # else is a failure. - logger.warning(f"unexpected response: {response}") + logger.warning(f"unexpected response: {response1}") raise DriverError("unexpected HCI response") # Read the firmware version. - response = await self.host.send_command( - HCI_Intel_Read_Version_Command(param0=0xFF) + response2 = await self.host.send_sync_command( + HCI_Intel_Read_Version_Command(param0=0xFF), check_status=False ) - if not isinstance(response, hci.HCI_Command_Complete_Event): - raise DriverError("unexpected HCI response") - - if response.return_parameters.status != 0: # type: ignore + if response2.status != 0: # type: ignore raise DriverError("HCI_Intel_Read_Version_Command error") - tlvs = _parse_tlv(response.return_parameters.tlv) # type: ignore + tlvs = _parse_tlv(response2.tlv) # type: ignore # Convert the list to a dict. That's Ok here because we only expect each type # to appear just once. diff --git a/bumble/drivers/rtk.py b/bumble/drivers/rtk.py index 49071bce..2519f71c 100644 --- a/bumble/drivers/rtk.py +++ b/bumble/drivers/rtk.py @@ -16,6 +16,7 @@ Based on various online bits of information, including the Linux kernel. (see `drivers/bluetooth/btrtl.c`) """ +from __future__ import annotations import asyncio import enum @@ -31,10 +32,14 @@ # Imports # ----------------------------------------------------------------------------- from dataclasses import dataclass, field +from typing import TYPE_CHECKING from bumble import core, hci from bumble.drivers import common +if TYPE_CHECKING: + from bumble.host import Host + # ----------------------------------------------------------------------------- # Logging # ----------------------------------------------------------------------------- @@ -188,23 +193,36 @@ class RtlProjectId(enum.IntEnum): hci.HCI_Command.register_commands(globals()) -@hci.HCI_Command.command @dataclass -class HCI_RTK_Read_ROM_Version_Command(hci.HCI_Command): - return_parameters_fields = [("status", hci.STATUS_SPEC), ("version", 1)] +class HCI_RTK_Read_ROM_Version_ReturnParameters(hci.HCI_StatusReturnParameters): + version: int = field(metadata=hci.metadata(1)) + + +@hci.HCI_SyncCommand.sync_command(HCI_RTK_Read_ROM_Version_ReturnParameters) +@dataclass +class HCI_RTK_Read_ROM_Version_Command( + hci.HCI_SyncCommand[HCI_RTK_Read_ROM_Version_ReturnParameters] +): + pass -@hci.HCI_Command.command @dataclass -class HCI_RTK_Download_Command(hci.HCI_Command): +class HCI_RTK_Download_ReturnParameters(hci.HCI_StatusReturnParameters): + index: int = field(metadata=hci.metadata(1)) + + +@hci.HCI_SyncCommand.sync_command(HCI_RTK_Download_ReturnParameters) +@dataclass +class HCI_RTK_Download_Command(hci.HCI_SyncCommand[HCI_RTK_Download_ReturnParameters]): index: int = field(metadata=hci.metadata(1)) payload: bytes = field(metadata=hci.metadata(RTK_FRAGMENT_LENGTH)) - return_parameters_fields = [("status", hci.STATUS_SPEC), ("index", 1)] -@hci.HCI_Command.command +@hci.HCI_SyncCommand.sync_command(hci.HCI_GenericReturnParameters) @dataclass -class HCI_RTK_Drop_Firmware_Command(hci.HCI_Command): +class HCI_RTK_Drop_Firmware_Command( + hci.HCI_SyncCommand[hci.HCI_GenericReturnParameters] +): pass @@ -490,7 +508,7 @@ def find_binary_path(file_name): return None @staticmethod - def check(host): + def check(host: Host) -> bool: if not host.hci_metadata: logger.debug("USB metadata not found") return False @@ -514,41 +532,39 @@ def check(host): return True @staticmethod - async def get_loaded_firmware_version(host): - response = await host.send_command(HCI_RTK_Read_ROM_Version_Command()) + async def get_loaded_firmware_version(host: Host) -> int | None: + response1 = await host.send_sync_command( + HCI_RTK_Read_ROM_Version_Command(), check_status=False + ) - if response.return_parameters.status != hci.HCI_SUCCESS: + if response1.status != hci.HCI_SUCCESS: return None - response = await host.send_command( - hci.HCI_Read_Local_Version_Information_Command(), check_result=True - ) - return ( - response.return_parameters.hci_subversion << 16 - | response.return_parameters.lmp_subversion + response2 = await host.send_sync_command( + hci.HCI_Read_Local_Version_Information_Command() ) + return response2.hci_subversion << 16 | response2.lmp_subversion @classmethod - async def driver_info_for_host(cls, host): + async def driver_info_for_host(cls, host: Host) -> DriverInfo | None: try: - await host.send_command( + await host.send_sync_command( hci.HCI_Reset_Command(), - check_result=True, response_timeout=cls.POST_RESET_DELAY, ) host.ready = True # Needed to let the host know the controller is ready. except asyncio.exceptions.TimeoutError: logger.warning("timeout waiting for hci reset, retrying") - await host.send_command(hci.HCI_Reset_Command(), check_result=True) + await host.send_sync_command(hci.HCI_Reset_Command()) host.ready = True command = hci.HCI_Read_Local_Version_Information_Command() - response = await host.send_command(command, check_result=True) - if response.command_opcode != command.op_code: + response = await host.send_sync_command(command, check_status=False) + if response.status != hci.HCI_SUCCESS: logger.error("failed to probe local version information") return None - local_version = response.return_parameters + local_version = response logger.debug( f"looking for a driver: 0x{local_version.lmp_subversion:04X} " @@ -569,7 +585,7 @@ async def driver_info_for_host(cls, host): return driver_info @classmethod - async def for_host(cls, host, force=False): + async def for_host(cls, host: Host, force: bool = False): # Check that a driver is needed for this host if not force and not cls.check(host): return None @@ -626,13 +642,13 @@ async def download_for_rtl8723a(self): async def download_for_rtl8723b(self): if self.driver_info.has_rom_version: - response = await self.host.send_command( - HCI_RTK_Read_ROM_Version_Command(), check_result=True + response1 = await self.host.send_sync_command( + HCI_RTK_Read_ROM_Version_Command(), check_status=False ) - if response.return_parameters.status != hci.HCI_SUCCESS: + if response1.status != hci.HCI_SUCCESS: logger.warning("can't get ROM version") return None - rom_version = response.return_parameters.version + rom_version = response1.version logger.debug(f"ROM version before download: {rom_version:04X}") else: rom_version = 0 @@ -667,21 +683,20 @@ async def download_for_rtl8723b(self): fragment_offset = fragment_index * RTK_FRAGMENT_LENGTH fragment = payload[fragment_offset : fragment_offset + RTK_FRAGMENT_LENGTH] logger.debug(f"downloading fragment {fragment_index}") - await self.host.send_command( - HCI_RTK_Download_Command(index=download_index, payload=fragment), - check_result=True, + await self.host.send_sync_command( + HCI_RTK_Download_Command(index=download_index, payload=fragment) ) logger.debug("download complete!") # Read the version again - response = await self.host.send_command( - HCI_RTK_Read_ROM_Version_Command(), check_result=True + response2 = await self.host.send_sync_command( + HCI_RTK_Read_ROM_Version_Command(), check_status=False ) - if response.return_parameters.status != hci.HCI_SUCCESS: + if response2.status != hci.HCI_SUCCESS: logger.warning("can't get ROM version") else: - rom_version = response.return_parameters.version + rom_version = response2.version logger.debug(f"ROM version after download: {rom_version:02X}") return firmware.version @@ -703,7 +718,7 @@ async def download_firmware(self): async def init_controller(self): await self.download_firmware() - await self.host.send_command(hci.HCI_Reset_Command(), check_result=True) + await self.host.send_sync_command(hci.HCI_Reset_Command()) logger.info(f"loaded FW image {self.driver_info.fw_name}") diff --git a/bumble/hci.py b/bumble/hci.py index 9ae60a34..7c314bb6 100644 --- a/bumble/hci.py +++ b/bumble/hci.py @@ -29,6 +29,7 @@ from typing import ( Any, ClassVar, + Generic, Literal, TypeVar, cast, @@ -77,7 +78,7 @@ def indent_lines(string): return '\n'.join([' ' + line for line in string.split('\n')]) -def map_null_terminated_utf8_string(utf8_bytes): +def map_null_terminated_utf8_string(utf8_bytes: bytes) -> str | bytes: try: terminator = utf8_bytes.find(0) if terminator < 0: @@ -87,7 +88,7 @@ def map_null_terminated_utf8_string(utf8_bytes): return utf8_bytes -def map_class_of_device(class_of_device): +def map_class_of_device(class_of_device: int) -> str: ( service_classes, major_device_class, @@ -208,7 +209,7 @@ def metadata( HCI_VENDOR_OGF = 0x3F # Specification Version -class SpecificationVersion(utils.OpenIntEnum): +class SpecificationVersion(SpecableEnum): BLUETOOTH_CORE_1_0B = 0 BLUETOOTH_CORE_1_1 = 1 BLUETOOTH_CORE_1_2 = 2 @@ -739,6 +740,76 @@ class SpecificationVersion(utils.OpenIntEnum): # HCI Error Codes # See Bluetooth spec Vol 2, Part D - 1.3 LIST OF ERROR CODES +class HCI_ErrorCode(SpecableEnum): + SUCCESS = 0x00 + UNKNOWN_HCI_COMMAND_ERROR = 0x01 + UNKNOWN_CONNECTION_IDENTIFIER_ERROR = 0x02 + HARDWARE_FAILURE_ERROR = 0x03 + PAGE_TIMEOUT_ERROR = 0x04 + AUTHENTICATION_FAILURE_ERROR = 0x05 + PIN_OR_KEY_MISSING_ERROR = 0x06 + MEMORY_CAPACITY_EXCEEDED_ERROR = 0x07 + CONNECTION_TIMEOUT_ERROR = 0x08 + CONNECTION_LIMIT_EXCEEDED_ERROR = 0x09 + SYNCHRONOUS_CONNECTION_LIMIT_TO_A_DEVICE_EXCEEDED_ERROR = 0x0A + CONNECTION_ALREADY_EXISTS_ERROR = 0x0B + COMMAND_DISALLOWED_ERROR = 0x0C + CONNECTION_REJECTED_DUE_TO_LIMITED_RESOURCES_ERROR = 0x0D + CONNECTION_REJECTED_DUE_TO_SECURITY_REASONS_ERROR = 0x0E + CONNECTION_REJECTED_DUE_TO_UNACCEPTABLE_BD_ADDR_ERROR = 0x0F + CONNECTION_ACCEPT_TIMEOUT_ERROR = 0x10 + UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE_ERROR = 0x11 + INVALID_COMMAND_PARAMETERS_ERROR = 0x12 + REMOTE_USER_TERMINATED_CONNECTION_ERROR = 0x13 + REMOTE_DEVICE_TERMINATED_CONNECTION_DUE_TO_LOW_RESOURCES_ERROR = 0x14 + REMOTE_DEVICE_TERMINATED_CONNECTION_DUE_TO_POWER_OFF_ERROR = 0x15 + CONNECTION_TERMINATED_BY_LOCAL_HOST_ERROR = 0x16 + REPEATED_ATTEMPTS_ERROR = 0X17 + PAIRING_NOT_ALLOWED_ERROR = 0X18 + UNKNOWN_LMP_PDU_ERROR = 0X19 + UNSUPPORTED_REMOTE_FEATURE_ERROR = 0X1A + SCO_OFFSET_REJECTED_ERROR = 0X1B + SCO_INTERVAL_REJECTED_ERROR = 0X1C + SCO_AIR_MODE_REJECTED_ERROR = 0X1D + INVALID_LMP_OR_LL_PARAMETERS_ERROR = 0X1E + UNSPECIFIED_ERROR_ERROR = 0X1F + UNSUPPORTED_LMP_OR_LL_PARAMETER_VALUE_ERROR = 0X20 + ROLE_CHANGE_NOT_ALLOWED_ERROR = 0X21 + LMP_OR_LL_RESPONSE_TIMEOUT_ERROR = 0X22 + LMP_ERROR_TRANSACTION_COLLISION_OR_LL_PROCEDURE_COLLISION_ERROR = 0X23 + LMP_PDU_NOT_ALLOWED_ERROR = 0X24 + ENCRYPTION_MODE_NOT_ACCEPTABLE_ERROR = 0X25 + LINK_KEY_CANNOT_BE_CHANGED_ERROR = 0X26 + REQUESTED_QOS_NOT_SUPPORTED_ERROR = 0X27 + INSTANT_PASSED_ERROR = 0X28 + PAIRING_WITH_UNIT_KEY_NOT_SUPPORTED_ERROR = 0X29 + DIFFERENT_TRANSACTION_COLLISION_ERROR = 0X2A + RESERVED_FOR_FUTURE_USE = 0X2B + QOS_UNACCEPTABLE_PARAMETER_ERROR = 0X2C + QOS_REJECTED_ERROR = 0X2D + CHANNEL_CLASSIFICATION_NOT_SUPPORTED_ERROR = 0X2E + INSUFFICIENT_SECURITY_ERROR = 0X2F + PARAMETER_OUT_OF_MANDATORY_RANGE_ERROR = 0X30 + ROLE_SWITCH_PENDING_ERROR = 0X32 + RESERVED_SLOT_VIOLATION_ERROR = 0X34 + ROLE_SWITCH_FAILED_ERROR = 0X35 + EXTENDED_INQUIRY_RESPONSE_TOO_LARGE_ERROR = 0X36 + SECURE_SIMPLE_PAIRING_NOT_SUPPORTED_BY_HOST_ERROR = 0X37 + HOST_BUSY_PAIRING_ERROR = 0X38 + CONNECTION_REJECTED_DUE_TO_NO_SUITABLE_CHANNEL_FOUND_ERROR = 0X39 + CONTROLLER_BUSY_ERROR = 0X3A + UNACCEPTABLE_CONNECTION_PARAMETERS_ERROR = 0X3B + ADVERTISING_TIMEOUT_ERROR = 0X3C + CONNECTION_TERMINATED_DUE_TO_MIC_FAILURE_ERROR = 0X3D + CONNECTION_FAILED_TO_BE_ESTABLISHED_ERROR = 0X3E + COARSE_CLOCK_ADJUSTMENT_REJECTED_BUT_WILL_TRY_TO_ADJUST_USING_CLOCK_DRAGGING_ERROR = 0X40 + TYPE0_SUBMAP_NOT_DEFINED_ERROR = 0X41 + UNKNOWN_ADVERTISING_IDENTIFIER_ERROR = 0X42 + LIMIT_REACHED_ERROR = 0X43 + OPERATION_CANCELLED_BY_HOST_ERROR = 0X44 + PACKET_TOO_LONG_ERROR = 0X45 + +# For backwards compatibility. HCI_SUCCESS = 0x00 HCI_UNKNOWN_HCI_COMMAND_ERROR = 0x01 HCI_UNKNOWN_CONNECTION_IDENTIFIER_ERROR = 0x02 @@ -808,13 +879,15 @@ class SpecificationVersion(utils.OpenIntEnum): HCI_PACKET_TOO_LONG_ERROR = 0X45 HCI_ERROR_NAMES = { - error_code: error_name for (error_name, error_code) in globals().items() - if error_name.startswith('HCI_') and error_name.endswith('_ERROR') + error_code: error_code.name for error_code in HCI_ErrorCode } -HCI_ERROR_NAMES[HCI_SUCCESS] = 'HCI_SUCCESS' # Command Status codes -HCI_COMMAND_STATUS_PENDING = 0 +class HCI_CommandStatus(SpecableEnum): + PENDING = 0x00 + +# For backward compatibility +HCI_COMMAND_STATUS_PENDING = HCI_CommandStatus.PENDING class Phy(SpecableEnum): @@ -1727,6 +1800,8 @@ def __init__(self, response): # Generic HCI object # ----------------------------------------------------------------------------- class HCI_Object: + fields: Fields + @staticmethod def init_from_fields(hci_object, fields, values): if isinstance(values, dict): @@ -2355,7 +2430,6 @@ class HCI_Command(HCI_Packet): command_classes: dict[int, type[HCI_Command]] = {} op_code: int fields: Fields = () - return_parameters_fields: Fields = () _parameters: bytes = b'' _Command = TypeVar("_Command", bound="HCI_Command") @@ -2424,20 +2498,6 @@ def command_name(cls, op_code: int) -> str: return subclass.name return f'[OGF=0x{op_code >> 10:02x}, OCF=0x{op_code & 0x3FF:04x}]' - @classmethod - def create_return_parameters(cls, **kwargs): - return HCI_Object(cls.return_parameters_fields, **kwargs) - - @classmethod - def parse_return_parameters(cls, parameters): - if not cls.return_parameters_fields: - return None - return_parameters = HCI_Object.from_bytes( - parameters, 0, cls.return_parameters_fields - ) - return_parameters.fields = cls.return_parameters_fields - return return_parameters - def __init__( self, parameters: bytes | None = None, @@ -2485,10 +2545,83 @@ def __str__(self): HCI_Command.register_commands(globals()) +# ----------------------------------------------------------------------------- +class HCI_AsyncCommand(HCI_Command): + ''' + HCI Commands for which the expected response is an HCI_Command_Status_Event. + ''' + + +# ----------------------------------------------------------------------------- +class HCI_ReturnParameters(HCI_Object): + @classmethod + def from_parameters(cls, parameters: bytes) -> Self: + return cls(**HCI_Object.dict_from_bytes(parameters, 0, cls.fields)) + + +@dataclasses.dataclass +class HCI_GenericReturnParameters(HCI_ReturnParameters): + fields = [('data', '*')] + data: bytes = field(metadata=metadata('*')) + + +@dataclasses.dataclass +class HCI_StatusReturnParameters(HCI_ReturnParameters): + status: HCI_ErrorCode = field(metadata=HCI_ErrorCode.type_metadata(1)) + + +@dataclasses.dataclass +class HCI_StatusAndAddressReturnParameters(HCI_StatusReturnParameters): + bd_addr: Address = field(metadata=metadata(Address.parse_address)) + + +@dataclasses.dataclass +class HCI_StatusAndConnectionHandleReturnParameters(HCI_StatusReturnParameters): + connection_handle: int = field(metadata=metadata(2)) + + +_RP = TypeVar('_RP', bound=HCI_ReturnParameters) + + +class HCI_SyncCommand(HCI_Command, Generic[_RP]): + ''' + HCI Commands for which the expected response is an HCI_Command_Complete_Event. + ''' + + return_parameters_class: type[_RP] + + _SyncCommand = TypeVar("_SyncCommand", bound="HCI_SyncCommand") + + @classmethod + def sync_command( + cls: type[_SyncCommand], return_parameters_class: type[_RP] + ) -> Callable[[type[_SyncCommand]], type[_SyncCommand]]: + ''' + Decorator used to declare and register subclasses and setup their + `return_parameters_class` attribute. + ''' + + _SyncCommand = TypeVar("_SyncCommand", bound="HCI_SyncCommand") + + def inner(cls: type[_SyncCommand]) -> type[_SyncCommand]: + cls = HCI_Command.command(cls) + cls.return_parameters_class = return_parameters_class + return_parameters_class.fields = HCI_Object.fields_from_dataclass( + cls.return_parameters_class + ) + return cls + + return inner + + @classmethod + def parse_return_parameters(cls, parameters: bytes) -> _RP: + return cls.return_parameters_class.from_parameters(parameters) + + # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_Inquiry_Command(HCI_Command): +class HCI_Inquiry_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.1.1 Inquiry Command ''' @@ -2501,9 +2634,9 @@ class HCI_Inquiry_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_Inquiry_Cancel_Command(HCI_Command): +class HCI_Inquiry_Cancel_Command(HCI_SyncCommand[HCI_StatusReturnParameters]): ''' See Bluetooth spec @ 7.1.2 Inquiry Cancel Command ''' @@ -2512,7 +2645,7 @@ class HCI_Inquiry_Cancel_Command(HCI_Command): # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_Create_Connection_Command(HCI_Command): +class HCI_Create_Connection_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.1.5 Create Connection Command ''' @@ -2528,7 +2661,7 @@ class HCI_Create_Connection_Command(HCI_Command): # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_Disconnect_Command(HCI_Command): +class HCI_Disconnect_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.1.6 Disconnect Command ''' @@ -2538,25 +2671,22 @@ class HCI_Disconnect_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndAddressReturnParameters) @dataclasses.dataclass -class HCI_Create_Connection_Cancel_Command(HCI_Command): +class HCI_Create_Connection_Cancel_Command( + HCI_SyncCommand[HCI_StatusAndAddressReturnParameters] +): ''' See Bluetooth spec @ 7.1.7 Create Connection Cancel Command ''' bd_addr: Address = field(metadata=metadata(Address.parse_address)) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('bd_addr', Address.parse_address), - ] - # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_Accept_Connection_Request_Command(HCI_Command): +class HCI_Accept_Connection_Request_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.1.8 Accept Connection Request Command ''' @@ -2568,7 +2698,7 @@ class HCI_Accept_Connection_Request_Command(HCI_Command): # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_Reject_Connection_Request_Command(HCI_Command): +class HCI_Reject_Connection_Request_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.1.9 Reject Connection Request Command ''' @@ -2578,9 +2708,11 @@ class HCI_Reject_Connection_Request_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndAddressReturnParameters) @dataclasses.dataclass -class HCI_Link_Key_Request_Reply_Command(HCI_Command): +class HCI_Link_Key_Request_Reply_Command( + HCI_SyncCommand[HCI_StatusAndAddressReturnParameters] +): ''' See Bluetooth spec @ 7.1.10 Link Key Request Reply Command ''' @@ -2590,25 +2722,24 @@ class HCI_Link_Key_Request_Reply_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndAddressReturnParameters) @dataclasses.dataclass -class HCI_Link_Key_Request_Negative_Reply_Command(HCI_Command): +class HCI_Link_Key_Request_Negative_Reply_Command( + HCI_SyncCommand[HCI_StatusAndAddressReturnParameters] +): ''' See Bluetooth spec @ 7.1.11 Link Key Request Negative Reply Command ''' bd_addr: Address = field(metadata=metadata(Address.parse_address)) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('bd_addr', Address.parse_address), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndAddressReturnParameters) @dataclasses.dataclass -class HCI_PIN_Code_Request_Reply_Command(HCI_Command): +class HCI_PIN_Code_Request_Reply_Command( + HCI_SyncCommand[HCI_StatusAndAddressReturnParameters] +): ''' See Bluetooth spec @ 7.1.12 PIN Code Request Reply Command ''' @@ -2617,32 +2748,24 @@ class HCI_PIN_Code_Request_Reply_Command(HCI_Command): pin_code_length: int = field(metadata=metadata(1)) pin_code: bytes = field(metadata=metadata(16)) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('bd_addr', Address.parse_address), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndAddressReturnParameters) @dataclasses.dataclass -class HCI_PIN_Code_Request_Negative_Reply_Command(HCI_Command): +class HCI_PIN_Code_Request_Negative_Reply_Command( + HCI_SyncCommand[HCI_StatusAndAddressReturnParameters] +): ''' See Bluetooth spec @ 7.1.13 PIN Code Request Negative Reply Command ''' bd_addr: Address = field(metadata=metadata(Address.parse_address)) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('bd_addr', Address.parse_address), - ] - # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_Change_Connection_Packet_Type_Command(HCI_Command): +class HCI_Change_Connection_Packet_Type_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.1.14 Change Connection Packet Type Command ''' @@ -2654,7 +2777,7 @@ class HCI_Change_Connection_Packet_Type_Command(HCI_Command): # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_Authentication_Requested_Command(HCI_Command): +class HCI_Authentication_Requested_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.1.15 Authentication Requested Command ''' @@ -2665,7 +2788,7 @@ class HCI_Authentication_Requested_Command(HCI_Command): # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_Set_Connection_Encryption_Command(HCI_Command): +class HCI_Set_Connection_Encryption_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.1.16 Set Connection Encryption Command ''' @@ -2677,7 +2800,7 @@ class HCI_Set_Connection_Encryption_Command(HCI_Command): # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_Remote_Name_Request_Command(HCI_Command): +class HCI_Remote_Name_Request_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.1.19 Remote Name Request Command ''' @@ -2695,7 +2818,7 @@ class HCI_Remote_Name_Request_Command(HCI_Command): # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_Read_Remote_Supported_Features_Command(HCI_Command): +class HCI_Read_Remote_Supported_Features_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.1.21 Read Remote Supported Features Command ''' @@ -2706,7 +2829,7 @@ class HCI_Read_Remote_Supported_Features_Command(HCI_Command): # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_Read_Remote_Extended_Features_Command(HCI_Command): +class HCI_Read_Remote_Extended_Features_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.1.22 Read Remote Extended Features Command ''' @@ -2718,7 +2841,7 @@ class HCI_Read_Remote_Extended_Features_Command(HCI_Command): # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_Read_Remote_Version_Information_Command(HCI_Command): +class HCI_Read_Remote_Version_Information_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.1.23 Read Remote Version Information Command ''' @@ -2729,9 +2852,9 @@ class HCI_Read_Remote_Version_Information_Command(HCI_Command): # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_Read_Clock_Offset_Command(HCI_Command): +class HCI_Read_Clock_Offset_Command(HCI_AsyncCommand): ''' - See Bluetooth spec @ 7.1.23 Read Clock Offset Command + See Bluetooth spec @ 7.1.24 Read Clock Offset Command ''' connection_handle: int = field(metadata=metadata(2)) @@ -2740,7 +2863,7 @@ class HCI_Read_Clock_Offset_Command(HCI_Command): # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_Reject_Synchronous_Connection_Request_Command(HCI_Command): +class HCI_Reject_Synchronous_Connection_Request_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.1.28 Reject Synchronous Connection Request Command ''' @@ -2750,9 +2873,11 @@ class HCI_Reject_Synchronous_Connection_Request_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndAddressReturnParameters) @dataclasses.dataclass -class HCI_IO_Capability_Request_Reply_Command(HCI_Command): +class HCI_IO_Capability_Request_Reply_Command( + HCI_SyncCommand[HCI_StatusAndAddressReturnParameters] +): ''' See Bluetooth spec @ 7.1.29 IO Capability Request Reply Command ''' @@ -2764,48 +2889,39 @@ class HCI_IO_Capability_Request_Reply_Command(HCI_Command): metadata=AuthenticationRequirements.type_metadata(1) ) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('bd_addr', Address.parse_address), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndAddressReturnParameters) @dataclasses.dataclass -class HCI_User_Confirmation_Request_Reply_Command(HCI_Command): +class HCI_User_Confirmation_Request_Reply_Command( + HCI_SyncCommand[HCI_StatusAndAddressReturnParameters] +): ''' See Bluetooth spec @ 7.1.30 User Confirmation Request Reply Command ''' bd_addr: Address = field(metadata=metadata(Address.parse_address)) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('bd_addr', Address.parse_address), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndAddressReturnParameters) @dataclasses.dataclass -class HCI_User_Confirmation_Request_Negative_Reply_Command(HCI_Command): +class HCI_User_Confirmation_Request_Negative_Reply_Command( + HCI_SyncCommand[HCI_StatusAndAddressReturnParameters] +): ''' See Bluetooth spec @ 7.1.31 User Confirmation Request Negative Reply Command ''' bd_addr: Address = field(metadata=metadata(Address.parse_address)) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('bd_addr', Address.parse_address), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndAddressReturnParameters) @dataclasses.dataclass -class HCI_User_Passkey_Request_Reply_Command(HCI_Command): +class HCI_User_Passkey_Request_Reply_Command( + HCI_SyncCommand[HCI_StatusAndAddressReturnParameters] +): ''' See Bluetooth spec @ 7.1.32 User Passkey Request Reply Command ''' @@ -2813,32 +2929,26 @@ class HCI_User_Passkey_Request_Reply_Command(HCI_Command): bd_addr: Address = field(metadata=metadata(Address.parse_address)) numeric_value: int = field(metadata=metadata(4)) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('bd_addr', Address.parse_address), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndAddressReturnParameters) @dataclasses.dataclass -class HCI_User_Passkey_Request_Negative_Reply_Command(HCI_Command): +class HCI_User_Passkey_Request_Negative_Reply_Command( + HCI_SyncCommand[HCI_StatusAndAddressReturnParameters] +): ''' See Bluetooth spec @ 7.1.33 User Passkey Request Negative Reply Command ''' bd_addr: Address = field(metadata=metadata(Address.parse_address)) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('bd_addr', Address.parse_address), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndAddressReturnParameters) @dataclasses.dataclass -class HCI_Remote_OOB_Data_Request_Reply_Command(HCI_Command): +class HCI_Remote_OOB_Data_Request_Reply_Command( + HCI_SyncCommand[HCI_StatusAndAddressReturnParameters] +): ''' See Bluetooth spec @ 7.1.34 Remote OOB Data Request Reply Command ''' @@ -2847,32 +2957,26 @@ class HCI_Remote_OOB_Data_Request_Reply_Command(HCI_Command): c: bytes = field(metadata=metadata(16)) r: bytes = field(metadata=metadata(16)) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('bd_addr', Address.parse_address), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndAddressReturnParameters) @dataclasses.dataclass -class HCI_Remote_OOB_Data_Request_Negative_Reply_Command(HCI_Command): +class HCI_Remote_OOB_Data_Request_Negative_Reply_Command( + HCI_SyncCommand[HCI_StatusAndAddressReturnParameters] +): ''' See Bluetooth spec @ 7.1.35 Remote OOB Data Request Negative Reply Command ''' bd_addr: Address = field(metadata=metadata(Address.parse_address)) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('bd_addr', Address.parse_address), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndAddressReturnParameters) @dataclasses.dataclass -class HCI_IO_Capability_Request_Negative_Reply_Command(HCI_Command): +class HCI_IO_Capability_Request_Negative_Reply_Command( + HCI_SyncCommand[HCI_StatusAndAddressReturnParameters] +): ''' See Bluetooth spec @ 7.1.36 IO Capability Request Negative Reply Command ''' @@ -2880,16 +2984,11 @@ class HCI_IO_Capability_Request_Negative_Reply_Command(HCI_Command): bd_addr: Address = field(metadata=metadata(Address.parse_address)) reason: int = field(metadata=metadata(1)) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('bd_addr', Address.parse_address), - ] - # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_Enhanced_Setup_Synchronous_Connection_Command(HCI_Command): +class HCI_Enhanced_Setup_Synchronous_Connection_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.1.45 Enhanced Setup Synchronous Connection Command ''' @@ -2954,7 +3053,7 @@ class PacketType(SpecableFlag): # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_Enhanced_Accept_Synchronous_Connection_Request_Command(HCI_Command): +class HCI_Enhanced_Accept_Synchronous_Connection_Request_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.1.46 Enhanced Accept Synchronous Connection Request Command ''' @@ -2990,7 +3089,7 @@ class HCI_Enhanced_Accept_Synchronous_Connection_Request_Command(HCI_Command): # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_Truncated_Page_Command(HCI_Command): +class HCI_Truncated_Page_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.1.47 Truncated Page Command ''' @@ -3001,25 +3100,34 @@ class HCI_Truncated_Page_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndAddressReturnParameters) @dataclasses.dataclass -class HCI_Truncated_Page_Cancel_Command(HCI_Command): +class HCI_Truncated_Page_Cancel_Command( + HCI_SyncCommand[HCI_StatusAndAddressReturnParameters] +): ''' See Bluetooth spec @ 7.1.48 Truncated Page Cancel Command ''' bd_addr: Address = field(metadata=metadata(Address.parse_address)) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('bd_addr', Address.parse_address), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_Set_Connectionless_Peripheral_Broadcast_Command(HCI_Command): +class HCI_Set_Connectionless_Peripheral_Broadcast_ReturnParameters( + HCI_StatusReturnParameters +): + lt_addr: int = field(metadata=metadata(1)) + interval: int = field(metadata=metadata(2)) + + +@HCI_SyncCommand.sync_command( + HCI_Set_Connectionless_Peripheral_Broadcast_ReturnParameters +) +@dataclasses.dataclass +class HCI_Set_Connectionless_Peripheral_Broadcast_Command( + HCI_SyncCommand[HCI_Set_Connectionless_Peripheral_Broadcast_ReturnParameters] +): ''' See Bluetooth spec @ 7.1.49 Set Connectionless Peripheral Broadcast Command ''' @@ -3032,17 +3140,25 @@ class HCI_Set_Connectionless_Peripheral_Broadcast_Command(HCI_Command): interval_max: int = field(metadata=metadata(2)) supervision_timeout: int = field(metadata=metadata(2)) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('lt_addr', 1), - ('interval', 2), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_Set_Connectionless_Peripheral_Broadcast_Receive_Command(HCI_Command): +class HCI_Set_Connectionless_Peripheral_Broadcast_Receive_ReturnParameters( + HCI_StatusReturnParameters +): + bd_addr: Address = field(metadata=metadata(Address.parse_address)) + lt_addr: int = field(metadata=metadata(1)) + + +@HCI_SyncCommand.sync_command( + HCI_Set_Connectionless_Peripheral_Broadcast_Receive_ReturnParameters +) +@dataclasses.dataclass +class HCI_Set_Connectionless_Peripheral_Broadcast_Receive_Command( + HCI_SyncCommand[ + HCI_Set_Connectionless_Peripheral_Broadcast_Receive_ReturnParameters + ] +): ''' See Bluetooth spec @ 7.1.50 Set Connectionless Peripheral Broadcast Receive Command ''' @@ -3059,17 +3175,11 @@ class HCI_Set_Connectionless_Peripheral_Broadcast_Receive_Command(HCI_Command): packet_type: int = field(metadata=metadata(2)) afh_channel_map: bytes = field(metadata=metadata(10)) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('bd_addr', Address.parse_address), - ('lt_addr', 1), - ] - # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_Start_Synchronization_Train_Command(HCI_Command): +class HCI_Start_Synchronization_Train_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.1.51 Start Synchronization Train Command ''' @@ -3078,7 +3188,7 @@ class HCI_Start_Synchronization_Train_Command(HCI_Command): # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_Receive_Synchronization_Train_Command(HCI_Command): +class HCI_Receive_Synchronization_Train_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.1.52 Receive Synchronization Train Command ''' @@ -3090,9 +3200,11 @@ class HCI_Receive_Synchronization_Train_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_Remote_OOB_Extended_Data_Request_Reply_Command(HCI_Command): +class HCI_Remote_OOB_Extended_Data_Request_Reply_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.1.53 Remote OOB Extended Data Request Reply Command ''' @@ -3103,16 +3215,11 @@ class HCI_Remote_OOB_Extended_Data_Request_Reply_Command(HCI_Command): c_256: bytes = field(metadata=metadata(16)) r_256: bytes = field(metadata=metadata(16)) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('bd_addr', Address.parse_address), - ] - # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_Sniff_Mode_Command(HCI_Command): +class HCI_Sniff_Mode_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.2.2 Sniff Mode Command ''' @@ -3127,7 +3234,7 @@ class HCI_Sniff_Mode_Command(HCI_Command): # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_Exit_Sniff_Mode_Command(HCI_Command): +class HCI_Exit_Sniff_Mode_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.2.3 Exit Sniff Mode Command ''' @@ -3138,7 +3245,7 @@ class HCI_Exit_Sniff_Mode_Command(HCI_Command): # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_Switch_Role_Command(HCI_Command): +class HCI_Switch_Role_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.2.8 Switch Role Command ''' @@ -3148,9 +3255,11 @@ class HCI_Switch_Role_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndConnectionHandleReturnParameters) @dataclasses.dataclass -class HCI_Write_Link_Policy_Settings_Command(HCI_Command): +class HCI_Write_Link_Policy_Settings_Command( + HCI_SyncCommand[HCI_StatusAndConnectionHandleReturnParameters] +): ''' See Bluetooth spec @ 7.2.10 Write Link Policy Settings Command ''' @@ -3160,9 +3269,11 @@ class HCI_Write_Link_Policy_Settings_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_Write_Default_Link_Policy_Settings_Command(HCI_Command): +class HCI_Write_Default_Link_Policy_Settings_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.2.12 Write Default Link Policy Settings Command ''' @@ -3171,9 +3282,11 @@ class HCI_Write_Default_Link_Policy_Settings_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndConnectionHandleReturnParameters) @dataclasses.dataclass -class HCI_Sniff_Subrating_Command(HCI_Command): +class HCI_Sniff_Subrating_Command( + HCI_SyncCommand[HCI_StatusAndConnectionHandleReturnParameters] +): ''' See Bluetooth spec @ 7.2.14 Sniff Subrating Command ''' @@ -3185,9 +3298,9 @@ class HCI_Sniff_Subrating_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_Set_Event_Mask_Command(HCI_Command): +class HCI_Set_Event_Mask_Command(HCI_SyncCommand[HCI_StatusReturnParameters]): ''' See Bluetooth spec @ 7.3.1 Set Event Mask Command ''' @@ -3210,18 +3323,18 @@ def mask(event_codes: Iterable[int]) -> bytes: # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_Reset_Command(HCI_Command): +class HCI_Reset_Command(HCI_SyncCommand[HCI_StatusReturnParameters]): ''' See Bluetooth spec @ 7.3.2 Reset Command ''' # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_Set_Event_Filter_Command(HCI_Command): +class HCI_Set_Event_Filter_Command(HCI_SyncCommand[HCI_StatusReturnParameters]): ''' See Bluetooth spec @ 7.3.3 Set Event Filter Command ''' @@ -3231,9 +3344,17 @@ class HCI_Set_Event_Filter_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_Read_Stored_Link_Key_Command(HCI_Command): +class HCI_Read_Stored_Link_Key_ReturnParameters(HCI_StatusReturnParameters): + max_num_keys: int = field(metadata=metadata(2)) + num_keys_read: int = field(metadata=metadata(2)) + + +@HCI_SyncCommand.sync_command(HCI_Read_Stored_Link_Key_ReturnParameters) +@dataclasses.dataclass +class HCI_Read_Stored_Link_Key_Command( + HCI_SyncCommand[HCI_Read_Stored_Link_Key_ReturnParameters] +): ''' See Bluetooth spec @ 7.3.8 Read Stored Link Key Command ''' @@ -3241,17 +3362,18 @@ class HCI_Read_Stored_Link_Key_Command(HCI_Command): bd_addr: Address = field(metadata=metadata(Address.parse_address)) read_all_flag: int = field(metadata=metadata(1)) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('max_num_keys', 2), - ('num_keys_read', 2), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_Delete_Stored_Link_Key_Command(HCI_Command): +class HCI_Delete_Stored_Link_Key_ReturnParameters(HCI_StatusReturnParameters): + num_keys_deleted: int = field(metadata=metadata(2)) + + +@HCI_SyncCommand.sync_command(HCI_Delete_Stored_Link_Key_ReturnParameters) +@dataclasses.dataclass +class HCI_Delete_Stored_Link_Key_Command( + HCI_SyncCommand[HCI_Delete_Stored_Link_Key_ReturnParameters] +): ''' See Bluetooth spec @ 7.3.10 Delete Stored Link Key Command ''' @@ -3259,13 +3381,11 @@ class HCI_Delete_Stored_Link_Key_Command(HCI_Command): bd_addr: Address = field(metadata=metadata(Address.parse_address)) delete_all_flag: int = field(metadata=metadata(1)) - return_parameters_fields = [('status', STATUS_SPEC), ('num_keys_deleted', 2)] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_Write_Local_Name_Command(HCI_Command): +class HCI_Write_Local_Name_Command(HCI_SyncCommand[HCI_StatusReturnParameters]): ''' See Bluetooth spec @ 7.3.11 Write Local Name Command ''' @@ -3276,23 +3396,29 @@ class HCI_Write_Local_Name_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_Read_Local_Name_Command(HCI_Command): +class HCI_Read_Local_Name_ReturnParameters(HCI_StatusReturnParameters): + local_name: bytes = field( + metadata=metadata({'size': 248, 'mapper': map_null_terminated_utf8_string}) + ) + + +@HCI_SyncCommand.sync_command(HCI_Read_Local_Name_ReturnParameters) +@dataclasses.dataclass +class HCI_Read_Local_Name_Command( + HCI_SyncCommand[HCI_Read_Local_Name_ReturnParameters] +): ''' See Bluetooth spec @ 7.3.12 Read Local Name Command ''' - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('local_name', {'size': 248, 'mapper': map_null_terminated_utf8_string}), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_Write_Connection_Accept_Timeout_Command(HCI_Command): +class HCI_Write_Connection_Accept_Timeout_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.3.14 Write Connection Accept Timeout Command ''' @@ -3301,9 +3427,9 @@ class HCI_Write_Connection_Accept_Timeout_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_Write_Page_Timeout_Command(HCI_Command): +class HCI_Write_Page_Timeout_Command(HCI_SyncCommand[HCI_StatusReturnParameters]): ''' See Bluetooth spec @ 7.3.16 Write Page Timeout Command ''' @@ -3312,9 +3438,9 @@ class HCI_Write_Page_Timeout_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_Write_Scan_Enable_Command(HCI_Command): +class HCI_Write_Scan_Enable_Command(HCI_SyncCommand[HCI_StatusReturnParameters]): ''' See Bluetooth spec @ 7.3.18 Write Scan Enable Command ''' @@ -3323,24 +3449,26 @@ class HCI_Write_Scan_Enable_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_Read_Page_Scan_Activity_Command(HCI_Command): +class HCI_Read_Page_Scan_Activity_ReturnParameters(HCI_StatusReturnParameters): + page_scan_interval: int = field(metadata=metadata(2)) + page_scan_window: int = field(metadata=metadata(2)) + + +@HCI_SyncCommand.sync_command(HCI_Read_Page_Scan_Activity_ReturnParameters) +@dataclasses.dataclass +class HCI_Read_Page_Scan_Activity_Command( + HCI_SyncCommand[HCI_Read_Page_Scan_Activity_ReturnParameters] +): ''' See Bluetooth spec @ 7.3.19 Read Page Scan Activity Command ''' - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('page_scan_interval', 2), - ('page_scan_window', 2), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_Write_Page_Scan_Activity_Command(HCI_Command): +class HCI_Write_Page_Scan_Activity_Command(HCI_SyncCommand[HCI_StatusReturnParameters]): ''' See Bluetooth spec @ 7.3.20 Write Page Scan Activity Command ''' @@ -3350,9 +3478,11 @@ class HCI_Write_Page_Scan_Activity_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_Write_Inquiry_Scan_Activity_Command(HCI_Command): +class HCI_Write_Inquiry_Scan_Activity_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.3.22 Write Inquiry Scan Activity Command ''' @@ -3362,23 +3492,27 @@ class HCI_Write_Inquiry_Scan_Activity_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_Read_Authentication_Enable_Command(HCI_Command): +class HCI_Read_Authentication_Enable_ReturnParameters(HCI_StatusReturnParameters): + authentication_enable: int = field(metadata=metadata(1)) + + +@HCI_SyncCommand.sync_command(HCI_Read_Authentication_Enable_ReturnParameters) +@dataclasses.dataclass +class HCI_Read_Authentication_Enable_Command( + HCI_SyncCommand[HCI_Read_Authentication_Enable_ReturnParameters] +): ''' See Bluetooth spec @ 7.3.23 Read Authentication Enable Command ''' - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('authentication_enable', 1), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_Write_Authentication_Enable_Command(HCI_Command): +class HCI_Write_Authentication_Enable_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.3.24 Write Authentication Enable Command ''' @@ -3387,23 +3521,27 @@ class HCI_Write_Authentication_Enable_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_Read_Class_Of_Device_Command(HCI_Command): +class HCI_Read_Class_Of_Device_ReturnParameters(HCI_StatusReturnParameters): + class_of_device: int = field( + metadata=metadata({'size': 3, 'mapper': map_class_of_device}) + ) + + +@HCI_SyncCommand.sync_command(HCI_Read_Class_Of_Device_ReturnParameters) +@dataclasses.dataclass +class HCI_Read_Class_Of_Device_Command( + HCI_SyncCommand[HCI_Read_Class_Of_Device_ReturnParameters] +): ''' See Bluetooth spec @ 7.3.25 Read Class of Device Command ''' - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('class_of_device', COD_SPEC), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_Write_Class_Of_Device_Command(HCI_Command): +class HCI_Write_Class_Of_Device_Command(HCI_SyncCommand[HCI_StatusReturnParameters]): ''' See Bluetooth spec @ 7.3.26 Write Class of Device Command ''' @@ -3412,20 +3550,25 @@ class HCI_Write_Class_Of_Device_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_Read_Voice_Setting_Command(HCI_Command): +class HCI_Read_Voice_Setting_ReturnParameters(HCI_StatusReturnParameters): + voice_setting: int = field(metadata=metadata(2)) + + +@HCI_SyncCommand.sync_command(HCI_Read_Voice_Setting_ReturnParameters) +@dataclasses.dataclass +class HCI_Read_Voice_Setting_Command( + HCI_SyncCommand[HCI_Read_Voice_Setting_ReturnParameters] +): ''' See Bluetooth spec @ 7.3.27 Read Voice Setting Command ''' - return_parameters_fields = [('status', STATUS_SPEC), ('voice_setting', 2)] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_Write_Voice_Setting_Command(HCI_Command): +class HCI_Write_Voice_Setting_Command(HCI_SyncCommand[HCI_StatusReturnParameters]): ''' See Bluetooth spec @ 7.3.28 Write Voice Setting Command ''' @@ -3434,23 +3577,29 @@ class HCI_Write_Voice_Setting_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_Read_Synchronous_Flow_Control_Enable_Command(HCI_Command): +class HCI_Read_Synchronous_Flow_Control_Enable_ReturnParameters( + HCI_StatusReturnParameters +): + synchronous_flow_control_enable: int = field(metadata=metadata(1)) + + +@HCI_SyncCommand.sync_command(HCI_Read_Synchronous_Flow_Control_Enable_ReturnParameters) +@dataclasses.dataclass +class HCI_Read_Synchronous_Flow_Control_Enable_Command( + HCI_SyncCommand[HCI_Read_Synchronous_Flow_Control_Enable_ReturnParameters] +): ''' See Bluetooth spec @ 7.3.36 Read Synchronous Flow Control Enable Command ''' - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('synchronous_flow_control_enable', 1), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_Write_Synchronous_Flow_Control_Enable_Command(HCI_Command): +class HCI_Write_Synchronous_Flow_Control_Enable_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.3.37 Write Synchronous Flow Control Enable Command ''' @@ -3459,9 +3608,11 @@ class HCI_Write_Synchronous_Flow_Control_Enable_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_Set_Controller_To_Host_Flow_Control_Command(HCI_Command): +class HCI_Set_Controller_To_Host_Flow_Control_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.3.38 Set Controller To Host Flow Control command ''' @@ -3470,9 +3621,9 @@ class HCI_Set_Controller_To_Host_Flow_Control_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_Host_Buffer_Size_Command(HCI_Command): +class HCI_Host_Buffer_Size_Command(HCI_SyncCommand[HCI_StatusReturnParameters]): ''' See Bluetooth spec @ 7.3.39 Host Buffer Size Command ''' @@ -3484,9 +3635,16 @@ class HCI_Host_Buffer_Size_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_Write_Link_Supervision_Timeout_Command(HCI_Command): +class HCI_Write_Link_Supervision_Timeout_ReturnParameters(HCI_StatusReturnParameters): + handle: int = field(metadata=metadata(2)) + + +@HCI_SyncCommand.sync_command(HCI_Write_Link_Supervision_Timeout_ReturnParameters) +@dataclasses.dataclass +class HCI_Write_Link_Supervision_Timeout_Command( + HCI_SyncCommand[HCI_Write_Link_Supervision_Timeout_ReturnParameters] +): ''' See Bluetooth spec @ 7.3.42 Write Link Supervision Timeout Command ''' @@ -3494,42 +3652,44 @@ class HCI_Write_Link_Supervision_Timeout_Command(HCI_Command): handle: int = field(metadata=metadata(2)) link_supervision_timeout: int = field(metadata=metadata(2)) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('handle', 2), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_Read_Number_Of_Supported_IAC_Command(HCI_Command): +class HCI_Read_Number_Of_Supported_IAC_ReturnParameters(HCI_StatusReturnParameters): + num_support_iac: int = field(metadata=metadata(1)) + + +@HCI_SyncCommand.sync_command(HCI_Read_Number_Of_Supported_IAC_ReturnParameters) +@dataclasses.dataclass +class HCI_Read_Number_Of_Supported_IAC_Command( + HCI_SyncCommand[HCI_Read_Number_Of_Supported_IAC_ReturnParameters] +): ''' See Bluetooth spec @ 7.3.43 Read Number Of Supported IAC Command ''' - return_parameters_fields = [('status', STATUS_SPEC), ('num_support_iac', 1)] - # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_Read_Current_IAC_LAP_Command(HCI_Command): +class HCI_Read_Current_IAC_LAP_ReturnParameters(HCI_StatusReturnParameters): + num_support_iac: int = field(metadata=metadata(1)) + iac_lap: bytes = field(metadata=metadata('*')) # TODO: should be parsed as an array + + +@HCI_SyncCommand.sync_command(HCI_Read_Current_IAC_LAP_ReturnParameters) +@dataclasses.dataclass +class HCI_Read_Current_IAC_LAP_Command( + HCI_SyncCommand[HCI_Read_Current_IAC_LAP_ReturnParameters] +): ''' See Bluetooth spec @ 7.3.44 Read Current IAC LAP Command ''' - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('num_current_iac', 1), - ('iac_lap', '*'), # TODO: this should be parsed as an array - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_Write_Inquiry_Scan_Type_Command(HCI_Command): +class HCI_Write_Inquiry_Scan_Type_Command(HCI_SyncCommand[HCI_StatusReturnParameters]): ''' See Bluetooth spec @ 7.3.48 Write Inquiry Scan Type Command ''' @@ -3538,9 +3698,9 @@ class HCI_Write_Inquiry_Scan_Type_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_Write_Inquiry_Mode_Command(HCI_Command): +class HCI_Write_Inquiry_Mode_Command(HCI_SyncCommand[HCI_StatusReturnParameters]): ''' See Bluetooth spec @ 7.3.50 Write Inquiry Mode Command ''' @@ -3549,20 +3709,25 @@ class HCI_Write_Inquiry_Mode_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_Read_Page_Scan_Type_Command(HCI_Command): +class HCI_Read_Page_Scan_Type_ReturnParameters(HCI_StatusReturnParameters): + page_scan_type: int = field(metadata=metadata(1)) + + +@HCI_SyncCommand.sync_command(HCI_Read_Page_Scan_Type_ReturnParameters) +@dataclasses.dataclass +class HCI_Read_Page_Scan_Type_Command( + HCI_SyncCommand[HCI_Read_Page_Scan_Type_ReturnParameters] +): ''' See Bluetooth spec @ 7.3.51 Read Page Scan Type Command ''' - return_parameters_fields = [('status', STATUS_SPEC), ('page_scan_type', 1)] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_Write_Page_Scan_Type_Command(HCI_Command): +class HCI_Write_Page_Scan_Type_Command(HCI_SyncCommand[HCI_StatusReturnParameters]): ''' See Bluetooth spec @ 7.3.52 Write Page Scan Type Command ''' @@ -3571,9 +3736,11 @@ class HCI_Write_Page_Scan_Type_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_Write_Extended_Inquiry_Response_Command(HCI_Command): +class HCI_Write_Extended_Inquiry_Response_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.3.56 Write Extended Inquiry Response Command ''' @@ -3585,9 +3752,11 @@ class HCI_Write_Extended_Inquiry_Response_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_Write_Simple_Pairing_Mode_Command(HCI_Command): +class HCI_Write_Simple_Pairing_Mode_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.3.59 Write Simple Pairing Mode Command ''' @@ -3596,49 +3765,62 @@ class HCI_Write_Simple_Pairing_Mode_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_Read_Local_OOB_Data_Command(HCI_Command): +class HCI_Read_Local_OOB_Data_ReturnParameters(HCI_StatusReturnParameters): + c: bytes = field(metadata=metadata(16)) + r: bytes = field(metadata=metadata(16)) + + +@HCI_SyncCommand.sync_command(HCI_Read_Local_OOB_Data_ReturnParameters) +@dataclasses.dataclass +class HCI_Read_Local_OOB_Data_Command( + HCI_SyncCommand[HCI_Read_Local_OOB_Data_ReturnParameters] +): ''' See Bluetooth spec @ 7.3.60 Read Local OOB Data Command ''' - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('c', 16), - ('r', 16), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_Read_Inquiry_Response_Transmit_Power_Level_Command(HCI_Command): +class HCI_Read_Inquiry_Response_Transmit_Power_Level_ReturnParameters( + HCI_StatusReturnParameters +): + tx_power: int = field(metadata=metadata(-1)) + + +@HCI_SyncCommand.sync_command( + HCI_Read_Inquiry_Response_Transmit_Power_Level_ReturnParameters +) +@dataclasses.dataclass +class HCI_Read_Inquiry_Response_Transmit_Power_Level_Command( + HCI_SyncCommand[HCI_Read_Inquiry_Response_Transmit_Power_Level_ReturnParameters] +): ''' See Bluetooth spec @ 7.3.61 Read Inquiry Response Transmit Power Level Command ''' - return_parameters_fields = [('status', STATUS_SPEC), ('tx_power', -1)] - # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_Read_Default_Erroneous_Data_Reporting_Command(HCI_Command): +class HCI_Read_Default_Erroneous_Data_ReturnParameters(HCI_StatusReturnParameters): + erroneous_data_reporting: int = field(metadata=metadata(1)) + + +@HCI_SyncCommand.sync_command(HCI_Read_Default_Erroneous_Data_ReturnParameters) +@dataclasses.dataclass +class HCI_Read_Default_Erroneous_Data_Reporting_Command( + HCI_SyncCommand[HCI_Read_Default_Erroneous_Data_ReturnParameters] +): ''' See Bluetooth spec @ 7.3.64 Read Default Erroneous Data Reporting Command ''' - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('erroneous_data_reporting', 1), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_Set_Event_Mask_Page_2_Command(HCI_Command): +class HCI_Set_Event_Mask_Page_2_Command(HCI_SyncCommand[HCI_StatusReturnParameters]): ''' See Bluetooth spec @ 7.3.69 Set Event Mask Page 2 Command ''' @@ -3661,24 +3843,26 @@ def mask(event_codes: Iterable[int]) -> bytes: # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_Read_LE_Host_Support_Command(HCI_Command): +class HCI_Read_LE_Host_Support_ReturnParameters(HCI_StatusReturnParameters): + le_supported_host: int = field(metadata=metadata(1)) + unused: int = field(metadata=metadata(1)) + + +@HCI_SyncCommand.sync_command(HCI_Read_LE_Host_Support_ReturnParameters) +@dataclasses.dataclass +class HCI_Read_LE_Host_Support_Command( + HCI_SyncCommand[HCI_Read_LE_Host_Support_ReturnParameters] +): ''' See Bluetooth spec @ 7.3.78 Read LE Host Support Command ''' - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('le_supported_host', 1), - ('unused', 1), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_Write_LE_Host_Support_Command(HCI_Command): +class HCI_Write_LE_Host_Support_Command(HCI_SyncCommand[HCI_StatusReturnParameters]): ''' See Bluetooth spec @ 7.3.79 Write LE Host Support Command ''' @@ -3688,9 +3872,11 @@ class HCI_Write_LE_Host_Support_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_Write_Secure_Connections_Host_Support_Command(HCI_Command): +class HCI_Write_Secure_Connections_Host_Support_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.3.92 Write Secure Connections Host Support Command ''' @@ -3699,9 +3885,11 @@ class HCI_Write_Secure_Connections_Host_Support_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_Write_Authenticated_Payload_Timeout_Command(HCI_Command): +class HCI_Write_Authenticated_Payload_Timeout_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.3.94 Write Authenticated Payload Timeout Command ''' @@ -3711,209 +3899,251 @@ class HCI_Write_Authenticated_Payload_Timeout_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_Read_Local_OOB_Extended_Data_Command(HCI_Command): +class HCI_Read_Local_OOB_Extended_Data_ReturnParameters(HCI_StatusReturnParameters): + le_supported_host: int = field(metadata=metadata(1)) + c_192: bytes = field(metadata=metadata(16)) + r_192: bytes = field(metadata=metadata(16)) + c_256: bytes = field(metadata=metadata(16)) + r_256: bytes = field(metadata=metadata(16)) + + +@HCI_SyncCommand.sync_command(HCI_Read_Local_OOB_Extended_Data_ReturnParameters) +@dataclasses.dataclass +class HCI_Read_Local_OOB_Extended_Data_Command( + HCI_SyncCommand[HCI_Read_Local_OOB_Extended_Data_ReturnParameters] +): ''' See Bluetooth spec @ 7.3.95 Read Local OOB Extended Data Command ''' - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('c_192', 16), - ('r_192', 16), - ('c_256', 16), - ('r_256', 16), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_Read_Local_Version_Information_Command(HCI_Command): +class HCI_Read_Local_Version_Information_ReturnParameters(HCI_StatusReturnParameters): + hci_version: int = field(metadata=SpecificationVersion.type_metadata(1)) + hci_subversion: int = field(metadata=metadata(2)) + lmp_version: int = field(metadata=SpecificationVersion.type_metadata(1)) + company_identifier: int = field(metadata=metadata(2)) + lmp_subversion: int = field(metadata=metadata(2)) + + +@HCI_SyncCommand.sync_command(HCI_Read_Local_Version_Information_ReturnParameters) +@dataclasses.dataclass +class HCI_Read_Local_Version_Information_Command( + HCI_SyncCommand[HCI_Read_Local_Version_Information_ReturnParameters] +): ''' See Bluetooth spec @ 7.4.1 Read Local Version Information Command ''' - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('hci_version', 1), - ('hci_subversion', 2), - ('lmp_version', 1), - ('company_identifier', 2), - ('lmp_subversion', 2), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_Read_Local_Supported_Commands_Command(HCI_Command): +class HCI_Read_Local_Supported_Commands_ReturnParameters(HCI_StatusReturnParameters): + supported_commands: bytes = field(metadata=metadata(64)) + + +@HCI_SyncCommand.sync_command(HCI_Read_Local_Supported_Commands_ReturnParameters) +@dataclasses.dataclass +class HCI_Read_Local_Supported_Commands_Command( + HCI_SyncCommand[HCI_Read_Local_Supported_Commands_ReturnParameters] +): ''' See Bluetooth spec @ 7.4.2 Read Local Supported Commands Command ''' - return_parameters_fields = [('status', STATUS_SPEC), ('supported_commands', 64)] - # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_Read_Local_Supported_Features_Command(HCI_Command): +class HCI_Read_Local_Supported_Features_ReturnParameters(HCI_StatusReturnParameters): + lmp_features: bytes = field(metadata=metadata(8)) + + +@HCI_SyncCommand.sync_command(HCI_Read_Local_Supported_Features_ReturnParameters) +@dataclasses.dataclass +class HCI_Read_Local_Supported_Features_Command( + HCI_SyncCommand[HCI_Read_Local_Supported_Features_ReturnParameters] +): ''' See Bluetooth spec @ 7.4.3 Read Local Supported Features Command ''' - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('lmp_features', 8), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_Read_Local_Extended_Features_Command(HCI_Command): +class HCI_Read_Local_Extended_Features_ReturnParameters(HCI_StatusReturnParameters): + page_number: int = field(metadata=metadata(1)) + maximum_page_number: int = field(metadata=metadata(1)) + extended_lmp_features: bytes = field(metadata=metadata(8)) + + +@HCI_SyncCommand.sync_command(HCI_Read_Local_Extended_Features_ReturnParameters) +@dataclasses.dataclass +class HCI_Read_Local_Extended_Features_Command( + HCI_SyncCommand[HCI_Read_Local_Extended_Features_ReturnParameters] +): ''' See Bluetooth spec @ 7.4.4 Read Local Extended Features Command ''' page_number: int = field(metadata=metadata(1)) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('page_number', 1), - ('maximum_page_number', 1), - ('extended_lmp_features', 8), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_Read_Buffer_Size_Command(HCI_Command): +class HCI_Read_Buffer_Size_ReturnParameters(HCI_StatusReturnParameters): + hc_acl_data_packet_length: int = field(metadata=metadata(2)) + hc_synchronous_data_packet_length: int = field(metadata=metadata(1)) + hc_total_num_acl_data_packets: int = field(metadata=metadata(2)) + hc_total_num_synchronous_data_packets: int = field(metadata=metadata(2)) + + +@HCI_SyncCommand.sync_command(HCI_Read_Buffer_Size_ReturnParameters) +@dataclasses.dataclass +class HCI_Read_Buffer_Size_Command( + HCI_SyncCommand[HCI_Read_Buffer_Size_ReturnParameters] +): ''' See Bluetooth spec @ 7.4.5 Read Buffer Size Command ''' - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('hc_acl_data_packet_length', 2), - ('hc_synchronous_data_packet_length', 1), - ('hc_total_num_acl_data_packets', 2), - ('hc_total_num_synchronous_data_packets', 2), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_Read_BD_ADDR_Command(HCI_Command): +class HCI_Read_BD_ADDR_ReturnParameters(HCI_StatusReturnParameters): + bd_addr: Address = field(metadata=metadata(Address.parse_address)) + + +@HCI_SyncCommand.sync_command(HCI_Read_BD_ADDR_ReturnParameters) +@dataclasses.dataclass +class HCI_Read_BD_ADDR_Command(HCI_SyncCommand[HCI_Read_BD_ADDR_ReturnParameters]): ''' See Bluetooth spec @ 7.4.6 Read BD_ADDR Command ''' - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('bd_addr', Address.parse_address), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_Read_Local_Supported_Codecs_Command(HCI_Command): - ''' - See Bluetooth spec @ 7.4.8 Read Local Supported Codecs Command - ''' - - return_parameters_fields = [ - ("status", STATUS_SPEC), - [("standard_codec_ids", 1)], - [("vendor_specific_codec_ids", 4)], - ] +class HCI_Read_Local_Supported_Codecs_ReturnParameters(HCI_StatusReturnParameters): + standard_codec_ids: Sequence[CodecID] = field( + metadata=CodecID.type_metadata(1, list_begin=True, list_end=True) + ) + vendor_specific_codec_ids: Sequence[int] = field( + metadata=metadata(4, list_begin=True, list_end=True) + ) -# ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_Read_Local_Supported_Codecs_ReturnParameters) @dataclasses.dataclass -class HCI_Read_Local_Supported_Codecs_V2_Command(HCI_Command): +class HCI_Read_Local_Supported_Codecs_Command( + HCI_SyncCommand[HCI_Read_Local_Supported_Codecs_ReturnParameters] +): ''' See Bluetooth spec @ 7.4.8 Read Local Supported Codecs Command ''' - return_parameters_fields = [ - ("status", STATUS_SPEC), - [("standard_codec_ids", 1), ("standard_codec_transports", 1)], - [("vendor_specific_codec_ids", 4), ("vendor_specific_codec_transports", 1)], - ] +# ----------------------------------------------------------------------------- +@dataclasses.dataclass +class HCI_Read_Local_Supported_Codecs_V2_ReturnParameters(HCI_StatusReturnParameters): class Transport(SpecableFlag): BR_EDR_ACL = 1 << 0 BR_EDR_SCO = 1 << 1 LE_CIS = 1 << 2 LE_BIS = 1 << 3 + standard_codec_ids: Sequence[CodecID] = field( + metadata=CodecID.type_metadata(1, list_begin=True) + ) + standard_codec_transports: Sequence[Transport] = field( + metadata=Transport.type_metadata(1, list_end=True) + ) + vendor_specific_codec_ids: Sequence[int] = field( + metadata=metadata(4, list_begin=True) + ) + vendor_specific_codec_transports: Sequence[Transport] = field( + metadata=Transport.type_metadata(1, list_end=True) + ) + + +@HCI_SyncCommand.sync_command(HCI_Read_Local_Supported_Codecs_V2_ReturnParameters) +@dataclasses.dataclass +class HCI_Read_Local_Supported_Codecs_V2_Command( + HCI_SyncCommand[HCI_Read_Local_Supported_Codecs_V2_ReturnParameters] +): + ''' + See Bluetooth spec @ 7.4.8 Read Local Supported Codecs Command + ''' + # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_Read_RSSI_Command(HCI_Command): +class HCI_Read_RSSI_ReturnParameters(HCI_StatusReturnParameters): + handle: int = field(metadata=metadata(2)) + rssi: int = field(metadata=metadata(-1)) + + +@HCI_SyncCommand.sync_command(HCI_Read_RSSI_ReturnParameters) +@dataclasses.dataclass +class HCI_Read_RSSI_Command(HCI_SyncCommand[HCI_Read_RSSI_ReturnParameters]): ''' See Bluetooth spec @ 7.5.4 Read RSSI Command ''' handle: int = field(metadata=metadata(2)) - return_parameters_fields = [('status', STATUS_SPEC), ('handle', 2), ('rssi', -1)] - # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_Read_Encryption_Key_Size_Command(HCI_Command): +class HCI_Read_Encryption_Key_Size_ReturnParameters(HCI_StatusReturnParameters): + connection_handle: int = field(metadata=metadata(2)) + key_size: int = field(metadata=metadata(1)) + + +@HCI_SyncCommand.sync_command(HCI_Read_Encryption_Key_Size_ReturnParameters) +@dataclasses.dataclass +class HCI_Read_Encryption_Key_Size_Command( + HCI_SyncCommand[HCI_Read_Encryption_Key_Size_ReturnParameters] +): ''' See Bluetooth spec @ 7.5.7 Read Encryption Key Size Command ''' connection_handle: int = field(metadata=metadata(2)) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('connection_handle', 2), - ('key_size', 1), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_Read_Loopback_Mode_Command(HCI_Command): +class HCI_Read_Loopback_Mode_ReturnParameters(HCI_StatusReturnParameters): + loopback_mode: LoopbackMode = field(metadata=LoopbackMode.type_metadata(1)) + + +@HCI_SyncCommand.sync_command(HCI_Read_Loopback_Mode_ReturnParameters) +@dataclasses.dataclass +class HCI_Read_Loopback_Mode_Command( + HCI_SyncCommand[HCI_Read_Loopback_Mode_ReturnParameters] +): ''' See Bluetooth spec @ 7.6.1 Read Loopback Mode Command ''' - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('loopback_mode', LoopbackMode.type_spec(1)), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_Write_Loopback_Mode_Command(HCI_Command): +class HCI_Write_Loopback_Mode_Command(HCI_SyncCommand[HCI_StatusReturnParameters]): ''' See Bluetooth spec @ 7.6.2 Write Loopback Mode Command ''' - loopback_mode: int = field(metadata=metadata(1)) + loopback_mode: LoopbackMode = field(metadata=LoopbackMode.type_metadata(1)) # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Set_Event_Mask_Command(HCI_Command): +class HCI_LE_Set_Event_Mask_Command(HCI_SyncCommand[HCI_StatusReturnParameters]): ''' See Bluetooth spec @ 7.8.1 LE Set Event Mask Command ''' @@ -3936,52 +4166,61 @@ def mask(event_codes: Iterable[int]) -> bytes: # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_LE_Read_Buffer_Size_Command(HCI_Command): +class HCI_LE_Read_Buffer_Size_ReturnParameters(HCI_StatusReturnParameters): + le_acl_data_packet_length: int = field(metadata=metadata(2)) + total_num_le_acl_data_packets: int = field(metadata=metadata(1)) + + +@HCI_SyncCommand.sync_command(HCI_LE_Read_Buffer_Size_ReturnParameters) +@dataclasses.dataclass +class HCI_LE_Read_Buffer_Size_Command( + HCI_SyncCommand[HCI_LE_Read_Buffer_Size_ReturnParameters] +): ''' See Bluetooth spec @ 7.8.2 LE Read Buffer Size Command ''' - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('le_acl_data_packet_length', 2), - ('total_num_le_acl_data_packets', 1), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_LE_Read_Buffer_Size_V2_Command(HCI_Command): +class HCI_LE_Read_Buffer_Size_V2_ReturnParameters(HCI_StatusReturnParameters): + le_acl_data_packet_length: int = field(metadata=metadata(2)) + total_num_le_acl_data_packets: int = field(metadata=metadata(1)) + iso_data_packet_length: int = field(metadata=metadata(2)) + total_num_iso_data_packets: int = field(metadata=metadata(1)) + + +@HCI_SyncCommand.sync_command(HCI_LE_Read_Buffer_Size_V2_ReturnParameters) +@dataclasses.dataclass +class HCI_LE_Read_Buffer_Size_V2_Command( + HCI_SyncCommand[HCI_LE_Read_Buffer_Size_V2_ReturnParameters] +): ''' See Bluetooth spec @ 7.8.2 LE Read Buffer Size V2 Command ''' - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('le_acl_data_packet_length', 2), - ('total_num_le_acl_data_packets', 1), - ('iso_data_packet_length', 2), - ('total_num_iso_data_packets', 1), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_LE_Read_Local_Supported_Features_Command(HCI_Command): +class HCI_LE_Read_Local_Supported_Features_ReturnParameters(HCI_StatusReturnParameters): + le_features: bytes = field(metadata=metadata(8)) + + +@HCI_SyncCommand.sync_command(HCI_LE_Read_Local_Supported_Features_ReturnParameters) +@dataclasses.dataclass +class HCI_LE_Read_Local_Supported_Features_Command( + HCI_SyncCommand[HCI_LE_Read_Local_Supported_Features_ReturnParameters] +): ''' See Bluetooth spec @ 7.8.3 LE Read Local Supported Features Command ''' - return_parameters_fields = [('status', STATUS_SPEC), ('le_features', 8)] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Set_Random_Address_Command(HCI_Command): +class HCI_LE_Set_Random_Address_Command(HCI_SyncCommand[HCI_StatusReturnParameters]): ''' See Bluetooth spec @ 7.8.4 LE Set Random Address Command ''' @@ -3996,9 +4235,11 @@ class HCI_LE_Set_Random_Address_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Set_Advertising_Parameters_Command(HCI_Command): +class HCI_LE_Set_Advertising_Parameters_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.8.5 LE Set Advertising Parameters Command ''' @@ -4023,23 +4264,29 @@ class AdvertisingType(SpecableEnum): # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_LE_Read_Advertising_Physical_Channel_Tx_Power_Command(HCI_Command): +class HCI_LE_Read_Advertising_Physical_Channel_Tx_Power_ReturnParameters( + HCI_StatusReturnParameters +): + tx_power_level: int = field(metadata=metadata(-1)) + + +@HCI_SyncCommand.sync_command( + HCI_LE_Read_Advertising_Physical_Channel_Tx_Power_ReturnParameters +) +@dataclasses.dataclass +class HCI_LE_Read_Advertising_Physical_Channel_Tx_Power_Command( + HCI_SyncCommand[HCI_LE_Read_Advertising_Physical_Channel_Tx_Power_ReturnParameters] +): ''' See Bluetooth spec @ 7.8.6 LE Read Advertising Physical Channel Tx Power Command ''' - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('tx_power_level', 1), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Set_Advertising_Data_Command(HCI_Command): +class HCI_LE_Set_Advertising_Data_Command(HCI_SyncCommand[HCI_StatusReturnParameters]): ''' See Bluetooth spec @ 7.8.7 LE Set Advertising Data Command ''' @@ -4057,9 +4304,11 @@ class HCI_LE_Set_Advertising_Data_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Set_Scan_Response_Data_Command(HCI_Command): +class HCI_LE_Set_Scan_Response_Data_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.8.8 LE Set Scan Response Data Command ''' @@ -4077,9 +4326,11 @@ class HCI_LE_Set_Scan_Response_Data_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Set_Advertising_Enable_Command(HCI_Command): +class HCI_LE_Set_Advertising_Enable_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.8.9 LE Set Advertising Enable Command ''' @@ -4088,9 +4339,9 @@ class HCI_LE_Set_Advertising_Enable_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Set_Scan_Parameters_Command(HCI_Command): +class HCI_LE_Set_Scan_Parameters_Command(HCI_SyncCommand[HCI_StatusReturnParameters]): ''' See Bluetooth spec @ 7.8.10 LE Set Scan Parameters Command ''' @@ -4111,9 +4362,9 @@ class HCI_LE_Set_Scan_Parameters_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Set_Scan_Enable_Command(HCI_Command): +class HCI_LE_Set_Scan_Enable_Command(HCI_SyncCommand[HCI_StatusReturnParameters]): ''' See Bluetooth spec @ 7.8.11 LE Set Scan Enable Command ''' @@ -4125,7 +4376,7 @@ class HCI_LE_Set_Scan_Enable_Command(HCI_Command): # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_LE_Create_Connection_Command(HCI_Command): +class HCI_LE_Create_Connection_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.8.12 LE Create Connection Command ''' @@ -4147,41 +4398,49 @@ class HCI_LE_Create_Connection_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Create_Connection_Cancel_Command(HCI_Command): +class HCI_LE_Create_Connection_Cancel_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.8.13 LE Create Connection Cancel Command ''' # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_LE_Read_Filter_Accept_List_Size_Command(HCI_Command): +class HCI_LE_Read_Filter_Accept_List_Size_ReturnParameters(HCI_StatusReturnParameters): + filter_accept_list_size: int = field(metadata=metadata(1)) + + +@HCI_SyncCommand.sync_command(HCI_LE_Read_Filter_Accept_List_Size_ReturnParameters) +@dataclasses.dataclass +class HCI_LE_Read_Filter_Accept_List_Size_Command( + HCI_SyncCommand[HCI_LE_Read_Filter_Accept_List_Size_ReturnParameters] +): ''' See Bluetooth spec @ 7.8.14 LE Read Filter Accept List Size Command ''' - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('filter_accept_list_size', 1), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Clear_Filter_Accept_List_Command(HCI_Command): +class HCI_LE_Clear_Filter_Accept_List_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.8.15 LE Clear Filter Accept List Command ''' # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Add_Device_To_Filter_Accept_List_Command(HCI_Command): +class HCI_LE_Add_Device_To_Filter_Accept_List_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.8.16 LE Add Device To Filter Accept List Command ''' @@ -4191,9 +4450,11 @@ class HCI_LE_Add_Device_To_Filter_Accept_List_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Remove_Device_From_Filter_Accept_List_Command(HCI_Command): +class HCI_LE_Remove_Device_From_Filter_Accept_List_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.8.17 LE Remove Device From Filter Accept List Command ''' @@ -4205,7 +4466,7 @@ class HCI_LE_Remove_Device_From_Filter_Accept_List_Command(HCI_Command): # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_LE_Connection_Update_Command(HCI_Command): +class HCI_LE_Connection_Update_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.8.18 LE Connection Update Command ''' @@ -4222,7 +4483,7 @@ class HCI_LE_Connection_Update_Command(HCI_Command): # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_LE_Read_Remote_Features_Command(HCI_Command): +class HCI_LE_Read_Remote_Features_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.8.21 LE Read Remote Features Command ''' @@ -4231,20 +4492,23 @@ class HCI_LE_Read_Remote_Features_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_LE_Rand_Command(HCI_Command): +class HCI_LE_Rand_ReturnParameters(HCI_StatusReturnParameters): + random_number: bytes = field(metadata=metadata(8)) + + +@HCI_SyncCommand.sync_command(HCI_LE_Rand_ReturnParameters) +@dataclasses.dataclass +class HCI_LE_Rand_Command(HCI_SyncCommand[HCI_LE_Rand_ReturnParameters]): """ See Bluetooth spec @ 7.8.23 LE Rand Command """ - return_parameters_fields = [("status", STATUS_SPEC), ("random_number", 8)] - # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_LE_Enable_Encryption_Command(HCI_Command): +class HCI_LE_Enable_Encryption_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.8.24 LE Enable Encryption Command (renamed from "LE Start Encryption Command" in version prior to 5.2 of the @@ -4258,9 +4522,11 @@ class HCI_LE_Enable_Encryption_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndConnectionHandleReturnParameters) @dataclasses.dataclass -class HCI_LE_Long_Term_Key_Request_Reply_Command(HCI_Command): +class HCI_LE_Long_Term_Key_Request_Reply_Command( + HCI_SyncCommand[HCI_StatusAndConnectionHandleReturnParameters] +): ''' See Bluetooth spec @ 7.8.25 LE Long Term Key Request Reply Command ''' @@ -4270,9 +4536,11 @@ class HCI_LE_Long_Term_Key_Request_Reply_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndConnectionHandleReturnParameters) @dataclasses.dataclass -class HCI_LE_Long_Term_Key_Request_Negative_Reply_Command(HCI_Command): +class HCI_LE_Long_Term_Key_Request_Negative_Reply_Command( + HCI_SyncCommand[HCI_StatusAndConnectionHandleReturnParameters] +): ''' See Bluetooth spec @ 7.8.26 LE Long Term Key Request Negative Reply Command ''' @@ -4281,23 +4549,27 @@ class HCI_LE_Long_Term_Key_Request_Negative_Reply_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_LE_Read_Supported_States_Command(HCI_Command): +class HCI_LE_Read_Supported_States_ReturnParameters(HCI_StatusReturnParameters): + le_states: bytes = field(metadata=metadata(8)) + + +@HCI_SyncCommand.sync_command(HCI_LE_Read_Supported_States_ReturnParameters) +@dataclasses.dataclass +class HCI_LE_Read_Supported_States_Command( + HCI_SyncCommand[HCI_LE_Read_Supported_States_ReturnParameters] +): ''' See Bluetooth spec @ 7.8.27 LE Read Supported States Command ''' - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('le_states', 8), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndConnectionHandleReturnParameters) @dataclasses.dataclass -class HCI_LE_Remote_Connection_Parameter_Request_Reply_Command(HCI_Command): +class HCI_LE_Remote_Connection_Parameter_Request_Reply_Command( + HCI_SyncCommand[HCI_StatusAndConnectionHandleReturnParameters] +): ''' See Bluetooth spec @ 7.8.31 LE Remote Connection Parameter Request Reply Command ''' @@ -4312,9 +4584,11 @@ class HCI_LE_Remote_Connection_Parameter_Request_Reply_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndConnectionHandleReturnParameters) @dataclasses.dataclass -class HCI_LE_Remote_Connection_Parameter_Request_Negative_Reply_Command(HCI_Command): +class HCI_LE_Remote_Connection_Parameter_Request_Negative_Reply_Command( + HCI_SyncCommand[HCI_StatusAndConnectionHandleReturnParameters] +): ''' See Bluetooth spec @ 7.8.32 LE Remote Connection Parameter Request Negative Reply Command @@ -4325,9 +4599,11 @@ class HCI_LE_Remote_Connection_Parameter_Request_Negative_Reply_Command(HCI_Comm # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndConnectionHandleReturnParameters) @dataclasses.dataclass -class HCI_LE_Set_Data_Length_Command(HCI_Command): +class HCI_LE_Set_Data_Length_Command( + HCI_SyncCommand[HCI_StatusAndConnectionHandleReturnParameters] +): ''' See Bluetooth spec @ 7.8.33 LE Set Data Length Command ''' @@ -4336,28 +4612,34 @@ class HCI_LE_Set_Data_Length_Command(HCI_Command): tx_octets: int = field(metadata=metadata(2)) tx_time: int = field(metadata=metadata(2)) - return_parameters_fields = [('status', STATUS_SPEC), ('connection_handle', 2)] - # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_LE_Read_Suggested_Default_Data_Length_Command(HCI_Command): +class HCI_LE_Read_Suggested_Default_Data_Length_ReturnParameters( + HCI_StatusReturnParameters +): + suggested_max_tx_octets: int = field(metadata=metadata(2)) + suggested_max_tx_time: int = field(metadata=metadata(2)) + + +@HCI_SyncCommand.sync_command( + HCI_LE_Read_Suggested_Default_Data_Length_ReturnParameters +) +@dataclasses.dataclass +class HCI_LE_Read_Suggested_Default_Data_Length_Command( + HCI_SyncCommand[HCI_LE_Read_Suggested_Default_Data_Length_ReturnParameters] +): ''' See Bluetooth spec @ 7.8.34 LE Read Suggested Default Data Length Command ''' - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('suggested_max_tx_octets', 2), - ('suggested_max_tx_time', 2), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Write_Suggested_Default_Data_Length_Command(HCI_Command): +class HCI_LE_Write_Suggested_Default_Data_Length_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.8.35 LE Write Suggested Default Data Length Command ''' @@ -4369,16 +4651,18 @@ class HCI_LE_Write_Suggested_Default_Data_Length_Command(HCI_Command): # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_LE_Read_Local_P_256_Public_Key_Command(HCI_Command): +class HCI_LE_Read_Local_P_256_Public_Key_Command(HCI_AsyncCommand): ''' - See Bluetooth spec @ 7.8.36 LE LE Read Local P-256 Public Key command + See Bluetooth spec @ 7.8.36 LE Read Local P-256 Public Key command ''' # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Add_Device_To_Resolving_List_Command(HCI_Command): +class HCI_LE_Add_Device_To_Resolving_List_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.8.38 LE Add Device To Resolving List Command ''' @@ -4394,27 +4678,36 @@ class HCI_LE_Add_Device_To_Resolving_List_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Clear_Resolving_List_Command(HCI_Command): +class HCI_LE_Clear_Resolving_List_Command(HCI_SyncCommand[HCI_StatusReturnParameters]): ''' See Bluetooth spec @ 7.8.40 LE Clear Resolving List Command ''' # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_LE_Read_Resolving_List_Size_Command(HCI_Command): +class HCI_LE_Read_Resolving_List_Size_ReturnParameters(HCI_StatusReturnParameters): + resolving_list_size: bytes = field(metadata=metadata(1)) + + +@HCI_SyncCommand.sync_command(HCI_LE_Read_Resolving_List_Size_ReturnParameters) +@dataclasses.dataclass +class HCI_LE_Read_Resolving_List_Size_Command( + HCI_SyncCommand[HCI_LE_Read_Resolving_List_Size_ReturnParameters] +): ''' See Bluetooth spec @ 7.8.41 LE Read Resolving List Size command ''' # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Set_Address_Resolution_Enable_Command(HCI_Command): +class HCI_LE_Set_Address_Resolution_Enable_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.8.44 LE Set Address Resolution Enable Command ''' @@ -4423,9 +4716,11 @@ class HCI_LE_Set_Address_Resolution_Enable_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Set_Resolvable_Private_Address_Timeout_Command(HCI_Command): +class HCI_LE_Set_Resolvable_Private_Address_Timeout_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.8.45 LE Set Resolvable Private Address Timeout Command ''' @@ -4434,44 +4729,45 @@ class HCI_LE_Set_Resolvable_Private_Address_Timeout_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_LE_Read_Maximum_Data_Length_Command(HCI_Command): +class HCI_LE_Read_Maximum_Data_Length_ReturnParameters(HCI_StatusReturnParameters): + supported_max_tx_octets: int = field(metadata=metadata(2)) + supported_max_tx_time: int = field(metadata=metadata(2)) + supported_max_rx_octets: int = field(metadata=metadata(2)) + supported_max_rx_time: int = field(metadata=metadata(2)) + + +@HCI_SyncCommand.sync_command(HCI_LE_Read_Maximum_Data_Length_ReturnParameters) +@dataclasses.dataclass +class HCI_LE_Read_Maximum_Data_Length_Command( + HCI_SyncCommand[HCI_LE_Read_Maximum_Data_Length_ReturnParameters] +): ''' See Bluetooth spec @ 7.8.46 LE Read Maximum Data Length Command ''' - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('supported_max_tx_octets', 2), - ('supported_max_tx_time', 2), - ('supported_max_rx_octets', 2), - ('supported_max_rx_time', 2), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_LE_Read_PHY_Command(HCI_Command): +class HCI_LE_Read_PHY_ReturnParameters(HCI_StatusAndConnectionHandleReturnParameters): + tx_phy: Phy = field(metadata=Phy.type_metadata(1)) + rx_phy: Phy = field(metadata=Phy.type_metadata(1)) + + +@HCI_SyncCommand.sync_command(HCI_LE_Read_PHY_ReturnParameters) +@dataclasses.dataclass +class HCI_LE_Read_PHY_Command(HCI_SyncCommand[HCI_LE_Read_PHY_ReturnParameters]): ''' See Bluetooth spec @ 7.8.47 LE Read PHY Command ''' connection_handle: int = field(metadata=metadata(2)) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('connection_handle', 2), - ('tx_phy', Phy.type_spec(1)), - ('rx_phy', Phy.type_spec(1)), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Set_Default_PHY_Command(HCI_Command): +class HCI_LE_Set_Default_PHY_Command(HCI_SyncCommand[HCI_StatusReturnParameters]): ''' See Bluetooth spec @ 7.8.48 LE Set Default PHY Command ''' @@ -4488,7 +4784,7 @@ class AnyPhy(SpecableFlag): # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_LE_Set_PHY_Command(HCI_Command): +class HCI_LE_Set_PHY_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.8.49 LE Set PHY Command ''' @@ -4503,9 +4799,11 @@ class HCI_LE_Set_PHY_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Set_Advertising_Set_Random_Address_Command(HCI_Command): +class HCI_LE_Set_Advertising_Set_Random_Address_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.8.52 LE Set Advertising Set Random Address Command ''' @@ -4521,15 +4819,24 @@ class HCI_LE_Set_Advertising_Set_Random_Address_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_LE_Set_Extended_Advertising_Parameters_Command(HCI_Command): +class HCI_LE_Set_Extended_Advertising_Parameters_ReturnParameters( + HCI_StatusReturnParameters +): + selected_tx_power: int = field(metadata=metadata(-1)) + + +@HCI_SyncCommand.sync_command( + HCI_LE_Set_Extended_Advertising_Parameters_ReturnParameters +) +@dataclasses.dataclass +class HCI_LE_Set_Extended_Advertising_Parameters_Command( + HCI_SyncCommand[HCI_LE_Set_Extended_Advertising_Parameters_ReturnParameters] +): ''' See Bluetooth spec @ 7.8.53 LE Set Extended Advertising Parameters Command ''' - return_parameters_fields = [('status', STATUS_SPEC), ('selected_tx_power', 1)] - TX_POWER_NO_PREFERENCE = 0x7F SHOULD_NOT_FRAGMENT = 0x01 @@ -4583,9 +4890,11 @@ def __str__(self) -> str: # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Set_Extended_Advertising_Data_Command(HCI_Command): +class HCI_LE_Set_Extended_Advertising_Data_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.8.54 LE Set Extended Advertising Data Command ''' @@ -4604,9 +4913,11 @@ class Operation(SpecableEnum): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Set_Extended_Scan_Response_Data_Command(HCI_Command): +class HCI_LE_Set_Extended_Scan_Response_Data_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.8.55 LE Set Extended Scan Response Data Command ''' @@ -4620,9 +4931,11 @@ class HCI_LE_Set_Extended_Scan_Response_Data_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Set_Extended_Advertising_Enable_Command(HCI_Command): +class HCI_LE_Set_Extended_Advertising_Enable_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.8.56 LE Set Extended Advertising Enable Command ''' @@ -4636,37 +4949,51 @@ class HCI_LE_Set_Extended_Advertising_Enable_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_LE_Read_Maximum_Advertising_Data_Length_Command(HCI_Command): +class HCI_LE_Read_Maximum_Advertising_Data_Length_ReturnParameters( + HCI_StatusReturnParameters +): + max_advertising_data_length: int = field(metadata=metadata(2)) + + +@HCI_SyncCommand.sync_command( + HCI_LE_Read_Maximum_Advertising_Data_Length_ReturnParameters +) +@dataclasses.dataclass +class HCI_LE_Read_Maximum_Advertising_Data_Length_Command( + HCI_SyncCommand[HCI_LE_Read_Maximum_Advertising_Data_Length_ReturnParameters] +): ''' See Bluetooth spec @ 7.8.57 LE Read Maximum Advertising Data Length Command ''' - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('max_advertising_data_length', 2), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_LE_Read_Number_Of_Supported_Advertising_Sets_Command(HCI_Command): +class HCI_LE_Read_Number_Of_Supported_Advertising_Sets_ReturnParameters( + HCI_StatusReturnParameters +): + num_supported_advertising_sets: int = field(metadata=metadata(1)) + + +@HCI_SyncCommand.sync_command( + HCI_LE_Read_Number_Of_Supported_Advertising_Sets_ReturnParameters +) +@dataclasses.dataclass +class HCI_LE_Read_Number_Of_Supported_Advertising_Sets_Command( + HCI_SyncCommand[HCI_LE_Read_Number_Of_Supported_Advertising_Sets_ReturnParameters] +): ''' See Bluetooth spec @ 7.8.58 LE Read Number of Supported Advertising Sets Command ''' - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('num_supported_advertising_sets', 1), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Remove_Advertising_Set_Command(HCI_Command): +class HCI_LE_Remove_Advertising_Set_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.8.59 LE Remove Advertising Set Command ''' @@ -4675,18 +5002,22 @@ class HCI_LE_Remove_Advertising_Set_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Clear_Advertising_Sets_Command(HCI_Command): +class HCI_LE_Clear_Advertising_Sets_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.8.60 LE Clear Advertising Sets Command ''' # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Set_Periodic_Advertising_Parameters_Command(HCI_Command): +class HCI_LE_Set_Periodic_Advertising_Parameters_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.8.61 LE Set Periodic Advertising Parameters command ''' @@ -4701,9 +5032,11 @@ class Properties(SpecableFlag): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Set_Periodic_Advertising_Data_Command(HCI_Command): +class HCI_LE_Set_Periodic_Advertising_Data_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.8.62 LE Set Periodic Advertising Data command ''' @@ -4716,9 +5049,11 @@ class HCI_LE_Set_Periodic_Advertising_Data_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Set_Periodic_Advertising_Enable_Command(HCI_Command): +class HCI_LE_Set_Periodic_Advertising_Enable_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.8.63 LE Set Periodic Advertising Enable Command ''' @@ -4728,8 +5063,10 @@ class HCI_LE_Set_Periodic_Advertising_Enable_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command -class HCI_LE_Set_Extended_Scan_Parameters_Command(HCI_Command): +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) +class HCI_LE_Set_Extended_Scan_Parameters_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.8.64 LE Set Extended Scan Parameters Command ''' @@ -4839,9 +5176,11 @@ def __str__(self): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Set_Extended_Scan_Enable_Command(HCI_Command): +class HCI_LE_Set_Extended_Scan_Enable_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.8.65 LE Set Extended Scan Enable Command ''' @@ -4854,7 +5193,7 @@ class HCI_LE_Set_Extended_Scan_Enable_Command(HCI_Command): # ----------------------------------------------------------------------------- @HCI_Command.command -class HCI_LE_Extended_Create_Connection_Command(HCI_Command): +class HCI_LE_Extended_Create_Connection_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.8.66 LE Extended Create Connection Command ''' @@ -5026,7 +5365,7 @@ def __str__(self): # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_LE_Periodic_Advertising_Create_Sync_Command(HCI_Command): +class HCI_LE_Periodic_Advertising_Create_Sync_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.8.67 LE Periodic Advertising Create Sync command ''' @@ -5055,18 +5394,22 @@ class CteType(SpecableFlag): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Periodic_Advertising_Create_Sync_Cancel_Command(HCI_Command): +class HCI_LE_Periodic_Advertising_Create_Sync_Cancel_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.8.68 LE Periodic Advertising Create Sync Cancel Command ''' # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Periodic_Advertising_Terminate_Sync_Command(HCI_Command): +class HCI_LE_Periodic_Advertising_Terminate_Sync_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.8.69 LE Periodic Advertising Terminate Sync Command ''' @@ -5075,18 +5418,26 @@ class HCI_LE_Periodic_Advertising_Terminate_Sync_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_LE_Read_Transmit_Power_Command(HCI_Command): +class HCI_LE_Read_Transmit_Power_ReturnParameters(HCI_StatusReturnParameters): + min_tx_power: int = field(metadata=metadata(1)) + max_tx_power: int = field(metadata=metadata(1)) + + +@HCI_SyncCommand.sync_command(HCI_LE_Read_Transmit_Power_ReturnParameters) +@dataclasses.dataclass +class HCI_LE_Read_Transmit_Power_Command( + HCI_SyncCommand[HCI_LE_Read_Transmit_Power_ReturnParameters] +): ''' See Bluetooth spec @ 7.8.74 LE Read Transmit Power command ''' # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Set_Privacy_Mode_Command(HCI_Command): +class HCI_LE_Set_Privacy_Mode_Command(HCI_SyncCommand[HCI_StatusReturnParameters]): ''' See Bluetooth spec @ 7.8.77 LE Set Privacy Mode Command ''' @@ -5105,9 +5456,11 @@ class PrivacyMode(SpecableEnum): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Set_Periodic_Advertising_Receive_Enable_Command(HCI_Command): +class HCI_LE_Set_Periodic_Advertising_Receive_Enable_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.8.88 LE Set Periodic Advertising Receive Enable Command ''' @@ -5121,9 +5474,11 @@ class Enable(SpecableFlag): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndConnectionHandleReturnParameters) @dataclasses.dataclass -class HCI_LE_Periodic_Advertising_Sync_Transfer_Command(HCI_Command): +class HCI_LE_Periodic_Advertising_Sync_Transfer_Command( + HCI_SyncCommand[HCI_StatusAndConnectionHandleReturnParameters] +): ''' See Bluetooth spec @ 7.8.89 LE Periodic Advertising Sync Transfer Command ''' @@ -5132,16 +5487,13 @@ class HCI_LE_Periodic_Advertising_Sync_Transfer_Command(HCI_Command): service_data: int = field(metadata=metadata(2)) sync_handle: int = field(metadata=metadata(2)) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('connection_handle', 2), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndConnectionHandleReturnParameters) @dataclasses.dataclass -class HCI_LE_Periodic_Advertising_Set_Info_Transfer_Command(HCI_Command): +class HCI_LE_Periodic_Advertising_Set_Info_Transfer_Command( + HCI_SyncCommand[HCI_StatusAndConnectionHandleReturnParameters] +): ''' See Bluetooth spec @ 7.8.90 LE Periodic Advertising Set Info Transfer Command ''' @@ -5150,16 +5502,13 @@ class HCI_LE_Periodic_Advertising_Set_Info_Transfer_Command(HCI_Command): service_data: int = field(metadata=metadata(2)) advertising_handle: int = field(metadata=metadata(1)) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('connection_handle', 2), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndConnectionHandleReturnParameters) @dataclasses.dataclass -class HCI_LE_Set_Periodic_Advertising_Sync_Transfer_Parameters_Command(HCI_Command): +class HCI_LE_Set_Periodic_Advertising_Sync_Transfer_Parameters_Command( + HCI_SyncCommand[HCI_StatusAndConnectionHandleReturnParameters] +): ''' See Bluetooth spec @ 7.8.91 LE Set Periodic Advertising Sync Transfer Parameters command ''' @@ -5170,17 +5519,12 @@ class HCI_LE_Set_Periodic_Advertising_Sync_Transfer_Parameters_Command(HCI_Comma sync_timeout: int = field(metadata=metadata(2)) cte_type: int = field(metadata=CteType.type_metadata(1)) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('connection_handle', 2), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass class HCI_LE_Set_Default_Periodic_Advertising_Sync_Transfer_Parameters_Command( - HCI_Command + HCI_SyncCommand[HCI_StatusReturnParameters] ): ''' See Bluetooth spec @ 7.8.92 LE Set Default Periodic Advertising Sync Transfer Parameters command @@ -5191,34 +5535,43 @@ class HCI_LE_Set_Default_Periodic_Advertising_Sync_Transfer_Parameters_Command( sync_timeout: int = field(metadata=metadata(2)) cte_type: int = field(metadata=CteType.type_metadata(1)) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_LE_Read_ISO_TX_Sync_Command(HCI_Command): +class HCI_LE_Read_ISO_TX_Sync_ReturnParameters( + HCI_StatusAndConnectionHandleReturnParameters +): + packet_sequence_number: int = field(metadata=metadata(2)) + tx_time_stamp: int = field(metadata=metadata(4)) + time_offset: int = field(metadata=metadata(4)) + + +@HCI_SyncCommand.sync_command(HCI_LE_Read_ISO_TX_Sync_ReturnParameters) +@dataclasses.dataclass +class HCI_LE_Read_ISO_TX_Sync_Command( + HCI_SyncCommand[HCI_LE_Read_ISO_TX_Sync_ReturnParameters] +): ''' See Bluetooth spec @ 7.8.96 LE Read ISO TX Sync command ''' connection_handle: int = field(metadata=metadata(2)) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('connection_handle', 2), - ('packet_sequence_number', 2), - ('tx_time_stamp', 4), - ('time_offset', 3), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_LE_Set_CIG_Parameters_Command(HCI_Command): +class HCI_LE_Set_CIG_Parameters_ReturnParameters(HCI_StatusReturnParameters): + cig_id: int = field(metadata=metadata(1)) + connection_handle: Sequence[int] = field( + metadata=metadata(2, list_begin=True, list_end=True) + ) + + +@HCI_SyncCommand.sync_command(HCI_LE_Set_CIG_Parameters_ReturnParameters) +@dataclasses.dataclass +class HCI_LE_Set_CIG_Parameters_Command( + HCI_SyncCommand[HCI_LE_Set_CIG_Parameters_ReturnParameters] +): ''' See Bluetooth spec @ 7.8.97 LE Set CIG Parameters Command ''' @@ -5239,17 +5592,11 @@ class HCI_LE_Set_CIG_Parameters_Command(HCI_Command): rtn_c_to_p: Sequence[int] = field(metadata=metadata(1)) rtn_p_to_c: Sequence[int] = field(metadata=metadata(1, list_end=True)) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('cig_id', 1), - [('connection_handle', 2)], - ] - # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_LE_Create_CIS_Command(HCI_Command): +class HCI_LE_Create_CIS_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.8.99 LE Create CIS command ''' @@ -5259,22 +5606,25 @@ class HCI_LE_Create_CIS_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_LE_Remove_CIG_Command(HCI_Command): +class HCI_LE_Remove_CIG_ReturnParameters(HCI_StatusReturnParameters): + cig_id: int = field(metadata=metadata(1)) + + +@HCI_SyncCommand.sync_command(HCI_LE_Remove_CIG_ReturnParameters) +@dataclasses.dataclass +class HCI_LE_Remove_CIG_Command(HCI_SyncCommand[HCI_LE_Remove_CIG_ReturnParameters]): ''' See Bluetooth spec @ 7.8.100 LE Remove CIG command ''' cig_id: int = field(metadata=metadata(1)) - return_parameters_fields = [('status', STATUS_SPEC), ('cig_id', 1)] - # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_LE_Accept_CIS_Request_Command(HCI_Command): +class HCI_LE_Accept_CIS_Request_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.8.101 LE Accept CIS Request command ''' @@ -5283,9 +5633,11 @@ class HCI_LE_Accept_CIS_Request_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndConnectionHandleReturnParameters) @dataclasses.dataclass -class HCI_LE_Reject_CIS_Request_Command(HCI_Command): +class HCI_LE_Reject_CIS_Request_Command( + HCI_SyncCommand[HCI_StatusAndConnectionHandleReturnParameters] +): ''' See Bluetooth spec @ 7.8.102 LE Reject CIS Request command ''' @@ -5297,7 +5649,7 @@ class HCI_LE_Reject_CIS_Request_Command(HCI_Command): # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_LE_Create_BIG_Command(HCI_Command): +class HCI_LE_Create_BIG_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.8.103 LE Create BIG command ''' @@ -5319,7 +5671,7 @@ class HCI_LE_Create_BIG_Command(HCI_Command): # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_LE_Terminate_BIG_Command(HCI_Command): +class HCI_LE_Terminate_BIG_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.8.105 LE Terminate BIG command ''' @@ -5331,7 +5683,7 @@ class HCI_LE_Terminate_BIG_Command(HCI_Command): # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_LE_BIG_Create_Sync_Command(HCI_Command): +class HCI_LE_BIG_Create_Sync_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.8.106 LE BIG Create Sync command ''' @@ -5346,25 +5698,29 @@ class HCI_LE_BIG_Create_Sync_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_LE_BIG_Terminate_Sync_Command(HCI_Command): +class HCI_LE_BIG_Terminate_Sync_ReturnParameters(HCI_StatusReturnParameters): + big_handle: int = field(metadata=metadata(1)) + + +@HCI_SyncCommand.sync_command(HCI_LE_BIG_Terminate_Sync_ReturnParameters) +@dataclasses.dataclass +class HCI_LE_BIG_Terminate_Sync_Command( + HCI_SyncCommand[HCI_LE_BIG_Terminate_Sync_ReturnParameters] +): ''' See Bluetooth spec @ 7.8.107. LE BIG Terminate Sync command ''' big_handle: int = field(metadata=metadata(1)) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('big_handle', 1), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndConnectionHandleReturnParameters) @dataclasses.dataclass -class HCI_LE_Setup_ISO_Data_Path_Command(HCI_Command): +class HCI_LE_Setup_ISO_Data_Path_Command( + HCI_SyncCommand[HCI_StatusAndConnectionHandleReturnParameters] +): ''' See Bluetooth spec @ 7.8.109 LE Setup ISO Data Path command ''' @@ -5380,16 +5736,13 @@ class Direction(SpecableEnum): controller_delay: int = field(metadata=metadata(3)) codec_configuration: bytes = field(metadata=metadata("v")) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('connection_handle', 2), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndConnectionHandleReturnParameters) @dataclasses.dataclass -class HCI_LE_Remove_ISO_Data_Path_Command(HCI_Command): +class HCI_LE_Remove_ISO_Data_Path_Command( + HCI_SyncCommand[HCI_StatusAndConnectionHandleReturnParameters] +): ''' See Bluetooth spec @ 7.8.110 LE Remove ISO Data Path command ''' @@ -5397,16 +5750,11 @@ class HCI_LE_Remove_ISO_Data_Path_Command(HCI_Command): connection_handle: int = field(metadata=metadata(2)) data_path_direction: int = field(metadata=metadata(1)) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('connection_handle', 2), - ] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Set_Host_Feature_Command(HCI_Command): +class HCI_LE_Set_Host_Feature_Command(HCI_SyncCommand[HCI_StatusReturnParameters]): ''' See Bluetooth spec @ 7.8.115 LE Set Host Feature Command ''' @@ -5416,9 +5764,9 @@ class HCI_LE_Set_Host_Feature_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_Set_Default_Subrate_Command(HCI_Command): +class HCI_LE_Set_Default_Subrate_Command(HCI_SyncCommand[HCI_StatusReturnParameters]): ''' See Bluetooth spec @ 7.8.123 LE Set Default Subrate command ''' @@ -5433,7 +5781,7 @@ class HCI_LE_Set_Default_Subrate_Command(HCI_Command): # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_LE_Subrate_Request_Command(HCI_Command): +class HCI_LE_Subrate_Request_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.8.124 LE Subrate Request command ''' @@ -5447,42 +5795,48 @@ class HCI_LE_Subrate_Request_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command @dataclasses.dataclass -class HCI_LE_CS_Read_Local_Supported_Capabilities_Command(HCI_Command): +class HHCI_LE_CS_Read_Local_Supported_Capabilities_ReturnParameters( + HCI_StatusReturnParameters +): + num_config_supported: int = field(metadata=metadata(1)) + max_consecutive_procedures_supported: int = field(metadata=metadata(2)) + num_antennas_supported: int = field(metadata=metadata(1)) + max_antenna_paths_supported: int = field(metadata=metadata(1)) + roles_supported: int = field(metadata=metadata(1)) + modes_supported: int = field(metadata=metadata(1)) + rtt_capability: int = field(metadata=metadata(1)) + rtt_aa_only_n: int = field(metadata=metadata(1)) + rtt_sounding_n: int = field(metadata=metadata(1)) + rtt_random_payload_n: int = field(metadata=metadata(1)) + nadm_sounding_capability: int = field(metadata=metadata(2)) + nadm_random_capability: int = field(metadata=metadata(2)) + cs_sync_phys_supported: int = field(metadata=metadata(CS_SYNC_PHY_SUPPORTED_SPEC)) + subfeatures_supported: int = field(metadata=metadata(2)) + t_ip1_times_supported: int = field(metadata=metadata(2)) + t_ip2_times_supported: int = field(metadata=metadata(2)) + t_fcs_times_supported: int = field(metadata=metadata(2)) + t_pm_times_supported: int = field(metadata=metadata(2)) + t_sw_time_supported: int = field(metadata=metadata(1)) + tx_snr_capability: int = field(metadata=metadata(CS_SNR_SPEC)) + + +@HCI_SyncCommand.sync_command( + HHCI_LE_CS_Read_Local_Supported_Capabilities_ReturnParameters +) +@dataclasses.dataclass +class HCI_LE_CS_Read_Local_Supported_Capabilities_Command( + HCI_SyncCommand[HHCI_LE_CS_Read_Local_Supported_Capabilities_ReturnParameters] +): ''' See Bluetooth spec @ 7.8.130 LE CS Read Local Supported Capabilities command ''' - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('num_config_supported', 1), - ('max_consecutive_procedures_supported', 2), - ('num_antennas_supported', 1), - ('max_antenna_paths_supported', 1), - ('roles_supported', 1), - ('modes_supported', 1), - ('rtt_capability', 1), - ('rtt_aa_only_n', 1), - ('rtt_sounding_n', 1), - ('rtt_random_payload_n', 1), - ('nadm_sounding_capability', 2), - ('nadm_random_capability', 2), - ('cs_sync_phys_supported', CS_SYNC_PHY_SUPPORTED_SPEC), - ('subfeatures_supported', 2), - ('t_ip1_times_supported', 2), - ('t_ip2_times_supported', 2), - ('t_fcs_times_supported', 2), - ('t_pm_times_supported', 2), - ('t_sw_time_supported', 1), - ('tx_snr_capability', CS_SNR_SPEC), - ] - # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_LE_CS_Read_Remote_Supported_Capabilities_Command(HCI_Command): +class HCI_LE_CS_Read_Remote_Supported_Capabilities_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.8.131 LE CS Read Remote Supported Capabilities command ''' @@ -5491,9 +5845,11 @@ class HCI_LE_CS_Read_Remote_Supported_Capabilities_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndConnectionHandleReturnParameters) @dataclasses.dataclass -class HCI_LE_CS_Write_Cached_Remote_Supported_Capabilities_Command(HCI_Command): +class HCI_LE_CS_Write_Cached_Remote_Supported_Capabilities_Command( + HCI_SyncCommand[HCI_StatusAndConnectionHandleReturnParameters] +): ''' See Bluetooth spec @ 7.8.132 LE CS Write Cached Remote Supported Capabilities command ''' @@ -5520,16 +5876,11 @@ class HCI_LE_CS_Write_Cached_Remote_Supported_Capabilities_Command(HCI_Command): t_sw_time_supported: int = field(metadata=metadata(1)) tx_snr_capability: int = field(metadata=metadata(CS_SNR_SPEC)) - return_parameters_fields = [ - ('status', STATUS_SPEC), - ('connection_handle', 2), - ] - # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_LE_CS_Security_Enable_Command(HCI_Command): +class HCI_LE_CS_Security_Enable_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.8.133 LE CS Security Enable command ''' @@ -5538,9 +5889,11 @@ class HCI_LE_CS_Security_Enable_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndConnectionHandleReturnParameters) @dataclasses.dataclass -class HCI_LE_CS_Set_Default_Settings_Command(HCI_Command): +class HCI_LE_CS_Set_Default_Settings_Command( + HCI_SyncCommand[HCI_StatusAndConnectionHandleReturnParameters] +): ''' See Bluetooth spec @ 7.8.134 LE CS Security Enable command ''' @@ -5550,13 +5903,11 @@ class HCI_LE_CS_Set_Default_Settings_Command(HCI_Command): cs_sync_antenna_selection: int = field(metadata=metadata(1)) max_tx_power: int = field(metadata=metadata(1)) - return_parameters_fields = [('status', STATUS_SPEC), ('connection_handle', 2)] - # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_LE_CS_Read_Remote_FAE_Table_Command(HCI_Command): +class HCI_LE_CS_Read_Remote_FAE_Table_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.8.135 LE CS Read Remote FAE Table command ''' @@ -5565,9 +5916,11 @@ class HCI_LE_CS_Read_Remote_FAE_Table_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndConnectionHandleReturnParameters) @dataclasses.dataclass -class HCI_LE_CS_Write_Cached_Remote_FAE_Table_Command(HCI_Command): +class HCI_LE_CS_Write_Cached_Remote_FAE_Table_Command( + HCI_SyncCommand[HCI_StatusAndConnectionHandleReturnParameters] +): ''' See Bluetooth spec @ 7.8.136 LE CS Write Cached Remote FAE Table command ''' @@ -5575,13 +5928,11 @@ class HCI_LE_CS_Write_Cached_Remote_FAE_Table_Command(HCI_Command): connection_handle: int = field(metadata=metadata(2)) remote_fae_table: bytes = field(metadata=metadata(72)) - return_parameters_fields = [('status', STATUS_SPEC), ('connection_handle', 2)] - # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_LE_CS_Create_Config_Command(HCI_Command): +class HCI_LE_CS_Create_Config_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.8.137 LE CS Create Config command ''' @@ -5617,7 +5968,7 @@ class Ch3cShape(SpecableEnum): # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_LE_CS_Remove_Config_Command(HCI_Command): +class HCI_LE_CS_Remove_Config_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.8.138 LE CS Remove Config command ''' @@ -5627,22 +5978,24 @@ class HCI_LE_CS_Remove_Config_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_CS_Set_Channel_Classification_Command(HCI_Command): +class HCI_LE_CS_Set_Channel_Classification_Command( + HCI_SyncCommand[HCI_StatusReturnParameters] +): ''' See Bluetooth spec @ 7.8.139 LE CS Set Channel Classification command ''' channel_classification: bytes = field(metadata=metadata(10)) - return_parameters_fields = [('status', STATUS_SPEC)] - # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusAndConnectionHandleReturnParameters) @dataclasses.dataclass -class HCI_LE_CS_Set_Procedure_Parameters_Command(HCI_Command): +class HCI_LE_CS_Set_Procedure_Parameters_Command( + HCI_SyncCommand[HCI_StatusAndConnectionHandleReturnParameters] +): ''' See Bluetooth spec @ 7.8.140 LE CS Set Procedure Parameters command ''' @@ -5662,13 +6015,11 @@ class HCI_LE_CS_Set_Procedure_Parameters_Command(HCI_Command): snr_control_initiator: int = field(metadata=metadata(CS_SNR_SPEC)) snr_control_reflector: int = field(metadata=metadata(CS_SNR_SPEC)) - return_parameters_fields = [('status', STATUS_SPEC), ('connection_handle', 2)] - # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_LE_CS_Procedure_Enable_Command(HCI_Command): +class HCI_LE_CS_Procedure_Enable_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.8.141 LE CS Procedure Enable command ''' @@ -5679,9 +6030,9 @@ class HCI_LE_CS_Procedure_Enable_Command(HCI_Command): # ----------------------------------------------------------------------------- -@HCI_Command.command +@HCI_SyncCommand.sync_command(HCI_StatusReturnParameters) @dataclasses.dataclass -class HCI_LE_CS_Test_Command(HCI_Command): +class HCI_LE_CS_Test_Command(HCI_SyncCommand[HCI_StatusReturnParameters]): ''' See Bluetooth spec @ 7.8.142 LE CS Test command ''' @@ -5716,7 +6067,7 @@ class HCI_LE_CS_Test_Command(HCI_Command): # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass -class HCI_LE_CS_Test_End_Command(HCI_Command): +class HCI_LE_CS_Test_End_Command(HCI_AsyncCommand): ''' See Bluetooth spec @ 7.8.143 LE CS Test End command ''' @@ -5807,10 +6158,19 @@ def remove_vendor_factory( @classmethod def from_bytes(cls, packet: bytes) -> HCI_Event: event_code = packet[1] - length = packet[2] - parameters = packet[3:] - if len(parameters) != length: - raise InvalidPacketError('invalid packet length') + parameters_length = packet[2] + if len(packet) < 3 + parameters_length: + raise InvalidPacketError( + f'invalid parameters length (packet={packet.hex()}): ' + f'expected {parameters_length}, got {len(packet) - 3})' + ) + if len(packet) > 3 + parameters_length: + logger.warning( + f'truncating parameters (packet={packet.hex()}): ' + f'from {len(packet) - 3} to {parameters_length}' + ) + + parameters = packet[3 : 3 + parameters_length] subclass: type[HCI_Event] | None if event_code == HCI_LE_META_EVENT: @@ -6970,7 +7330,7 @@ class ServiceType(SpecableEnum): # ----------------------------------------------------------------------------- @HCI_Event.event @dataclasses.dataclass -class HCI_Command_Complete_Event(HCI_Event): +class HCI_Command_Complete_Event(HCI_Event, Generic[_RP]): ''' See Bluetooth spec @ 7.7.14 Command Complete Event ''' @@ -6979,42 +7339,35 @@ class HCI_Command_Complete_Event(HCI_Event): command_opcode: int = field( metadata=metadata({'size': 2, 'mapper': HCI_Command.command_name}) ) - return_parameters: bytes | HCI_Object | int = field(metadata=metadata("*")) - - def map_return_parameters(self, return_parameters): - '''Map simple 'status' return parameters to their named constant form''' - - if isinstance(return_parameters, bytes) and len(return_parameters) == 1: - # Byte-array form - return HCI_Constant.status_name(return_parameters[0]) - - if isinstance(return_parameters, int): - # Already converted to an integer status code - return HCI_Constant.status_name(return_parameters) - - return return_parameters + return_parameters: _RP = field(metadata=metadata("*")) @classmethod def from_parameters(cls, parameters: bytes) -> Self: event = cls(**HCI_Object.dict_from_bytes(parameters, 0, cls.fields)) event.parameters = parameters - # Parse the return parameters - if ( - isinstance(event.return_parameters, bytes) - and len(event.return_parameters) == 1 - ): - # All commands with 1-byte return parameters return a 'status' field, - # convert it to an integer - event.return_parameters = event.return_parameters[0] - else: - subclass = HCI_Command.command_classes.get(event.command_opcode) - if subclass: - # Try to parse the return parameters bytes into an object. - return_parameters = subclass.parse_return_parameters( - event.return_parameters + + # Find the class for the matching command. + subclass = HCI_Command.command_classes.get(event.command_opcode) + + # Deal with unknown commands. + if subclass is None or not issubclass(subclass, HCI_SyncCommand): + if subclass is not None: + # Check that the subclass is one that has return parameters. + logger.warning( + 'HCI Command Complete event with opcode for a class that is not' + ' an HCI_SyncCommand subclass: ' + f'opcode={event.command_opcode:#04x}, ' + f'type={type(subclass).__name__}' ) - if return_parameters is not None: - event.return_parameters = return_parameters + event.return_parameters = HCI_GenericReturnParameters( + data=event.return_parameters # type: ignore[arg-type] + ) # type: ignore[assignment] + return event + + # Parse the return parameters bytes into an object. + event.return_parameters = subclass.parse_return_parameters( + event.return_parameters # type: ignore[arg-type] + ) # type: ignore[assignment] return event @@ -7023,7 +7376,7 @@ def __str__(self): self.__dict__, self.fields, ' ', - {'return_parameters': self.map_return_parameters}, + {'return_parameters': lambda x: "\n" + x.to_string(indentation=' ')}, ) diff --git a/bumble/host.py b/bumble/host.py index 929480b1..94aae921 100644 --- a/bumble/host.py +++ b/bumble/host.py @@ -23,11 +23,15 @@ import logging import struct from collections.abc import Awaitable, Callable -from typing import TYPE_CHECKING, Any, cast +from typing import TYPE_CHECKING, Any, TypeVar, cast, overload from bumble import drivers, hci, utils from bumble.colors import color -from bumble.core import ConnectionPHY, InvalidStateError, PhysicalTransport +from bumble.core import ( + ConnectionPHY, + InvalidStateError, + PhysicalTransport, +) from bumble.l2cap import L2CAP_PDU from bumble.snoop import Snooper from bumble.transport.common import TransportLostError @@ -35,7 +39,6 @@ if TYPE_CHECKING: from bumble.transport.common import TransportSink, TransportSource - # ----------------------------------------------------------------------------- # Logging # ----------------------------------------------------------------------------- @@ -236,6 +239,9 @@ class IsoLink: # ----------------------------------------------------------------------------- +_RP = TypeVar('_RP', bound=hci.HCI_ReturnParameters) + + class Host(utils.EventEmitter): connections: dict[int, Connection] cis_links: dict[int, IsoLink] @@ -264,11 +270,13 @@ def __init__( self.bis_links = {} # BIS links, by connection handle self.sco_links = {} # SCO links, by connection handle self.bigs = {} # BIG Handle to BIS Handles - self.pending_command = None + self.pending_command: hci.HCI_SyncCommand | hci.HCI_AsyncCommand | None = None self.pending_response: asyncio.Future[Any] | None = None self.number_of_supported_advertising_sets = 0 self.maximum_advertising_data_length = 31 - self.local_version = None + self.local_version: ( + hci.HCI_Read_Local_Version_Information_ReturnParameters | None + ) = None self.local_supported_commands = 0 self.local_le_features = 0 self.local_lmp_features = hci.LmpFeatureMask(0) # Classic LMP features @@ -312,7 +320,7 @@ async def flush(self) -> None: self.emit('flush') self.command_semaphore.release() - async def reset(self, driver_factory=drivers.get_driver_for_host): + async def reset(self, driver_factory=drivers.get_driver_for_host) -> None: if self.ready: self.ready = False await self.flush() @@ -330,57 +338,53 @@ async def reset(self, driver_factory=drivers.get_driver_for_host): # Send a reset command unless a driver has already done so. if reset_needed: - await self.send_command(hci.HCI_Reset_Command(), check_result=True) + await self.send_sync_command(hci.HCI_Reset_Command()) self.ready = True - response = await self.send_command( - hci.HCI_Read_Local_Supported_Commands_Command(), check_result=True + response1 = await self.send_sync_command( + hci.HCI_Read_Local_Supported_Commands_Command() ) self.local_supported_commands = int.from_bytes( - response.return_parameters.supported_commands, 'little' + response1.supported_commands, 'little' ) if self.supports_command(hci.HCI_LE_READ_LOCAL_SUPPORTED_FEATURES_COMMAND): - response = await self.send_command( - hci.HCI_LE_Read_Local_Supported_Features_Command(), check_result=True + response2 = await self.send_sync_command( + hci.HCI_LE_Read_Local_Supported_Features_Command() ) - self.local_le_features = struct.unpack( - ' None: if self.hci_sink: self.hci_sink.on_packet(bytes(packet)) - async def send_command( - self, command, check_result=False, response_timeout: int | None = None - ): + async def _send_command( + self, + command: hci.HCI_SyncCommand | hci.HCI_AsyncCommand, + response_timeout: float | None = None, + ) -> hci.HCI_Command_Complete_Event | hci.HCI_Command_Status_Event: # Wait until we can send (only one pending command at a time) async with self.command_semaphore: assert self.pending_command is None @@ -668,29 +656,9 @@ async def send_command( try: self.send_hci_packet(command) - await asyncio.wait_for(self.pending_response, timeout=response_timeout) - response = self.pending_response.result() - - # Check the return parameters if required - if check_result: - if isinstance(response, hci.HCI_Command_Status_Event): - status = response.status # type: ignore[attr-defined] - elif isinstance(response.return_parameters, int): - status = response.return_parameters - elif isinstance(response.return_parameters, bytes): - # return parameters first field is a one byte status code - status = response.return_parameters[0] - else: - status = response.return_parameters.status - - if status != hci.HCI_SUCCESS: - logger.warning( - f'{command.name} failed ' - f'({hci.HCI_Constant.error_name(status)})' - ) - raise hci.HCI_Error(status) - - return response + return await asyncio.wait_for( + self.pending_response, timeout=response_timeout + ) except Exception: logger.exception(color("!!! Exception while sending command:", "red")) raise @@ -698,12 +666,107 @@ async def send_command( self.pending_command = None self.pending_response = None - # Use this method to send a command from a task - def send_command_sync(self, command: hci.HCI_Command) -> None: - async def send_command(command: hci.HCI_Command) -> None: - await self.send_command(command) + @overload + async def send_command( + self, + command: hci.HCI_SyncCommand[_RP], + check_result: bool = False, + response_timeout: float | None = None, + ) -> hci.HCI_Command_Complete_Event[_RP]: ... - asyncio.create_task(send_command(command)) + @overload + async def send_command( + self, + command: hci.HCI_AsyncCommand, + check_result: bool = False, + response_timeout: float | None = None, + ) -> hci.HCI_Command_Status_Event: ... + + async def send_command( + self, + command: hci.HCI_SyncCommand[_RP] | hci.HCI_AsyncCommand, + check_result: bool = False, + response_timeout: float | None = None, + ) -> hci.HCI_Command_Complete_Event[_RP] | hci.HCI_Command_Status_Event: + response = await self._send_command(command, response_timeout) + + # Check the return parameters if required + if check_result: + if isinstance(response, hci.HCI_Command_Status_Event): + status = response.status # type: ignore[attr-defined] + elif isinstance(response.return_parameters, int): + status = response.return_parameters + elif isinstance(response.return_parameters, bytes): + # return parameters first field is a one byte status code + status = response.return_parameters[0] + elif isinstance( + response.return_parameters, hci.HCI_GenericReturnParameters + ): + # FIXME: temporary workaround + # NO STATUS + status = hci.HCI_SUCCESS + else: + status = response.return_parameters.status + + if status != hci.HCI_SUCCESS: + logger.warning( + f'{command.name} failed ' f'({hci.HCI_Constant.error_name(status)})' + ) + raise hci.HCI_Error(status) + + return response + + async def send_sync_command( + self, + command: hci.HCI_SyncCommand[_RP], + check_status: bool = True, + response_timeout: float | None = None, + ) -> _RP: + response = await self._send_command(command, response_timeout) + + # Check that the response is of the expected type + assert isinstance(response, hci.HCI_Command_Complete_Event) + return_parameters: _RP = response.return_parameters + assert isinstance(return_parameters, command.return_parameters_class) + + # Check the return parameters if required + if check_status: + if isinstance(return_parameters, hci.HCI_StatusReturnParameters): + status = return_parameters.status + if status != hci.HCI_SUCCESS: + logger.warning( + f'{command.name} failed ' + f'({hci.HCI_Constant.error_name(status)})' + ) + raise hci.HCI_Error(status) + + return return_parameters + + async def send_async_command( + self, + command: hci.HCI_AsyncCommand, + check_status: bool = True, + response_timeout: float | None = None, + ) -> hci.HCI_ErrorCode: + response = await self._send_command(command, response_timeout) + + # Check that the response is of the expected type + assert isinstance(response, hci.HCI_Command_Status_Event) + + # Check the return parameters if required + status = response.status + if check_status: + if status != hci.HCI_CommandStatus.PENDING: + logger.warning( + f'{command.name} failed ' f'({hci.HCI_Constant.error_name(status)})' + ) + raise hci.HCI_Error(status) + + return hci.HCI_ErrorCode(status) + + @utils.deprecated("Use utils.AsyncRunner.spawn() instead.") + def send_command_sync(self, command: hci.HCI_AsyncCommand) -> None: + utils.AsyncRunner.spawn(self.send_async_command(command)) def send_acl_sdu(self, connection_handle: int, sdu: bytes) -> None: if not (connection := self.connections.get(connection_handle)): @@ -1338,15 +1401,17 @@ def on_hci_le_remote_connection_parameter_request_event( # For now, just accept everything # TODO: delegate the decision - self.send_command_sync( - hci.HCI_LE_Remote_Connection_Parameter_Request_Reply_Command( - connection_handle=event.connection_handle, - interval_min=event.interval_min, - interval_max=event.interval_max, - max_latency=event.max_latency, - timeout=event.timeout, - min_ce_length=0, - max_ce_length=0, + utils.AsyncRunner.spawn( + self.send_sync_command( + hci.HCI_LE_Remote_Connection_Parameter_Request_Reply_Command( + connection_handle=event.connection_handle, + interval_min=event.interval_min, + interval_max=event.interval_max, + max_latency=event.max_latency, + timeout=event.timeout, + min_ce_length=0, + max_ce_length=0, + ) ) ) @@ -1382,9 +1447,9 @@ async def send_long_term_key(): connection_handle=event.connection_handle ) - await self.send_command(response) + await self.send_sync_command(response) - asyncio.create_task(send_long_term_key()) + utils.AsyncRunner.spawn(send_long_term_key()) def on_hci_synchronous_connection_complete_event( self, event: hci.HCI_Synchronous_Connection_Complete_Event @@ -1583,9 +1648,9 @@ async def send_link_key(): bd_addr=event.bd_addr ) - await self.send_command(response) + await self.send_sync_command(response) - asyncio.create_task(send_link_key()) + utils.AsyncRunner.spawn(send_link_key()) def on_hci_io_capability_request_event( self, event: hci.HCI_IO_Capability_Request_Event diff --git a/bumble/profiles/battery_service.py b/bumble/profiles/battery_service.py index 765f4a02..7c1c3c40 100644 --- a/bumble/profiles/battery_service.py +++ b/bumble/profiles/battery_service.py @@ -18,10 +18,7 @@ # ----------------------------------------------------------------------------- from collections.abc import Callable -from bumble import device -from bumble import gatt -from bumble import gatt_adapters -from bumble import gatt_client +from bumble import device, gatt, gatt_adapters, gatt_client # ----------------------------------------------------------------------------- diff --git a/bumble/profiles/heart_rate_service.py b/bumble/profiles/heart_rate_service.py index ca028940..ffd7b239 100644 --- a/bumble/profiles/heart_rate_service.py +++ b/bumble/profiles/heart_rate_service.py @@ -19,19 +19,14 @@ from __future__ import annotations import dataclasses +import enum +import struct +from collections.abc import Callable, Sequence from typing import Any + from typing_extensions import Self -from collections.abc import Sequence, Callable -import struct -import enum -from bumble import core -from bumble import device -from bumble import utils -from bumble import att -from bumble import gatt -from bumble import gatt_adapters -from bumble import gatt_client +from bumble import att, core, device, gatt, gatt_adapters, gatt_client, utils # ----------------------------------------------------------------------------- diff --git a/bumble/vendor/android/hci.py b/bumble/vendor/android/hci.py index f6783b4a..27f8e3da 100644 --- a/bumble/vendor/android/hci.py +++ b/bumble/vendor/android/hci.py @@ -43,44 +43,53 @@ # ----------------------------------------------------------------------------- -@hci.HCI_Command.command @dataclasses.dataclass -class HCI_LE_Get_Vendor_Capabilities_Command(hci.HCI_Command): +class HCI_LE_Get_Vendor_Capabilities_ReturnParameters(hci.HCI_StatusReturnParameters): + max_advt_instances: int = field(metadata=hci.metadata(1), default=0) + offloaded_resolution_of_private_address: int = field( + metadata=hci.metadata(1), default=0 + ) + total_scan_results_storage: int = field(metadata=hci.metadata(2), default=0) + max_irk_list_sz: int = field(metadata=hci.metadata(1), default=0) + filtering_support: int = field(metadata=hci.metadata(1), default=0) + max_filter: int = field(metadata=hci.metadata(1), default=0) + activity_energy_info_support: int = field(metadata=hci.metadata(1), default=0) + version_supported: int = field(metadata=hci.metadata(2), default=0) + total_num_of_advt_tracked: int = field(metadata=hci.metadata(2), default=0) + extended_scan_support: int = field(metadata=hci.metadata(1), default=0) + debug_logging_supported: int = field(metadata=hci.metadata(1), default=0) + le_address_generation_offloading_support: int = field( + metadata=hci.metadata(1), default=0 + ) + a2dp_source_offload_capability_mask: int = field( + metadata=hci.metadata(4), default=0 + ) + bluetooth_quality_report_support: int = field(metadata=hci.metadata(1), default=0) + dynamic_audio_buffer_support: int = field(metadata=hci.metadata(4), default=0) + + +@hci.HCI_SyncCommand.sync_command(HCI_LE_Get_Vendor_Capabilities_ReturnParameters) +@dataclasses.dataclass +class HCI_LE_Get_Vendor_Capabilities_Command( + hci.HCI_SyncCommand[HCI_LE_Get_Vendor_Capabilities_ReturnParameters] +): # pylint: disable=line-too-long ''' See https://source.android.com/docs/core/connect/bluetooth/hci_requirements#vendor-specific-capabilities ''' - return_parameters_fields = [ - ('status', hci.STATUS_SPEC), - ('max_advt_instances', 1), - ('offloaded_resolution_of_private_address', 1), - ('total_scan_results_storage', 2), - ('max_irk_list_sz', 1), - ('filtering_support', 1), - ('max_filter', 1), - ('activity_energy_info_support', 1), - ('version_supported', 2), - ('total_num_of_advt_tracked', 2), - ('extended_scan_support', 1), - ('debug_logging_supported', 1), - ('le_address_generation_offloading_support', 1), - ('a2dp_source_offload_capability_mask', 4), - ('bluetooth_quality_report_support', 1), - ('dynamic_audio_buffer_support', 4), - ] - @classmethod def parse_return_parameters(cls, parameters): # There are many versions of this data structure, so we need to parse until - # there are no more bytes to parse, and leave un-signal parameters set to - # None (older versions) - nones = {field: None for field, _ in cls.return_parameters_fields} - return_parameters = hci.HCI_Object(cls.return_parameters_fields, **nones) + # there are no more bytes to parse, and leave un-signaled parameters set to + # 0 + return_parameters = HCI_LE_Get_Vendor_Capabilities_ReturnParameters( + hci.HCI_ErrorCode.SUCCESS + ) try: offset = 0 - for field in cls.return_parameters_fields: + for field in cls.return_parameters_class.fields: field_name, field_type = field field_value, field_size = hci.HCI_Object.parse_field( parameters, offset, field_type @@ -94,9 +103,30 @@ def parse_return_parameters(cls, parameters): # ----------------------------------------------------------------------------- -@hci.HCI_Command.command +# APCF Subcommands +class LeApcfOpcode(hci.SpecableEnum): + ENABLE = 0x00 + SET_FILTERING_PARAMETERS = 0x01 + BROADCASTER_ADDRESS = 0x02 + SERVICE_UUID = 0x03 + SERVICE_SOLICITATION_UUID = 0x04 + LOCAL_NAME = 0x05 + MANUFACTURER_DATA = 0x06 + SERVICE_DATA = 0x07 + TRANSPORT_DISCOVERY_SERVICE = 0x08 + AD_TYPE_FILTER = 0x09 + READ_EXTENDED_FEATURES = 0xFF + + +@dataclasses.dataclass +class HCI_LE_APCF_ReturnParameters(hci.HCI_StatusReturnParameters): + opcode: int = field(metadata=LeApcfOpcode.type_metadata(1)) + payload: bytes = field(metadata=hci.metadata('*')) + + +@hci.HCI_SyncCommand.sync_command(HCI_LE_APCF_ReturnParameters) @dataclasses.dataclass -class HCI_LE_APCF_Command(hci.HCI_Command): +class HCI_LE_APCF_Command(hci.HCI_SyncCommand[HCI_LE_APCF_ReturnParameters]): # pylint: disable=line-too-long ''' See https://source.android.com/docs/core/connect/bluetooth/hci_requirements#le_apcf_command @@ -105,52 +135,52 @@ class HCI_LE_APCF_Command(hci.HCI_Command): implementation. A future enhancement may define subcommand-specific data structures. ''' - # APCF Subcommands - class Opcode(hci.SpecableEnum): - ENABLE = 0x00 - SET_FILTERING_PARAMETERS = 0x01 - BROADCASTER_ADDRESS = 0x02 - SERVICE_UUID = 0x03 - SERVICE_SOLICITATION_UUID = 0x04 - LOCAL_NAME = 0x05 - MANUFACTURER_DATA = 0x06 - SERVICE_DATA = 0x07 - TRANSPORT_DISCOVERY_SERVICE = 0x08 - AD_TYPE_FILTER = 0x09 - READ_EXTENDED_FEATURES = 0xFF - - opcode: int = dataclasses.field(metadata=Opcode.type_metadata(1)) + opcode: int = dataclasses.field(metadata=LeApcfOpcode.type_metadata(1)) payload: bytes = dataclasses.field(metadata=hci.metadata("*")) - return_parameters_fields = [ - ('status', hci.STATUS_SPEC), - ('opcode', Opcode.type_spec(1)), - ('payload', '*'), - ] - # ----------------------------------------------------------------------------- -@hci.HCI_Command.command @dataclasses.dataclass -class HCI_Get_Controller_Activity_Energy_Info_Command(hci.HCI_Command): +class HCI_Get_Controller_Activity_Energy_Info_ReturnParameters( + hci.HCI_StatusReturnParameters +): + total_tx_time_ms: int = field(metadata=hci.metadata(4)) + total_rx_time_ms: int = field(metadata=hci.metadata(4)) + total_idle_time_ms: int = field(metadata=hci.metadata(4)) + total_energy_used: int = field(metadata=hci.metadata(4)) + + +@hci.HCI_SyncCommand.sync_command( + HCI_Get_Controller_Activity_Energy_Info_ReturnParameters +) +@dataclasses.dataclass +class HCI_Get_Controller_Activity_Energy_Info_Command( + hci.HCI_SyncCommand[HCI_Get_Controller_Activity_Energy_Info_ReturnParameters] +): # pylint: disable=line-too-long ''' See https://source.android.com/docs/core/connect/bluetooth/hci_requirements#le_get_controller_activity_energy_info ''' - return_parameters_fields = [ - ('status', hci.STATUS_SPEC), - ('total_tx_time_ms', 4), - ('total_rx_time_ms', 4), - ('total_idle_time_ms', 4), - ('total_energy_used', 4), - ] - # ----------------------------------------------------------------------------- -@hci.HCI_Command.command +# A2DP Hardware Offload Subcommands +class A2dpHardwareOffloadOpcode(hci.SpecableEnum): + START_A2DP_OFFLOAD = 0x01 + STOP_A2DP_OFFLOAD = 0x02 + + +@dataclasses.dataclass +class HCI_A2DP_Hardware_Offload_ReturnParameters(hci.HCI_StatusReturnParameters): + opcode: int = dataclasses.field(metadata=A2dpHardwareOffloadOpcode.type_metadata(1)) + payload: bytes = dataclasses.field(metadata=hci.metadata("*")) + + +@hci.HCI_SyncCommand.sync_command(HCI_A2DP_Hardware_Offload_ReturnParameters) @dataclasses.dataclass -class HCI_A2DP_Hardware_Offload_Command(hci.HCI_Command): +class HCI_A2DP_Hardware_Offload_Command( + hci.HCI_SyncCommand[HCI_A2DP_Hardware_Offload_ReturnParameters] +): # pylint: disable=line-too-long ''' See https://source.android.com/docs/core/connect/bluetooth/hci_requirements#a2dp-hardware-offload-support @@ -159,25 +189,27 @@ class HCI_A2DP_Hardware_Offload_Command(hci.HCI_Command): implementation. A future enhancement may define subcommand-specific data structures. ''' - # A2DP Hardware Offload Subcommands - class Opcode(hci.SpecableEnum): - START_A2DP_OFFLOAD = 0x01 - STOP_A2DP_OFFLOAD = 0x02 - - opcode: int = dataclasses.field(metadata=Opcode.type_metadata(1)) + opcode: int = dataclasses.field(metadata=A2dpHardwareOffloadOpcode.type_metadata(1)) payload: bytes = dataclasses.field(metadata=hci.metadata("*")) - return_parameters_fields = [ - ('status', hci.STATUS_SPEC), - ('opcode', Opcode.type_spec(1)), - ('payload', '*'), - ] - # ----------------------------------------------------------------------------- -@hci.HCI_Command.command +# Dynamic Audio Buffer Subcommands +class DynamicAudioBufferOpcode(hci.SpecableEnum): + GET_AUDIO_BUFFER_TIME_CAPABILITY = 0x01 + + @dataclasses.dataclass -class HCI_Dynamic_Audio_Buffer_Command(hci.HCI_Command): +class HCI_Dynamic_Audio_Buffer_ReturnParameters(hci.HCI_StatusReturnParameters): + opcode: int = dataclasses.field(metadata=DynamicAudioBufferOpcode.type_metadata(1)) + payload: bytes = dataclasses.field(metadata=hci.metadata("*")) + + +@hci.HCI_SyncCommand.sync_command(HCI_Dynamic_Audio_Buffer_ReturnParameters) +@dataclasses.dataclass +class HCI_Dynamic_Audio_Buffer_Command( + hci.HCI_SyncCommand[HCI_Dynamic_Audio_Buffer_ReturnParameters] +): # pylint: disable=line-too-long ''' See https://source.android.com/docs/core/connect/bluetooth/hci_requirements#dynamic-audio-buffer-command @@ -186,19 +218,9 @@ class HCI_Dynamic_Audio_Buffer_Command(hci.HCI_Command): implementation. A future enhancement may define subcommand-specific data structures. ''' - # Dynamic Audio Buffer Subcommands - class Opcode(hci.SpecableEnum): - GET_AUDIO_BUFFER_TIME_CAPABILITY = 0x01 - - opcode: int = dataclasses.field(metadata=Opcode.type_metadata(1)) + opcode: int = dataclasses.field(metadata=DynamicAudioBufferOpcode.type_metadata(1)) payload: bytes = dataclasses.field(metadata=hci.metadata("*")) - return_parameters_fields = [ - ('status', hci.STATUS_SPEC), - ('opcode', Opcode.type_spec(1)), - ('payload', '*'), - ] - # ----------------------------------------------------------------------------- class HCI_Android_Vendor_Event(hci.HCI_Extended_Event): diff --git a/bumble/vendor/zephyr/hci.py b/bumble/vendor/zephyr/hci.py index 8aff44f7..bad59e9b 100644 --- a/bumble/vendor/zephyr/hci.py +++ b/bumble/vendor/zephyr/hci.py @@ -46,9 +46,19 @@ class TX_Power_Level_Command: # ----------------------------------------------------------------------------- -@hci.HCI_Command.command @dataclasses.dataclass -class HCI_Write_Tx_Power_Level_Command(hci.HCI_Command, TX_Power_Level_Command): +class HCI_Write_Tx_Power_Level_ReturnParameters(hci.HCI_StatusReturnParameters): + handle_type: int = hci.field(metadata=hci.metadata(1)) + connection_handle: int = hci.field(metadata=hci.metadata(2)) + selected_tx_power_level: int = hci.field(metadata=hci.metadata(-1)) + + +@hci.HCI_SyncCommand.sync_command(HCI_Write_Tx_Power_Level_ReturnParameters) +@dataclasses.dataclass +class HCI_Write_Tx_Power_Level_Command( + hci.HCI_SyncCommand[HCI_Write_Tx_Power_Level_ReturnParameters], + TX_Power_Level_Command, +): ''' Write TX power level. See BT_HCI_OP_VS_WRITE_TX_POWER_LEVEL in https://github.com/zephyrproject-rtos/zephyr/blob/main/include/zephyr/bluetooth/hci_vs.h @@ -61,18 +71,21 @@ class HCI_Write_Tx_Power_Level_Command(hci.HCI_Command, TX_Power_Level_Command): connection_handle: int = dataclasses.field(metadata=hci.metadata(2)) tx_power_level: int = dataclasses.field(metadata=hci.metadata(-1)) - return_parameters_fields = [ - ('status', hci.STATUS_SPEC), - ('handle_type', 1), - ('connection_handle', 2), - ('selected_tx_power_level', -1), - ] - # ----------------------------------------------------------------------------- -@hci.HCI_Command.command @dataclasses.dataclass -class HCI_Read_Tx_Power_Level_Command(hci.HCI_Command, TX_Power_Level_Command): +class HCI_Read_Tx_Power_Level_ReturnParameters(hci.HCI_StatusReturnParameters): + handle_type: int = hci.field(metadata=hci.metadata(1)) + connection_handle: int = hci.field(metadata=hci.metadata(2)) + tx_power_level: int = hci.field(metadata=hci.metadata(-1)) + + +@hci.HCI_SyncCommand.sync_command(HCI_Read_Tx_Power_Level_ReturnParameters) +@dataclasses.dataclass +class HCI_Read_Tx_Power_Level_Command( + hci.HCI_SyncCommand[HCI_Read_Tx_Power_Level_ReturnParameters], + TX_Power_Level_Command, +): ''' Read TX power level. See BT_HCI_OP_VS_READ_TX_POWER_LEVEL in https://github.com/zephyrproject-rtos/zephyr/blob/main/include/zephyr/bluetooth/hci_vs.h @@ -83,10 +96,3 @@ class HCI_Read_Tx_Power_Level_Command(hci.HCI_Command, TX_Power_Level_Command): handle_type: int = dataclasses.field(metadata=hci.metadata(1)) connection_handle: int = dataclasses.field(metadata=hci.metadata(2)) - - return_parameters_fields = [ - ('status', hci.STATUS_SPEC), - ('handle_type', 1), - ('connection_handle', 2), - ('tx_power_level', -1), - ] diff --git a/docs/mkdocs/src/platforms/zephyr.md b/docs/mkdocs/src/platforms/zephyr.md index 0e68247f..dea70644 100644 --- a/docs/mkdocs/src/platforms/zephyr.md +++ b/docs/mkdocs/src/platforms/zephyr.md @@ -37,15 +37,16 @@ The vendor specific HCI commands to read and write TX power are defined in from bumble.vendor.zephyr.hci import HCI_Write_Tx_Power_Level_Command # set advertising power to -4 dB -response = await host.send_command( +response = await host.send_sync_command( HCI_Write_Tx_Power_Level_Command( handle_type=HCI_Write_Tx_Power_Level_Command.TX_POWER_HANDLE_TYPE_ADV, connection_handle=0, tx_power_level=-4, - ) + ), + check_status=False ) -if response.return_parameters.status == HCI_SUCCESS: - print(f"TX power set to {response.return_parameters.selected_tx_power_level}") +if response.status == HCI_SUCCESS: + print(f"TX power set to {response.selected_tx_power_level}") ``` diff --git a/tests/device_test.py b/tests/device_test.py index 511dbba0..c4d55644 100644 --- a/tests/device_test.py +++ b/tests/device_test.py @@ -42,7 +42,6 @@ HCI_CREATE_CONNECTION_COMMAND, HCI_SUCCESS, Address, - HCI_Command_Complete_Event, HCI_Command_Status_Event, HCI_Connection_Complete_Event, HCI_Connection_Request_Event, @@ -154,10 +153,10 @@ def d1_flow(): assert packet.name == 'HCI_ACCEPT_CONNECTION_REQUEST_COMMAND' d1.host.on_hci_packet( - HCI_Command_Complete_Event( + HCI_Command_Status_Event( + status=HCI_COMMAND_STATUS_PENDING, num_hci_command_packets=1, command_opcode=HCI_ACCEPT_CONNECTION_REQUEST_COMMAND, - return_parameters=b"\x00", ) ) @@ -188,10 +187,10 @@ def d2_flow(): assert packet.name == 'HCI_ACCEPT_CONNECTION_REQUEST_COMMAND' d2.host.on_hci_packet( - HCI_Command_Complete_Event( + HCI_Command_Status_Event( + status=HCI_COMMAND_STATUS_PENDING, num_hci_command_packets=1, command_opcode=HCI_ACCEPT_CONNECTION_REQUEST_COMMAND, - return_parameters=b"\x00", ) ) diff --git a/tests/hci_test.py b/tests/hci_test.py index b21e33cc..6014a765 100644 --- a/tests/hci_test.py +++ b/tests/hci_test.py @@ -20,7 +20,7 @@ import pytest -from bumble import hci +from bumble import hci, utils # ----------------------------------------------------------------------------- # pylint: disable=invalid-name @@ -136,43 +136,25 @@ def test_HCI_LE_Channel_Selection_Algorithm_Event(): # ----------------------------------------------------------------------------- def test_HCI_Command_Complete_Event(): # With a serializable object - event = hci.HCI_Command_Complete_Event( + event1 = hci.HCI_Command_Complete_Event( num_hci_command_packets=34, command_opcode=hci.HCI_LE_READ_BUFFER_SIZE_COMMAND, - return_parameters=hci.HCI_LE_Read_Buffer_Size_Command.create_return_parameters( + return_parameters=hci.HCI_LE_Read_Buffer_Size_Command.return_parameters_class( status=0, le_acl_data_packet_length=1234, total_num_le_acl_data_packets=56, ), ) - basic_check(event) - - # With an arbitrary byte array - event = hci.HCI_Command_Complete_Event( - num_hci_command_packets=1, - command_opcode=hci.HCI_RESET_COMMAND, - return_parameters=bytes([1, 2, 3, 4]), - ) - basic_check(event) - - # With a simple status as a 1-byte array - event = hci.HCI_Command_Complete_Event( - num_hci_command_packets=1, - command_opcode=hci.HCI_RESET_COMMAND, - return_parameters=bytes([7]), - ) - basic_check(event) - event = hci.HCI_Packet.from_bytes(bytes(event)) - assert event.return_parameters == 7 + basic_check(event1) # With a simple status as an integer status - event = hci.HCI_Command_Complete_Event( + event3 = hci.HCI_Command_Complete_Event( num_hci_command_packets=1, command_opcode=hci.HCI_RESET_COMMAND, - return_parameters=9, + return_parameters=hci.HCI_StatusReturnParameters(hci.HCI_ErrorCode(9)), ) - basic_check(event) - assert event.return_parameters == 9 + basic_check(event3) + assert event3.return_parameters.status == 9 # ----------------------------------------------------------------------------- @@ -229,6 +211,28 @@ def create_event(payload): assert isinstance(parsed, hci.HCI_Vendor_Event) +# ----------------------------------------------------------------------------- +def test_return_parameters() -> None: + params = hci.HCI_Reset_Command.parse_return_parameters(bytes.fromhex('3C')) + assert params.status == hci.HCI_ErrorCode.ADVERTISING_TIMEOUT_ERROR + assert isinstance(params.status, utils.OpenIntEnum) + + params = hci.HCI_Read_BD_ADDR_Command.parse_return_parameters( + bytes.fromhex('3C001122334455') + ) + assert params.status == hci.HCI_ErrorCode.ADVERTISING_TIMEOUT_ERROR + assert isinstance(params.status, utils.OpenIntEnum) + assert isinstance(params.bd_addr, hci.Address) + + params = hci.HCI_Read_Local_Name_Command.parse_return_parameters( + bytes.fromhex('0068656c6c6f') + bytes(248 - 5) + ) + assert params.status == hci.HCI_ErrorCode.SUCCESS + assert isinstance(params.local_name, bytes) + assert len(params.local_name) == 248 + assert hci.map_null_terminated_utf8_string(params.local_name) == 'hello' + + # ----------------------------------------------------------------------------- def test_HCI_Command(): command = hci.HCI_Command(op_code=0x5566) @@ -291,7 +295,7 @@ class CustomEvent(hci.HCI_LE_Meta_Event): for clazz in inspect.getmembers(hci) if isinstance(clazz[1], type) and issubclass(clazz[1], hci.HCI_Command) - and clazz[1] is not hci.HCI_Command + and clazz[1] not in (hci.HCI_Command, hci.HCI_SyncCommand, hci.HCI_AsyncCommand) ], ) def test_hci_command_subclasses_op_code(clazz: type[hci.HCI_Command]): @@ -620,21 +624,19 @@ def test_HCI_Read_Local_Supported_Codecs_Command_Complete(): # ----------------------------------------------------------------------------- def test_HCI_Read_Local_Supported_Codecs_V2_Command_Complete(): - returned_parameters = ( - hci.HCI_Read_Local_Supported_Codecs_V2_Command.parse_return_parameters( - bytes( - [ - hci.HCI_SUCCESS, - 3, - hci.CodecID.A_LOG, - hci.HCI_Read_Local_Supported_Codecs_V2_Command.Transport.BR_EDR_ACL, - hci.CodecID.CVSD, - hci.HCI_Read_Local_Supported_Codecs_V2_Command.Transport.BR_EDR_SCO, - hci.CodecID.LINEAR_PCM, - hci.HCI_Read_Local_Supported_Codecs_V2_Command.Transport.LE_CIS, - 0, - ] - ) + returned_parameters = hci.HCI_Read_Local_Supported_Codecs_V2_Command.parse_return_parameters( + bytes( + [ + hci.HCI_SUCCESS, + 3, + hci.CodecID.A_LOG, + hci.HCI_Read_Local_Supported_Codecs_V2_ReturnParameters.Transport.BR_EDR_ACL, + hci.CodecID.CVSD, + hci.HCI_Read_Local_Supported_Codecs_V2_ReturnParameters.Transport.BR_EDR_SCO, + hci.CodecID.LINEAR_PCM, + hci.HCI_Read_Local_Supported_Codecs_V2_ReturnParameters.Transport.LE_CIS, + 0, + ] ) ) assert returned_parameters.standard_codec_ids == [ @@ -643,9 +645,9 @@ def test_HCI_Read_Local_Supported_Codecs_V2_Command_Complete(): hci.CodecID.LINEAR_PCM, ] assert returned_parameters.standard_codec_transports == [ - hci.HCI_Read_Local_Supported_Codecs_V2_Command.Transport.BR_EDR_ACL, - hci.HCI_Read_Local_Supported_Codecs_V2_Command.Transport.BR_EDR_SCO, - hci.HCI_Read_Local_Supported_Codecs_V2_Command.Transport.LE_CIS, + hci.HCI_Read_Local_Supported_Codecs_V2_ReturnParameters.Transport.BR_EDR_ACL, + hci.HCI_Read_Local_Supported_Codecs_V2_ReturnParameters.Transport.BR_EDR_SCO, + hci.HCI_Read_Local_Supported_Codecs_V2_ReturnParameters.Transport.LE_CIS, ] @@ -737,6 +739,7 @@ def run_test_commands(): if __name__ == '__main__': run_test_events() run_test_commands() + test_return_parameters() test_address() test_custom() test_iso_data_packet() diff --git a/tests/heart_rate_service_test.py b/tests/heart_rate_service_test.py index bfc0a1ab..274e329e 100644 --- a/tests/heart_rate_service_test.py +++ b/tests/heart_rate_service_test.py @@ -12,9 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -from collections.abc import Sequence import asyncio import itertools +from collections.abc import Sequence import pytest diff --git a/tests/host_test.py b/tests/host_test.py index 1665f935..895e4ada 100644 --- a/tests/host_test.py +++ b/tests/host_test.py @@ -15,6 +15,7 @@ # ----------------------------------------------------------------------------- # Imports # ----------------------------------------------------------------------------- +import asyncio import logging import unittest import unittest.mock @@ -22,9 +23,17 @@ import pytest from bumble.controller import Controller -from bumble.hci import HCI_AclDataPacket +from bumble.hci import ( + HCI_AclDataPacket, + HCI_Command_Complete_Event, + HCI_Error, + HCI_ErrorCode, + HCI_Event, + HCI_Reset_Command, + HCI_StatusReturnParameters, +) from bumble.host import DataPacketQueue, Host -from bumble.transport.common import AsyncPipeSink +from bumble.transport.common import AsyncPipeSink, TransportSink # ----------------------------------------------------------------------------- # Logging @@ -151,3 +160,58 @@ def test_data_packet_queue(): assert drain_listener.on_flow.call_count == 1 assert queue.queued == 15 assert queue.completed == 15 + + +# ----------------------------------------------------------------------------- +class Source: + terminated: asyncio.Future[None] + sink: TransportSink + + def set_packet_sink(self, sink: TransportSink) -> None: + self.sink = sink + + +class Sink: + response: HCI_Event + + def __init__(self, source: Source, response: HCI_Event) -> None: + self.source = source + self.response = response + + def on_packet(self, packet: bytes) -> None: + self.source.sink.on_packet(bytes(self.response)) + + +@pytest.mark.asyncio +async def test_send_sync_command() -> None: + source = Source() + sink = Sink( + source, + HCI_Command_Complete_Event( + 1, + HCI_Reset_Command.op_code, + HCI_StatusReturnParameters(status=HCI_ErrorCode.SUCCESS), + ), + ) + + host = Host(source, sink) + + # Sync command with success + response1 = await host.send_sync_command(HCI_Reset_Command()) + assert response1.status == HCI_ErrorCode.SUCCESS + + # Sync command with error status should raise + error_response = HCI_Command_Complete_Event( + 1, + HCI_Reset_Command.op_code, + HCI_StatusReturnParameters(status=HCI_ErrorCode.COMMAND_DISALLOWED_ERROR), + ) + sink.response = error_response + with pytest.raises(HCI_Error) as excinfo: + await host.send_sync_command(HCI_Reset_Command()) + + assert excinfo.value.error_code == error_response.return_parameters.status + + # Sync command with error status should not raise when `check_status` is False + response2 = await host.send_sync_command(HCI_Reset_Command(), check_status=False) + assert response2.status == HCI_ErrorCode.COMMAND_DISALLOWED_ERROR diff --git a/web/heart_rate_monitor/heart_rate_monitor.py b/web/heart_rate_monitor/heart_rate_monitor.py index 36e5619b..4cf02760 100644 --- a/web/heart_rate_monitor/heart_rate_monitor.py +++ b/web/heart_rate_monitor/heart_rate_monitor.py @@ -89,7 +89,7 @@ async def start(self): async def stop(self): # TODO: replace this once a proper reset is implemented in the lib. - await self.device.host.send_command(HCI_Reset_Command()) + await self.device.host.send_sync_command(HCI_Reset_Command()) await self.device.power_off() print('### Monitor stopped') diff --git a/web/scanner/scanner.py b/web/scanner/scanner.py index 0c3f950a..8e24f408 100644 --- a/web/scanner/scanner.py +++ b/web/scanner/scanner.py @@ -60,7 +60,7 @@ async def start(self): async def stop(self): # TODO: replace this once a proper reset is implemented in the lib. - await self.device.host.send_command(HCI_Reset_Command()) + await self.device.host.send_sync_command(HCI_Reset_Command()) await self.device.power_off() print('### Scanner stopped') diff --git a/web/speaker/speaker.py b/web/speaker/speaker.py index b020a05f..1440eaf8 100644 --- a/web/speaker/speaker.py +++ b/web/speaker/speaker.py @@ -311,7 +311,7 @@ async def start(self): async def stop(self): # TODO: replace this once a proper reset is implemented in the lib. - await self.device.host.send_command(HCI_Reset_Command()) + await self.device.host.send_sync_command(HCI_Reset_Command()) await self.device.power_off() print('Speaker stopped')