mirror of
https://github.com/google/bumble.git
synced 2026-04-16 00:25:31 +00:00
Merge pull request #880 from google/gbg/command-status-hack
add workaround for some buggy controllers
This commit is contained in:
@@ -616,22 +616,28 @@ class Host(utils.EventEmitter):
|
|||||||
if self.supports_command(
|
if self.supports_command(
|
||||||
hci.HCI_LE_READ_NUMBER_OF_SUPPORTED_ADVERTISING_SETS_COMMAND
|
hci.HCI_LE_READ_NUMBER_OF_SUPPORTED_ADVERTISING_SETS_COMMAND
|
||||||
):
|
):
|
||||||
response10 = await self.send_sync_command(
|
try:
|
||||||
hci.HCI_LE_Read_Number_Of_Supported_Advertising_Sets_Command()
|
response10 = await self.send_sync_command(
|
||||||
)
|
hci.HCI_LE_Read_Number_Of_Supported_Advertising_Sets_Command()
|
||||||
self.number_of_supported_advertising_sets = (
|
)
|
||||||
response10.num_supported_advertising_sets
|
self.number_of_supported_advertising_sets = (
|
||||||
)
|
response10.num_supported_advertising_sets
|
||||||
|
)
|
||||||
|
except hci.HCI_Error:
|
||||||
|
logger.warning('Failed to read number of supported advertising sets')
|
||||||
|
|
||||||
if self.supports_command(
|
if self.supports_command(
|
||||||
hci.HCI_LE_READ_MAXIMUM_ADVERTISING_DATA_LENGTH_COMMAND
|
hci.HCI_LE_READ_MAXIMUM_ADVERTISING_DATA_LENGTH_COMMAND
|
||||||
):
|
):
|
||||||
response11 = await self.send_sync_command(
|
try:
|
||||||
hci.HCI_LE_Read_Maximum_Advertising_Data_Length_Command()
|
response11 = await self.send_sync_command(
|
||||||
)
|
hci.HCI_LE_Read_Maximum_Advertising_Data_Length_Command()
|
||||||
self.maximum_advertising_data_length = (
|
)
|
||||||
response11.max_advertising_data_length
|
self.maximum_advertising_data_length = (
|
||||||
)
|
response11.max_advertising_data_length
|
||||||
|
)
|
||||||
|
except hci.HCI_Error:
|
||||||
|
logger.warning('Failed to read maximum advertising data length')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def controller(self) -> TransportSink | None:
|
def controller(self) -> TransportSink | None:
|
||||||
@@ -776,6 +782,20 @@ class Host(utils.EventEmitter):
|
|||||||
) -> hci.HCI_Command_Complete_Event[_RP]:
|
) -> hci.HCI_Command_Complete_Event[_RP]:
|
||||||
response = await self._send_command(command, response_timeout)
|
response = await self._send_command(command, response_timeout)
|
||||||
|
|
||||||
|
# For unknown HCI commands, some controllers return Command Status instead of
|
||||||
|
# Command Complete.
|
||||||
|
if (
|
||||||
|
isinstance(response, hci.HCI_Command_Status_Event)
|
||||||
|
and response.status == hci.HCI_ErrorCode.UNKNOWN_HCI_COMMAND_ERROR
|
||||||
|
):
|
||||||
|
return hci.HCI_Command_Complete_Event(
|
||||||
|
num_hci_command_packets=response.num_hci_command_packets,
|
||||||
|
command_opcode=command.op_code,
|
||||||
|
return_parameters=hci.HCI_StatusReturnParameters(
|
||||||
|
status=hci.HCI_ErrorCode(response.status)
|
||||||
|
), # type: ignore
|
||||||
|
)
|
||||||
|
|
||||||
# Check that the response is of the expected type
|
# Check that the response is of the expected type
|
||||||
assert isinstance(response, hci.HCI_Command_Complete_Event)
|
assert isinstance(response, hci.HCI_Command_Complete_Event)
|
||||||
|
|
||||||
@@ -789,19 +809,25 @@ class Host(utils.EventEmitter):
|
|||||||
) -> hci.HCI_ErrorCode:
|
) -> hci.HCI_ErrorCode:
|
||||||
response = await self._send_command(command, response_timeout)
|
response = await self._send_command(command, response_timeout)
|
||||||
|
|
||||||
# Check that the response is of the expected type
|
# For unknown HCI commands, some controllers return Command Complete instead of
|
||||||
assert isinstance(response, hci.HCI_Command_Status_Event)
|
# Command Status.
|
||||||
|
if isinstance(response, hci.HCI_Command_Complete_Event):
|
||||||
|
# Assume the first byte of the return parameters is the status
|
||||||
|
if (
|
||||||
|
status := hci.HCI_ErrorCode(response.parameters[3])
|
||||||
|
) != hci.HCI_ErrorCode.UNKNOWN_HCI_COMMAND_ERROR:
|
||||||
|
logger.warning(f'unexpected return paramerers status {status}')
|
||||||
|
else:
|
||||||
|
assert isinstance(response, hci.HCI_Command_Status_Event)
|
||||||
|
status = hci.HCI_ErrorCode(response.status)
|
||||||
|
|
||||||
# Check the return parameters if required
|
# Check the status if required
|
||||||
status = response.status
|
|
||||||
if check_status:
|
if check_status:
|
||||||
if status != hci.HCI_CommandStatus.PENDING:
|
if status != hci.HCI_CommandStatus.PENDING:
|
||||||
logger.warning(
|
logger.warning(f'{command.name} failed ' f'({status.name})')
|
||||||
f'{command.name} failed ' f'({hci.HCI_Constant.error_name(status)})'
|
|
||||||
)
|
|
||||||
raise hci.HCI_Error(status)
|
raise hci.HCI_Error(status)
|
||||||
|
|
||||||
return hci.HCI_ErrorCode(status)
|
return status
|
||||||
|
|
||||||
@utils.deprecated("Use utils.AsyncRunner.spawn() instead.")
|
@utils.deprecated("Use utils.AsyncRunner.spawn() instead.")
|
||||||
def send_command_sync(self, command: hci.HCI_AsyncCommand) -> None:
|
def send_command_sync(self, command: hci.HCI_AsyncCommand) -> None:
|
||||||
|
|||||||
@@ -232,6 +232,14 @@ def test_return_parameters() -> None:
|
|||||||
assert len(params.local_name) == 248
|
assert len(params.local_name) == 248
|
||||||
assert hci.map_null_terminated_utf8_string(params.local_name) == 'hello'
|
assert hci.map_null_terminated_utf8_string(params.local_name) == 'hello'
|
||||||
|
|
||||||
|
# Some return parameters may be shorter than the full length
|
||||||
|
# (for Command Complete events with errors)
|
||||||
|
params = hci.HCI_Read_BD_ADDR_Command.parse_return_parameters(
|
||||||
|
bytes.fromhex('010011223344')
|
||||||
|
)
|
||||||
|
assert isinstance(params, hci.HCI_StatusReturnParameters)
|
||||||
|
assert params.status == hci.HCI_ErrorCode.UNKNOWN_HCI_COMMAND_ERROR
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
def test_HCI_Command():
|
def test_HCI_Command():
|
||||||
|
|||||||
@@ -26,11 +26,14 @@ from bumble.controller import Controller
|
|||||||
from bumble.hci import (
|
from bumble.hci import (
|
||||||
HCI_AclDataPacket,
|
HCI_AclDataPacket,
|
||||||
HCI_Command_Complete_Event,
|
HCI_Command_Complete_Event,
|
||||||
|
HCI_Command_Status_Event,
|
||||||
|
HCI_CommandStatus,
|
||||||
HCI_Disconnect_Command,
|
HCI_Disconnect_Command,
|
||||||
HCI_Error,
|
HCI_Error,
|
||||||
HCI_ErrorCode,
|
HCI_ErrorCode,
|
||||||
HCI_Event,
|
HCI_Event,
|
||||||
HCI_GenericReturnParameters,
|
HCI_GenericReturnParameters,
|
||||||
|
HCI_LE_Terminate_BIG_Command,
|
||||||
HCI_Reset_Command,
|
HCI_Reset_Command,
|
||||||
HCI_StatusReturnParameters,
|
HCI_StatusReturnParameters,
|
||||||
)
|
)
|
||||||
@@ -229,3 +232,47 @@ async def test_send_sync_command() -> None:
|
|||||||
)
|
)
|
||||||
response3 = await host.send_sync_command_raw(command) # type: ignore
|
response3 = await host.send_sync_command_raw(command) # type: ignore
|
||||||
assert isinstance(response3.return_parameters, HCI_GenericReturnParameters)
|
assert isinstance(response3.return_parameters, HCI_GenericReturnParameters)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_send_async_command() -> None:
|
||||||
|
source = Source()
|
||||||
|
sink = Sink(
|
||||||
|
source,
|
||||||
|
HCI_Command_Status_Event(
|
||||||
|
HCI_CommandStatus.PENDING,
|
||||||
|
1,
|
||||||
|
HCI_Reset_Command.op_code,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
host = Host(source, sink)
|
||||||
|
host.ready = True
|
||||||
|
|
||||||
|
# Normal pending status
|
||||||
|
response = await host.send_async_command(
|
||||||
|
HCI_LE_Terminate_BIG_Command(big_handle=0, reason=0)
|
||||||
|
)
|
||||||
|
assert response == HCI_CommandStatus.PENDING
|
||||||
|
|
||||||
|
# Unknown HCI command result returned as a Command Status
|
||||||
|
sink.response = HCI_Command_Status_Event(
|
||||||
|
HCI_ErrorCode.UNKNOWN_HCI_COMMAND_ERROR,
|
||||||
|
1,
|
||||||
|
HCI_LE_Terminate_BIG_Command.op_code,
|
||||||
|
)
|
||||||
|
response = await host.send_async_command(
|
||||||
|
HCI_LE_Terminate_BIG_Command(big_handle=0, reason=0), check_status=False
|
||||||
|
)
|
||||||
|
assert response == HCI_ErrorCode.UNKNOWN_HCI_COMMAND_ERROR
|
||||||
|
|
||||||
|
# Unknown HCI command result returned as a Command Complete
|
||||||
|
sink.response = HCI_Command_Complete_Event(
|
||||||
|
1,
|
||||||
|
HCI_LE_Terminate_BIG_Command.op_code,
|
||||||
|
HCI_StatusReturnParameters(HCI_ErrorCode.UNKNOWN_HCI_COMMAND_ERROR),
|
||||||
|
)
|
||||||
|
response = await host.send_async_command(
|
||||||
|
HCI_LE_Terminate_BIG_Command(big_handle=0, reason=0), check_status=False
|
||||||
|
)
|
||||||
|
assert response == HCI_ErrorCode.UNKNOWN_HCI_COMMAND_ERROR
|
||||||
|
|||||||
Reference in New Issue
Block a user