Skip to content

Commit

Permalink
Force reconnect when GATT services are missing to re-resolve services (
Browse files Browse the repository at this point in the history
  • Loading branch information
bdraco authored Nov 5, 2022
1 parent 11031f6 commit 4f7afa0
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 3 deletions.
13 changes: 11 additions & 2 deletions aiohomekit/controller/ble/bleak.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from bleak.backends.characteristic import BleakGATTCharacteristic
from bleak.backends.device import BLEDevice
from bleak.exc import BleakError
from bleak_retry_connector import BleakClientWithServiceCache

from .const import HAP_MIN_REQUIRED_MTU
Expand All @@ -18,6 +19,14 @@
ATT_HEADER_SIZE = 3


class BleakCharacteristicMissing(BleakError):
"""Raised when a characteristic is missing from a service."""


class BleakServiceMissing(BleakError):
"""Raised when a service is missing."""


@lru_cache(maxsize=64, typed=True)
def _determine_fragment_size(
address: str,
Expand Down Expand Up @@ -132,11 +141,11 @@ async def get_characteristic(
available_services = [
service.uuid for service in self.services.services.values()
]
raise ValueError(
raise BleakServiceMissing(
f"{self.__name}: Service {service_uuid} not found, available services: {available_services}"
)
available_chars = [char.uuid for char in service.characteristics]
raise ValueError(
raise BleakCharacteristicMissing(
f"{self.__name}: Characteristic {characteristic_uuid} not found, available characteristics: {available_chars}"
)

Expand Down
41 changes: 40 additions & 1 deletion aiohomekit/controller/ble/pairing.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@
from aiohomekit.uuid import normalize_uuid

from ..abstract import AbstractPairing, AbstractPairingData
from .bleak import AIOHomeKitBleakClient
from .bleak import (
AIOHomeKitBleakClient,
BleakCharacteristicMissing,
BleakServiceMissing,
)
from .client import (
PDUStatusError,
ble_request,
Expand Down Expand Up @@ -157,6 +161,31 @@ async def _async_operation_lock_wrap(
return cast(WrapFuncType, _async_operation_lock_wrap)


def disconnect_on_missing_services(func: WrapFuncType) -> WrapFuncType:
"""Define a wrapper to disconnect on missing services and characteristics.
This must be placed after the retry_bluetooth_connection_error
decorator.
"""

async def _async_disconnect_on_missing_services_wrap(
self: BlePairing, *args: Any, **kwargs: Any
) -> None:
try:
return await func(self, *args, **kwargs)
except (BleakServiceMissing, BleakCharacteristicMissing) as ex:
logger.warning(
"%s: Missing service or characteristic, disconnecting to force refetch of GATT services: %s",
self.name,
ex,
)
if self.client:
await self.client.disconnect()
raise

return cast(WrapFuncType, _async_disconnect_on_missing_services_wrap)


def restore_connection_and_resume(func: WrapFuncType) -> WrapFuncType:
"""Define a wrapper restore connection, populate data, and then resume when the operation completes."""

Expand Down Expand Up @@ -527,6 +556,7 @@ async def _process_disconnected_events(self) -> None:

@operation_lock
@retry_bluetooth_connection_error()
@disconnect_on_missing_services
@restore_connection_and_resume
async def _process_disconnected_events_with_retry(
self,
Expand Down Expand Up @@ -830,6 +860,7 @@ async def _close_while_locked(self) -> None:

@operation_lock
@retry_bluetooth_connection_error()
@disconnect_on_missing_services
@restore_connection_and_resume
async def list_accessories_and_characteristics(self) -> list[dict[str, Any]]:
return self.accessories.serialize()
Expand Down Expand Up @@ -930,6 +961,7 @@ async def async_populate_accessories_state(

@operation_lock
@retry_bluetooth_connection_error()
@disconnect_on_missing_services
async def _async_populate_accessories_state(
self, force_update: bool = False, attempts: int | None = None
) -> None:
Expand Down Expand Up @@ -1115,6 +1147,7 @@ async def _async_start_notify_subscriptions(self) -> None:

@operation_lock
@retry_bluetooth_connection_error()
@disconnect_on_missing_services
async def _process_config_changed(self, config_num: int) -> None:
"""Process a config change.
Expand All @@ -1126,6 +1159,7 @@ async def _process_config_changed(self, config_num: int) -> None:

@operation_lock
@retry_bluetooth_connection_error()
@disconnect_on_missing_services
@restore_connection_and_resume
async def list_pairings(self):
request_tlv = TLV.encode_list(
Expand Down Expand Up @@ -1167,6 +1201,7 @@ async def list_pairings(self):
return tmp

@retry_bluetooth_connection_error()
@disconnect_on_missing_services
async def get_characteristics(
self,
characteristics: list[tuple[int, int]],
Expand Down Expand Up @@ -1275,6 +1310,7 @@ async def _get_characteristics_while_connected(

@operation_lock
@retry_bluetooth_connection_error()
@disconnect_on_missing_services
@restore_connection_and_resume
async def put_characteristics(
self, characteristics: list[tuple[int, int, Any]]
Expand Down Expand Up @@ -1351,6 +1387,7 @@ async def subscribe(self, characteristics: Iterable[tuple[int, int]]) -> None:

@operation_lock
@retry_bluetooth_connection_error()
@disconnect_on_missing_services
@restore_connection_and_resume
async def _async_subscribe(self, new_chars: Iterable[tuple[int, int]]) -> None:
"""Subscribe to new characteristics."""
Expand Down Expand Up @@ -1393,6 +1430,7 @@ async def identify(self):

@operation_lock
@retry_bluetooth_connection_error()
@disconnect_on_missing_services
@restore_connection_and_resume
async def add_pairing(
self, additional_controller_pairing_identifier, ios_device_ltpk, permissions
Expand Down Expand Up @@ -1449,6 +1487,7 @@ async def add_pairing(

@operation_lock
@retry_bluetooth_connection_error(attempts=10)
@disconnect_on_missing_services
@restore_connection_and_resume
async def remove_pairing(self, pairingId: str) -> bool:
request_tlv = TLV.encode_list(
Expand Down

0 comments on commit 4f7afa0

Please sign in to comment.