mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +00:00
Add support for esphome ble client connections v3 (#82815)
This commit is contained in:
parent
230b50d099
commit
fb98128b9f
@ -36,6 +36,13 @@ DISCONNECT_TIMEOUT = 5.0
|
|||||||
CONNECT_FREE_SLOT_TIMEOUT = 2.0
|
CONNECT_FREE_SLOT_TIMEOUT = 2.0
|
||||||
GATT_READ_TIMEOUT = 30.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
|
DEFAULT_MAX_WRITE_WITHOUT_RESPONSE = DEFAULT_MTU - GATT_HEADER_SIZE
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -137,6 +144,9 @@ class ESPHomeClient(BaseBleakClient):
|
|||||||
self._cancel_connection_state: CALLBACK_TYPE | None = None
|
self._cancel_connection_state: CALLBACK_TYPE | None = None
|
||||||
self._notify_cancels: dict[int, Callable[[], Coroutine[Any, Any, None]]] = {}
|
self._notify_cancels: dict[int, Callable[[], Coroutine[Any, Any, None]]] = {}
|
||||||
self._disconnected_event: asyncio.Event | None = 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:
|
def __str__(self) -> str:
|
||||||
"""Return the string representation of the client."""
|
"""Return the string representation of the client."""
|
||||||
@ -206,7 +216,14 @@ class ESPHomeClient(BaseBleakClient):
|
|||||||
Boolean representing connection status.
|
Boolean representing connection status.
|
||||||
"""
|
"""
|
||||||
await self._wait_for_free_connection_slot(CONNECT_FREE_SLOT_TIMEOUT)
|
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()
|
connected_future: asyncio.Future[bool] = asyncio.Future()
|
||||||
|
|
||||||
def _on_bluetooth_connection_state(
|
def _on_bluetooth_connection_state(
|
||||||
@ -224,7 +241,9 @@ class ESPHomeClient(BaseBleakClient):
|
|||||||
)
|
)
|
||||||
if connected:
|
if connected:
|
||||||
self._is_connected = True
|
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:
|
else:
|
||||||
self._async_ble_device_disconnected()
|
self._async_ble_device_disconnected()
|
||||||
|
|
||||||
@ -258,7 +277,7 @@ class ESPHomeClient(BaseBleakClient):
|
|||||||
self._ble_device.name,
|
self._ble_device.name,
|
||||||
self._ble_device.address,
|
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)
|
connected_future.set_result(connected)
|
||||||
|
|
||||||
timeout = kwargs.get("timeout", self._timeout)
|
timeout = kwargs.get("timeout", self._timeout)
|
||||||
@ -271,6 +290,8 @@ class ESPHomeClient(BaseBleakClient):
|
|||||||
self._address_as_int,
|
self._address_as_int,
|
||||||
_on_bluetooth_connection_state,
|
_on_bluetooth_connection_state,
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
|
has_cache=has_cache,
|
||||||
|
version=self._connection_version,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
@ -344,9 +365,13 @@ class ESPHomeClient(BaseBleakClient):
|
|||||||
"""
|
"""
|
||||||
address_as_int = self._address_as_int
|
address_as_int = self._address_as_int
|
||||||
entry_data = self.entry_data
|
entry_data = self.entry_data
|
||||||
if dangerous_use_bleak_cache and (
|
# If the connection version >= 3, we must use the cache
|
||||||
cached_services := entry_data.get_gatt_services_cache(address_as_int)
|
# 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(
|
_LOGGER.debug(
|
||||||
"%s: %s - %s: Cached services hit",
|
"%s: %s - %s: Cached services hit",
|
||||||
self._source,
|
self._source,
|
||||||
@ -516,6 +541,14 @@ class ESPHomeClient(BaseBleakClient):
|
|||||||
f"characteristic:{characteristic.uuid} "
|
f"characteristic:{characteristic.uuid} "
|
||||||
f"handle:{ble_handle}"
|
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(
|
cancel_coro = await self._client.bluetooth_gatt_start_notify(
|
||||||
self._address_as_int,
|
self._address_as_int,
|
||||||
ble_handle,
|
ble_handle,
|
||||||
@ -523,6 +556,37 @@ class ESPHomeClient(BaseBleakClient):
|
|||||||
)
|
)
|
||||||
self._notify_cancels[ble_handle] = cancel_coro
|
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
|
@api_error_as_bleak_error
|
||||||
async def stop_notify(
|
async def stop_notify(
|
||||||
self,
|
self,
|
||||||
|
@ -98,6 +98,9 @@ class RuntimeEntryData:
|
|||||||
_gatt_services_cache: MutableMapping[int, BleakGATTServiceCollection] = field(
|
_gatt_services_cache: MutableMapping[int, BleakGATTServiceCollection] = field(
|
||||||
default_factory=lambda: LRU(MAX_CACHED_SERVICES) # type: ignore[no-any-return]
|
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
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
@ -116,6 +119,14 @@ class RuntimeEntryData:
|
|||||||
"""Set the BleakGATTServiceCollection for the given address."""
|
"""Set the BleakGATTServiceCollection for the given address."""
|
||||||
self._gatt_services_cache[address] = services
|
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
|
@callback
|
||||||
def async_update_ble_connection_limits(self, free: int, limit: int) -> None:
|
def async_update_ble_connection_limits(self, free: int, limit: int) -> None:
|
||||||
"""Update the BLE connection limits."""
|
"""Update the BLE connection limits."""
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "ESPHome",
|
"name": "ESPHome",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/esphome",
|
"documentation": "https://www.home-assistant.io/integrations/esphome",
|
||||||
"requirements": ["aioesphomeapi==12.0.1"],
|
"requirements": ["aioesphomeapi==12.1.0"],
|
||||||
"zeroconf": ["_esphomelib._tcp.local."],
|
"zeroconf": ["_esphomelib._tcp.local."],
|
||||||
"dhcp": [{ "registered_devices": true }],
|
"dhcp": [{ "registered_devices": true }],
|
||||||
"codeowners": ["@OttoWinter", "@jesserockz"],
|
"codeowners": ["@OttoWinter", "@jesserockz"],
|
||||||
|
@ -156,7 +156,7 @@ aioecowitt==2022.09.3
|
|||||||
aioemonitor==1.0.5
|
aioemonitor==1.0.5
|
||||||
|
|
||||||
# homeassistant.components.esphome
|
# homeassistant.components.esphome
|
||||||
aioesphomeapi==12.0.1
|
aioesphomeapi==12.1.0
|
||||||
|
|
||||||
# homeassistant.components.flo
|
# homeassistant.components.flo
|
||||||
aioflo==2021.11.0
|
aioflo==2021.11.0
|
||||||
|
@ -143,7 +143,7 @@ aioecowitt==2022.09.3
|
|||||||
aioemonitor==1.0.5
|
aioemonitor==1.0.5
|
||||||
|
|
||||||
# homeassistant.components.esphome
|
# homeassistant.components.esphome
|
||||||
aioesphomeapi==12.0.1
|
aioesphomeapi==12.1.0
|
||||||
|
|
||||||
# homeassistant.components.flo
|
# homeassistant.components.flo
|
||||||
aioflo==2021.11.0
|
aioflo==2021.11.0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user