From 82a0073d20b187696df7f1f2a8a0ab38f19b5914 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 9 Aug 2022 09:31:42 -1000 Subject: [PATCH 1/6] Add support for bleak max_write_without_response_size --- aiohomekit/controller/ble/bleak.py | 2 +- aiohomekit/controller/ble/client.py | 26 ++++++++++++++++++++------ aiohomekit/controller/ble/discovery.py | 4 ++-- aiohomekit/controller/ble/pairing.py | 6 +++--- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/aiohomekit/controller/ble/bleak.py b/aiohomekit/controller/ble/bleak.py index ea094046..738fd364 100644 --- a/aiohomekit/controller/ble/bleak.py +++ b/aiohomekit/controller/ble/bleak.py @@ -43,7 +43,7 @@ async def get_characteristic_iid( iid_handle = char.get_descriptor(CHAR_DESCRIPTOR_UUID) if iid_handle is None: return None - value = bytes(await self.read_gatt_descriptor(iid_handle.handle)) + value = bytes(await self.read_gatt_descriptor(iid_handle)) iid = int.from_bytes(value, byteorder="little") self._iid_cache[char] = iid return iid diff --git a/aiohomekit/controller/ble/client.py b/aiohomekit/controller/ble/client.py index a4085072..0c6d41c5 100644 --- a/aiohomekit/controller/ble/client.py +++ b/aiohomekit/controller/ble/client.py @@ -15,7 +15,7 @@ # from __future__ import annotations - +from bleak.backends.characteristic import BleakGATTCharacteristic import logging import random from typing import Any, Callable, TypeVar, cast @@ -76,7 +76,7 @@ async def ble_request( encryption_key: EncryptionKey | None, decryption_key: DecryptionKey | None, opcode: OpCode, - handle: int, + handle: BleakGATTCharacteristic, iid: int, data: bytes | None = None, ) -> tuple[PDUStatus, bytes]: @@ -85,8 +85,22 @@ async def ble_request( # We think there is a 3 byte overhead for ATT # https://github.com/jlusiardi/homekit_python/issues/211#issuecomment-996751939 # But we haven't confirmed that this isn't already taken into account + if max_write_without_response_size := getattr( + handle, "max_write_without_response_size", None + ): + logger.debug( + "max_write_without_response_size: %s, mtu_size-3: %s", + max_write_without_response_size, + client.mtu_size - 3, + ) + fragment_size = max(max_write_without_response_size, client.mtu_size - 3) + else: + logger.debug( + "no max_write_without_response_size, using mtu_size-3: %s", + client.mtu_size - 3, + ) + fragment_size = client.mtu_size - 3 - fragment_size = client.mtu_size - 3 if encryption_key: # Secure session means an extra 16 bytes of overhead fragment_size -= 16 @@ -138,7 +152,7 @@ async def char_write( client: BleakClient, encryption_key: EncryptionKey | None, decryption_key: DecryptionKey | None, - handle: int, + handle: BleakGATTCharacteristic, iid: int, body: bytes, ): @@ -158,7 +172,7 @@ async def char_read( client: BleakClient, encryption_key: EncryptionKey | None, decryption_key: DecryptionKey | None, - handle: int, + handle: BleakGATTCharacteristic, iid: int, ): pdu_status, data = await ble_request( @@ -188,7 +202,7 @@ async def drive_pairing_state_machine( client, None, None, - char.handle, + char, iid, body, ) diff --git a/aiohomekit/controller/ble/discovery.py b/aiohomekit/controller/ble/discovery.py index 52a3c757..22bd38f7 100644 --- a/aiohomekit/controller/ble/discovery.py +++ b/aiohomekit/controller/ble/discovery.py @@ -111,7 +111,7 @@ async def _async_start_pairing(self, alias: str) -> tuple[bytearray, bytearray]: CharacteristicsTypes.PAIRING_FEATURES, ) ff_iid = await self.client.get_characteristic_iid(ff_char) - ff_raw = await char_read(self.client, None, None, ff_char.handle, ff_iid) + ff_raw = await char_read(self.client, None, None, ff_char, ff_iid) ff = FeatureFlags(ff_raw[0]) return await drive_pairing_state_machine( self.client, @@ -182,7 +182,7 @@ async def async_identify(self) -> None: ) iid = await self.client.get_characteristic_iid(char) - await char_write(self.client, None, None, char.handle, iid, b"\x01") + await char_write(self.client, None, None, char, iid, b"\x01") def _async_process_advertisement( self, device: BLEDevice, description: HomeKitAdvertisement diff --git a/aiohomekit/controller/ble/pairing.py b/aiohomekit/controller/ble/pairing.py index 4ca4a7e4..33368bc7 100644 --- a/aiohomekit/controller/ble/pairing.py +++ b/aiohomekit/controller/ble/pairing.py @@ -276,7 +276,7 @@ async def _async_request_under_lock( self._encryption_key, self._decryption_key, opcode, - endpoint.handle, + endpoint, char.iid, data, ) @@ -448,12 +448,12 @@ async def _async_fetch_gatt_database(self) -> Accessories: iid, ): await self.client.write_gatt_char( - char.handle, + char, data, "write-without-response" not in char.properties, ) - payload = await self.client.read_gatt_char(char.handle) + payload = await self.client.read_gatt_char(char) status, _, signature = decode_pdu(tid, payload) if status != PDUStatus.SUCCESS: From 4586d1a6248f1a4ec01fd5d16a7296cdef977245 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 9 Aug 2022 09:35:30 -1000 Subject: [PATCH 2/6] lint --- aiohomekit/controller/ble/client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/aiohomekit/controller/ble/client.py b/aiohomekit/controller/ble/client.py index 0c6d41c5..eb6082ff 100644 --- a/aiohomekit/controller/ble/client.py +++ b/aiohomekit/controller/ble/client.py @@ -15,12 +15,13 @@ # from __future__ import annotations -from bleak.backends.characteristic import BleakGATTCharacteristic + import logging import random from typing import Any, Callable, TypeVar, cast from bleak import BleakClient +from bleak.backends.characteristic import BleakGATTCharacteristic from aiohomekit.controller.ble.key import DecryptionKey, EncryptionKey from aiohomekit.exceptions import EncryptionError From 65fb6546b82427192d18ab154acd10e6eeb9c344 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 9 Aug 2022 09:36:58 -1000 Subject: [PATCH 3/6] fix --- aiohomekit/controller/ble/bleak.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiohomekit/controller/ble/bleak.py b/aiohomekit/controller/ble/bleak.py index 738fd364..ea094046 100644 --- a/aiohomekit/controller/ble/bleak.py +++ b/aiohomekit/controller/ble/bleak.py @@ -43,7 +43,7 @@ async def get_characteristic_iid( iid_handle = char.get_descriptor(CHAR_DESCRIPTOR_UUID) if iid_handle is None: return None - value = bytes(await self.read_gatt_descriptor(iid_handle)) + value = bytes(await self.read_gatt_descriptor(iid_handle.handle)) iid = int.from_bytes(value, byteorder="little") self._iid_cache[char] = iid return iid From b155e88e085a16f43b755de1f41d09a3441b2100 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 9 Aug 2022 09:54:40 -1000 Subject: [PATCH 4/6] make it work for current bleak as well --- aiohomekit/controller/ble/client.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/aiohomekit/controller/ble/client.py b/aiohomekit/controller/ble/client.py index eb6082ff..3a30d405 100644 --- a/aiohomekit/controller/ble/client.py +++ b/aiohomekit/controller/ble/client.py @@ -86,6 +86,8 @@ async def ble_request( # We think there is a 3 byte overhead for ATT # https://github.com/jlusiardi/homekit_python/issues/211#issuecomment-996751939 # But we haven't confirmed that this isn't already taken into account + + # Newer bleak, not currently released if max_write_without_response_size := getattr( handle, "max_write_without_response_size", None ): @@ -95,6 +97,13 @@ async def ble_request( client.mtu_size - 3, ) fragment_size = max(max_write_without_response_size, client.mtu_size - 3) + # Bleak 0.15.1 and below + elif ( + (char_obj := getattr(handle, "obj", None)) + and isinstance(char_obj, dict) + and (char_mtu := char_obj.get("MTU")) + ): + fragment_size = max(char_mtu - 3, client.mtu_size - 3) else: logger.debug( "no max_write_without_response_size, using mtu_size-3: %s", From 76e97aa2eaf073f286c6a867bd73bca5449d5d57 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 9 Aug 2022 09:55:30 -1000 Subject: [PATCH 5/6] make it work for current bleak as well --- aiohomekit/controller/ble/client.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/aiohomekit/controller/ble/client.py b/aiohomekit/controller/ble/client.py index 3a30d405..702ed1e5 100644 --- a/aiohomekit/controller/ble/client.py +++ b/aiohomekit/controller/ble/client.py @@ -103,6 +103,11 @@ async def ble_request( and isinstance(char_obj, dict) and (char_mtu := char_obj.get("MTU")) ): + logger.debug( + "bleak obj MTU: %s, mtu_size-3: %s", + char_mtu, + client.mtu_size - 3, + ) fragment_size = max(char_mtu - 3, client.mtu_size - 3) else: logger.debug( From 5ad7f66779651120834abb6adde6931279ddb1ae Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 9 Aug 2022 09:58:30 -1000 Subject: [PATCH 6/6] make it work for current bleak as well --- aiohomekit/controller/ble/client.py | 45 +++++++++++++++++------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/aiohomekit/controller/ble/client.py b/aiohomekit/controller/ble/client.py index 702ed1e5..7ea951ad 100644 --- a/aiohomekit/controller/ble/client.py +++ b/aiohomekit/controller/ble/client.py @@ -86,16 +86,18 @@ async def ble_request( # We think there is a 3 byte overhead for ATT # https://github.com/jlusiardi/homekit_python/issues/211#issuecomment-996751939 # But we haven't confirmed that this isn't already taken into account + debug_enabled = logger.isEnabledFor(logging.DEBUG) # Newer bleak, not currently released if max_write_without_response_size := getattr( handle, "max_write_without_response_size", None ): - logger.debug( - "max_write_without_response_size: %s, mtu_size-3: %s", - max_write_without_response_size, - client.mtu_size - 3, - ) + if debug_enabled: + logger.debug( + "max_write_without_response_size: %s, mtu_size-3: %s", + max_write_without_response_size, + client.mtu_size - 3, + ) fragment_size = max(max_write_without_response_size, client.mtu_size - 3) # Bleak 0.15.1 and below elif ( @@ -103,30 +105,34 @@ async def ble_request( and isinstance(char_obj, dict) and (char_mtu := char_obj.get("MTU")) ): - logger.debug( - "bleak obj MTU: %s, mtu_size-3: %s", - char_mtu, - client.mtu_size - 3, - ) + if debug_enabled: + logger.debug( + "bleak obj MTU: %s, mtu_size-3: %s", + char_mtu, + client.mtu_size - 3, + ) fragment_size = max(char_mtu - 3, client.mtu_size - 3) else: - logger.debug( - "no max_write_without_response_size, using mtu_size-3: %s", - client.mtu_size - 3, - ) + if debug_enabled: + logger.debug( + "no bleak obj MTU or max_write_without_response_size, using mtu_size-3: %s", + client.mtu_size - 3, + ) fragment_size = client.mtu_size - 3 if encryption_key: # Secure session means an extra 16 bytes of overhead fragment_size -= 16 - logger.debug("Using fragment size: %s", fragment_size) + if debug_enabled: + logger.debug("Using fragment size: %s", fragment_size) # Wrap data in one or more PDU's split at fragment_size # And write each one to the target characterstic handle writes = [] for data in encode_pdu(opcode, tid, iid, data, fragment_size): - logger.debug("Queuing fragment for write: %s", data) + if debug_enabled: + logger.debug("Queuing fragment for write: %s", data) if encryption_key: data = encryption_key.encrypt(data) writes.append(data) @@ -139,7 +145,9 @@ async def ble_request( data = decryption_key.decrypt(data) if data is False: raise EncryptionError("Decryption failed") - logger.debug("Read fragment: %s", data) + + if debug_enabled: + logger.debug("Read fragment: %s", data) # Validate the PDU header status, expected_length, data = decode_pdu(tid, data) @@ -156,7 +164,8 @@ async def ble_request( next = decryption_key.decrypt(next) if next is False: raise EncryptionError("Decryption failed") - logger.debug("Read fragment: %s", next) + if debug_enabled: + logger.debug("Read fragment: %s", next) data += decode_pdu_continuation(tid, next)