From 8a5f6a61d59a3df4636f9a10bc9d5b6da66c8fef Mon Sep 17 00:00:00 2001 From: William Escande Date: Fri, 5 Sep 2025 13:10:50 -0700 Subject: [PATCH] HAP: wait for MTU to process reconnection event When HAP reconnect, it sends indication of all events that happen during the disconnection. But it should wait for the profile to be ready and for the MTU to have been negotiated or else the remote may not be ready yet. As a side effect of this, the current GattServer doesn't re-populate the handle of subscriber during a reconnection, we have to bypass this check to send the notification --- bumble/profiles/hap.py | 37 +++++++++++++++++++++++++++++++++---- tests/hap_test.py | 4 +++- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/bumble/profiles/hap.py b/bumble/profiles/hap.py index 502390b..15194d9 100644 --- a/bumble/profiles/hap.py +++ b/bumble/profiles/hap.py @@ -273,12 +273,19 @@ class HearingAccessService(gatt.TemplateService): def on_disconnection(_reason) -> None: self.currently_connected_clients.discard(connection) + @connection.on(connection.EVENT_CONNECTION_ATT_MTU_UPDATE) + def on_mtu_update(*_: Any) -> None: + self.on_incoming_connection(connection) + + @connection.on(connection.EVENT_CONNECTION_ENCRYPTION_CHANGE) + def on_encryption_change(*_: Any) -> None: + self.on_incoming_connection(connection) + @connection.on(connection.EVENT_PAIRING) def on_pairing(*_: Any) -> None: - self.on_incoming_paired_connection(connection) + self.on_incoming_connection(connection) - if connection.peer_resolvable_address: - self.on_incoming_paired_connection(connection) + self.on_incoming_connection(connection) self.hearing_aid_features_characteristic = gatt.Characteristic( uuid=gatt.GATT_HEARING_AID_FEATURES_CHARACTERISTIC, @@ -315,9 +322,30 @@ class HearingAccessService(gatt.TemplateService): ] ) - def on_incoming_paired_connection(self, connection: Connection): + def on_incoming_connection(self, connection: Connection): '''Setup initial operations to handle a remote bonded HAP device''' # TODO Should we filter on HAP device only ? + + if not connection.is_encrypted: + logging.debug(f'HAS: {connection.peer_address} is not encrypted') + return + + if not connection.peer_resolvable_address: + logging.debug(f'HAS: {connection.peer_address} is not paired') + return + + if connection.att_mtu < 49: + logging.debug( + f'HAS: {connection.peer_address} invalid MTU={connection.att_mtu}' + ) + return + + if connection.peer_address in self.currently_connected_clients: + logging.debug( + f'HAS: Already connected to {connection.peer_address} nothing to do' + ) + return + self.currently_connected_clients.add(connection) if ( connection.peer_address @@ -457,6 +485,7 @@ class HearingAccessService(gatt.TemplateService): connection, self.hearing_aid_preset_control_point, value=op_list[0].to_bytes(len(op_list) == 1), + force=True, # TODO GATT notification subscription should be persistent ) # Remove item once sent, and keep the non sent item in the list op_list.pop(0) diff --git a/tests/hap_test.py b/tests/hap_test.py index e72f995..3e1effc 100644 --- a/tests/hap_test.py +++ b/tests/hap_test.py @@ -82,7 +82,6 @@ async def hap_client(): ) await devices.setup_connection() - # TODO negotiate MTU > 49 to not truncate preset names # Mock encryption. devices.connections[0].encryption = 1 # type: ignore @@ -93,6 +92,9 @@ async def hap_client(): ) peer = device.Peer(devices.connections[1]) # type: ignore + await peer.request_mtu(49) + peer2 = device.Peer(devices.connections[0]) # type: ignore + await peer2.request_mtu(49) hap_client = await peer.discover_service_and_create_proxy( hap.HearingAccessServiceProxy )