Skip to content

Commit

Permalink
Merge pull request #573 from ypomortsev/yegor
Browse files Browse the repository at this point in the history
HFP: Fix reading multiple AT commands from a single data packet
  • Loading branch information
zxzxwu authored Oct 23, 2024
2 parents 1de7d2c + c6bf27f commit d03fc14
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 48 deletions.
100 changes: 52 additions & 48 deletions bumble/hfp.py
Original file line number Diff line number Diff line change
Expand Up @@ -795,29 +795,32 @@ def _read_at(self, data: bytes):
# Append to the read buffer.
self.read_buffer.extend(data)

# Locate header and trailer.
header = self.read_buffer.find(b'\r\n')
trailer = self.read_buffer.find(b'\r\n', header + 2)
if header == -1 or trailer == -1:
return

# Isolate the AT response code and parameters.
raw_response = self.read_buffer[header + 2 : trailer]
response = AtResponse.parse_from(raw_response)
logger.debug(f"<<< {raw_response.decode()}")

# Consume the response bytes.
self.read_buffer = self.read_buffer[trailer + 2 :]

# Forward the received code to the correct queue.
if self.command_lock.locked() and (
response.code in STATUS_CODES or response.code in RESPONSE_CODES
):
self.response_queue.put_nowait(response)
elif response.code in UNSOLICITED_CODES:
self.unsolicited_queue.put_nowait(response)
else:
logger.warning(f"dropping unexpected response with code '{response.code}'")
while self.read_buffer:
# Locate header and trailer.
header = self.read_buffer.find(b'\r\n')
trailer = self.read_buffer.find(b'\r\n', header + 2)
if header == -1 or trailer == -1:
return

# Isolate the AT response code and parameters.
raw_response = self.read_buffer[header + 2 : trailer]
response = AtResponse.parse_from(raw_response)
logger.debug(f"<<< {raw_response.decode()}")

# Consume the response bytes.
self.read_buffer = self.read_buffer[trailer + 2 :]

# Forward the received code to the correct queue.
if self.command_lock.locked() and (
response.code in STATUS_CODES or response.code in RESPONSE_CODES
):
self.response_queue.put_nowait(response)
elif response.code in UNSOLICITED_CODES:
self.unsolicited_queue.put_nowait(response)
else:
logger.warning(
f"dropping unexpected response with code '{response.code}'"
)

async def execute_command(
self,
Expand Down Expand Up @@ -1244,31 +1247,32 @@ def _read_at(self, data: bytes):
# Append to the read buffer.
self.read_buffer.extend(data)

# Locate the trailer.
trailer = self.read_buffer.find(b'\r')
if trailer == -1:
return

# Isolate the AT response code and parameters.
raw_command = self.read_buffer[:trailer]
command = AtCommand.parse_from(raw_command)
logger.debug(f"<<< {raw_command.decode()}")

# Consume the response bytes.
self.read_buffer = self.read_buffer[trailer + 1 :]

if command.sub_code == AtCommand.SubCode.TEST:
handler_name = f'_on_{command.code.lower()}_test'
elif command.sub_code == AtCommand.SubCode.READ:
handler_name = f'_on_{command.code.lower()}_read'
else:
handler_name = f'_on_{command.code.lower()}'

if handler := getattr(self, handler_name, None):
handler(*command.parameters)
else:
logger.warning('Handler %s not found', handler_name)
self.send_response('ERROR')
while self.read_buffer:
# Locate the trailer.
trailer = self.read_buffer.find(b'\r')
if trailer == -1:
return

# Isolate the AT response code and parameters.
raw_command = self.read_buffer[:trailer]
command = AtCommand.parse_from(raw_command)
logger.debug(f"<<< {raw_command.decode()}")

# Consume the response bytes.
self.read_buffer = self.read_buffer[trailer + 1 :]

if command.sub_code == AtCommand.SubCode.TEST:
handler_name = f'_on_{command.code.lower()}_test'
elif command.sub_code == AtCommand.SubCode.READ:
handler_name = f'_on_{command.code.lower()}_read'
else:
handler_name = f'_on_{command.code.lower()}'

if handler := getattr(self, handler_name, None):
handler(*command.parameters)
else:
logger.warning('Handler %s not found', handler_name)
self.send_response('ERROR')

def send_response(self, response: str) -> None:
"""Sends an AT response."""
Expand Down
31 changes: 31 additions & 0 deletions tests/hfp_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,37 @@ def on_sco_request(_connection, _link_type: int):
await asyncio.gather(*sco_disconnection_futures)


# -----------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_hf_batched_response(
hfp_connections: Tuple[hfp.HfProtocol, hfp.AgProtocol]
):
hf, ag = hfp_connections

ag.dlc.write(b'\r\n+BIND: (1,2)\r\n\r\nOK\r\n')

await hf.execute_command("AT+BIND=?", response_type=hfp.AtResponseType.SINGLE)


# -----------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_ag_batched_commands(
hfp_connections: Tuple[hfp.HfProtocol, hfp.AgProtocol]
):
hf, ag = hfp_connections

answer_future = asyncio.get_running_loop().create_future()
ag.on('answer', lambda: answer_future.set_result(None))

hang_up_future = asyncio.get_running_loop().create_future()
ag.on('hang_up', lambda: hang_up_future.set_result(None))

hf.dlc.write(b'ATA\rAT+CHUP\r')

await answer_future
await hang_up_future


# -----------------------------------------------------------------------------
async def run():
await test_slc()
Expand Down

0 comments on commit d03fc14

Please sign in to comment.