Skip to content

Commit cf5ac86

Browse files
committed
[Feat] Wait for connections to establish before callback and reattempt
* Delay onConnect until connection is likely established using a timer (7x conn interval), with early completion on peer activity. * Suppress onDisconnect for 0x3e and route terminal 0x3e failures to onConnectFail. * Add configurable 0x3e reconnect attempts via client config (connectFailRetryCount, default 1). * Reattempt logic suppresses callbacks while retrying; callbacks fire only on final outcome. * Refactor connect-start path into a shared helper used by initial connect and retries.
1 parent 5d48f94 commit cf5ac86

2 files changed

Lines changed: 192 additions & 55 deletions

File tree

‎src/NimBLEClient.cpp‎

Lines changed: 184 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ NimBLEClient::NimBLEClient(const NimBLEAddress& peerAddress)
6969
m_asyncSecureAttempt{0},
7070
m_config{},
7171
m_connStatus{DISCONNECTED},
72+
m_connectCallbackPending{false},
73+
m_connectFailRetryCount{0},
7274
# if MYNEWT_VAL(BLE_EXT_ADV)
7375
m_phyMask{BLE_GAP_LE_PHY_1M_MASK | BLE_GAP_LE_PHY_2M_MASK | BLE_GAP_LE_PHY_CODED_MASK},
7476
# endif
@@ -80,13 +82,21 @@ NimBLEClient::NimBLEClient(const NimBLEAddress& peerAddress)
8082
BLE_GAP_INITIAL_SUPERVISION_TIMEOUT,
8183
BLE_GAP_INITIAL_CONN_MIN_CE_LEN,
8284
BLE_GAP_INITIAL_CONN_MAX_CE_LEN} {
85+
m_config.connectFailRetryCount = 1;
86+
ble_npl_callout_init(&m_connectEstablishedTimer,
87+
nimble_port_get_dflt_eventq(),
88+
NimBLEClient::connectEstablishedTimerCb,
89+
this);
8390
} // NimBLEClient
8491

