Skip to content

Commit c11a7f6

Browse files
authored
Ensure third party callbacks on BLE notify do not cause disconnect (#1212)
1 parent 782a429 commit c11a7f6

File tree

2 files changed

+49
-1
lines changed

2 files changed

+49
-1
lines changed

aioesphomeapi/client_base.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,14 @@ def on_bluetooth_gatt_notify_data_response(
112112
) -> None:
113113
"""Handle a BluetoothGATTNotifyDataResponse message."""
114114
if address == msg.address and handle == msg.handle:
115-
on_bluetooth_gatt_notify(handle, bytearray(msg.data))
115+
try:
116+
on_bluetooth_gatt_notify(handle, bytearray(msg.data))
117+
except Exception:
118+
_LOGGER.exception(
119+
"Unexpected error in Bluetooth GATT notify callback for address %s, handle %s",
120+
address,
121+
handle,
122+
)
116123

117124

118125
def on_bluetooth_scanner_state_response(

tests/test_client.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1661,6 +1661,47 @@ def on_bluetooth_gatt_notify(handle: int, data: bytearray) -> None:
16611661
)
16621662

16631663

1664+
async def test_bluetooth_gatt_notify_callback_raises(
1665+
api_client: tuple[
1666+
APIClient, APIConnection, asyncio.Transport, APIPlaintextFrameHelper
1667+
],
1668+
caplog: pytest.LogCaptureFixture,
1669+
) -> None:
1670+
"""Test that exceptions in bluetooth gatt notify callbacks are caught."""
1671+
client, connection, transport, protocol = api_client
1672+
1673+
def on_bluetooth_gatt_notify(handle: int, data: bytearray) -> None:
1674+
raise ValueError("Test exception in notify callback")
1675+
1676+
notify_task = asyncio.create_task(
1677+
client.bluetooth_gatt_start_notify(1234, 1, on_bluetooth_gatt_notify)
1678+
)
1679+
await asyncio.sleep(0)
1680+
notify_response: message.Message = BluetoothGATTNotifyResponse(
1681+
address=1234, handle=1
1682+
)
1683+
mock_data_received(protocol, generate_plaintext_packet(notify_response))
1684+
await notify_task
1685+
1686+
# Clear any logs from the notify setup
1687+
caplog.clear()
1688+
1689+
# Send data that will trigger the exception
1690+
data_response: message.Message = BluetoothGATTNotifyDataResponse(
1691+
address=1234, handle=1, data=b"test_data"
1692+
)
1693+
mock_data_received(protocol, generate_plaintext_packet(data_response))
1694+
await asyncio.sleep(0)
1695+
1696+
# Verify the exception was caught and logged
1697+
assert "Unexpected error in Bluetooth GATT notify callback" in caplog.text
1698+
assert "ValueError: Test exception in notify callback" in caplog.text
1699+
assert "address 1234, handle 1" in caplog.text
1700+
1701+
# Verify the connection is still alive
1702+
assert connection.is_connected
1703+
1704+
16641705
async def test_subscribe_bluetooth_le_advertisements(
16651706
api_client: tuple[
16661707
APIClient, APIConnection, asyncio.Transport, APIPlaintextFrameHelper

0 commit comments

Comments
 (0)