Merge pull request #892 from zxzxwu/hfp

HFP: Fix response handling
This commit is contained in:
Josh Wu
2026-02-26 13:18:03 +08:00
committed by GitHub
2 changed files with 61 additions and 90 deletions

View File

@@ -27,8 +27,8 @@ from collections.abc import (
Awaitable, Awaitable,
Callable, Callable,
Iterable, Iterable,
Sequence,
Mapping, Mapping,
Sequence,
) )
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import ClassVar, SupportsBytes, TypeVar from typing import ClassVar, SupportsBytes, TypeVar

View File

@@ -26,7 +26,7 @@ import logging
import re import re
import traceback import traceback
from collections.abc import Iterable from collections.abc import Iterable
from typing import TYPE_CHECKING, Any, ClassVar from typing import Any, ClassVar, Literal, overload
from typing_extensions import Self from typing_extensions import Self
@@ -420,61 +420,6 @@ class CmeError(enum.IntEnum):
# Hands-Free Control Interoperability Requirements # Hands-Free Control Interoperability Requirements
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Response codes.
RESPONSE_CODES = {
"+APLSIRI",
"+BAC",
"+BCC",
"+BCS",
"+BIA",
"+BIEV",
"+BIND",
"+BINP",
"+BLDN",
"+BRSF",
"+BTRH",
"+BVRA",
"+CCWA",
"+CHLD",
"+CHUP",
"+CIND",
"+CLCC",
"+CLIP",
"+CMEE",
"+CMER",
"+CNUM",
"+COPS",
"+IPHONEACCEV",
"+NREC",
"+VGM",
"+VGS",
"+VTS",
"+XAPL",
"A",
"D",
}
# Unsolicited responses and statuses.
UNSOLICITED_CODES = {
"+APLSIRI",
"+BCS",
"+BIND",
"+BSIR",
"+BTRH",
"+BVRA",
"+CCWA",
"+CIEV",
"+CLIP",
"+VGM",
"+VGS",
"BLACKLISTED",
"BUSY",
"DELAYED",
"NO ANSWER",
"NO CARRIER",
"RING",
}
# Status codes # Status codes
STATUS_CODES = { STATUS_CODES = {
"+CME ERROR", "+CME ERROR",
@@ -727,12 +672,9 @@ class HfProtocol(utils.EventEmitter):
dlc: rfcomm.DLC dlc: rfcomm.DLC
command_lock: asyncio.Lock command_lock: asyncio.Lock
if TYPE_CHECKING: pending_command: str | None = None
response_queue: asyncio.Queue[AtResponse] response_queue: asyncio.Queue[AtResponse]
unsolicited_queue: asyncio.Queue[AtResponse | None] unsolicited_queue: asyncio.Queue[AtResponse | None]
else:
response_queue: asyncio.Queue
unsolicited_queue: asyncio.Queue
read_buffer: bytearray read_buffer: bytearray
active_codec: AudioCodec active_codec: AudioCodec
@@ -805,16 +747,39 @@ class HfProtocol(utils.EventEmitter):
self.read_buffer = self.read_buffer[trailer + 2 :] self.read_buffer = self.read_buffer[trailer + 2 :]
# Forward the received code to the correct queue. # Forward the received code to the correct queue.
if self.command_lock.locked() and ( if self.pending_command and (
response.code in STATUS_CODES or response.code in RESPONSE_CODES response.code in STATUS_CODES or response.code in self.pending_command
): ):
self.response_queue.put_nowait(response) self.response_queue.put_nowait(response)
elif response.code in UNSOLICITED_CODES:
self.unsolicited_queue.put_nowait(response)
else: else:
logger.warning( self.unsolicited_queue.put_nowait(response)
f"dropping unexpected response with code '{response.code}'"
) @overload
async def execute_command(
self,
cmd: str,
timeout: float = 1.0,
*,
response_type: Literal[AtResponseType.NONE] = AtResponseType.NONE,
) -> None: ...
@overload
async def execute_command(
self,
cmd: str,
timeout: float = 1.0,
*,
response_type: Literal[AtResponseType.SINGLE],
) -> AtResponse: ...
@overload
async def execute_command(
self,
cmd: str,
timeout: float = 1.0,
*,
response_type: Literal[AtResponseType.MULTIPLE],
) -> list[AtResponse]: ...
async def execute_command( async def execute_command(
self, self,
@@ -835,7 +800,9 @@ class HfProtocol(utils.EventEmitter):
asyncio.TimeoutError: the status is not received after a timeout (default 1 second). asyncio.TimeoutError: the status is not received after a timeout (default 1 second).
ProtocolError: the status is not OK. ProtocolError: the status is not OK.
""" """
try:
async with self.command_lock: async with self.command_lock:
self.pending_command = cmd
logger.debug(f">>> {cmd}") logger.debug(f">>> {cmd}")
self.dlc.write(cmd + '\r') self.dlc.write(cmd + '\r')
responses: list[AtResponse] = [] responses: list[AtResponse] = []
@@ -845,7 +812,10 @@ class HfProtocol(utils.EventEmitter):
self.response_queue.get(), timeout=timeout self.response_queue.get(), timeout=timeout
) )
if result.code == 'OK': if result.code == 'OK':
if response_type == AtResponseType.SINGLE and len(responses) != 1: if (
response_type == AtResponseType.SINGLE
and len(responses) != 1
):
raise HfpProtocolError("NO ANSWER") raise HfpProtocolError("NO ANSWER")
if response_type == AtResponseType.MULTIPLE: if response_type == AtResponseType.MULTIPLE:
@@ -856,6 +826,8 @@ class HfProtocol(utils.EventEmitter):
if result.code in STATUS_CODES: if result.code in STATUS_CODES:
raise HfpProtocolError(result.code) raise HfpProtocolError(result.code)
responses.append(result) responses.append(result)
finally:
self.pending_command = None
async def initiate_slc(self): async def initiate_slc(self):
"""4.2.1 Service Level Connection Initialization.""" """4.2.1 Service Level Connection Initialization."""
@@ -1067,7 +1039,6 @@ class HfProtocol(utils.EventEmitter):
responses = await self.execute_command( responses = await self.execute_command(
"AT+CLCC", response_type=AtResponseType.MULTIPLE "AT+CLCC", response_type=AtResponseType.MULTIPLE
) )
assert isinstance(responses, list)
calls = [] calls = []
for response in responses: for response in responses: