Bump aioesphomeapi to 19.1.7 (#104644)

* Bump aioesphomeapi to 19.1.5

changelog: https://github.com/esphome/aioesphomeapi/compare/v19.1.4...v19.1.5

- Removes the need to watch for BLE connection drops with a seperate
  future as the library now raises BluetoothConnectionDroppedError when
  the connection drops during a BLE operation

* reduce stack

* .6

* tweak

* 19.1.7
This commit is contained in:
J. Nick Koston 2023-11-28 14:51:35 -06:00 committed by GitHub
parent 63ef9efa26
commit 93aa31c835
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 29 additions and 51 deletions

View File

@ -22,6 +22,7 @@ from aioesphomeapi import (
APIClient, APIClient,
APIVersion, APIVersion,
BLEConnectionError, BLEConnectionError,
BluetoothConnectionDroppedError,
BluetoothProxyFeature, BluetoothProxyFeature,
DeviceInfo, DeviceInfo,
) )
@ -30,7 +31,6 @@ from aioesphomeapi.core import (
BluetoothGATTAPIError, BluetoothGATTAPIError,
TimeoutAPIError, TimeoutAPIError,
) )
from async_interrupt import interrupt
from bleak.backends.characteristic import BleakGATTCharacteristic from bleak.backends.characteristic import BleakGATTCharacteristic
from bleak.backends.client import BaseBleakClient, NotifyCallback from bleak.backends.client import BaseBleakClient, NotifyCallback
from bleak.backends.device import BLEDevice from bleak.backends.device import BLEDevice
@ -68,39 +68,25 @@ def mac_to_int(address: str) -> int:
return int(address.replace(":", ""), 16) return int(address.replace(":", ""), 16)
def verify_connected(func: _WrapFuncType) -> _WrapFuncType:
"""Define a wrapper throw BleakError if not connected."""
async def _async_wrap_bluetooth_connected_operation(
self: ESPHomeClient, *args: Any, **kwargs: Any
) -> Any:
# pylint: disable=protected-access
if not self._is_connected:
raise BleakError(f"{self._description} is not connected")
loop = self._loop
disconnected_futures = self._disconnected_futures
disconnected_future = loop.create_future()
disconnected_futures.add(disconnected_future)
disconnect_message = f"{self._description}: Disconnected during operation"
try:
async with interrupt(disconnected_future, BleakError, disconnect_message):
return await func(self, *args, **kwargs)
finally:
disconnected_futures.discard(disconnected_future)
return cast(_WrapFuncType, _async_wrap_bluetooth_connected_operation)
def api_error_as_bleak_error(func: _WrapFuncType) -> _WrapFuncType: def api_error_as_bleak_error(func: _WrapFuncType) -> _WrapFuncType:
"""Define a wrapper throw esphome api errors as BleakErrors.""" """Define a wrapper throw esphome api errors as BleakErrors."""
async def _async_wrap_bluetooth_operation( async def _async_wrap_bluetooth_operation(
self: ESPHomeClient, *args: Any, **kwargs: Any self: ESPHomeClient, *args: Any, **kwargs: Any
) -> Any: ) -> Any:
# pylint: disable=protected-access
try: try:
return await func(self, *args, **kwargs) return await func(self, *args, **kwargs)
except TimeoutAPIError as err: except TimeoutAPIError as err:
raise asyncio.TimeoutError(str(err)) from err raise asyncio.TimeoutError(str(err)) from err
except BluetoothConnectionDroppedError as ex:
_LOGGER.debug(
"%s: BLE device disconnected during %s operation",
self._description,
func.__name__,
)
self._async_ble_device_disconnected()
raise BleakError(str(ex)) from ex
except BluetoothGATTAPIError as ex: except BluetoothGATTAPIError as ex:
# If the device disconnects in the middle of an operation # If the device disconnects in the middle of an operation
# be sure to mark it as disconnected so any library using # be sure to mark it as disconnected so any library using
@ -111,7 +97,6 @@ def api_error_as_bleak_error(func: _WrapFuncType) -> _WrapFuncType:
# before the callback is delivered. # before the callback is delivered.
if ex.error.error == -1: if ex.error.error == -1:
# pylint: disable=protected-access
_LOGGER.debug( _LOGGER.debug(
"%s: BLE device disconnected during %s operation", "%s: BLE device disconnected during %s operation",
self._description, self._description,
@ -169,7 +154,6 @@ class ESPHomeClient(BaseBleakClient):
self._notify_cancels: dict[ self._notify_cancels: dict[
int, tuple[Callable[[], Coroutine[Any, Any, None]], Callable[[], None]] int, tuple[Callable[[], Coroutine[Any, Any, None]], Callable[[], None]]
] = {} ] = {}
self._disconnected_futures: set[asyncio.Future[None]] = set()
self._device_info = client_data.device_info self._device_info = client_data.device_info
self._feature_flags = device_info.bluetooth_proxy_feature_flags_compat( self._feature_flags = device_info.bluetooth_proxy_feature_flags_compat(
client_data.api_version client_data.api_version
@ -185,7 +169,7 @@ class ESPHomeClient(BaseBleakClient):
def __str__(self) -> str: def __str__(self) -> str:
"""Return the string representation of the client.""" """Return the string representation of the client."""
return f"ESPHomeClient ({self.address})" return f"ESPHomeClient ({self._description})"
def _unsubscribe_connection_state(self) -> None: def _unsubscribe_connection_state(self) -> None:
"""Unsubscribe from connection state updates.""" """Unsubscribe from connection state updates."""
@ -211,10 +195,6 @@ class ESPHomeClient(BaseBleakClient):
for _, notify_abort in self._notify_cancels.values(): for _, notify_abort in self._notify_cancels.values():
notify_abort() notify_abort()
self._notify_cancels.clear() self._notify_cancels.clear()
for future in self._disconnected_futures:
if not future.done():
future.set_result(None)
self._disconnected_futures.clear()
self._disconnect_callbacks.discard(self._async_esp_disconnected) self._disconnect_callbacks.discard(self._async_esp_disconnected)
self._unsubscribe_connection_state() self._unsubscribe_connection_state()
@ -406,7 +386,6 @@ class ESPHomeClient(BaseBleakClient):
"""Get ATT MTU size for active connection.""" """Get ATT MTU size for active connection."""
return self._mtu or DEFAULT_MTU return self._mtu or DEFAULT_MTU
@verify_connected
@api_error_as_bleak_error @api_error_as_bleak_error
async def pair(self, *args: Any, **kwargs: Any) -> bool: async def pair(self, *args: Any, **kwargs: Any) -> bool:
"""Attempt to pair.""" """Attempt to pair."""
@ -415,6 +394,7 @@ class ESPHomeClient(BaseBleakClient):
"Pairing is not available in this version ESPHome; " "Pairing is not available in this version ESPHome; "
f"Upgrade the ESPHome version on the {self._device_info.name} device." f"Upgrade the ESPHome version on the {self._device_info.name} device."
) )
self._raise_if_not_connected()
response = await self._client.bluetooth_device_pair(self._address_as_int) response = await self._client.bluetooth_device_pair(self._address_as_int)
if response.paired: if response.paired:
return True return True
@ -423,7 +403,6 @@ class ESPHomeClient(BaseBleakClient):
) )
return False return False
@verify_connected
@api_error_as_bleak_error @api_error_as_bleak_error
async def unpair(self) -> bool: async def unpair(self) -> bool:
"""Attempt to unpair.""" """Attempt to unpair."""
@ -432,6 +411,7 @@ class ESPHomeClient(BaseBleakClient):
"Unpairing is not available in this version ESPHome; " "Unpairing is not available in this version ESPHome; "
f"Upgrade the ESPHome version on the {self._device_info.name} device." f"Upgrade the ESPHome version on the {self._device_info.name} device."
) )
self._raise_if_not_connected()
response = await self._client.bluetooth_device_unpair(self._address_as_int) response = await self._client.bluetooth_device_unpair(self._address_as_int)
if response.success: if response.success:
return True return True
@ -454,7 +434,6 @@ class ESPHomeClient(BaseBleakClient):
dangerous_use_bleak_cache=dangerous_use_bleak_cache, **kwargs dangerous_use_bleak_cache=dangerous_use_bleak_cache, **kwargs
) )
@verify_connected
async def _get_services( async def _get_services(
self, dangerous_use_bleak_cache: bool = False, **kwargs: Any self, dangerous_use_bleak_cache: bool = False, **kwargs: Any
) -> BleakGATTServiceCollection: ) -> BleakGATTServiceCollection:
@ -462,6 +441,7 @@ class ESPHomeClient(BaseBleakClient):
Must only be called from get_services or connected Must only be called from get_services or connected
""" """
self._raise_if_not_connected()
address_as_int = self._address_as_int address_as_int = self._address_as_int
cache = self._cache cache = self._cache
# If the connection version >= 3, we must use the cache # If the connection version >= 3, we must use the cache
@ -527,7 +507,6 @@ class ESPHomeClient(BaseBleakClient):
) )
return characteristic return characteristic
@verify_connected
@api_error_as_bleak_error @api_error_as_bleak_error
async def clear_cache(self) -> bool: async def clear_cache(self) -> bool:
"""Clear the GATT cache.""" """Clear the GATT cache."""
@ -541,6 +520,7 @@ class ESPHomeClient(BaseBleakClient):
self._device_info.name, self._device_info.name,
) )
return True return True
self._raise_if_not_connected()
response = await self._client.bluetooth_device_clear_cache(self._address_as_int) response = await self._client.bluetooth_device_clear_cache(self._address_as_int)
if response.success: if response.success:
return True return True
@ -551,7 +531,6 @@ class ESPHomeClient(BaseBleakClient):
) )
return False return False
@verify_connected
@api_error_as_bleak_error @api_error_as_bleak_error
async def read_gatt_char( async def read_gatt_char(
self, self,
@ -570,12 +549,12 @@ class ESPHomeClient(BaseBleakClient):
Returns: Returns:
(bytearray) The read data. (bytearray) The read data.
""" """
self._raise_if_not_connected()
characteristic = self._resolve_characteristic(char_specifier) characteristic = self._resolve_characteristic(char_specifier)
return await self._client.bluetooth_gatt_read( return await self._client.bluetooth_gatt_read(
self._address_as_int, characteristic.handle, GATT_READ_TIMEOUT self._address_as_int, characteristic.handle, GATT_READ_TIMEOUT
) )
@verify_connected
@api_error_as_bleak_error @api_error_as_bleak_error
async def read_gatt_descriptor(self, handle: int, **kwargs: Any) -> bytearray: async def read_gatt_descriptor(self, handle: int, **kwargs: Any) -> bytearray:
"""Perform read operation on the specified GATT descriptor. """Perform read operation on the specified GATT descriptor.
@ -587,11 +566,11 @@ class ESPHomeClient(BaseBleakClient):
Returns: Returns:
(bytearray) The read data. (bytearray) The read data.
""" """
self._raise_if_not_connected()
return await self._client.bluetooth_gatt_read_descriptor( return await self._client.bluetooth_gatt_read_descriptor(
self._address_as_int, handle, GATT_READ_TIMEOUT self._address_as_int, handle, GATT_READ_TIMEOUT
) )
@verify_connected
@api_error_as_bleak_error @api_error_as_bleak_error
async def write_gatt_char( async def write_gatt_char(
self, self,
@ -610,12 +589,12 @@ class ESPHomeClient(BaseBleakClient):
response (bool): If write-with-response operation should be done. response (bool): If write-with-response operation should be done.
Defaults to `False`. Defaults to `False`.
""" """
self._raise_if_not_connected()
characteristic = self._resolve_characteristic(characteristic) characteristic = self._resolve_characteristic(characteristic)
await self._client.bluetooth_gatt_write( await self._client.bluetooth_gatt_write(
self._address_as_int, characteristic.handle, bytes(data), response self._address_as_int, characteristic.handle, bytes(data), response
) )
@verify_connected
@api_error_as_bleak_error @api_error_as_bleak_error
async def write_gatt_descriptor(self, handle: int, data: Buffer) -> None: async def write_gatt_descriptor(self, handle: int, data: Buffer) -> None:
"""Perform a write operation on the specified GATT descriptor. """Perform a write operation on the specified GATT descriptor.
@ -624,11 +603,11 @@ class ESPHomeClient(BaseBleakClient):
handle (int): The handle of the descriptor to read from. handle (int): The handle of the descriptor to read from.
data (bytes or bytearray): The data to send. data (bytes or bytearray): The data to send.
""" """
self._raise_if_not_connected()
await self._client.bluetooth_gatt_write_descriptor( await self._client.bluetooth_gatt_write_descriptor(
self._address_as_int, handle, bytes(data) self._address_as_int, handle, bytes(data)
) )
@verify_connected
@api_error_as_bleak_error @api_error_as_bleak_error
async def start_notify( async def start_notify(
self, self,
@ -655,6 +634,7 @@ class ESPHomeClient(BaseBleakClient):
callback (function): The function to be called on notification. callback (function): The function to be called on notification.
kwargs: Unused. kwargs: Unused.
""" """
self._raise_if_not_connected()
ble_handle = characteristic.handle ble_handle = characteristic.handle
if ble_handle in self._notify_cancels: if ble_handle in self._notify_cancels:
raise BleakError( raise BleakError(
@ -709,7 +689,6 @@ class ESPHomeClient(BaseBleakClient):
wait_for_response=False, wait_for_response=False,
) )
@verify_connected
@api_error_as_bleak_error @api_error_as_bleak_error
async def stop_notify( async def stop_notify(
self, self,
@ -723,6 +702,7 @@ class ESPHomeClient(BaseBleakClient):
specified by either integer handle, UUID or directly by the specified by either integer handle, UUID or directly by the
BleakGATTCharacteristic object representing it. BleakGATTCharacteristic object representing it.
""" """
self._raise_if_not_connected()
characteristic = self._resolve_characteristic(char_specifier) characteristic = self._resolve_characteristic(char_specifier)
# Do not raise KeyError if notifications are not enabled on this characteristic # Do not raise KeyError if notifications are not enabled on this characteristic
# to be consistent with the behavior of the BlueZ backend # to be consistent with the behavior of the BlueZ backend
@ -730,6 +710,11 @@ class ESPHomeClient(BaseBleakClient):
notify_stop, _ = notify_cancel notify_stop, _ = notify_cancel
await notify_stop() await notify_stop()
def _raise_if_not_connected(self) -> None:
"""Raise a BleakError if not connected."""
if not self._is_connected:
raise BleakError(f"{self._description} is not connected")
def __del__(self) -> None: def __del__(self) -> None:
"""Destructor to make sure the connection state is unsubscribed.""" """Destructor to make sure the connection state is unsubscribed."""
if self._cancel_connection_state: if self._cancel_connection_state:

View File

@ -15,8 +15,7 @@
"iot_class": "local_push", "iot_class": "local_push",
"loggers": ["aioesphomeapi", "noiseprotocol"], "loggers": ["aioesphomeapi", "noiseprotocol"],
"requirements": [ "requirements": [
"async-interrupt==1.1.1", "aioesphomeapi==19.1.7",
"aioesphomeapi==19.1.4",
"bluetooth-data-tools==1.15.0", "bluetooth-data-tools==1.15.0",
"esphome-dashboard-api==1.2.3" "esphome-dashboard-api==1.2.3"
], ],

View File

@ -236,7 +236,7 @@ aioelectricitymaps==0.1.5
aioemonitor==1.0.5 aioemonitor==1.0.5
# homeassistant.components.esphome # homeassistant.components.esphome
aioesphomeapi==19.1.4 aioesphomeapi==19.1.7
# homeassistant.components.flo # homeassistant.components.flo
aioflo==2021.11.0 aioflo==2021.11.0
@ -463,9 +463,6 @@ asmog==0.0.6
# homeassistant.components.asterisk_mbox # homeassistant.components.asterisk_mbox
asterisk_mbox==0.5.0 asterisk_mbox==0.5.0
# homeassistant.components.esphome
async-interrupt==1.1.1
# homeassistant.components.dlna_dmr # homeassistant.components.dlna_dmr
# homeassistant.components.dlna_dms # homeassistant.components.dlna_dms
# homeassistant.components.samsungtv # homeassistant.components.samsungtv

View File

@ -215,7 +215,7 @@ aioelectricitymaps==0.1.5
aioemonitor==1.0.5 aioemonitor==1.0.5
# homeassistant.components.esphome # homeassistant.components.esphome
aioesphomeapi==19.1.4 aioesphomeapi==19.1.7
# homeassistant.components.flo # homeassistant.components.flo
aioflo==2021.11.0 aioflo==2021.11.0
@ -415,9 +415,6 @@ aranet4==2.2.2
# homeassistant.components.arcam_fmj # homeassistant.components.arcam_fmj
arcam-fmj==1.4.0 arcam-fmj==1.4.0
# homeassistant.components.esphome
async-interrupt==1.1.1
# homeassistant.components.dlna_dmr # homeassistant.components.dlna_dmr
# homeassistant.components.dlna_dms # homeassistant.components.dlna_dms
# homeassistant.components.samsungtv # homeassistant.components.samsungtv