Skip to content

Commit 8ebd2a3

Browse files
authored
feat: allow custom GATT read timeouts (#264)
1 parent 647efd4 commit 8ebd2a3

File tree

2 files changed

+105
-4
lines changed

2 files changed

+105
-4
lines changed

src/bleak_esphome/backend/client.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -524,16 +524,18 @@ async def read_gatt_char(
524524
Args:
525525
----
526526
characteristic: The BleakGATTCharacteristic to read from.
527-
**kwargs: Unused
527+
**kwargs:
528+
timeout (float): Read timeout in seconds. Defaults to 30.0.
528529
529530
Returns:
530531
-------
531532
(bytearray) The read data.
532533
533534
"""
534535
self._raise_if_not_connected()
536+
timeout = kwargs.get("timeout", GATT_READ_TIMEOUT)
535537
return await self._client.bluetooth_gatt_read(
536-
self._address_as_int, characteristic.handle, GATT_READ_TIMEOUT
538+
self._address_as_int, characteristic.handle, timeout
537539
)
538540

539541
@api_error_as_bleak_error
@@ -546,16 +548,18 @@ async def read_gatt_descriptor(
546548
Args:
547549
----
548550
descriptor: The BleakGATTDescriptor to read from.
549-
**kwargs: Unused
551+
**kwargs:
552+
timeout (float): Read timeout in seconds. Defaults to 30.0.
550553
551554
Returns:
552555
-------
553556
(bytearray) The read data.
554557
555558
"""
556559
self._raise_if_not_connected()
560+
timeout = kwargs.get("timeout", GATT_READ_TIMEOUT)
557561
return await self._client.bluetooth_gatt_read_descriptor(
558-
self._address_as_int, descriptor.handle, GATT_READ_TIMEOUT
562+
self._address_as_int, descriptor.handle, timeout
559563
)
560564

561565
@api_error_as_bleak_error

tests/backend/test_client.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,103 @@ async def test_client_get_services_and_read_write(
333333
mock_read.assert_called_once_with(225106397622015, 20, 30)
334334

335335

336+
@pytest.mark.asyncio
337+
async def test_client_read_gatt_char_with_custom_timeout(
338+
client_data: ESPHomeClientData,
339+
esphome_bluetooth_gatt_services: ESPHomeBluetoothGATTServices,
340+
) -> None:
341+
"""Test reading a GATT char with custom timeout."""
342+
ble_device = generate_ble_device(
343+
"CC:BB:AA:DD:EE:FF", details={"source": ESP_MAC_ADDRESS, "address_type": 1}
344+
)
345+
346+
client = ESPHomeClient(ble_device, client_data=client_data)
347+
client._is_connected = True
348+
with patch.object(
349+
client._client,
350+
"bluetooth_gatt_get_services",
351+
return_value=esphome_bluetooth_gatt_services,
352+
):
353+
services = await client._get_services()
354+
355+
char = services.get_characteristic("090b7847-e12b-09a8-b04b-8e0922a9abab")
356+
assert char is not None
357+
358+
with patch.object(
359+
client._client,
360+
"bluetooth_gatt_read",
361+
) as mock_read:
362+
await client.read_gatt_char(char, timeout=90.0)
363+
364+
mock_read.assert_called_once_with(225106397622015, 20, 90.0)
365+
366+
367+
@pytest.mark.asyncio
368+
async def test_client_read_gatt_descriptor_default_timeout(
369+
client_data: ESPHomeClientData,
370+
esphome_bluetooth_gatt_services: ESPHomeBluetoothGATTServices,
371+
) -> None:
372+
"""Test reading a GATT descriptor uses the default timeout."""
373+
ble_device = generate_ble_device(
374+
"CC:BB:AA:DD:EE:FF", details={"source": ESP_MAC_ADDRESS, "address_type": 1}
375+
)
376+
377+
client = ESPHomeClient(ble_device, client_data=client_data)
378+
client._is_connected = True
379+
with patch.object(
380+
client._client,
381+
"bluetooth_gatt_get_services",
382+
return_value=esphome_bluetooth_gatt_services,
383+
):
384+
services = await client._get_services()
385+
386+
char = services.get_characteristic("00002a05-0000-1000-8000-00805f9b34fb")
387+
assert char is not None
388+
descriptor = char.get_descriptor("00002902-0000-1000-8000-00805f9b34fb")
389+
assert descriptor is not None
390+
391+
with patch.object(
392+
client._client,
393+
"bluetooth_gatt_read_descriptor",
394+
) as mock_read_descriptor:
395+
await client.read_gatt_descriptor(descriptor)
396+
397+
mock_read_descriptor.assert_called_once_with(225106397622015, 9, 30.0)
398+
399+
400+
@pytest.mark.asyncio
401+
async def test_client_read_gatt_descriptor_with_custom_timeout(
402+
client_data: ESPHomeClientData,
403+
esphome_bluetooth_gatt_services: ESPHomeBluetoothGATTServices,
404+
) -> None:
405+
"""Test reading a GATT descriptor with custom timeout."""
406+
ble_device = generate_ble_device(
407+
"CC:BB:AA:DD:EE:FF", details={"source": ESP_MAC_ADDRESS, "address_type": 1}
408+
)
409+
410+
client = ESPHomeClient(ble_device, client_data=client_data)
411+
client._is_connected = True
412+
with patch.object(
413+
client._client,
414+
"bluetooth_gatt_get_services",
415+
return_value=esphome_bluetooth_gatt_services,
416+
):
417+
services = await client._get_services()
418+
419+
char = services.get_characteristic("00002a05-0000-1000-8000-00805f9b34fb")
420+
assert char is not None
421+
descriptor = char.get_descriptor("00002902-0000-1000-8000-00805f9b34fb")
422+
assert descriptor is not None
423+
424+
with patch.object(
425+
client._client,
426+
"bluetooth_gatt_read_descriptor",
427+
) as mock_read_descriptor:
428+
await client.read_gatt_descriptor(descriptor, timeout=90.0)
429+
430+
mock_read_descriptor.assert_called_once_with(225106397622015, 9, 90.0)
431+
432+
336433
@pytest.mark.asyncio
337434
async def test_bleak_client_get_services_and_read_write(
338435
client_data: ESPHomeClientData,

0 commit comments

Comments
 (0)