Skip to content

Commit

Permalink
Fix missing services with esp32 proxies (#83192)
Browse files Browse the repository at this point in the history
  • Loading branch information
bdraco authored and balloob committed Dec 3, 2022
1 parent b015c5a commit 1a2f23f
Show file tree
Hide file tree
Showing 9 changed files with 85 additions and 5 deletions.
2 changes: 1 addition & 1 deletion homeassistant/components/bluetooth/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"quality_scale": "internal",
"requirements": [
"bleak==0.19.2",
"bleak-retry-connector==2.8.9",
"bleak-retry-connector==2.9.0",
"bluetooth-adapters==0.11.0",
"bluetooth-auto-recovery==0.5.4",
"bluetooth-data-tools==0.3.0",
Expand Down
8 changes: 7 additions & 1 deletion homeassistant/components/bluetooth/wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from bleak.backends.client import BaseBleakClient, get_platform_client_backend_type
from bleak.backends.device import BLEDevice
from bleak.backends.scanner import AdvertisementDataCallback, BaseBleakScanner
from bleak_retry_connector import NO_RSSI_VALUE, ble_device_description
from bleak_retry_connector import NO_RSSI_VALUE, ble_device_description, clear_cache

from homeassistant.core import CALLBACK_TYPE, callback as hass_callback
from homeassistant.helpers.frame import report
Expand Down Expand Up @@ -169,6 +169,12 @@ def is_connected(self) -> bool:
"""Return True if the client is connected to a device."""
return self._backend is not None and self._backend.is_connected

async def clear_cache(self) -> bool:
"""Clear the GATT cache."""
if self._backend is not None and hasattr(self._backend, "clear_cache"):
return await self._backend.clear_cache() # type: ignore[no-any-return]
return await clear_cache(self.__address)

def set_disconnected_callback(
self,
callback: Callable[[BleakClient], None] | None,
Expand Down
5 changes: 5 additions & 0 deletions homeassistant/components/esphome/bluetooth/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,11 @@ def _resolve_characteristic(
raise BleakError(f"Characteristic {char_specifier} was not found!")
return characteristic

async def clear_cache(self) -> None:
"""Clear the GATT cache."""
self.entry_data.clear_gatt_services_cache(self._address_as_int)
self.entry_data.clear_gatt_mtu_cache(self._address_as_int)

@verify_connected
@api_error_as_bleak_error
async def read_gatt_char(
Expand Down
8 changes: 8 additions & 0 deletions homeassistant/components/esphome/entry_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ def set_gatt_services_cache(
"""Set the BleakGATTServiceCollection for the given address."""
self._gatt_services_cache[address] = services

def clear_gatt_services_cache(self, address: int) -> None:
"""Clear the BleakGATTServiceCollection for the given address."""
self._gatt_services_cache.pop(address, None)

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)
Expand All @@ -127,6 +131,10 @@ 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

def clear_gatt_mtu_cache(self, address: int) -> None:
"""Clear the mtu cache for the given address."""
self._gatt_mtu_cache.pop(address, None)

@callback
def async_update_ble_connection_limits(self, free: int, limit: int) -> None:
"""Update the BLE connection limits."""
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/package_constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ atomicwrites-homeassistant==1.4.1
attrs==21.2.0
awesomeversion==22.9.0
bcrypt==3.1.7
bleak-retry-connector==2.8.9
bleak-retry-connector==2.9.0
bleak==0.19.2
bluetooth-adapters==0.11.0
bluetooth-auto-recovery==0.5.4
Expand Down
2 changes: 1 addition & 1 deletion requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ bimmer_connected==0.10.4
bizkaibus==0.1.1

# homeassistant.components.bluetooth
bleak-retry-connector==2.8.9
bleak-retry-connector==2.9.0

# homeassistant.components.bluetooth
bleak==0.19.2
Expand Down
2 changes: 1 addition & 1 deletion requirements_test_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ bellows==0.34.5
bimmer_connected==0.10.4

# homeassistant.components.bluetooth
bleak-retry-connector==2.8.9
bleak-retry-connector==2.9.0

# homeassistant.components.bluetooth
bleak==0.19.2
Expand Down
4 changes: 4 additions & 0 deletions tests/components/bluetooth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,3 +219,7 @@ async def disconnect(self, *args, **kwargs):
async def get_services(self, *args, **kwargs):
"""Mock get_services."""
return []

async def clear_cache(self, *args, **kwargs):
"""Mock clear_cache."""
return True
57 changes: 57 additions & 0 deletions tests/components/bluetooth/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ async def test_wrapped_bleak_client_raises_device_missing(hass, enable_bluetooth
await client.connect()
assert client.is_connected is False
await client.disconnect()
assert await client.clear_cache() is False


async def test_wrapped_bleak_client_set_disconnected_callback_before_connected(
Expand Down Expand Up @@ -168,6 +169,62 @@ async def test_ble_device_with_proxy_client_out_of_connections(
await client.disconnect()


async def test_ble_device_with_proxy_clear_cache(hass, enable_bluetooth, one_adapter):
"""Test we can clear cache on the proxy."""
manager = _get_manager()

switchbot_proxy_device_with_connection_slot = BLEDevice(
"44:44:33:11:23:45",
"wohand",
{
"connector": HaBluetoothConnector(
MockBleakClient, "mock_bleak_client", lambda: True
),
"path": "/org/bluez/hci0/dev_44_44_33_11_23_45",
},
rssi=-30,
)
switchbot_adv = generate_advertisement_data(
local_name="wohand", service_uuids=[], manufacturer_data={1: b"\x01"}
)

class FakeScanner(BaseHaScanner):
@property
def discovered_devices_and_advertisement_data(
self,
) -> dict[str, tuple[BLEDevice, AdvertisementData]]:
"""Return a list of discovered devices."""
return {
switchbot_proxy_device_with_connection_slot.address: (
switchbot_proxy_device_with_connection_slot,
switchbot_adv,
)
}

async def async_get_device_by_address(self, address: str) -> BLEDevice | None:
"""Return a list of discovered devices."""
if address == switchbot_proxy_device_with_connection_slot.address:
return switchbot_adv
return None

scanner = FakeScanner(hass, "esp32", "esp32")
cancel = manager.async_register_scanner(scanner, True)
inject_advertisement_with_source(
hass, switchbot_proxy_device_with_connection_slot, switchbot_adv, "esp32"
)

assert manager.async_discovered_devices(True) == [
switchbot_proxy_device_with_connection_slot
]

client = HaBleakClientWrapper(switchbot_proxy_device_with_connection_slot)
await client.connect()
assert client.is_connected is True
assert await client.clear_cache() is True
await client.disconnect()
cancel()


async def test_ble_device_with_proxy_client_out_of_connections_uses_best_available(
hass, enable_bluetooth, one_adapter
):
Expand Down

0 comments on commit 1a2f23f

Please sign in to comment.