8592
/**
8693
* @brief Destructor, private - only callable by NimBLEDevice::deleteClient
8794
* to ensure proper disconnect and removal from device list.
8895
*/
8996
NimBLEClient::~NimBLEClient() {
97+
ble_npl_callout_stop(&m_connectEstablishedTimer);
98+
ble_npl_callout_deinit(&m_connectEstablishedTimer);
99+
90100
// We may have allocated service references associated with this client.
91101
// Before we are finished with the client, we must release resources.
92102
deleteServices();
@@ -159,45 +169,8 @@ bool NimBLEClient::connect(bool deleteAttributes, bool asyncConnect, bool exchan
159169
return connect(m_peerAddress, deleteAttributes, asyncConnect, exchangeMTU);
160170
} // connect
161171

162-
/**
163-
* @brief Connect to a BLE Server by address.
164-
* @param [in] address The address of the server.
165-
* @param [in] deleteAttributes If true this will delete any attribute objects this client may already\n
166-
* have created when last connected.
167-
* @param [in] asyncConnect If true, the connection will be made asynchronously and this function will return immediately.\n
168-
* If false, this function will block until the connection is established or the connection attempt times out.
169-
* @param [in] exchangeMTU If true, the client will attempt to exchange MTU with the server after connection.\n
170-
* If false, the client will use the default MTU size and the application will need to call exchangeMTU() later.
171-
* @return true on success.
172-
*/
173-
bool NimBLEClient::connect(const NimBLEAddress& address, bool deleteAttributes, bool asyncConnect, bool exchangeMTU) {
174-
NIMBLE_LOGD(LOG_TAG, ">> connect(%s)", address.toString().c_str());
175-
NimBLETaskData taskData(this);
176-
const ble_addr_t* peerAddr = address.getBase();
177-
int rc = 0;
178-
179-
if (!NimBLEDevice::m_synced) {
180-
NIMBLE_LOGE(LOG_TAG, "Host not synced with controller.");
181-
rc = BLE_HS_ENOTSYNCED;
182-
goto error;
183-
}
184-
185-
if (m_connStatus != DISCONNECTED) {
186-
NIMBLE_LOGE(LOG_TAG, "Client not disconnected, cannot connect");
187-
rc = BLE_HS_EREJECT;
188-
goto error;
189-
}
190-
191-
if (address.isNull()) {
192-
NIMBLE_LOGE(LOG_TAG, "Invalid peer address; (NULL)");
193-
rc = BLE_HS_EINVAL;
194-
goto error;
195-
}
196-
197-
m_connStatus = CONNECTING;
198-
m_peerAddress = address;
199-
m_config.asyncConnect = asyncConnect;
200-
m_config.exchangeMTU = exchangeMTU;
172+
int NimBLEClient::startConnectionAttempt(const ble_addr_t* peerAddr) {
173+
int rc = 0;
201174

202175
do {
203176
# if MYNEWT_VAL(BLE_EXT_ADV)
@@ -255,6 +228,53 @@ bool NimBLEClient::connect(const NimBLEAddress& address, bool deleteAttributes,
255228

256229
} while (rc == BLE_HS_EBUSY);
257230

231+
return rc;
232+
}
233+
234+
/**
235+
* @brief Connect to a BLE Server by address.
236+
* @param [in] address The address of the server.
237+
* @param [in] deleteAttributes If true this will delete any attribute objects this client may already\n
238+
* have created when last connected.
239+
* @param [in] asyncConnect If true, the connection will be made asynchronously and this function will return immediately.\n
240+
* If false, this function will block until the connection is established or the connection attempt times out.
241+
* @param [in] exchangeMTU If true, the client will attempt to exchange MTU with the server after connection.\n
242+
* If false, the client will use the default MTU size and the application will need to call exchangeMTU() later.
243+
* @return true on success.
244+
*/
245+
bool NimBLEClient::connect(const NimBLEAddress& address, bool deleteAttributes, bool asyncConnect, bool exchangeMTU) {
246+
NIMBLE_LOGD(LOG_TAG, ">> connect(%s)", address.toString().c_str());
247+
NimBLETaskData taskData(this);
248+
const ble_addr_t* peerAddr = address.getBase();
249+
int rc = 0;
250+
251+
if (!NimBLEDevice::m_synced) {
252+
NIMBLE_LOGE(LOG_TAG, "Host not synced with controller.");
253+
rc = BLE_HS_ENOTSYNCED;
254+
goto error;
255+
}
256+
257+
if (m_connStatus != DISCONNECTED) {
258+
NIMBLE_LOGE(LOG_TAG, "Client not disconnected, cannot connect");
259+
rc = BLE_HS_EREJECT;
260+
goto error;
261+
}
262+
263+
if (address.isNull()) {
264+
NIMBLE_LOGE(LOG_TAG, "Invalid peer address; (NULL)");
265+
rc = BLE_HS_EINVAL;
266+
goto error;
267+
}
268+
269+
m_connStatus = CONNECTING;
270+
m_peerAddress = address;
271+
m_config.asyncConnect = asyncConnect;
272+
m_config.exchangeMTU = exchangeMTU;
273+
m_connectCallbackPending = false;
274+
m_connectFailRetryCount = 0;
275+
276+
rc = startConnectionAttempt(peerAddr);
277+
258278
if (deleteAttributes) {
259279
deleteServices();
260280
}
@@ -289,7 +309,6 @@ bool NimBLEClient::connect(const NimBLEAddress& address, bool deleteAttributes,
289309
goto error;
290310
}
291311

292-
m_pClientCallbacks->onConnect(this);
293312
NIMBLE_LOGD(LOG_TAG, "<< connect()");
294313
return true;
295314

@@ -912,7 +931,7 @@ int NimBLEClient::exchangeMTUCb(uint16_t conn_handle, const ble_gatt_error* erro
912931
*/
913932
bool NimBLEClient::exchangeMTU() {
914933
int rc = ble_gattc_exchange_mtu(m_connHandle, NimBLEClient::exchangeMTUCb, this);
915-
if (rc != 0) {
934+
if (rc != 0 && rc != BLE_HS_EALREADY) {
916935
NIMBLE_LOGE(LOG_TAG, "MTU exchange error; rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
917936
m_lastErr = rc;
918937
return false;
@@ -921,6 +940,48 @@ bool NimBLEClient::exchangeMTU() {
921940
return true;
922941
} // exchangeMTU
923942

943+
void NimBLEClient::startConnectEstablishedTimer(uint16_t connInterval) {
944+
// As per Bluetooth spec, the connection is only established after receiving a PDU
945+
// within 6 connections events, so we wait for 7 connection events plus a small margin for processing.
946+
uint32_t waitMs = (static_cast<uint32_t>(connInterval) * 35U + 3U) / 4U; // 7 * 1.25ms units
947+
if (waitMs == 0) {
948+
waitMs = 1;
949+
}
950+
951+
ble_npl_time_t waitTicks = 1;
952+
ble_npl_time_ms_to_ticks(waitMs, &waitTicks);
953+
if (waitTicks == 0) {
954+
waitTicks = 1;
955+
}
956+
957+
ble_npl_callout_reset(&m_connectEstablishedTimer, waitTicks);
958+
} // startConnectEstablishedTimer
959+
960+
bool NimBLEClient::completeConnectEstablished() {
961+
if (!m_connectCallbackPending) {
962+
return false;
963+
}
964+
965+
m_connectCallbackPending = false;
966+
ble_npl_callout_stop(&m_connectEstablishedTimer);
967+
m_pClientCallbacks->onConnect(this);
968+
969+
if (m_pTaskData != nullptr) {
970+
NimBLEUtils::taskRelease(*m_pTaskData, 0);
971+
}
972+
973+
return true;
974+
} // completeConnectEstablished
975+
976+
void NimBLEClient::connectEstablishedTimerCb(struct ble_npl_event* event) {
977+
auto* pClient = static_cast<NimBLEClient*>(ble_npl_event_get_arg(event));
978+
if (pClient == nullptr || pClient->m_connStatus != CONNECTED) {
979+
return;
980+
}
981+
982+
pClient->completeConnectEstablished();
983+
} // connectEstablishedTimerCb
984+
924985
/**
925986
* @brief Handle a received GAP event.
926987
* @param [in] event The event structure sent by the NimBLE stack.
@@ -968,12 +1029,33 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
9681029

9691030
NIMBLE_LOGD(LOG_TAG, "disconnect; reason=%d, %s", rc, NimBLEUtils::returnCodeToString(rc));
9701031

1032+
pClient->m_connectCallbackPending = false;
1033+
ble_npl_callout_stop(&pClient->m_connectEstablishedTimer);
9711034
pClient->m_terminateFailCount = 0;
9721035
pClient->m_asyncSecureAttempt = 0;
9731036
pClient->m_connStatus = DISCONNECTED;
9741037

975-
// Don't call the disconnect callback if we are waiting for a connection to complete and it fails
976-
if (rc == (BLE_HS_ERR_HCI_BASE + BLE_ERR_CONN_ESTABLISHMENT) && pClient->m_config.asyncConnect) {
1038+
const int connEstablishFailReason = BLE_HS_ERR_HCI_BASE + BLE_ERR_CONN_ESTABLISHMENT;
1039+
if (rc == connEstablishFailReason && pClient->m_connectFailRetryCount < pClient->m_config.connectFailRetryCount) {
1040+
pClient->m_connHandle = BLE_HS_CONN_HANDLE_NONE;
1041+
++pClient->m_connectFailRetryCount;
1042+
pClient->m_connStatus = CONNECTING;
1043+
NIMBLE_LOGW(LOG_TAG,
1044+
"Connection establishment failed (0x3e), retry %u/%u",
1045+
pClient->m_connectFailRetryCount,
1046+
pClient->m_config.connectFailRetryCount);
1047+
1048+
const int retryRc = pClient->startConnectionAttempt(pClient->m_peerAddress.getBase());
1049+
if (retryRc == 0) {
1050+
// A retry attempt is in progress; suppress user callbacks until final outcome.
1051+
return 0;
1052+
}
1053+
1054+
pClient->m_connStatus = DISCONNECTED;
1055+
NIMBLE_LOGE(LOG_TAG, "Retry connect start failed, rc=%d %s", retryRc, NimBLEUtils::returnCodeToString(retryRc));
1056+
}
1057+
1058+
if (rc == connEstablishFailReason) {
9771059
pClient->m_pClientCallbacks->onConnectFail(pClient, rc);
9781060
} else {
9791061
pClient->m_pClientCallbacks->onDisconnect(pClient, rc);
@@ -985,7 +1067,7 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
9851067
// If we are set to self delete on disconnect but we have a task waiting on the connection
9861068
// completion we will set the flag to delete on connect fail instead of deleting here
9871069
// to prevent segmentation faults or double deleting
988-
if (pTaskData != nullptr && rc == (BLE_HS_ERR_HCI_BASE + BLE_ERR_CONN_ESTABLISHMENT)) {
1070+
if (pTaskData != nullptr && rc == connEstablishFailReason) {
9891071
pClient->m_config.deleteOnConnectFail = true;
9901072
break;
9911073
}
@@ -1009,23 +1091,28 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
10091091
}
10101092

10111093
if (rc == 0) {
1012-
pClient->m_connStatus = CONNECTED;
1013-
pClient->m_connHandle = event->connect.conn_handle;
1094+
pClient->m_connStatus = CONNECTED;
1095+
pClient->m_connHandle = event->connect.conn_handle;
1096+
pClient->m_connectCallbackPending = true;
10141097

1015-
if (pClient->m_config.asyncConnect) {
1016-
pClient->m_pClientCallbacks->onConnect(pClient);
1098+
ble_gap_conn_desc desc;
1099+
if (ble_gap_conn_find(event->connect.conn_handle, &desc) == 0) {
1100+
pClient->startConnectEstablishedTimer(desc.conn_itvl);
1101+
} else {
1102+
pClient->startConnectEstablishedTimer(pClient->m_connParams.itvl_max);
10171103
}
10181104

10191105
if (pClient->m_config.exchangeMTU) {
1020-
if (!pClient->exchangeMTU()) {
1021-
break; // if exchangeMTU fails we will just continue, likely will disconnect immediately after
1022-
}
1023-
1024-
return 0; // return as we may have a task waiting for the MTU before releasing it.
1106+
pClient->exchangeMTU();
10251107
}
1108+
// return as we may have a task waiting on the connection completion
1109+
// and will release it in the timer callback after the connection is fully established.
1110+
return 0;
10261111
} else {
1027-
pClient->m_connStatus = DISCONNECTED;
1028-
pClient->m_connHandle = BLE_HS_CONN_HANDLE_NONE;
1112+
pClient->m_connStatus = DISCONNECTED;
1113+
pClient->m_connHandle = BLE_HS_CONN_HANDLE_NONE;
1114+
pClient->m_connectCallbackPending = false;
1115+
ble_npl_callout_stop(&pClient->m_connectEstablishedTimer);
10291116

10301117
if (pClient->m_config.asyncConnect) {
10311118
pClient->m_pClientCallbacks->onConnectFail(pClient, rc);
@@ -1057,6 +1144,10 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
10571144
return 0;
10581145
}
10591146

1147+
if (pClient->completeConnectEstablished()) {
1148+
pTaskData = nullptr;
1149+
}
1150+
10601151
NIMBLE_LOGD(LOG_TAG, "Notify Received for handle: %d", event->notify_rx.attr_handle);
10611152

10621153
NimBLERemoteCharacteristic* pChr = pClient->getCharacteristic(event->notify_rx.attr_handle);
@@ -1103,6 +1194,11 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
11031194
if (pClient->m_connHandle != event->conn_update_req.conn_handle) {
11041195
return 0;
11051196
}
1197+
1198+
if (pClient->completeConnectEstablished()) {
1199+
pTaskData = nullptr;
1200+
}
1201+
11061202
NIMBLE_LOGD(LOG_TAG, "Peer requesting to update connection parameters");
11071203
NIMBLE_LOGD(LOG_TAG,
11081204
"MinInterval: %d, MaxInterval: %d, Latency: %d, Timeout: %d",
@@ -1130,6 +1226,11 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
11301226
if (pClient->m_connHandle != event->conn_update.conn_handle) {
11311227
return 0;
11321228
}
1229+
1230+
if (pClient->completeConnectEstablished()) {
1231+
pTaskData = nullptr;
1232+
}
1233+
11331234
if (event->conn_update.status == 0) {
11341235
NIMBLE_LOGI(LOG_TAG, "Connection parameters updated.");
11351236
} else {
@@ -1143,6 +1244,10 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
11431244
return 0;
11441245
}
11451246

1247+
if (pClient->completeConnectEstablished()) {
1248+
pTaskData = nullptr;
1249+
}
1250+
11461251
if (event->enc_change.status == 0 ||
11471252
event->enc_change.status == (BLE_HS_ERR_HCI_BASE + BLE_ERR_PINKEY_MISSING)) {
11481253
NimBLEConnInfo peerInfo;
@@ -1170,6 +1275,14 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
11701275
} // BLE_GAP_EVENT_ENC_CHANGE
11711276

11721277
case BLE_GAP_EVENT_IDENTITY_RESOLVED: {
1278+
if (pClient->m_connHandle != event->identity_resolved.conn_handle) {
1279+
return 0;
1280+
}
1281+
1282+
if (pClient->completeConnectEstablished()) {
1283+
pTaskData = nullptr;
1284+
}
1285+
11731286
NimBLEConnInfo peerInfo;
11741287
rc = ble_gap_conn_find(event->identity_resolved.conn_handle, &peerInfo.m_desc);
11751288
if (rc != 0) {
@@ -1182,6 +1295,14 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
11821295
} // BLE_GAP_EVENT_IDENTITY_RESOLVED
11831296

11841297
case BLE_GAP_EVENT_PHY_UPDATE_COMPLETE: {
1298+
if (pClient->m_connHandle != event->phy_updated.conn_handle) {
1299+
return 0;
1300+
}
1301+
1302+
if (pClient->completeConnectEstablished()) {
1303+
pTaskData = nullptr;
1304+
}
1305+
11851306
NimBLEConnInfo peerInfo;
11861307
rc = ble_gap_conn_find(event->phy_updated.conn_handle, &peerInfo.m_desc);
11871308
if (rc != 0) {
@@ -1197,6 +1318,10 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
11971318
return 0;
11981319
}
11991320

1321+
if (pClient->completeConnectEstablished()) {
1322+
pTaskData = nullptr;
1323+
}
1324+
12001325
NIMBLE_LOGI(LOG_TAG, "mtu update: mtu=%d", event->mtu.value);
12011326
pClient->m_pClientCallbacks->onMTUChange(pClient, event->mtu.value);
12021327
rc = 0;
@@ -1208,6 +1333,10 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event* event, void* arg) {
12081333
return 0;
12091334
}
12101335

1336+
if (pClient->completeConnectEstablished()) {
1337+
pTaskData = nullptr;
1338+
}
1339+
12111340
NimBLEConnInfo peerInfo;
12121341
rc = ble_gap_conn_find(event->passkey.conn_handle, &peerInfo.m_desc);
12131342
if (rc != 0) {

0 commit comments

Comments
 (0)