Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bump aioesphomeapi to 19.1.7 #104644

Merged
merged 7 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
67 changes: 26 additions & 41 deletions homeassistant/components/esphome/bluetooth/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
APIClient,
APIVersion,
BLEConnectionError,
BluetoothConnectionDroppedError,
BluetoothProxyFeature,
DeviceInfo,
)
Expand All @@ -30,7 +31,6 @@
BluetoothGATTAPIError,
TimeoutAPIError,
)
from async_interrupt import interrupt
from bleak.backends.characteristic import BleakGATTCharacteristic
from bleak.backends.client import BaseBleakClient, NotifyCallback
from bleak.backends.device import BLEDevice
Expand Down Expand Up @@ -68,39 +68,25 @@ def mac_to_int(address: str) -> int:
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:
"""Define a wrapper throw esphome api errors as BleakErrors."""

async def _async_wrap_bluetooth_operation(
self: ESPHomeClient, *args: Any, **kwargs: Any
) -> Any:
# pylint: disable=protected-access
try:
return await func(self, *args, **kwargs)
except TimeoutAPIError as 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:
# If the device disconnects in the middle of an operation
# be sure to mark it as disconnected so any library using
Expand All @@ -111,7 +97,6 @@ async def _async_wrap_bluetooth_operation(
# before the callback is delivered.

if ex.error.error == -1:
# pylint: disable=protected-access
_LOGGER.debug(
"%s: BLE device disconnected during %s operation",
self._description,
Expand Down Expand Up @@ -169,7 +154,6 @@ def __init__(
self._notify_cancels: dict[
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._feature_flags = device_info.bluetooth_proxy_feature_flags_compat(
client_data.api_version
Expand All @@ -185,7 +169,7 @@ def __init__(

def __str__(self) -> str:
"""Return the string representation of the client."""
return f"ESPHomeClient ({self.address})"
return f"ESPHomeClient ({self._description})"

def _unsubscribe_connection_state(self) -> None:
bdraco marked this conversation as resolved.
Show resolved Hide resolved
"""Unsubscribe from connection state updates."""
Expand All @@ -211,10 +195,6 @@ def _async_disconnected_cleanup(self) -> None:
for _, notify_abort in self._notify_cancels.values():
notify_abort()
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._unsubscribe_connection_state()

Expand Down Expand Up @@ -406,7 +386,6 @@ def mtu_size(self) -> int:
"""Get ATT MTU size for active connection."""
return self._mtu or DEFAULT_MTU

@verify_connected
@api_error_as_bleak_error
async def pair(self, *args: Any, **kwargs: Any) -> bool:
"""Attempt to pair."""
Expand All @@ -415,6 +394,7 @@ async def pair(self, *args: Any, **kwargs: Any) -> bool:
"Pairing is not available in this version ESPHome; "
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)
if response.paired:
return True
Expand All @@ -423,7 +403,6 @@ async def pair(self, *args: Any, **kwargs: Any) -> bool:
)
return False

@verify_connected
@api_error_as_bleak_error
async def unpair(self) -> bool:
"""Attempt to unpair."""
Expand All @@ -432,6 +411,7 @@ async def unpair(self) -> bool:
"Unpairing is not available in this version ESPHome; "
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)
if response.success:
return True
Expand All @@ -454,14 +434,14 @@ async def get_services(
dangerous_use_bleak_cache=dangerous_use_bleak_cache, **kwargs
)

@verify_connected
async def _get_services(
self, dangerous_use_bleak_cache: bool = False, **kwargs: Any
) -> BleakGATTServiceCollection:
"""Get all services registered for this GATT server.

Must only be called from get_services or connected
"""
self._raise_if_not_connected()
address_as_int = self._address_as_int
cache = self._cache
# If the connection version >= 3, we must use the cache
Expand Down Expand Up @@ -527,7 +507,6 @@ def _resolve_characteristic(
)
return characteristic

@verify_connected
@api_error_as_bleak_error
async def clear_cache(self) -> bool:
"""Clear the GATT cache."""
Expand All @@ -541,6 +520,7 @@ async def clear_cache(self) -> bool:
self._device_info.name,
)
return True
self._raise_if_not_connected()
response = await self._client.bluetooth_device_clear_cache(self._address_as_int)
if response.success:
return True
Expand All @@ -551,7 +531,6 @@ async def clear_cache(self) -> bool:
)
return False

@verify_connected
@api_error_as_bleak_error
async def read_gatt_char(
self,
Expand All @@ -570,12 +549,12 @@ async def read_gatt_char(
Returns:
(bytearray) The read data.
"""
self._raise_if_not_connected()
characteristic = self._resolve_characteristic(char_specifier)
return await self._client.bluetooth_gatt_read(
self._address_as_int, characteristic.handle, GATT_READ_TIMEOUT
)

@verify_connected
@api_error_as_bleak_error
async def read_gatt_descriptor(self, handle: int, **kwargs: Any) -> bytearray:
"""Perform read operation on the specified GATT descriptor.
Expand All @@ -587,11 +566,11 @@ async def read_gatt_descriptor(self, handle: int, **kwargs: Any) -> bytearray:
Returns:
(bytearray) The read data.
"""
self._raise_if_not_connected()
return await self._client.bluetooth_gatt_read_descriptor(
self._address_as_int, handle, GATT_READ_TIMEOUT
)

@verify_connected
@api_error_as_bleak_error
async def write_gatt_char(
self,
Expand All @@ -610,12 +589,12 @@ async def write_gatt_char(
response (bool): If write-with-response operation should be done.
Defaults to `False`.
"""
self._raise_if_not_connected()
characteristic = self._resolve_characteristic(characteristic)
await self._client.bluetooth_gatt_write(
self._address_as_int, characteristic.handle, bytes(data), response
)

@verify_connected
@api_error_as_bleak_error
async def write_gatt_descriptor(self, handle: int, data: Buffer) -> None:
"""Perform a write operation on the specified GATT descriptor.
Expand All @@ -624,11 +603,11 @@ async def write_gatt_descriptor(self, handle: int, data: Buffer) -> None:
handle (int): The handle of the descriptor to read from.
data (bytes or bytearray): The data to send.
"""
self._raise_if_not_connected()
await self._client.bluetooth_gatt_write_descriptor(
self._address_as_int, handle, bytes(data)
)

@verify_connected
@api_error_as_bleak_error
async def start_notify(
self,
Expand All @@ -655,6 +634,7 @@ def callback(sender: int, data: bytearray):
callback (function): The function to be called on notification.
kwargs: Unused.
"""
self._raise_if_not_connected()
ble_handle = characteristic.handle
if ble_handle in self._notify_cancels:
raise BleakError(
Expand Down Expand Up @@ -709,7 +689,6 @@ def callback(sender: int, data: bytearray):
wait_for_response=False,
)

@verify_connected
@api_error_as_bleak_error
async def stop_notify(
self,
Expand All @@ -723,13 +702,19 @@ async def stop_notify(
specified by either integer handle, UUID or directly by the
BleakGATTCharacteristic object representing it.
"""
self._raise_if_not_connected()
characteristic = self._resolve_characteristic(char_specifier)
# Do not raise KeyError if notifications are not enabled on this characteristic
# to be consistent with the behavior of the BlueZ backend
if notify_cancel := self._notify_cancels.pop(characteristic.handle, None):
notify_stop, _ = notify_cancel
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:
"""Destructor to make sure the connection state is unsubscribed."""
if self._cancel_connection_state:
Expand Down
3 changes: 1 addition & 2 deletions homeassistant/components/esphome/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@
"iot_class": "local_push",
"loggers": ["aioesphomeapi", "noiseprotocol"],
"requirements": [
"async-interrupt==1.1.1",
"aioesphomeapi==19.1.4",
"aioesphomeapi==19.1.7",
"bluetooth-data-tools==1.15.0",
"esphome-dashboard-api==1.2.3"
],
Expand Down
5 changes: 1 addition & 4 deletions requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ aioelectricitymaps==0.1.5
aioemonitor==1.0.5

# homeassistant.components.esphome
aioesphomeapi==19.1.4
aioesphomeapi==19.1.7

# homeassistant.components.flo
aioflo==2021.11.0
Expand Down Expand Up @@ -463,9 +463,6 @@ asmog==0.0.6
# homeassistant.components.asterisk_mbox
asterisk_mbox==0.5.0

# homeassistant.components.esphome
async-interrupt==1.1.1

# homeassistant.components.dlna_dmr
# homeassistant.components.dlna_dms
# homeassistant.components.samsungtv
Expand Down
5 changes: 1 addition & 4 deletions requirements_test_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ aioelectricitymaps==0.1.5
aioemonitor==1.0.5

# homeassistant.components.esphome
aioesphomeapi==19.1.4
aioesphomeapi==19.1.7

# homeassistant.components.flo
aioflo==2021.11.0
Expand Down Expand Up @@ -415,9 +415,6 @@ aranet4==2.2.2
# homeassistant.components.arcam_fmj
arcam-fmj==1.4.0

# homeassistant.components.esphome
async-interrupt==1.1.1

# homeassistant.components.dlna_dmr
# homeassistant.components.dlna_dms
# homeassistant.components.samsungtv
Expand Down