Add support for esphome ble client connections v3 (#82815)

This commit is contained in:
J. Nick Koston 2022-11-28 16:56:18 -10:00 committed by GitHub
parent 230b50d099
commit fb98128b9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 84 additions and 9 deletions

View File

@ -36,6 +36,13 @@ DISCONNECT_TIMEOUT = 5.0
CONNECT_FREE_SLOT_TIMEOUT = 2.0
GATT_READ_TIMEOUT = 30.0
# CCCD (Characteristic Client Config Descriptor)
CCCD_UUID = "00002902-0000-1000-8000-00805f9b34fb"
CCCD_NOTIFY_BYTES = b"\x01\x00"
CCCD_INDICATE_BYTES = b"\x02\x00"
MIN_BLUETOOTH_PROXY_VERSION_HAS_CACHE = 3
DEFAULT_MAX_WRITE_WITHOUT_RESPONSE = DEFAULT_MTU - GATT_HEADER_SIZE
_LOGGER = logging.getLogger(__name__)
@ -137,6 +144,9 @@ class ESPHomeClient(BaseBleakClient):
self._cancel_connection_state: CALLBACK_TYPE | None = None
self._notify_cancels: dict[int, Callable[[], Coroutine[Any, Any, None]]] = {}
self._disconnected_event: asyncio.Event | None = None
device_info = self.entry_data.device_info
assert device_info is not None
self._connection_version = device_info.bluetooth_proxy_version
def __str__(self) -> str:
"""Return the string representation of the client."""
@ -206,7 +216,14 @@ class ESPHomeClient(BaseBleakClient):
Boolean representing connection status.
"""
await self._wait_for_free_connection_slot(CONNECT_FREE_SLOT_TIMEOUT)
entry_data = self.entry_data
self._mtu = entry_data.get_gatt_mtu_cache(self._address_as_int)
has_cache = bool(
dangerous_use_bleak_cache
and self._connection_version >= MIN_BLUETOOTH_PROXY_VERSION_HAS_CACHE
and entry_data.get_gatt_services_cache(self._address_as_int)
and self._mtu
)
connected_future: asyncio.Future[bool] = asyncio.Future()
def _on_bluetooth_connection_state(
@ -224,7 +241,9 @@ class ESPHomeClient(BaseBleakClient):
)
if connected:
self._is_connected = True
self._mtu = mtu
if not self._mtu:
self._mtu = mtu
entry_data.set_gatt_mtu_cache(self._address_as_int, mtu)
else:
self._async_ble_device_disconnected()
@ -258,7 +277,7 @@ class ESPHomeClient(BaseBleakClient):
self._ble_device.name,
self._ble_device.address,
)
self.entry_data.disconnect_callbacks.append(self._async_esp_disconnected)
entry_data.disconnect_callbacks.append(self._async_esp_disconnected)
connected_future.set_result(connected)
timeout = kwargs.get("timeout", self._timeout)
@ -271,6 +290,8 @@ class ESPHomeClient(BaseBleakClient):
self._address_as_int,
_on_bluetooth_connection_state,
timeout=timeout,
has_cache=has_cache,
version=self._connection_version,
)
)
except Exception: # pylint: disable=broad-except
@ -344,9 +365,13 @@ class ESPHomeClient(BaseBleakClient):
"""
address_as_int = self._address_as_int
entry_data = self.entry_data
if dangerous_use_bleak_cache and (
cached_services := entry_data.get_gatt_services_cache(address_as_int)
):
# If the connection version >= 3, we must use the cache
# because the esp has already wiped the services list to
# save memory.
if (
self._connection_version >= MIN_BLUETOOTH_PROXY_VERSION_HAS_CACHE
or dangerous_use_bleak_cache
) and (cached_services := entry_data.get_gatt_services_cache(address_as_int)):
_LOGGER.debug(
"%s: %s - %s: Cached services hit",
self._source,
@ -516,6 +541,14 @@ class ESPHomeClient(BaseBleakClient):
f"characteristic:{characteristic.uuid} "
f"handle:{ble_handle}"
)
if (
"notify" not in characteristic.properties
and "indicate" not in characteristic.properties
):
raise BleakError(
f"Characteristic {characteristic.uuid} does not have notify or indicate property set."
)
cancel_coro = await self._client.bluetooth_gatt_start_notify(
self._address_as_int,
ble_handle,
@ -523,6 +556,37 @@ class ESPHomeClient(BaseBleakClient):
)
self._notify_cancels[ble_handle] = cancel_coro
if self._connection_version < MIN_BLUETOOTH_PROXY_VERSION_HAS_CACHE:
return
# For connection v3 we are responsible for enabling notifications
# on the cccd (characteristic client config descriptor) handle since
# the esp32 will not have resolved the characteristic descriptors to
# save memory since doing so can exhaust the memory and cause a soft
# reset
cccd_descriptor = characteristic.get_descriptor(CCCD_UUID)
if not cccd_descriptor:
raise BleakError(
f"Characteristic {characteristic.uuid} does not have a "
"characteristic client config descriptor."
)
_LOGGER.debug(
"%s: %s - %s: Writing to CCD descriptor %s for notifications with properties=%s",
self._source,
self._ble_device.name,
self._ble_device.address,
cccd_descriptor.handle,
characteristic.properties,
)
supports_notify = "notify" in characteristic.properties
await self._client.bluetooth_gatt_write_descriptor(
self._address_as_int,
cccd_descriptor.handle,
CCCD_NOTIFY_BYTES if supports_notify else CCCD_INDICATE_BYTES,
wait_for_response=False,
)
@api_error_as_bleak_error
async def stop_notify(
self,

View File

@ -98,6 +98,9 @@ class RuntimeEntryData:
_gatt_services_cache: MutableMapping[int, BleakGATTServiceCollection] = field(
default_factory=lambda: LRU(MAX_CACHED_SERVICES) # type: ignore[no-any-return]
)
_gatt_mtu_cache: MutableMapping[int, int] = field(
default_factory=lambda: LRU(MAX_CACHED_SERVICES) # type: ignore[no-any-return]
)
@property
def name(self) -> str:
@ -116,6 +119,14 @@ class RuntimeEntryData:
"""Set the BleakGATTServiceCollection for the given address."""
self._gatt_services_cache[address] = services
def get_gatt_mtu_cache(self, address: int) -> int | None:
"""Get the mtu cache for the given address."""
return self._gatt_mtu_cache.get(address)
def set_gatt_mtu_cache(self, address: int, mtu: int) -> None:
"""Set the mtu cache for the given address."""
self._gatt_mtu_cache[address] = mtu
@callback
def async_update_ble_connection_limits(self, free: int, limit: int) -> None:
"""Update the BLE connection limits."""

View File

@ -3,7 +3,7 @@
"name": "ESPHome",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/esphome",
"requirements": ["aioesphomeapi==12.0.1"],
"requirements": ["aioesphomeapi==12.1.0"],
"zeroconf": ["_esphomelib._tcp.local."],
"dhcp": [{ "registered_devices": true }],
"codeowners": ["@OttoWinter", "@jesserockz"],

View File

@ -156,7 +156,7 @@ aioecowitt==2022.09.3
aioemonitor==1.0.5
# homeassistant.components.esphome
aioesphomeapi==12.0.1
aioesphomeapi==12.1.0
# homeassistant.components.flo
aioflo==2021.11.0

View File

@ -143,7 +143,7 @@ aioecowitt==2022.09.3
aioemonitor==1.0.5
# homeassistant.components.esphome
aioesphomeapi==12.0.1
aioesphomeapi==12.1.0
# homeassistant.components.flo
aioflo==2021.11.0