diff --git a/aioesphomeapi/client_base.py b/aioesphomeapi/client_base.py index eb4a9f04..e678ef1f 100644 --- a/aioesphomeapi/client_base.py +++ b/aioesphomeapi/client_base.py @@ -112,7 +112,14 @@ def on_bluetooth_gatt_notify_data_response( ) -> None: """Handle a BluetoothGATTNotifyDataResponse message.""" if address == msg.address and handle == msg.handle: - on_bluetooth_gatt_notify(handle, bytearray(msg.data)) + try: + on_bluetooth_gatt_notify(handle, bytearray(msg.data)) + except Exception: + _LOGGER.exception( + "Unexpected error in Bluetooth GATT notify callback for address %s, handle %s", + address, + handle, + ) def on_bluetooth_scanner_state_response( diff --git a/tests/test_client.py b/tests/test_client.py index 3287456c..bbd4e195 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1661,6 +1661,47 @@ def on_bluetooth_gatt_notify(handle: int, data: bytearray) -> None: ) +async def test_bluetooth_gatt_notify_callback_raises( + api_client: tuple[ + APIClient, APIConnection, asyncio.Transport, APIPlaintextFrameHelper + ], + caplog: pytest.LogCaptureFixture, +) -> None: + """Test that exceptions in bluetooth gatt notify callbacks are caught.""" + client, connection, transport, protocol = api_client + + def on_bluetooth_gatt_notify(handle: int, data: bytearray) -> None: + raise ValueError("Test exception in notify callback") + + notify_task = asyncio.create_task( + client.bluetooth_gatt_start_notify(1234, 1, on_bluetooth_gatt_notify) + ) + await asyncio.sleep(0) + notify_response: message.Message = BluetoothGATTNotifyResponse( + address=1234, handle=1 + ) + mock_data_received(protocol, generate_plaintext_packet(notify_response)) + await notify_task + + # Clear any logs from the notify setup + caplog.clear() + + # Send data that will trigger the exception + data_response: message.Message = BluetoothGATTNotifyDataResponse( + address=1234, handle=1, data=b"test_data" + ) + mock_data_received(protocol, generate_plaintext_packet(data_response)) + await asyncio.sleep(0) + + # Verify the exception was caught and logged + assert "Unexpected error in Bluetooth GATT notify callback" in caplog.text + assert "ValueError: Test exception in notify callback" in caplog.text + assert "address 1234, handle 1" in caplog.text + + # Verify the connection is still alive + assert connection.is_connected + + async def test_subscribe_bluetooth_le_advertisements( api_client: tuple[ APIClient, APIConnection, asyncio.Transport, APIPlaintextFrameHelper