From 92738572beeb997938f0a1d56304bac3eee23d29 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 14 May 2026 23:51:32 +0000 Subject: [PATCH 1/2] Initial plan From 242c0abba0e616677bc4daf4a2677de8a4b50463 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 00:21:31 +0000 Subject: [PATCH 2/2] Fix: onDisconnect not called when central powers off with 0x3E reason after established connection When a BLE connection was established (BLE_GAP_EVENT_CONNECT with status 0) and then disconnects with BLE_ERR_CONN_ESTABLISHMENT (0x3E) - which can happen when the peer powers off within the first 6 connection events - the 2.5.0 retry logic incorrectly suppressed onDisconnect and called onConnectFail after retries (up to 60+ seconds later). Fix: only retry on 0x3E if the connection was never established (m_connStatus != CONNECTED). When the connection was previously established, call onDisconnect (not onConnectFail) so user reconnect logic fires correctly. Agent-Logs-Url: https://github.com/h2zero/NimBLE-Arduino/sessions/ca79e82b-2ceb-4ae8-83d3-c6344ed13cea Co-authored-by: h2zero <32826625+h2zero@users.noreply.github.com> --- CHANGELOG.md | 1 + src/NimBLEClient.cpp | 21 +++++++++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f0b45e6..5d835d2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. ## Fixed - `NimBLEClient` connection state tracking. +- `NimBLEClient` no longer retries connection or calls `onConnectFail` when a disconnect with error code `0x3E` (Connection Failed to be Established) occurs after the connection was previously confirmed; instead `onDisconnect` is called correctly. - Calling disconnect will no longer return false if the HCI response is "Unknown ID". - Remote descriptors not found when characteristic vector handles out of order. - `setValue` with char inputs now calculates the data length correctly. diff --git a/src/NimBLEClient.cpp b/src/NimBLEClient.cpp index 2d219a73..743222b4 100644 --- a/src/NimBLEClient.cpp +++ b/src/NimBLEClient.cpp @@ -1059,8 +1059,17 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) { // set this incase the client instance was changed due to incorrect event arg bug above pTaskData = pClient->m_pTaskData; + // Save the connection status before any modifications to determine if the connection + // was previously established. This is used to differentiate between a genuine + // connection establishment failure and a disconnect after a successful connection. + const bool wasConnected = (pClient->m_connStatus == CONNECTED); + const int connEstablishFailReason = BLE_HS_HCI_ERR(BLE_ERR_CONN_ESTABLISHMENT); - if (rc == connEstablishFailReason && pClient->m_connectFailRetryCount < pClient->m_config.connectFailRetries) { + // Only retry connection establishment failures if the connection was not previously + // established. If the connection was established (wasConnected), this is a genuine + // disconnect (e.g., peer powered off), so we should not retry. + if (rc == connEstablishFailReason && !wasConnected && + pClient->m_connectFailRetryCount < pClient->m_config.connectFailRetries) { pClient->m_connHandle = BLE_HS_CONN_HANDLE_NONE; ++pClient->m_connectFailRetryCount; pClient->m_connStatus = CONNECTING; @@ -1078,7 +1087,11 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) { NIMBLE_LOGE(LOG_TAG, "Retry connect start failed, rc=%d %s", retryRc, NimBLEUtils::returnCodeToString(retryRc)); } - if (rc == connEstablishFailReason) { + // Treat as a connect failure only if the connection was never established. + // If the connection was established (wasConnected), call onDisconnect even for 0x3E, + // since the peer was already connected and then disconnected. + const bool isConnectFail = (rc == connEstablishFailReason && !wasConnected); + if (isConnectFail) { pClient->m_pClientCallbacks->onConnectFail(pClient, rc); } else { pClient->m_pClientCallbacks->onDisconnect(pClient, rc); @@ -1088,10 +1101,10 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) { pClient->m_connStatus = DISCONNECTED; if (pClient->m_config.deleteOnDisconnect || - (rc == connEstablishFailReason && pClient->m_config.deleteOnConnectFail)) { + (isConnectFail && pClient->m_config.deleteOnConnectFail)) { // If we are set to self delete on disconnect but we have a task waiting on the connection // completion we will set the flag to delete on connect fail instead of deleting here - if (pTaskData != nullptr && rc == connEstablishFailReason) { + if (pTaskData != nullptr && isConnectFail) { pClient->m_config.deleteOnConnectFail = true; break; }