Improve bluetooth error reporting (#297)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
J. Nick Koston 2022-10-31 15:32:40 -05:00 committed by GitHub
parent 4f751f3740
commit c6e824e778
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 74 additions and 4 deletions

View File

@ -3,6 +3,7 @@ from .ble_defs import ESP_CONNECTION_ERROR_DESCRIPTION, BLEConnectionError
from .client import APIClient
from .connection import APIConnection, ConnectionParams
from .core import (
ESPHOME_GATT_ERRORS,
MESSAGE_TYPE_TO_PROTO,
APIConnectionError,
BadNameAPIError,

View File

@ -96,7 +96,12 @@ from .api_pb2 import ( # type: ignore
TextSensorStateResponse,
)
from .connection import APIConnection, ConnectionParams
from .core import APIConnectionError, BluetoothGATTAPIError, TimeoutAPIError
from .core import (
APIConnectionError,
BluetoothGATTAPIError,
TimeoutAPIError,
to_human_readable_address,
)
from .host_resolver import ZeroconfInstanceType
from .model import (
APIVersion,
@ -514,12 +519,20 @@ class APIClient:
async with async_timeout.timeout(timeout):
await event.wait()
except asyncio.TimeoutError as err:
unsub()
try:
unsub()
except ValueError:
_LOGGER.warning(
"%s: Bluetooth device connection timed out but already unsubscribed",
to_human_readable_address(address),
)
# Disconnect before raising the exception to ensure
# the slot is recovered before the timeout is raised
# to avoid race were we run out even though we have a slot.
await self.bluetooth_device_disconnect(address)
raise TimeoutAPIError("Timeout waiting for connect response") from err
raise TimeoutAPIError(
f"Timeout waiting for connect response while connecting to {to_human_readable_address(address)}"
) from err
return unsub

View File

@ -91,6 +91,53 @@ from .api_pb2 import ( # type: ignore
TWO_CHAR = re.compile(r".{2}")
# Taken from esp_gatt_status_t in esp_gatt_defs.h
ESPHOME_GATT_ERRORS = {
-1: "Not connected", # Custom ESPHome error
1: "Invalid handle",
2: "Read not permitted",
3: "Write not permitted",
4: "Invalid PDU",
5: "Insufficient authentication",
6: "Request not supported",
7: "Invalid offset",
8: "Insufficient authorization",
9: "Prepare queue full",
10: "Attribute not found",
11: "Attribute not long",
12: "Insufficient key size",
13: "Invalid attribute length",
14: "Unlikely error",
15: "Insufficient encryption",
16: "Unsupported group type",
17: "Insufficient resources",
128: "Application error",
129: "Internal error",
130: "Wrong state",
131: "Database full",
132: "Busy",
133: "Error",
134: "Command started",
135: "Illegal parameter",
136: "Pending",
137: "Auth fail",
138: "More",
139: "Invalid configuration",
140: "Service started",
141: "Encrypted no mitm",
142: "Not encrypted",
143: "Congested",
144: "Duplicate registration",
145: "Already open",
146: "Cancel",
224: "Stack RSP",
225: "App RSP",
239: "Unknown error",
253: "CCC config error",
254: "Procedure already in progress",
255: "Out of range",
}
class APIConnectionError(Exception):
pass
@ -153,10 +200,19 @@ def to_human_readable_address(address: int) -> str:
return ":".join(TWO_CHAR.findall(f"{address:012X}"))
def to_human_readable_gatt_error(error: int) -> str:
"""Convert a GATT error to a human readable format."""
return ESPHOME_GATT_ERRORS.get(error, "Unknown error")
class BluetoothGATTAPIError(APIConnectionError):
def __init__(self, error: BluetoothGATTError) -> None:
super().__init__(
f"Bluetooth GATT Error address={to_human_readable_address(error.address)} handle={error.handle} error={error.error}"
f"Bluetooth GATT Error "
f"address={to_human_readable_address(error.address)} "
f"handle={error.handle} "
f"error={error.error} "
f"description={to_human_readable_gatt_error(error.error)}"
)
self.error = error